Merge commit 'master' into discussions
Conflicts: app/assets/stylesheets/sections/notes.scss app/contexts/notes/load_context.rb app/models/project.rb app/observers/note_observer.rb app/roles/votes.rb app/views/commit/show.html.haml app/views/merge_requests/_show.html.haml app/views/merge_requests/diffs.js.haml app/views/merge_requests/show.js.haml app/views/notes/_note.html.haml features/steps/project/project_merge_requests.rb spec/models/note_spec.rb
This commit is contained in:
commit
3022786948
930 changed files with 80374 additions and 103682 deletions
92
app/assets/fonts/OFL.txt
Normal file
92
app/assets/fonts/OFL.txt
Normal file
|
@ -0,0 +1,92 @@
|
|||
Copyright (c) 2010, Jan Gerner (post@yanone.de)
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
app/assets/fonts/YanoneKaffeesatz-Light.ttf
Normal file
BIN
app/assets/fonts/YanoneKaffeesatz-Light.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 674 B |
30
app/assets/javascripts/dashboard.js.coffee
Normal file
30
app/assets/javascripts/dashboard.js.coffee
Normal file
|
@ -0,0 +1,30 @@
|
|||
$ ->
|
||||
dashboardPage()
|
||||
|
||||
dashboardPage = ->
|
||||
Pager.init 20, true
|
||||
$(".event_filter_link").bind "click", (event) ->
|
||||
event.preventDefault()
|
||||
toggleFilter $(this)
|
||||
reloadActivities()
|
||||
|
||||
reloadActivities = ->
|
||||
$(".content_list").html ''
|
||||
Pager.init 20, true
|
||||
|
||||
toggleFilter = (sender) ->
|
||||
sender.parent().toggleClass "inactive"
|
||||
event_filters = $.cookie("event_filter")
|
||||
filter = sender.attr("id").split("_")[0]
|
||||
if event_filters
|
||||
event_filters = event_filters.split(",")
|
||||
else
|
||||
event_filters = new Array()
|
||||
|
||||
index = event_filters.indexOf(filter)
|
||||
if index is -1
|
||||
event_filters.push filter
|
||||
else
|
||||
event_filters.splice index, 1
|
||||
|
||||
$.cookie "event_filter", event_filters.join(",")
|
|
@ -11,7 +11,7 @@ function initIssuesSearch() {
|
|||
last_terms = terms;
|
||||
|
||||
if (terms.length >= 2 || terms.length == 0) {
|
||||
$.get(href, { 'f': status, 'terms': terms, 'milestone_id': milestone_id }, function(response) {
|
||||
$.get(href, { 'status': status, 'terms': terms, 'milestone_id': milestone_id }, function(response) {
|
||||
$('#issues-table').html(response);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
var MergeRequest = {
|
||||
diffs_loaded: false,
|
||||
commits_loaded: false,
|
||||
opts: false,
|
||||
|
||||
init:
|
||||
function(opts) {
|
||||
var self = this;
|
||||
self.opts = opts;
|
||||
|
||||
self.initTabs();
|
||||
self.initMergeWidget();
|
||||
|
||||
$(".mr_show_all_commits").bind("click", function() {
|
||||
self.showAllCommits();
|
||||
});
|
||||
},
|
||||
|
||||
initMergeWidget:
|
||||
function() {
|
||||
var self = this;
|
||||
self.showState(self.opts.current_state);
|
||||
|
||||
if($(".automerge_widget").length && self.opts.check_enable){
|
||||
$.get(self.opts.url_to_automerge_check, function(data){
|
||||
self.showState(data.state);
|
||||
}, "json");
|
||||
}
|
||||
|
||||
if(self.opts.ci_enable){
|
||||
$.get(self.opts.url_to_ci_check, function(data){
|
||||
self.showCiState(data.status);
|
||||
}, "json");
|
||||
}
|
||||
},
|
||||
|
||||
initTabs:
|
||||
function() {
|
||||
$(".mr_nav_tabs a").live("click", function() {
|
||||
$(".mr_nav_tabs a").parent().removeClass("active");
|
||||
$(this).parent().addClass("active");
|
||||
});
|
||||
|
||||
var current_tab;
|
||||
if(this.opts.action == "diffs") {
|
||||
current_tab = $(".mr_nav_tabs .merge-diffs-tab");
|
||||
} else {
|
||||
current_tab = $(".mr_nav_tabs .merge-notes-tab");
|
||||
}
|
||||
current_tab.parent().addClass("active");
|
||||
|
||||
this.initNotesTab();
|
||||
this.initDiffTab();
|
||||
},
|
||||
|
||||
initNotesTab:
|
||||
function() {
|
||||
$(".mr_nav_tabs a.merge-notes-tab").live("click", function(e) {
|
||||
$(".merge-request-diffs").hide();
|
||||
$(".merge_request_notes").show();
|
||||
var mr_path = $(".merge-notes-tab").attr("data-url");
|
||||
history.pushState({ path: mr_path }, '', mr_path);
|
||||
e.preventDefault();
|
||||
});
|
||||
},
|
||||
|
||||
initDiffTab:
|
||||
function() {
|
||||
$(".mr_nav_tabs a.merge-diffs-tab").live("click", function(e) {
|
||||
if(!MergeRequest.diffs_loaded) {
|
||||
MergeRequest.loadDiff();
|
||||
}
|
||||
$(".merge_request_notes").hide();
|
||||
$(".merge-request-diffs").show();
|
||||
var mr_diff_path = $(".merge-diffs-tab").attr("data-url");
|
||||
history.pushState({ path: mr_diff_path }, '', mr_diff_path);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
showState:
|
||||
function(state){
|
||||
$(".automerge_widget").hide();
|
||||
$(".automerge_widget." + state).show();
|
||||
},
|
||||
|
||||
showCiState:
|
||||
function(state){
|
||||
$(".ci_widget").hide();
|
||||
$(".ci_widget.ci-" + state).show();
|
||||
},
|
||||
|
||||
loadDiff:
|
||||
function() {
|
||||
$(".dashboard-loader").show();
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: $(".merge-diffs-tab").attr("data-url"),
|
||||
beforeSend: function(){ $('.status').addClass("loading")},
|
||||
complete: function(){
|
||||
MergeRequest.diffs_loaded = true;
|
||||
$(".merge_request_notes").hide();
|
||||
$('.status').removeClass("loading");
|
||||
},
|
||||
dataType: "script"});
|
||||
},
|
||||
|
||||
showAllCommits:
|
||||
function() {
|
||||
$(".first_mr_commits").remove();
|
||||
$(".all_mr_commits").removeClass("hide");
|
||||
},
|
||||
|
||||
already_cannot_be_merged:
|
||||
function(){
|
||||
$(".automerge_widget").hide();
|
||||
$(".merge_in_progress").hide();
|
||||
$(".automerge_widget.already_cannot_be_merged").show();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Filter merge requests
|
||||
*/
|
||||
function merge_requestsPage() {
|
||||
$("#assignee_id").chosen();
|
||||
$("#milestone_id").chosen();
|
||||
$("#milestone_id, #assignee_id").on("change", function(){
|
||||
$(this).closest("form").submit();
|
||||
});
|
||||
}
|
97
app/assets/javascripts/merge_requests.js.coffee
Normal file
97
app/assets/javascripts/merge_requests.js.coffee
Normal file
|
@ -0,0 +1,97 @@
|
|||
#
|
||||
# * Filter merge requests
|
||||
#
|
||||
@merge_requestsPage = ->
|
||||
$('#assignee_id').chosen()
|
||||
$('#milestone_id').chosen()
|
||||
$('#milestone_id, #assignee_id').on 'change', ->
|
||||
$(this).closest('form').submit()
|
||||
|
||||
class MergeRequest
|
||||
|
||||
constructor: (@opts) ->
|
||||
this.$el = $('.merge-request')
|
||||
@diffs_loaded = false
|
||||
@commits_loaded = false
|
||||
|
||||
this.activateTab(@opts.action)
|
||||
|
||||
this.bindEvents()
|
||||
|
||||
this.initMergeWidget()
|
||||
this.$('.show-all-commits').on 'click', =>
|
||||
this.showAllCommits()
|
||||
|
||||
# Local jQuery finder
|
||||
$: (selector) ->
|
||||
this.$el.find(selector)
|
||||
|
||||
initMergeWidget: ->
|
||||
this.showState( @opts.current_state )
|
||||
|
||||
if this.$('.automerge_widget').length and @opts.check_enable
|
||||
$.get @opts.url_to_automerge_check, (data) =>
|
||||
this.showState( data.state )
|
||||
, 'json'
|
||||
|
||||
if @opts.ci_enable
|
||||
$.get @opts.url_to_ci_check, (data) =>
|
||||
this.showCiState data.status
|
||||
, 'json'
|
||||
|
||||
bindEvents: ->
|
||||
this.$('.nav-tabs').on 'click', 'a', (event) =>
|
||||
a = $(event.currentTarget)
|
||||
|
||||
href = a.attr('href')
|
||||
History.replaceState {path: href}, document.title, href
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
this.$('.nav-tabs').on 'click', 'li', (event) =>
|
||||
this.activateTab($(event.currentTarget).data('action'))
|
||||
|
||||
activateTab: (action) ->
|
||||
this.$('.nav-tabs li').removeClass 'active'
|
||||
this.$('.tab-content').hide()
|
||||
switch action
|
||||
when 'diffs'
|
||||
this.$('.nav-tabs .diffs-tab').addClass 'active'
|
||||
this.loadDiff() unless @diffs_loaded
|
||||
this.$('.diffs').show()
|
||||
else
|
||||
this.$('.nav-tabs .notes-tab').addClass 'active'
|
||||
this.$('.notes').show()
|
||||
|
||||
showState: (state) ->
|
||||
$('.automerge_widget').hide()
|
||||
$('.automerge_widget.' + state).show()
|
||||
|
||||
showCiState: (state) ->
|
||||
$('.ci_widget').hide()
|
||||
$('.ci_widget.ci-' + state).show()
|
||||
|
||||
loadDiff: (event) ->
|
||||
$('.dashboard-loader').show()
|
||||
$.ajax
|
||||
type: 'GET'
|
||||
url: this.$('.nav-tabs .diffs-tab a').attr('href')
|
||||
beforeSend: =>
|
||||
this.$('.status').addClass 'loading'
|
||||
|
||||
complete: =>
|
||||
@diffs_loaded = true
|
||||
this.$('.status').removeClass 'loading'
|
||||
|
||||
dataType: 'script'
|
||||
|
||||
showAllCommits: ->
|
||||
this.$('.first-commits').remove()
|
||||
this.$('.all-commits').removeClass 'hide'
|
||||
|
||||
alreadyOrCannotBeMerged: ->
|
||||
this.$('.automerge_widget').hide()
|
||||
this.$('.merge-in-progress').hide()
|
||||
this.$('.automerge_widget.already_cannot_be_merged').show()
|
||||
|
||||
this.MergeRequest = MergeRequest
|
|
@ -1,14 +1,14 @@
|
|||
$ ->
|
||||
$('.milestone-issue-filter tr[data-closed]').addClass('hide')
|
||||
$('.milestone-issue-filter li[data-closed]').addClass('hide')
|
||||
|
||||
$('.milestone-issue-filter ul.nav li a').click ->
|
||||
$('.milestone-issue-filter li').toggleClass('active')
|
||||
$('.milestone-issue-filter tr[data-closed]').toggleClass('hide')
|
||||
$('.milestone-issue-filter li[data-closed]').toggleClass('hide')
|
||||
false
|
||||
|
||||
$('.milestone-merge-requests-filter tr[data-closed]').addClass('hide')
|
||||
$('.milestone-merge-requests-filter li[data-closed]').addClass('hide')
|
||||
|
||||
$('.milestone-merge-requests-filter ul.nav li a').click ->
|
||||
$('.milestone-merge-requests-filter li').toggleClass('active')
|
||||
$('.milestone-merge-requests-filter tr[data-closed]').toggleClass('hide')
|
||||
$('.milestone-merge-requests-filter li[data-closed]').toggleClass('hide')
|
||||
false
|
||||
|
|
|
@ -4,9 +4,16 @@ var Pager = {
|
|||
disable:false,
|
||||
|
||||
init:
|
||||
function(limit) {
|
||||
function(limit, preload) {
|
||||
this.limit=limit;
|
||||
this.offset=limit;
|
||||
|
||||
if(preload) {
|
||||
this.offset = 0;
|
||||
this.getOld();
|
||||
} else {
|
||||
this.offset = limit;
|
||||
}
|
||||
|
||||
this.initLoadMore();
|
||||
},
|
||||
|
||||
|
|
|
@ -117,34 +117,10 @@ span.update-author {
|
|||
}
|
||||
|
||||
.label {
|
||||
background-color: #474D57;
|
||||
|
||||
&.label-tag {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 4px 6px;
|
||||
color: #444;
|
||||
text-shadow: 0 0 1px #fff;
|
||||
|
||||
&.grouped {
|
||||
float: left;
|
||||
margin-right: 6px;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
&.label-issue {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
padding: 4px 6px;
|
||||
color: #444;
|
||||
text-shadow: 0 0 1px #fff;
|
||||
|
||||
&.grouped {
|
||||
float: left;
|
||||
margin-right: 6px;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
padding: 0px 4px;
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
background-color: $link_color;
|
||||
|
||||
&.label-success {
|
||||
background-color: #8D8;
|
||||
|
@ -425,7 +401,7 @@ li.note {
|
|||
|
||||
|
||||
.supp_diff_link,
|
||||
.mr_show_all_commits {
|
||||
.show-all-commits {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
/**
|
||||
* ===================================
|
||||
* Contain 3 main UI block elements:
|
||||
* .main_box - for show pages
|
||||
* .ui-box - for simple block & widgets
|
||||
* Contain UI block elements:
|
||||
* .ui-box - for any block & widgets
|
||||
* ===================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* UI box element
|
||||
* contains top, middle, bottom blocks
|
||||
* UI Block
|
||||
*
|
||||
*/
|
||||
.main_box {
|
||||
@extend .borders;
|
||||
@extend .prepend-top-20;
|
||||
@extend .append-bottom-20;
|
||||
border-width: 1px;
|
||||
.ui-box {
|
||||
background: #F9F9F9;
|
||||
margin-bottom: 25px;
|
||||
border: 1px solid #CCC;
|
||||
@include solid-shade;
|
||||
|
||||
&.ui-box-show {
|
||||
margin:20px 0;
|
||||
background: #FFF;
|
||||
}
|
||||
|
||||
img { max-width: 100%; }
|
||||
|
||||
|
@ -27,9 +28,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
.top_box_content,
|
||||
.middle_box_content,
|
||||
.bottom_box_content {
|
||||
.ui-box-head,
|
||||
.ui-box-body,
|
||||
.ui-box-bottom {
|
||||
padding: 15px;
|
||||
word-wrap: break-word;
|
||||
|
||||
|
@ -39,19 +40,25 @@
|
|||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.top_box_content {
|
||||
.ui-box-head {
|
||||
.box-title {
|
||||
color: $style_color;
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
line-height: 28px;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.middle_box_content {
|
||||
@include border-radius(0);
|
||||
.ui-box-body {
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
background-color: #f5f5f5;
|
||||
|
@ -59,24 +66,9 @@
|
|||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.bottom_box_content {
|
||||
.ui-box-bottom {
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Big UI Block for show page content
|
||||
*
|
||||
*/
|
||||
.ui-box {
|
||||
background: #F9F9F9;
|
||||
margin-bottom: 25px;
|
||||
|
||||
border: 1px solid #eaeaea;
|
||||
@include border-radius(4px);
|
||||
|
||||
border-color: #CCC;
|
||||
@include solid-shade;
|
||||
|
||||
&.white {
|
||||
background: #fff;
|
||||
|
@ -86,47 +78,47 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
h5, .title {
|
||||
padding: 0 10px;
|
||||
@include border-radius(4px 4px 0 0);
|
||||
.title {
|
||||
@include bg-gray-gradient;
|
||||
border-top: 1px solid #eaeaea;
|
||||
border-bottom: 1px solid #bbb;
|
||||
border-bottom: 1px solid #CCC;
|
||||
color: #456;
|
||||
font-size: 16px;
|
||||
text-shadow: 0 1px 1px #fff;
|
||||
padding: 0px 10px;
|
||||
line-height: 36px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
|
||||
> a {
|
||||
text-shadow: 0 1px 1px #fff;
|
||||
}
|
||||
|
||||
&.small {
|
||||
line-height: 28px;
|
||||
font-size: 14px;
|
||||
line-height: 28px;
|
||||
text-shadow: 0 1px 1px white;
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 9px 0;
|
||||
margin: 0px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.nav-pills {
|
||||
li {
|
||||
padding: 3px 0;
|
||||
&.active a { background-color: $style_color; }
|
||||
a {
|
||||
@include border-radius(7px);
|
||||
> li {
|
||||
> a {
|
||||
padding: 13px;
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
&.active {
|
||||
> a {
|
||||
background: #D5D5D5;
|
||||
color: $style_color;
|
||||
@include border-radius(0);
|
||||
border-radius: 0;
|
||||
border-left: 1px solid #CCC;
|
||||
border-right: 1px solid #CCC;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
@include bg-gray-gradient;
|
||||
@include border-radius(0 0 4px 4px);
|
||||
border-bottom: none;
|
||||
border-top: 1px solid #bbb;
|
||||
}
|
||||
|
||||
&.padded {
|
||||
h5, .title {
|
||||
margin: -20px;
|
||||
|
@ -143,6 +135,7 @@
|
|||
color: #777;
|
||||
}
|
||||
}
|
||||
|
||||
.row_title {
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
|
@ -151,8 +144,4 @@
|
|||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-box-body {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
color: #333;
|
||||
}
|
||||
|
||||
&.btn-white {
|
||||
background: #FFF;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background: #2a79A3;
|
||||
@include linear-gradient(#47A7b7, #2585b5);
|
||||
|
|
|
@ -17,20 +17,43 @@
|
|||
.padded { padding:20px }
|
||||
.ipadded { padding:20px!important }
|
||||
.lborder { border-left:1px solid #eee }
|
||||
.no-padding { padding:0 !important; }
|
||||
.underlined { border-bottom: 1px solid #CCC; }
|
||||
.no-borders { border: none; }
|
||||
.vlink { color: $link_color !important; }
|
||||
.underlined_link { text-decoration: underline; }
|
||||
.borders { border: 1px solid #ccc; @include shade; }
|
||||
.hint { font-style: italic; color: #999; }
|
||||
.light { color: #888 }
|
||||
.tiny { font-weight: normal }
|
||||
|
||||
/** PILLS & TABS**/
|
||||
.nav-pills a:hover { background-color: #888; }
|
||||
.nav-pills .active a { background-color: $style_color; }
|
||||
.nav-pills {
|
||||
.active a {
|
||||
background: $primary_color;
|
||||
}
|
||||
|
||||
> li > a {
|
||||
@include border-radius(0);
|
||||
}
|
||||
&.nav-stacked {
|
||||
> li > a {
|
||||
border-left: 4px solid #EEE;
|
||||
padding: 12px;
|
||||
}
|
||||
> .active > a {
|
||||
border-color: #29B;
|
||||
border-radius: 0;
|
||||
background: #F1F1F1;
|
||||
color: $style_color;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-pills > .active > a > i[class^="icon-"] { background: inherit; }
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* nav-tabs
|
||||
*
|
||||
*/
|
||||
.nav-tabs > li > a, .nav-pills > li > a { color: $style_color; }
|
||||
.nav.nav-tabs {
|
||||
li {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@font-face{
|
||||
font-family: Korolev;
|
||||
src: font-url('korolev-medium-compressed.otf');
|
||||
@font-face{
|
||||
font-family: Yanone;
|
||||
src: font-url('YanoneKaffeesatz-Light.ttf');
|
||||
}
|
||||
|
||||
/** Typo **/
|
||||
$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
|
||||
$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
|
||||
|
|
|
@ -23,14 +23,12 @@
|
|||
border-bottom: 1px solid #ADF;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
@include border-radius(4px 4px 0 0);
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@include border-radius(0 0 4px 4px);
|
||||
border: none;
|
||||
border-bottom: none;
|
||||
|
||||
&.bottom {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.author { color: #999; }
|
||||
|
|
|
@ -62,8 +62,8 @@
|
|||
@mixin header-font {
|
||||
color: $style_color;
|
||||
text-shadow: 0 1px 1px #FFF;
|
||||
font-family: 'Korolev', sans-serif;
|
||||
font-size: 28px;
|
||||
line-height: 48px;
|
||||
font-family: 'Yanone', sans-serif;
|
||||
font-size: 26px;
|
||||
line-height: 42px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ table {
|
|||
}
|
||||
|
||||
th, td {
|
||||
padding: 8px;
|
||||
padding: 10px;
|
||||
line-height: 18px;
|
||||
text-align: left;
|
||||
}
|
||||
|
|
|
@ -1,74 +1,17 @@
|
|||
.commit-box {
|
||||
@extend .main_box;
|
||||
|
||||
.commit-head {
|
||||
@extend .top_box_content;
|
||||
|
||||
.commit-title {
|
||||
line-height: 26px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.commit-description {
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
background-color: white;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.browse-button {
|
||||
@extend .btn;
|
||||
@extend .btn-small;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.commit-info {
|
||||
@extend .middle_box_content;
|
||||
@extend .clearfix;
|
||||
|
||||
.sha-block {
|
||||
text-align: right;
|
||||
&:first-child {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
a {
|
||||
border-bottom: 1px solid #aaa;
|
||||
margin-left: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
&.merge-commit .sha-block {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
.committer {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
.author a,
|
||||
.committer a {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
text-shadow: 0 1px 1px #fff;
|
||||
color: #777;
|
||||
&:hover {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* COMMIT SHOw
|
||||
*
|
||||
*/
|
||||
.commit-committer-link,
|
||||
.commit-author-link {
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
&:hover {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.diff_file {
|
||||
border: 1px solid #CCC;
|
||||
margin-bottom: 1em;
|
||||
|
@ -258,13 +201,6 @@
|
|||
min-width: 65px;
|
||||
font-family: $monospace;
|
||||
}
|
||||
|
||||
.commit-author-name {
|
||||
color: #777;
|
||||
&:hover {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.diff_file_header a,
|
||||
|
|
|
@ -47,6 +47,12 @@
|
|||
.event-info {
|
||||
color: #666;
|
||||
}
|
||||
.event-note {
|
||||
padding-top: 5px;
|
||||
padding-left: 5px;
|
||||
display: inline-block;
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
.avatar {
|
||||
position: relative;
|
||||
|
|
|
@ -33,22 +33,29 @@ header {
|
|||
*
|
||||
*/
|
||||
.app_logo {
|
||||
width: 170px;
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
padding-top: 5px;
|
||||
|
||||
a {
|
||||
float: left;
|
||||
padding: 0px;
|
||||
margin: 0 10px;
|
||||
|
||||
h1 {
|
||||
width: 90px;
|
||||
background: url('logo_dark.png') no-repeat 0px 2px;
|
||||
float: left;
|
||||
margin-left: 2px;
|
||||
padding-left: 45px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
@include header-font;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background-color: #EEE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,7 +67,7 @@ header {
|
|||
position: relative;
|
||||
float: left;
|
||||
margin: 0;
|
||||
margin-right: 30px;
|
||||
margin-left: 15px;
|
||||
@include header-font;
|
||||
}
|
||||
|
||||
|
@ -233,7 +240,7 @@ header {
|
|||
.app_logo {
|
||||
a {
|
||||
h1 {
|
||||
background: url('logo_white.png') no-repeat 0px 2px;
|
||||
background: url('logo_white.png') no-repeat center center;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 1px #111;
|
||||
}
|
||||
|
@ -244,5 +251,23 @@ header {
|
|||
text-shadow: 0 1px 1px #111;
|
||||
}
|
||||
}
|
||||
|
||||
.app_logo {
|
||||
.separator {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
float: left;
|
||||
height: 60px;
|
||||
width: 1px;
|
||||
background: white;
|
||||
border-left: 1px solid #DDD;
|
||||
margin-top: -10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,34 +1,6 @@
|
|||
.issue_form_box {
|
||||
@extend .main_box;
|
||||
.issue_title {
|
||||
@extend .top_box_content;
|
||||
.clearfix {
|
||||
margin-bottom: 0px;
|
||||
input {
|
||||
@extend .span8;
|
||||
}
|
||||
}
|
||||
}
|
||||
.issue_middle_block {
|
||||
@extend .middle_box_content;
|
||||
height: 30px;
|
||||
.issue_assignee {
|
||||
@extend .span6;
|
||||
float: left;
|
||||
}
|
||||
.issue_milestone {
|
||||
@extend .span4;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
.issue_description {
|
||||
@extend .bottom_box_content;
|
||||
}
|
||||
}
|
||||
|
||||
.issues_table {
|
||||
.issue {
|
||||
padding: 7px 10px;
|
||||
padding: 10px;
|
||||
|
||||
.issue_check {
|
||||
float: left;
|
||||
|
@ -82,38 +54,34 @@ input.check_all_issues {
|
|||
}
|
||||
}
|
||||
|
||||
@media (min-width: 800px) { .issues_filters select { width: 160px; } }
|
||||
@media (min-width: 1000px) { .issues_filters select { width: 200px; } }
|
||||
@media (min-width: 800px) { .issues_filters select { width: 160px; } }
|
||||
@media (min-width: 1200px) { .issues_filters select { width: 220px; } }
|
||||
|
||||
@media (min-width: 800px) { .issues_bulk_update select { width: 120px; } }
|
||||
@media (min-width: 1200px) { .issues_bulk_update select { width: 160px; } }
|
||||
|
||||
#issues-table-holder {
|
||||
.issues_filters {
|
||||
form {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top:7px
|
||||
}
|
||||
}
|
||||
|
||||
.issues_bulk_update {
|
||||
margin: 0;
|
||||
form {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top:7px
|
||||
float:left;
|
||||
}
|
||||
|
||||
.update_selected_issues {
|
||||
position: relative;
|
||||
top:-2px;
|
||||
top:5px;
|
||||
margin-left: 4px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.update_issues_text {
|
||||
padding: 3px;
|
||||
line-height: 18px;
|
||||
line-height: 28px;
|
||||
float: left;
|
||||
color: #479;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,3 @@
|
|||
/**
|
||||
* MR form
|
||||
*
|
||||
*/
|
||||
|
||||
.mr_branch_box {
|
||||
@extend .ui-box;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.body {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* MR -> show: Automerge widget
|
||||
|
@ -47,6 +33,7 @@
|
|||
}
|
||||
label {
|
||||
color: #444;
|
||||
text-align: left
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +43,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.mr_nav_tabs {
|
||||
.merge-request .nav-tabs{
|
||||
li {
|
||||
a {
|
||||
font-weight: bold;
|
||||
|
@ -67,7 +54,7 @@
|
|||
}
|
||||
|
||||
li.merge_request {
|
||||
padding: 7px 10px;
|
||||
padding: 10px;
|
||||
img.avatar {
|
||||
width: 32px;
|
||||
margin-top: 1px;
|
||||
|
@ -78,7 +65,7 @@ li.merge_request {
|
|||
}
|
||||
}
|
||||
|
||||
.merge_in_progress {
|
||||
.merge-in-progress {
|
||||
@extend .padded;
|
||||
@extend .append-bottom-10;
|
||||
}
|
||||
|
@ -120,19 +107,3 @@ li.merge_request {
|
|||
.mr_direction_tip {
|
||||
margin-top:40px
|
||||
}
|
||||
|
||||
.merge_requests_form_box {
|
||||
@extend .main_box;
|
||||
.merge_requests_middle_box {
|
||||
@extend .middle_box_content;
|
||||
height: 30px;
|
||||
.merge_requests_assignee {
|
||||
@extend .span6;
|
||||
float: left;
|
||||
}
|
||||
.merge_requests_milestone {
|
||||
@extend .span4;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -294,6 +294,8 @@ ul.notes {
|
|||
padding: 4px 6px;
|
||||
}
|
||||
.note_text {
|
||||
border: 1px solid #DDD;
|
||||
box-shadow: none;
|
||||
font-size: 14px;
|
||||
height: 80px;
|
||||
width: 98.6%;
|
||||
|
|
|
@ -8,16 +8,12 @@
|
|||
|
||||
.groups_box,
|
||||
.projects_box {
|
||||
> h5 {
|
||||
color: $style_color;
|
||||
font-size: 16px;
|
||||
text-shadow: 0 1px 1px #fff;
|
||||
padding: 2px 10px;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
> .title {
|
||||
padding: 2px 15px;
|
||||
}
|
||||
.nav-projects-tabs li { padding: 0; }
|
||||
.well-list {
|
||||
li { padding: 15px; }
|
||||
.arrow {
|
||||
float: right;
|
||||
padding: 10px;
|
||||
|
@ -109,7 +105,7 @@ ul.nav.nav-projects-tabs {
|
|||
|
||||
li {
|
||||
a {
|
||||
padding: 4px 20px;
|
||||
padding: 6px 25px;
|
||||
margin-top: 2px;
|
||||
border-color: #DDD;
|
||||
background-color: #EEE;
|
||||
|
|
|
@ -4,21 +4,8 @@
|
|||
*
|
||||
*/
|
||||
.ui_basic {
|
||||
.app_logo {
|
||||
.separator {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
float: left;
|
||||
height: 60px;
|
||||
width: 1px;
|
||||
background: white;
|
||||
border-left: 1px solid #DDD;
|
||||
margin-top: -10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,15 @@
|
|||
&.navbar-gitlab {
|
||||
.navbar-inner {
|
||||
background: #657;
|
||||
.app_logo {
|
||||
&:hover {
|
||||
background-color: #6A5A7A;
|
||||
}
|
||||
}
|
||||
.separator {
|
||||
background: #546;
|
||||
border-left: 1px solid #706080;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,15 @@
|
|||
&.navbar-gitlab {
|
||||
.navbar-inner {
|
||||
background: #708090;
|
||||
.app_logo {
|
||||
&:hover {
|
||||
background-color: #6A7A8A;
|
||||
}
|
||||
}
|
||||
.separator {
|
||||
background: #607080;
|
||||
border-left: 1px solid #8090A0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,21 +46,26 @@
|
|||
.app_logo {
|
||||
a {
|
||||
h1 {
|
||||
background: url('logo_white.png') no-repeat 0px 2px;
|
||||
background: url('logo_white.png') no-repeat center center;
|
||||
color: #eee;
|
||||
text-shadow: 0 1px 1px #111;
|
||||
}
|
||||
}
|
||||
.separator {
|
||||
display: none;
|
||||
&:hover {
|
||||
background-color: #41464e;
|
||||
}
|
||||
|
||||
}
|
||||
.project_name {
|
||||
color: #eee;
|
||||
text-shadow: 0 1px 1px #111;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
background: #31363E;
|
||||
border-left: 1px solid #666;
|
||||
}
|
||||
|
||||
/*
|
||||
* End of Application Header
|
||||
*
|
||||
|
|
|
@ -17,6 +17,15 @@
|
|||
&.navbar-gitlab {
|
||||
.navbar-inner {
|
||||
background: #567;
|
||||
.app_logo {
|
||||
&:hover {
|
||||
background-color: #516171;
|
||||
}
|
||||
}
|
||||
.separator {
|
||||
background: #456;
|
||||
border-left: 1px solid #678;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,16 +9,16 @@ class CommitLoadContext < BaseContext
|
|||
status: :ok
|
||||
}
|
||||
|
||||
commit = project.commit(params[:id])
|
||||
commit = project.repository.commit(params[:id])
|
||||
|
||||
if commit
|
||||
commit = CommitDecorator.decorate(commit)
|
||||
line_notes = project.commit_line_notes(commit)
|
||||
line_notes = project.notes.for_commit_id(commit.id).inline
|
||||
|
||||
result[:commit] = commit
|
||||
result[:note] = project.build_commit_note(commit)
|
||||
result[:line_notes] = line_notes
|
||||
result[:notes_count] = line_notes.count + project.commit_notes(commit).count
|
||||
result[:notes_count] = project.notes.for_commit_id(commit.id).count
|
||||
|
||||
begin
|
||||
result[:suppress_diff] = true if commit.diffs.size > Commit::DIFF_SAFE_SIZE && !params[:force_show_diff]
|
||||
|
|
31
app/contexts/filter_context.rb
Normal file
31
app/contexts/filter_context.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
class FilterContext
|
||||
attr_accessor :items, :params
|
||||
|
||||
def initialize(items, params)
|
||||
@items = items
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
apply_filter(items)
|
||||
end
|
||||
|
||||
def apply_filter items
|
||||
if params[:project_id]
|
||||
items = items.where(project_id: params[:project_id])
|
||||
end
|
||||
|
||||
if params[:search].present?
|
||||
items = items.search(params[:search])
|
||||
end
|
||||
|
||||
case params[:status]
|
||||
when 'closed'
|
||||
items.closed
|
||||
when 'all'
|
||||
items
|
||||
else
|
||||
items.opened
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,7 +4,7 @@ class IssuesListContext < BaseContext
|
|||
attr_accessor :issues
|
||||
|
||||
def execute
|
||||
@issues = case params[:f]
|
||||
@issues = case params[:status]
|
||||
when issues_filter[:all] then @project.issues
|
||||
when issues_filter[:closed] then @project.issues.closed
|
||||
when issues_filter[:to_me] then @project.issues.opened.assigned(current_user)
|
||||
|
|
|
@ -9,7 +9,7 @@ module Notes
|
|||
|
||||
@notes = case target_type
|
||||
when "commit"
|
||||
project.commit_notes(project.commit(target_id)).fresh
|
||||
project.notes.for_commit_id(target_id).not_inline.fresh
|
||||
when "issue"
|
||||
project.issues.find(target_id).notes.inc_author.fresh
|
||||
when "merge_request"
|
||||
|
@ -18,7 +18,7 @@ module Notes
|
|||
project.snippets.find(target_id).notes.fresh
|
||||
when "wall"
|
||||
# this is the only case, where the order is DESC
|
||||
project.common_notes.order("created_at DESC, id DESC").limit(50)
|
||||
project.notes.common.inc_author_project.order("created_at DESC, id DESC").limit(50)
|
||||
end
|
||||
|
||||
@notes = if after_id
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class TestHookContext < BaseContext
|
||||
def execute
|
||||
hook = project.hooks.find(params[:id])
|
||||
commits = project.commits(project.default_branch, nil, 3)
|
||||
commits = project.repository.commits(project.default_branch, nil, 3)
|
||||
data = project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", current_user)
|
||||
hook.execute(data)
|
||||
end
|
||||
|
|
|
@ -3,10 +3,6 @@ class Admin::DashboardController < AdminController
|
|||
@projects = Project.order("created_at DESC").limit(10)
|
||||
@users = User.order("created_at DESC").limit(10)
|
||||
|
||||
@resque_accessible = true
|
||||
@workers = Resque.workers
|
||||
@pending_jobs = Resque.size(:post_receive)
|
||||
|
||||
rescue Redis::InheritedError
|
||||
@resque_accessible = false
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Admin::GroupsController < AdminController
|
||||
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update]
|
||||
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update]
|
||||
|
||||
def index
|
||||
@groups = Group.order('name ASC')
|
||||
|
@ -12,6 +12,8 @@ class Admin::GroupsController < AdminController
|
|||
@projects = @projects.not_in_group(@group) if @group.projects.present?
|
||||
@projects = @projects.all
|
||||
@projects.reject!(&:empty_repo?)
|
||||
|
||||
@users = User.active
|
||||
end
|
||||
|
||||
def new
|
||||
|
@ -65,7 +67,14 @@ class Admin::GroupsController < AdminController
|
|||
redirect_to :back, notice: 'Group was successfully updated.'
|
||||
end
|
||||
|
||||
def project_teams_update
|
||||
@group.add_users_to_project_teams(params[:user_ids], params[:project_access])
|
||||
redirect_to [:admin, @group], notice: 'Users was successfully added.'
|
||||
end
|
||||
|
||||
def destroy
|
||||
@group.truncate_teams
|
||||
|
||||
@group.destroy
|
||||
|
||||
redirect_to admin_groups_path, notice: 'Group was successfully deleted.'
|
||||
|
|
|
@ -10,6 +10,7 @@ class Admin::ProjectsController < AdminController
|
|||
end
|
||||
|
||||
def show
|
||||
@repository = @project.repository
|
||||
@users = User.active
|
||||
@users = @users.not_in_project(@project) if @project.users.present?
|
||||
@users = @users.all
|
||||
|
@ -19,7 +20,7 @@ class Admin::ProjectsController < AdminController
|
|||
end
|
||||
|
||||
def team_update
|
||||
@project.add_users_ids_to_team(params[:user_ids], params[:project_access])
|
||||
@project.team.add_users_ids(params[:user_ids], params[:project_access])
|
||||
|
||||
redirect_to [:admin, @project], notice: 'Project was successfully updated.'
|
||||
end
|
||||
|
@ -35,6 +36,9 @@ class Admin::ProjectsController < AdminController
|
|||
end
|
||||
|
||||
def destroy
|
||||
# Delete team first in order to prevent multiple gitolite calls
|
||||
@project.team.truncate
|
||||
|
||||
@project.destroy
|
||||
|
||||
redirect_to admin_projects_path, notice: 'Project was successfully deleted.'
|
||||
|
|
|
@ -3,13 +3,13 @@ class Admin::UsersController < AdminController
|
|||
@admin_users = User.scoped
|
||||
@admin_users = @admin_users.filter(params[:filter])
|
||||
@admin_users = @admin_users.search(params[:name]) if params[:name].present?
|
||||
@admin_users = @admin_users.order("name ASC").page(params[:page])
|
||||
@admin_users = @admin_users.alphabetically.page(params[:page])
|
||||
end
|
||||
|
||||
def show
|
||||
@admin_user = User.find(params[:id])
|
||||
|
||||
@projects = if @admin_user.projects.empty?
|
||||
@projects = if @admin_user.authorized_projects.empty?
|
||||
Project
|
||||
else
|
||||
Project.without_user(@admin_user)
|
||||
|
@ -19,9 +19,9 @@ class Admin::UsersController < AdminController
|
|||
def team_update
|
||||
@admin_user = User.find(params[:id])
|
||||
|
||||
UsersProject.user_bulk_import(
|
||||
@admin_user,
|
||||
UsersProject.add_users_into_projects(
|
||||
params[:project_ids],
|
||||
[@admin_user.id],
|
||||
params[:project_access]
|
||||
)
|
||||
|
||||
|
@ -98,7 +98,7 @@ class Admin::UsersController < AdminController
|
|||
|
||||
def destroy
|
||||
@admin_user = User.find(params[:id])
|
||||
if @admin_user.my_own_projects.count > 0
|
||||
if @admin_user.personal_projects.count > 0
|
||||
redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return
|
||||
end
|
||||
@admin_user.destroy
|
||||
|
|
|
@ -76,6 +76,12 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
end
|
||||
|
||||
def repository
|
||||
@repository ||= project.repository
|
||||
rescue Grit::NoSuchPathError
|
||||
nil
|
||||
end
|
||||
|
||||
def add_abilities
|
||||
abilities << Ability
|
||||
end
|
||||
|
|
|
@ -9,10 +9,10 @@ class CommitsController < ProjectResourceController
|
|||
before_filter :require_non_empty_project
|
||||
|
||||
def show
|
||||
@repo = @project.repo
|
||||
@repo = @project.repository
|
||||
@limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
|
||||
|
||||
@commits = @project.commits(@ref, @path, @limit, @offset)
|
||||
@commits = @repo.commits(@ref, @path, @limit, @offset)
|
||||
@commits = CommitDecorator.decorate(@commits)
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
@ -20,7 +20,7 @@ class DashboardController < ApplicationController
|
|||
|
||||
@projects = @projects.page(params[:page]).per(30)
|
||||
|
||||
@events = Event.in_projects(current_user.project_ids)
|
||||
@events = Event.in_projects(current_user.authorized_projects.pluck(:id))
|
||||
@events = @event_filter.apply_filter(@events)
|
||||
@events = @events.limit(20).offset(params[:offset] || 0)
|
||||
|
||||
|
@ -36,14 +36,14 @@ class DashboardController < ApplicationController
|
|||
# Get authored or assigned open merge requests
|
||||
def merge_requests
|
||||
@merge_requests = current_user.cared_merge_requests
|
||||
@merge_requests = dashboard_filter(@merge_requests)
|
||||
@merge_requests = FilterContext.new(@merge_requests, params).execute
|
||||
@merge_requests = @merge_requests.recent.page(params[:page]).per(20)
|
||||
end
|
||||
|
||||
# Get only assigned issues
|
||||
def issues
|
||||
@issues = current_user.assigned_issues
|
||||
@issues = dashboard_filter(@issues)
|
||||
@issues = FilterContext.new(@issues, params).execute
|
||||
@issues = @issues.recent.page(params[:page]).per(20)
|
||||
@issues = @issues.includes(:author, :project)
|
||||
|
||||
|
@ -60,25 +60,7 @@ class DashboardController < ApplicationController
|
|||
end
|
||||
|
||||
def event_filter
|
||||
@event_filter ||= EventFilter.new(params[:event_filter])
|
||||
end
|
||||
|
||||
def dashboard_filter items
|
||||
if params[:project_id]
|
||||
items = items.where(project_id: params[:project_id])
|
||||
end
|
||||
|
||||
if params[:search].present?
|
||||
items = items.search(params[:search])
|
||||
end
|
||||
|
||||
case params[:status]
|
||||
when 'closed'
|
||||
items.closed
|
||||
when 'all'
|
||||
items
|
||||
else
|
||||
items.opened
|
||||
end
|
||||
filters = cookies['event_filter'].split(',') if cookies['event_filter']
|
||||
@event_filter ||= EventFilter.new(filters)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,15 +21,16 @@ class GroupsController < ApplicationController
|
|||
|
||||
# Get authored or assigned open merge requests
|
||||
def merge_requests
|
||||
@merge_requests = current_user.cared_merge_requests.opened
|
||||
@merge_requests = @merge_requests.of_group(@group).recent.page(params[:page]).per(20)
|
||||
@merge_requests = current_user.cared_merge_requests.of_group(@group)
|
||||
@merge_requests = FilterContext.new(@merge_requests, params).execute
|
||||
@merge_requests = @merge_requests.recent.page(params[:page]).per(20)
|
||||
end
|
||||
|
||||
# Get only assigned issues
|
||||
def issues
|
||||
@user = current_user
|
||||
@issues = current_user.assigned_issues.opened
|
||||
@issues = @issues.of_group(@group).recent.page(params[:page]).per(20)
|
||||
@issues = current_user.assigned_issues.of_group(@group)
|
||||
@issues = FilterContext.new(@issues, params).execute
|
||||
@issues = @issues.recent.page(params[:page]).per(20)
|
||||
@issues = @issues.includes(:author, :project)
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -44,6 +45,7 @@ class GroupsController < ApplicationController
|
|||
@projects = result[:projects]
|
||||
@merge_requests = result[:merge_requests]
|
||||
@issues = result[:issues]
|
||||
@wiki_pages = result[:wiki_pages]
|
||||
end
|
||||
|
||||
def people
|
||||
|
@ -53,9 +55,16 @@ class GroupsController < ApplicationController
|
|||
|
||||
if @project
|
||||
@team_member = @project.users_projects.new
|
||||
else
|
||||
@team_member = UsersProject.new
|
||||
end
|
||||
end
|
||||
|
||||
def team_members
|
||||
@group.add_users_to_project_teams(params[:user_ids], params[:project_access])
|
||||
redirect_to people_group_path(@group), notice: 'Users was successfully added.'
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def group
|
||||
|
@ -63,7 +72,7 @@ class GroupsController < ApplicationController
|
|||
end
|
||||
|
||||
def projects
|
||||
@projects ||= group.projects.authorized_for(current_user).sorted_by_activity
|
||||
@projects ||= current_user.authorized_projects.where(namespace_id: group.id).sorted_by_activity
|
||||
end
|
||||
|
||||
def project_ids
|
||||
|
|
|
@ -74,6 +74,8 @@ class MergeRequestsController < ProjectResourceController
|
|||
@merge_request.check_if_can_be_merged
|
||||
end
|
||||
render json: {state: @merge_request.human_state}
|
||||
rescue Gitlab::SatelliteNotExistError
|
||||
render json: {state: :no_satellite}
|
||||
end
|
||||
|
||||
def automerge
|
||||
|
@ -88,12 +90,12 @@ class MergeRequestsController < ProjectResourceController
|
|||
end
|
||||
|
||||
def branch_from
|
||||
@commit = project.commit(params[:ref])
|
||||
@commit = @repository.commit(params[:ref])
|
||||
@commit = CommitDecorator.decorate(@commit)
|
||||
end
|
||||
|
||||
def branch_to
|
||||
@commit = project.commit(params[:ref])
|
||||
@commit = @repository.commit(params[:ref])
|
||||
@commit = CommitDecorator.decorate(@commit)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
class ProjectResourceController < ApplicationController
|
||||
before_filter :project
|
||||
before_filter :repository
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ require Rails.root.join('lib', 'gitlab', 'graph', 'json_builder')
|
|||
|
||||
class ProjectsController < ProjectResourceController
|
||||
skip_before_filter :project, only: [:new, :create]
|
||||
skip_before_filter :repository, only: [:new, :create]
|
||||
|
||||
# Authorize
|
||||
before_filter :authorize_read_project!, except: [:index, :new, :create]
|
||||
|
@ -58,7 +59,7 @@ class ProjectsController < ProjectResourceController
|
|||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
unless @project.empty_repo?
|
||||
if @project.repository && !@project.repository.empty?
|
||||
@last_push = current_user.recent_push(@project.id)
|
||||
render :show
|
||||
else
|
||||
|
@ -102,11 +103,10 @@ class ProjectsController < ProjectResourceController
|
|||
def destroy
|
||||
return access_denied! unless can?(current_user, :remove_project, project)
|
||||
|
||||
# Disable the UsersProject update_repository call, otherwise it will be
|
||||
# called once for every person removed from the project
|
||||
UsersProject.skip_callback(:destroy, :after, :update_repository)
|
||||
# Delete team first in order to prevent multiple gitolite calls
|
||||
project.team.truncate
|
||||
|
||||
project.destroy
|
||||
UsersProject.set_callback(:destroy, :after, :update_repository)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to root_path }
|
||||
|
|
|
@ -12,7 +12,7 @@ class RefsController < ProjectResourceController
|
|||
respond_to do |format|
|
||||
format.html do
|
||||
new_path = if params[:destination] == "tree"
|
||||
project_tree_path(@project, @ref)
|
||||
project_tree_path(@project, (@ref + "/" + params[:path]))
|
||||
else
|
||||
project_commits_path(@project, @ref)
|
||||
end
|
||||
|
@ -31,7 +31,7 @@ class RefsController < ProjectResourceController
|
|||
contents = @tree.contents
|
||||
@logs = contents.map do |content|
|
||||
file = params[:path] ? File.join(params[:path], content.name) : content.name
|
||||
last_commit = @project.commits(@commit.id, file, 1).last
|
||||
last_commit = @repo.commits(@commit.id, file, 1).last
|
||||
last_commit = CommitDecorator.decorate(last_commit)
|
||||
{
|
||||
file_name: content.name,
|
||||
|
@ -45,10 +45,10 @@ class RefsController < ProjectResourceController
|
|||
def define_tree_vars
|
||||
params[:path] = nil if params[:path].blank?
|
||||
|
||||
@repo = project.repo
|
||||
@commit = project.commit(@ref)
|
||||
@repo = project.repository
|
||||
@commit = @repo.commit(@ref)
|
||||
@commit = CommitDecorator.decorate(@commit)
|
||||
@tree = Tree.new(@commit.tree, project, @ref, params[:path])
|
||||
@tree = Tree.new(@commit.tree, @ref, params[:path])
|
||||
@tree = TreeDecorator.new(@tree)
|
||||
@hex_path = Digest::SHA1.hexdigest(params[:path] || "")
|
||||
|
||||
|
|
|
@ -5,19 +5,19 @@ class RepositoriesController < ProjectResourceController
|
|||
before_filter :require_non_empty_project
|
||||
|
||||
def show
|
||||
@activities = @project.commits_with_refs(20)
|
||||
@activities = @repository.commits_with_refs(20)
|
||||
end
|
||||
|
||||
def branches
|
||||
@branches = @project.branches
|
||||
@branches = @repository.branches
|
||||
end
|
||||
|
||||
def tags
|
||||
@tags = @project.tags
|
||||
@tags = @repository.tags
|
||||
end
|
||||
|
||||
def stats
|
||||
@stats = Gitlab::GitStats.new(@project.repo, @project.root_ref)
|
||||
@stats = Gitlab::GitStats.new(@repository.raw, @repository.root_ref)
|
||||
@graph = @stats.graph
|
||||
end
|
||||
|
||||
|
@ -27,7 +27,7 @@ class RepositoriesController < ProjectResourceController
|
|||
end
|
||||
|
||||
|
||||
file_path = @project.archive_repo(params[:ref])
|
||||
file_path = @repository.archive_repo(params[:ref])
|
||||
|
||||
if file_path
|
||||
# Send file to user
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class SearchController < ApplicationController
|
||||
def show
|
||||
result = SearchContext.new(current_user.project_ids, params).execute
|
||||
result = SearchContext.new(current_user.authorized_projects.map(&:id), params).execute
|
||||
|
||||
@projects = result[:projects]
|
||||
@merge_requests = result[:merge_requests]
|
||||
|
|
|
@ -26,7 +26,7 @@ class ServicesController < ProjectResourceController
|
|||
end
|
||||
|
||||
def test
|
||||
commits = project.commits(project.default_branch, nil, 3)
|
||||
commits = project.repository.commits(project.default_branch, nil, 3)
|
||||
data = project.post_receive_data(commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", current_user)
|
||||
|
||||
@service = project.gitlab_ci_service
|
||||
|
|
|
@ -16,7 +16,7 @@ class SnippetsController < ProjectResourceController
|
|||
respond_to :html
|
||||
|
||||
def index
|
||||
@snippets = @project.snippets.fresh
|
||||
@snippets = @project.snippets.fresh.non_expired
|
||||
end
|
||||
|
||||
def new
|
||||
|
|
|
@ -16,10 +16,9 @@ class TeamMembersController < ProjectResourceController
|
|||
end
|
||||
|
||||
def create
|
||||
@project.add_users_ids_to_team(
|
||||
params[:user_ids],
|
||||
params[:project_access]
|
||||
)
|
||||
users = User.where(id: params[:user_ids])
|
||||
|
||||
@project.team << [users, params[:project_access]]
|
||||
|
||||
if params[:redirect_to]
|
||||
redirect_to params[:redirect_to]
|
||||
|
@ -50,7 +49,7 @@ class TeamMembersController < ProjectResourceController
|
|||
|
||||
def apply_import
|
||||
giver = Project.find(params[:source_project_id])
|
||||
status = UsersProject.import_team(giver, project)
|
||||
status = @project.team.import(giver)
|
||||
notice = status ? "Succesfully imported" : "Import failed"
|
||||
|
||||
redirect_to project_team_members_path(project), notice: notice
|
||||
|
|
|
@ -22,7 +22,7 @@ class TreeController < ProjectResourceController
|
|||
end
|
||||
|
||||
def edit
|
||||
@last_commit = @project.last_commit_for(@ref, @path).sha
|
||||
@last_commit = @project.repository.last_commit_for(@ref, @path).sha
|
||||
end
|
||||
|
||||
def update
|
||||
|
|
|
@ -6,16 +6,14 @@ class TreeDecorator < ApplicationDecorator
|
|||
part_path = ""
|
||||
parts = path.split("\/")
|
||||
|
||||
#parts = parts[0...-1] if is_blob?
|
||||
|
||||
yield(h.link_to("..", "#")) if parts.count > max_links
|
||||
yield('..', nil) if parts.count > max_links
|
||||
|
||||
parts.each do |part|
|
||||
part_path = File.join(part_path, part) unless part_path.empty?
|
||||
part_path = part if part_path.empty?
|
||||
|
||||
next unless parts.last(2).include?(part) if parts.count > max_links
|
||||
yield(h.link_to(h.truncate(part, length: 40), h.project_tree_path(project, h.tree_join(ref, part_path))))
|
||||
yield(part, h.tree_join(ref, part_path))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -26,7 +24,7 @@ class TreeDecorator < ApplicationDecorator
|
|||
|
||||
def up_dir_path
|
||||
file = File.join(path, "..")
|
||||
h.project_tree_path(project, h.tree_join(ref, file))
|
||||
h.tree_join(ref, file)
|
||||
end
|
||||
|
||||
def readme
|
||||
|
|
|
@ -53,7 +53,7 @@ module ApplicationHelper
|
|||
|
||||
def last_commit(project)
|
||||
if project.repo_exists?
|
||||
time_ago_in_words(project.commit.committed_date) + " ago"
|
||||
time_ago_in_words(project.repository.commit.committed_date) + " ago"
|
||||
else
|
||||
"Never"
|
||||
end
|
||||
|
@ -62,9 +62,11 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def grouped_options_refs(destination = :tree)
|
||||
repository = @project.repository
|
||||
|
||||
options = [
|
||||
["Branch", @project.branch_names ],
|
||||
[ "Tag", @project.tag_names ]
|
||||
["Branch", repository.branch_names ],
|
||||
[ "Tag", repository.tag_names ]
|
||||
]
|
||||
|
||||
# If reference is commit id -
|
||||
|
@ -78,7 +80,8 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def search_autocomplete_source
|
||||
projects = current_user.projects.map{ |p| { label: p.name_with_namespace, url: project_path(p) } }
|
||||
projects = current_user.authorized_projects.map { |p| { label: p.name_with_namespace, url: project_path(p) } }
|
||||
groups = current_user.authorized_groups.map { |group| { label: "<group> #{group.name}", url: group_path(group) } }
|
||||
|
||||
default_nav = [
|
||||
{ label: "My Profile", url: profile_path },
|
||||
|
@ -99,21 +102,21 @@ module ApplicationHelper
|
|||
]
|
||||
|
||||
project_nav = []
|
||||
if @project && !@project.new_record?
|
||||
if @project && @project.repository && @project.repository.root_ref
|
||||
project_nav = [
|
||||
{ label: "#{@project.name} Issues", url: project_issues_path(@project) },
|
||||
{ label: "#{@project.name} Commits", url: project_commits_path(@project, @ref || @project.root_ref) },
|
||||
{ label: "#{@project.name} Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },
|
||||
{ label: "#{@project.name} Merge Requests", url: project_merge_requests_path(@project) },
|
||||
{ label: "#{@project.name} Milestones", url: project_milestones_path(@project) },
|
||||
{ label: "#{@project.name} Snippets", url: project_snippets_path(@project) },
|
||||
{ label: "#{@project.name} Team", url: project_team_index_path(@project) },
|
||||
{ label: "#{@project.name} Tree", url: project_tree_path(@project, @ref || @project.root_ref) },
|
||||
{ label: "#{@project.name} Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) },
|
||||
{ label: "#{@project.name} Wall", url: wall_project_path(@project) },
|
||||
{ label: "#{@project.name} Wiki", url: project_wikis_path(@project) },
|
||||
]
|
||||
end
|
||||
|
||||
[projects, default_nav, project_nav, help_nav].flatten.to_json
|
||||
[groups, projects, default_nav, project_nav, help_nav].flatten.to_json
|
||||
end
|
||||
|
||||
def emoji_autocomplete_source
|
||||
|
@ -139,6 +142,7 @@ module ApplicationHelper
|
|||
event.last_push_to_non_root? &&
|
||||
!event.rm_ref? &&
|
||||
event.project &&
|
||||
event.project.repository &&
|
||||
event.project.merge_requests_enabled
|
||||
end
|
||||
|
||||
|
|
|
@ -70,4 +70,12 @@ module CommitsHelper
|
|||
escape_javascript(render 'commits/commit', commit: commit)
|
||||
end
|
||||
end
|
||||
|
||||
def diff_line_content(line)
|
||||
if line.blank?
|
||||
" "
|
||||
else
|
||||
line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,25 +20,8 @@ module EventsHelper
|
|||
[event.action_name, target].join(" ")
|
||||
end
|
||||
|
||||
def event_image event
|
||||
event_image_path = if event.push?
|
||||
"event_push.png"
|
||||
elsif event.merged?
|
||||
"event_mr_merged.png"
|
||||
end
|
||||
|
||||
return nil unless event_image_path
|
||||
|
||||
content_tag :div, class: 'event_icon' do
|
||||
image_tag event_image_path
|
||||
end
|
||||
end
|
||||
|
||||
def event_filter_link key, tooltip
|
||||
key = key.to_s
|
||||
|
||||
filter = @event_filter.options key
|
||||
|
||||
inactive = if @event_filter.active? key
|
||||
nil
|
||||
else
|
||||
|
@ -46,7 +29,7 @@ module EventsHelper
|
|||
end
|
||||
|
||||
content_tag :div, class: "filter_icon #{inactive}" do
|
||||
link_to dashboard_path(event_filter: filter), class: 'has_tooltip', 'data-original-title' => tooltip do
|
||||
link_to dashboard_path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do
|
||||
image_tag "event_filter_#{key}.png"
|
||||
end
|
||||
end
|
||||
|
|
17
app/helpers/groups_helper.rb
Normal file
17
app/helpers/groups_helper.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
module GroupsHelper
|
||||
def group_filter_path(entity, options={})
|
||||
exist_opts = {
|
||||
status: params[:status],
|
||||
project_id: params[:project_id],
|
||||
}
|
||||
|
||||
options = exist_opts.merge(options)
|
||||
|
||||
case entity
|
||||
when 'issue' then
|
||||
issues_group_path(@group, options)
|
||||
when 'merge_request'
|
||||
merge_requests_group_path(@group, options)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,7 +4,7 @@ module MergeRequestsHelper
|
|||
event.project,
|
||||
merge_request: {
|
||||
source_branch: event.branch_name,
|
||||
target_branch: event.project.root_ref,
|
||||
target_branch: event.project.repository.root_ref,
|
||||
title: event.branch_name.titleize
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module NamespacesHelper
|
||||
def namespaces_options(selected = :current_user, scope = :default)
|
||||
groups = current_user.namespaces.select {|n| n.type == 'Group'}
|
||||
groups = current_user.owned_groups.select {|n| n.type == 'Group'}
|
||||
|
||||
users = if scope == :all
|
||||
Namespace.root
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
class Notify < ActionMailer::Base
|
||||
include Resque::Mailer
|
||||
|
||||
add_template_helper ApplicationHelper
|
||||
add_template_helper GitlabMarkdownHelper
|
||||
|
||||
default_url_options[:host] = Gitlab.config.gitlab.host
|
||||
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
|
||||
default_url_options[:port] = Gitlab.config.gitlab.port if Gitlab.config.gitlab_on_non_standard_port?
|
||||
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
|
||||
|
||||
default from: Gitlab.config.gitlab.email_from
|
||||
|
||||
|
@ -87,7 +88,7 @@ class Notify < ActionMailer::Base
|
|||
def note_wall_email(recipient_id, note_id)
|
||||
@note = Note.find(note_id)
|
||||
@project = @note.project
|
||||
mail(to: recipient(recipient_id), subject: subject)
|
||||
mail(to: recipient(recipient_id), subject: subject("note on wall"))
|
||||
end
|
||||
|
||||
|
||||
|
@ -147,12 +148,15 @@ class Notify < ActionMailer::Base
|
|||
# >> @project = Project.last
|
||||
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
|
||||
# >> subject('Lorem ipsum')
|
||||
# => "GitLab | Lorem ipsum | Ruby on Rails"
|
||||
# => "GitLab | Ruby on Rails | Lorem ipsum "
|
||||
#
|
||||
# # Accepts multiple arguments
|
||||
# >> subject('Lorem ipsum', 'Dolor sit amet')
|
||||
# => "GitLab | Lorem ipsum | Dolor sit amet"
|
||||
def subject(*extra)
|
||||
"GitLab | " << extra.join(' | ') << (@project ? " | #{@project.name}" : "")
|
||||
subject = "GitLab"
|
||||
subject << (@project ? " | #{@project.name_with_namespace}" : "")
|
||||
subject << " | " + extra.join(' | ') if extra.present?
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,35 +15,26 @@ class Ability
|
|||
def project_abilities(user, project)
|
||||
rules = []
|
||||
|
||||
team = project.team
|
||||
|
||||
# Rules based on role in project
|
||||
if project.master_access_for?(user)
|
||||
if team.masters.include?(user)
|
||||
rules << project_master_rules
|
||||
|
||||
elsif project.dev_access_for?(user)
|
||||
elsif team.developers.include?(user)
|
||||
rules << project_dev_rules
|
||||
|
||||
elsif project.report_access_for?(user)
|
||||
elsif team.reporters.include?(user)
|
||||
rules << project_report_rules
|
||||
|
||||
elsif project.guest_access_for?(user)
|
||||
elsif team.guests.include?(user)
|
||||
rules << project_guest_rules
|
||||
end
|
||||
|
||||
if project.namespace
|
||||
# If user own project namespace
|
||||
# (Ex. group owner or account owner)
|
||||
if project.namespace.owner == user
|
||||
rules << project_admin_rules
|
||||
end
|
||||
else
|
||||
# For compatibility with global projects
|
||||
# use projects.owner_id
|
||||
if project.owner == user
|
||||
rules << project_admin_rules
|
||||
end
|
||||
if project.owner == user
|
||||
rules << project_admin_rules
|
||||
end
|
||||
|
||||
|
||||
rules.flatten
|
||||
end
|
||||
|
||||
|
@ -107,9 +98,12 @@ class Ability
|
|||
def group_abilities user, group
|
||||
rules = []
|
||||
|
||||
rules << [
|
||||
:manage_group
|
||||
] if group.owner == user
|
||||
# Only group owner and administrators can manage group
|
||||
if group.owner == user || user.admin?
|
||||
rules << [
|
||||
:manage_group
|
||||
]
|
||||
end
|
||||
|
||||
rules.flatten
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ class Commit
|
|||
attr_accessor :commit, :head, :refs
|
||||
|
||||
delegate :message, :authored_date, :committed_date, :parents, :sha,
|
||||
:date, :committer, :author, :message, :diffs, :tree, :id,
|
||||
:date, :committer, :author, :diffs, :tree, :id, :stats,
|
||||
:to_patch, to: :commit
|
||||
|
||||
class << self
|
||||
|
@ -83,8 +83,8 @@ class Commit
|
|||
|
||||
return result unless from && to
|
||||
|
||||
first = project.commit(to.try(:strip))
|
||||
last = project.commit(from.try(:strip))
|
||||
first = project.repository.commit(to.try(:strip))
|
||||
last = project.repository.commit(from.try(:strip))
|
||||
|
||||
if first && last
|
||||
result[:same] = (first.id == last.id)
|
||||
|
@ -98,6 +98,8 @@ class Commit
|
|||
end
|
||||
|
||||
def initialize(raw_commit, head = nil)
|
||||
raise "Nil as raw commit passed" unless raw_commit
|
||||
|
||||
@commit = raw_commit
|
||||
@head = head
|
||||
end
|
||||
|
@ -136,17 +138,17 @@ class Commit
|
|||
end
|
||||
|
||||
def prev_commit
|
||||
parents.try :first
|
||||
@prev_commit ||= if parents.present?
|
||||
Commit.new(parents.first)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def prev_commit_id
|
||||
prev_commit.try :id
|
||||
end
|
||||
|
||||
def parents_count
|
||||
parents && parents.count || 0
|
||||
end
|
||||
|
||||
# Shows the diff between the commit's parent and the commit.
|
||||
#
|
||||
# Cuts out the header and stats from #to_patch and returns only the diff.
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
# == Issuable concern
|
||||
#
|
||||
# Contains common functionality shared between Issues and MergeRequests
|
||||
module IssueCommonality
|
||||
#
|
||||
# Used by Issue, MergeRequest
|
||||
#
|
||||
module Issuable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
|
@ -64,4 +69,38 @@ module IssueCommonality
|
|||
closed_changed? && !closed
|
||||
end
|
||||
|
||||
#
|
||||
# Votes
|
||||
#
|
||||
|
||||
# Return the number of -1 comments (downvotes)
|
||||
def downvotes
|
||||
notes.select(&:downvote?).size
|
||||
end
|
||||
|
||||
def downvotes_in_percent
|
||||
if votes_count.zero?
|
||||
0
|
||||
else
|
||||
100.0 - upvotes_in_percent
|
||||
end
|
||||
end
|
||||
|
||||
# Return the number of +1 comments (upvotes)
|
||||
def upvotes
|
||||
notes.select(&:upvote?).size
|
||||
end
|
||||
|
||||
def upvotes_in_percent
|
||||
if votes_count.zero?
|
||||
0
|
||||
else
|
||||
100.0 / votes_count * upvotes
|
||||
end
|
||||
end
|
||||
|
||||
# Return the total number of votes
|
||||
def votes_count
|
||||
upvotes + downvotes
|
||||
end
|
||||
end
|
|
@ -15,9 +15,6 @@
|
|||
#
|
||||
|
||||
class Event < ActiveRecord::Base
|
||||
include NoteEvent
|
||||
include PushEvent
|
||||
|
||||
attr_accessible :project, :action, :data, :author_id, :project_id,
|
||||
:target_id, :target_type
|
||||
|
||||
|
@ -113,26 +110,6 @@ class Event < ActiveRecord::Base
|
|||
target_type == "MergeRequest"
|
||||
end
|
||||
|
||||
def new_issue?
|
||||
target_type == "Issue" &&
|
||||
action == Created
|
||||
end
|
||||
|
||||
def new_merge_request?
|
||||
target_type == "MergeRequest" &&
|
||||
action == Created
|
||||
end
|
||||
|
||||
def changed_merge_request?
|
||||
target_type == "MergeRequest" &&
|
||||
[Closed, Reopened].include?(action)
|
||||
end
|
||||
|
||||
def changed_issue?
|
||||
target_type == "Issue" &&
|
||||
[Closed, Reopened].include?(action)
|
||||
end
|
||||
|
||||
def joined?
|
||||
action == Joined
|
||||
end
|
||||
|
@ -170,4 +147,143 @@ class Event < ActiveRecord::Base
|
|||
"opened"
|
||||
end
|
||||
end
|
||||
|
||||
def valid_push?
|
||||
data[:ref]
|
||||
rescue => ex
|
||||
false
|
||||
end
|
||||
|
||||
def tag?
|
||||
data[:ref]["refs/tags"]
|
||||
end
|
||||
|
||||
def branch?
|
||||
data[:ref]["refs/heads"]
|
||||
end
|
||||
|
||||
def new_branch?
|
||||
commit_from =~ /^00000/
|
||||
end
|
||||
|
||||
def new_ref?
|
||||
commit_from =~ /^00000/
|
||||
end
|
||||
|
||||
def rm_ref?
|
||||
commit_to =~ /^00000/
|
||||
end
|
||||
|
||||
def md_ref?
|
||||
!(rm_ref? || new_ref?)
|
||||
end
|
||||
|
||||
def commit_from
|
||||
data[:before]
|
||||
end
|
||||
|
||||
def commit_to
|
||||
data[:after]
|
||||
end
|
||||
|
||||
def ref_name
|
||||
if tag?
|
||||
tag_name
|
||||
else
|
||||
branch_name
|
||||
end
|
||||
end
|
||||
|
||||
def branch_name
|
||||
@branch_name ||= data[:ref].gsub("refs/heads/", "")
|
||||
end
|
||||
|
||||
def tag_name
|
||||
@tag_name ||= data[:ref].gsub("refs/tags/", "")
|
||||
end
|
||||
|
||||
# Max 20 commits from push DESC
|
||||
def commits
|
||||
@commits ||= data[:commits].map { |commit| repository.commit(commit[:id]) }.reverse
|
||||
end
|
||||
|
||||
def commits_count
|
||||
data[:total_commits_count] || commits.count || 0
|
||||
end
|
||||
|
||||
def ref_type
|
||||
tag? ? "tag" : "branch"
|
||||
end
|
||||
|
||||
def push_action_name
|
||||
if new_ref?
|
||||
"pushed new"
|
||||
elsif rm_ref?
|
||||
"deleted"
|
||||
else
|
||||
"pushed to"
|
||||
end
|
||||
end
|
||||
|
||||
def repository
|
||||
project.repository
|
||||
end
|
||||
|
||||
def parent_commit
|
||||
repository.commit(commit_from)
|
||||
rescue => ex
|
||||
nil
|
||||
end
|
||||
|
||||
def last_commit
|
||||
repository.commit(commit_to)
|
||||
rescue => ex
|
||||
nil
|
||||
end
|
||||
|
||||
def push_with_commits?
|
||||
md_ref? && commits.any? && parent_commit && last_commit
|
||||
rescue Grit::NoSuchPathError
|
||||
false
|
||||
end
|
||||
|
||||
def last_push_to_non_root?
|
||||
branch? && project.default_branch != branch_name
|
||||
end
|
||||
|
||||
def note_commit_id
|
||||
target.commit_id
|
||||
end
|
||||
|
||||
def note_short_commit_id
|
||||
note_commit_id[0..8]
|
||||
end
|
||||
|
||||
def note_commit?
|
||||
target.noteable_type == "Commit"
|
||||
end
|
||||
|
||||
def note_target
|
||||
target.noteable
|
||||
end
|
||||
|
||||
def note_target_id
|
||||
if note_commit?
|
||||
target.commit_id
|
||||
else
|
||||
target.noteable_id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def wall_note?
|
||||
target.noteable_type.blank?
|
||||
end
|
||||
|
||||
def note_target_type
|
||||
if target.noteable_type.present?
|
||||
target.noteable_type.titleize
|
||||
else
|
||||
"Wall"
|
||||
end.downcase
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,20 +23,12 @@ class GitlabCiService < Service
|
|||
|
||||
after_save :compose_service_hook, if: :activated?
|
||||
|
||||
def activated?
|
||||
active
|
||||
end
|
||||
|
||||
def compose_service_hook
|
||||
hook = service_hook || build_service_hook
|
||||
hook.url = [project_url, "/build", "?token=#{token}"].join("")
|
||||
hook.save
|
||||
end
|
||||
|
||||
def commit_badge_path sha
|
||||
project_url + "/status?sha=#{sha}"
|
||||
end
|
||||
|
||||
def commit_status_path sha
|
||||
project_url + "/builds/#{sha}/status.json?token=#{token}"
|
||||
end
|
||||
|
|
|
@ -12,6 +12,14 @@
|
|||
#
|
||||
|
||||
class Group < Namespace
|
||||
def add_users_to_project_teams(user_ids, project_access)
|
||||
UsersProject.add_users_into_projects(
|
||||
projects.map(&:id),
|
||||
user_ids,
|
||||
project_access
|
||||
)
|
||||
end
|
||||
|
||||
def users
|
||||
users = User.joins(:users_projects).where(users_projects: {project_id: project_ids})
|
||||
users = users << owner
|
||||
|
@ -21,4 +29,8 @@ class Group < Namespace
|
|||
def human_name
|
||||
name
|
||||
end
|
||||
|
||||
def truncate_teams
|
||||
UsersProject.truncate_teams(project_ids)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
#
|
||||
|
||||
class Issue < ActiveRecord::Base
|
||||
include IssueCommonality
|
||||
include Votes
|
||||
include Issuable
|
||||
|
||||
attr_accessible :title, :assignee_id, :closed, :position, :description,
|
||||
:milestone_id, :label_list, :author_id_of_changes
|
||||
|
|
|
@ -73,7 +73,7 @@ class Key < ActiveRecord::Base
|
|||
if is_deploy_key
|
||||
[project]
|
||||
else
|
||||
user.projects
|
||||
user.authorized_projects
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -20,11 +20,10 @@
|
|||
#
|
||||
|
||||
require Rails.root.join("app/models/commit")
|
||||
require Rails.root.join("app/roles/static_model")
|
||||
require Rails.root.join("lib/static_model")
|
||||
|
||||
class MergeRequest < ActiveRecord::Base
|
||||
include IssueCommonality
|
||||
include Votes
|
||||
include Issuable
|
||||
|
||||
attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id,
|
||||
:author_id_of_changes
|
||||
|
|
|
@ -29,7 +29,7 @@ class Milestone < ActiveRecord::Base
|
|||
|
||||
def expired?
|
||||
if due_date
|
||||
due_date < Date.today
|
||||
due_date.past?
|
||||
else
|
||||
false
|
||||
end
|
||||
|
@ -58,7 +58,13 @@ class Milestone < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def expires_at
|
||||
"expires at #{due_date.stamp("Aug 21, 2011")}" if due_date
|
||||
if due_date
|
||||
if due_date.past?
|
||||
"expired at #{due_date.stamp("Aug 21, 2011")}"
|
||||
else
|
||||
"expires at #{due_date.stamp("Aug 21, 2011")}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def can_be_closed?
|
||||
|
|
|
@ -27,10 +27,13 @@ class Namespace < ActiveRecord::Base
|
|||
|
||||
after_create :ensure_dir_exist
|
||||
after_update :move_dir
|
||||
after_commit :update_gitolite, on: :update, if: :require_update_gitolite
|
||||
after_destroy :rm_dir
|
||||
|
||||
scope :root, where('type IS NULL')
|
||||
|
||||
attr_accessor :require_update_gitolite
|
||||
|
||||
def self.search query
|
||||
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
|
||||
end
|
||||
|
@ -48,8 +51,17 @@ class Namespace < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def ensure_dir_exist
|
||||
namespace_dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
|
||||
system("mkdir -m 770 #{namespace_dir_path}") unless File.exists?(namespace_dir_path)
|
||||
unless dir_exists?
|
||||
FileUtils.mkdir( namespace_full_path, mode: 0770 )
|
||||
end
|
||||
end
|
||||
|
||||
def dir_exists?
|
||||
File.exists?(namespace_full_path)
|
||||
end
|
||||
|
||||
def namespace_full_path
|
||||
@namespace_full_path ||= File.join(Gitlab.config.gitolite.repos_path, path)
|
||||
end
|
||||
|
||||
def move_dir
|
||||
|
@ -59,16 +71,25 @@ class Namespace < ActiveRecord::Base
|
|||
if File.exists?(new_path)
|
||||
raise "Already exists"
|
||||
end
|
||||
|
||||
if system("mv #{old_path} #{new_path}")
|
||||
|
||||
begin
|
||||
FileUtils.mv( old_path, new_path )
|
||||
send_update_instructions
|
||||
@require_update_gitolite = true
|
||||
rescue Exception => e
|
||||
raise "Namespace move error #{old_path} #{new_path}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_gitolite
|
||||
@require_update_gitolite = false
|
||||
projects.each(&:update_repository)
|
||||
end
|
||||
|
||||
def rm_dir
|
||||
dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
|
||||
system("rm -rf #{dir_path}")
|
||||
FileUtils.rm_r( dir_path, force: true )
|
||||
end
|
||||
|
||||
def send_update_instructions
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#
|
||||
# id :integer not null, primary key
|
||||
# note :text
|
||||
# noteable_id :string(255)
|
||||
# noteable_type :string(255)
|
||||
# author_id :integer
|
||||
# created_at :datetime not null
|
||||
|
@ -12,6 +11,8 @@
|
|||
# project_id :integer
|
||||
# attachment :string(255)
|
||||
# line_code :string(255)
|
||||
# commit_id :string(255)
|
||||
# noteable_id :integer
|
||||
#
|
||||
|
||||
require 'carrierwave/orm/activerecord'
|
||||
|
@ -41,11 +42,11 @@ class Note < ActiveRecord::Base
|
|||
mount_uploader :attachment, AttachmentUploader
|
||||
|
||||
# Scopes
|
||||
scope :for_commits, ->{ where(noteable_type: "Commit") }
|
||||
scope :common, ->{ where(noteable_id: nil, commit_id: nil) }
|
||||
scope :today, ->{ where("created_at >= :date", date: Date.today) }
|
||||
scope :last_week, ->{ where("created_at >= :date", date: (Date.today - 7.days)) }
|
||||
scope :since, ->(day) { where("created_at >= :date", date: (day)) }
|
||||
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
|
||||
scope :inline, where("line_code IS NOT NULL")
|
||||
scope :not_inline, where("line_code IS NULL")
|
||||
|
||||
scope :common, ->{ where(noteable_type: ["", nil]) }
|
||||
scope :fresh, ->{ order("created_at ASC, id ASC") }
|
||||
scope :inc_author_project, ->{ includes(:project, :author) }
|
||||
scope :inc_author, ->{ includes(:author) }
|
||||
|
@ -126,7 +127,7 @@ class Note < ActiveRecord::Base
|
|||
# override to return commits, which are not active record
|
||||
def noteable
|
||||
if for_commit?
|
||||
project.commit(commit_id)
|
||||
project.repository.commit(commit_id)
|
||||
else
|
||||
super
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# private_flag :boolean default(TRUE), not null
|
||||
# owner_id :integer
|
||||
# creator_id :integer
|
||||
# default_branch :string(255)
|
||||
# issues_enabled :boolean default(TRUE), not null
|
||||
# wall_enabled :boolean default(TRUE), not null
|
||||
|
@ -21,18 +21,14 @@
|
|||
require "grit"
|
||||
|
||||
class Project < ActiveRecord::Base
|
||||
include Repository
|
||||
include PushObserver
|
||||
include Authority
|
||||
include Team
|
||||
include NamespacedProject
|
||||
include Gitolited
|
||||
|
||||
class TransferError < StandardError; end
|
||||
|
||||
attr_accessible :name, :path, :description, :default_branch, :issues_enabled,
|
||||
:wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin]
|
||||
|
||||
attr_accessible :namespace_id, :owner_id, as: :admin
|
||||
attr_accessible :namespace_id, :creator_id, as: :admin
|
||||
|
||||
attr_accessor :error_code
|
||||
|
||||
|
@ -40,10 +36,10 @@ class Project < ActiveRecord::Base
|
|||
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
|
||||
belongs_to :namespace
|
||||
|
||||
# TODO: replace owner with creator.
|
||||
# With namespaces a project owner will be a namespace owner
|
||||
# so this field makes sense only for global projects
|
||||
belongs_to :owner, class_name: "User"
|
||||
belongs_to :creator,
|
||||
class_name: "User",
|
||||
foreign_key: "creator_id"
|
||||
|
||||
has_many :users, through: :users_projects
|
||||
has_many :events, dependent: :destroy
|
||||
has_many :merge_requests, dependent: :destroy
|
||||
|
@ -62,9 +58,11 @@ class Project < ActiveRecord::Base
|
|||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||
|
||||
# Validations
|
||||
validates :owner, presence: true
|
||||
validates :creator, presence: true
|
||||
validates :description, length: { within: 0..2000 }
|
||||
validates :name, presence: true, length: { within: 0..255 }
|
||||
validates :name, presence: true, length: { within: 0..255 },
|
||||
format: { with: Gitlab::Regex.project_name_regex,
|
||||
message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter should be first" }
|
||||
validates :path, presence: true, length: { within: 0..255 },
|
||||
format: { with: Gitlab::Regex.path_regex,
|
||||
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
|
||||
|
@ -77,19 +75,14 @@ class Project < ActiveRecord::Base
|
|||
validate :check_limit, :repo_name
|
||||
|
||||
# Scopes
|
||||
scope :public_only, where(private_flag: false)
|
||||
scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.projects.map(&:id) ) }
|
||||
scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
|
||||
scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
|
||||
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
|
||||
scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
|
||||
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
|
||||
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
|
||||
|
||||
class << self
|
||||
def authorized_for user
|
||||
projects = includes(:users_projects, :namespace)
|
||||
projects = projects.where("users_projects.user_id = :user_id or projects.owner_id = :user_id or namespaces.owner_id = :user_id", user_id: user.id)
|
||||
end
|
||||
|
||||
def active
|
||||
joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
|
||||
end
|
||||
|
@ -101,8 +94,10 @@ class Project < ActiveRecord::Base
|
|||
def find_with_namespace(id)
|
||||
if id.include?("/")
|
||||
id = id.split("/")
|
||||
namespace_id = Namespace.find_by_path(id.first).id
|
||||
where(namespace_id: namespace_id).find_by_path(id.last)
|
||||
namespace = Namespace.find_by_path(id.first)
|
||||
return nil unless namespace
|
||||
|
||||
where(namespace_id: namespace.id).find_by_path(id.second)
|
||||
else
|
||||
where(path: id, namespace_id: nil).last
|
||||
end
|
||||
|
@ -122,7 +117,7 @@ class Project < ActiveRecord::Base
|
|||
#
|
||||
project.path = project.name.dup.parameterize
|
||||
|
||||
project.owner = user
|
||||
project.creator = user
|
||||
|
||||
# Apply namespace if user has access to it
|
||||
# else fallback to user namespace
|
||||
|
@ -162,6 +157,20 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def team
|
||||
@team ||= Team.new(self)
|
||||
end
|
||||
|
||||
def repository
|
||||
if path
|
||||
@repository ||= Repository.new(path_with_namespace, default_branch)
|
||||
else
|
||||
nil
|
||||
end
|
||||
rescue Grit::NoSuchPathError
|
||||
nil
|
||||
end
|
||||
|
||||
def git_error?
|
||||
error_code == :gitolite
|
||||
end
|
||||
|
@ -171,8 +180,8 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def check_limit
|
||||
unless owner.can_create_project?
|
||||
errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it")
|
||||
unless creator.can_create_project?
|
||||
errors[:base] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
|
||||
end
|
||||
rescue
|
||||
errors[:base] << ("Can't check your ability to create project")
|
||||
|
@ -198,30 +207,10 @@ class Project < ActiveRecord::Base
|
|||
[Gitlab.config.gitlab.url, path_with_namespace].join("/")
|
||||
end
|
||||
|
||||
def common_notes
|
||||
notes.where(noteable_type: ["", nil]).inc_author_project
|
||||
end
|
||||
|
||||
def build_commit_note(commit)
|
||||
notes.new(commit_id: commit.id, noteable_type: "Commit")
|
||||
end
|
||||
|
||||
def commit_notes(commit)
|
||||
notes.where(commit_id: commit.id, noteable_type: "Commit").where('line_code IS NULL OR line_code = ""')
|
||||
end
|
||||
|
||||
def commit_line_notes(commit)
|
||||
notes.where(commit_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL")
|
||||
end
|
||||
|
||||
def public?
|
||||
!private_flag
|
||||
end
|
||||
|
||||
def private?
|
||||
private_flag
|
||||
end
|
||||
|
||||
def last_activity
|
||||
last_event
|
||||
end
|
||||
|
@ -262,7 +251,282 @@ class Project < ActiveRecord::Base
|
|||
|
||||
def send_move_instructions
|
||||
self.users_projects.each do |member|
|
||||
Notify.project_was_moved_email(member.id).deliver
|
||||
Notify.delay.project_was_moved_email(member.id)
|
||||
end
|
||||
end
|
||||
|
||||
def owner
|
||||
if namespace
|
||||
namespace_owner
|
||||
else
|
||||
creator
|
||||
end
|
||||
end
|
||||
|
||||
def team_member_by_name_or_email(name = nil, email = nil)
|
||||
user = users.where("name like ? or email like ?", name, email).first
|
||||
users_projects.where(user: user) if user
|
||||
end
|
||||
|
||||
# Get Team Member record by user id
|
||||
def team_member_by_id(user_id)
|
||||
users_projects.find_by_user_id(user_id)
|
||||
end
|
||||
|
||||
def transfer(new_namespace)
|
||||
Project.transaction do
|
||||
old_namespace = namespace
|
||||
self.namespace = new_namespace
|
||||
|
||||
old_dir = old_namespace.try(:path) || ''
|
||||
new_dir = new_namespace.try(:path) || ''
|
||||
|
||||
old_repo = if old_dir.present?
|
||||
File.join(old_dir, self.path)
|
||||
else
|
||||
self.path
|
||||
end
|
||||
|
||||
if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present?
|
||||
raise TransferError.new("Project with same path in target namespace already exists")
|
||||
end
|
||||
|
||||
Gitlab::ProjectMover.new(self, old_dir, new_dir).execute
|
||||
|
||||
gitolite.move_repository(old_repo, self)
|
||||
|
||||
save!
|
||||
end
|
||||
rescue Gitlab::ProjectMover::ProjectMoveError => ex
|
||||
raise Project::TransferError.new(ex.message)
|
||||
end
|
||||
|
||||
def name_with_namespace
|
||||
@name_with_namespace ||= begin
|
||||
if namespace
|
||||
namespace.human_name + " / " + name
|
||||
else
|
||||
name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def namespace_owner
|
||||
namespace.try(:owner)
|
||||
end
|
||||
|
||||
def path_with_namespace
|
||||
if namespace
|
||||
namespace.path + '/' + path
|
||||
else
|
||||
path
|
||||
end
|
||||
end
|
||||
|
||||
# This method will be called after each post receive and only if the provided
|
||||
# user is present in GitLab.
|
||||
#
|
||||
# All callbacks for post receive should be placed here.
|
||||
def trigger_post_receive(oldrev, newrev, ref, user)
|
||||
data = post_receive_data(oldrev, newrev, ref, user)
|
||||
|
||||
# Create push event
|
||||
self.observe_push(data)
|
||||
|
||||
if push_to_branch? ref, oldrev
|
||||
# Close merged MR
|
||||
self.update_merge_requests(oldrev, newrev, ref, user)
|
||||
|
||||
# Execute web hooks
|
||||
self.execute_hooks(data.dup)
|
||||
|
||||
# Execute project services
|
||||
self.execute_services(data.dup)
|
||||
end
|
||||
|
||||
# Create satellite
|
||||
self.satellite.create unless self.satellite.exists?
|
||||
|
||||
# Discover the default branch, but only if it hasn't already been set to
|
||||
# something else
|
||||
if repository && default_branch.nil?
|
||||
update_attributes(default_branch: self.repository.discover_default_branch)
|
||||
end
|
||||
end
|
||||
|
||||
def push_to_branch? ref, oldrev
|
||||
ref_parts = ref.split('/')
|
||||
|
||||
# Return if this is not a push to a branch (e.g. new commits)
|
||||
!(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000")
|
||||
end
|
||||
|
||||
def observe_push(data)
|
||||
Event.create(
|
||||
project: self,
|
||||
action: Event::Pushed,
|
||||
data: data,
|
||||
author_id: data[:user_id]
|
||||
)
|
||||
end
|
||||
|
||||
def execute_hooks(data)
|
||||
hooks.each { |hook| hook.execute(data) }
|
||||
end
|
||||
|
||||
def execute_services(data)
|
||||
services.each do |service|
|
||||
|
||||
# Call service hook only if it is active
|
||||
service.execute(data) if service.active
|
||||
end
|
||||
end
|
||||
|
||||
# Produce a hash of post-receive data
|
||||
#
|
||||
# data = {
|
||||
# before: String,
|
||||
# after: String,
|
||||
# ref: String,
|
||||
# user_id: String,
|
||||
# user_name: String,
|
||||
# repository: {
|
||||
# name: String,
|
||||
# url: String,
|
||||
# description: String,
|
||||
# homepage: String,
|
||||
# },
|
||||
# commits: Array,
|
||||
# total_commits_count: Fixnum
|
||||
# }
|
||||
#
|
||||
def post_receive_data(oldrev, newrev, ref, user)
|
||||
|
||||
push_commits = repository.commits_between(oldrev, newrev)
|
||||
|
||||
# Total commits count
|
||||
push_commits_count = push_commits.size
|
||||
|
||||
# Get latest 20 commits ASC
|
||||
push_commits_limited = push_commits.last(20)
|
||||
|
||||
# Hash to be passed as post_receive_data
|
||||
data = {
|
||||
before: oldrev,
|
||||
after: newrev,
|
||||
ref: ref,
|
||||
user_id: user.id,
|
||||
user_name: user.name,
|
||||
repository: {
|
||||
name: name,
|
||||
url: url_to_repo,
|
||||
description: description,
|
||||
homepage: web_url,
|
||||
},
|
||||
commits: [],
|
||||
total_commits_count: push_commits_count
|
||||
}
|
||||
|
||||
# For perfomance purposes maximum 20 latest commits
|
||||
# will be passed as post receive hook data.
|
||||
#
|
||||
push_commits_limited.each do |commit|
|
||||
data[:commits] << {
|
||||
id: commit.id,
|
||||
message: commit.safe_message,
|
||||
timestamp: commit.date.xmlschema,
|
||||
url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}",
|
||||
author: {
|
||||
name: commit.author_name,
|
||||
email: commit.author_email
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
def update_merge_requests(oldrev, newrev, ref, user)
|
||||
return true unless ref =~ /heads/
|
||||
branch_name = ref.gsub("refs/heads/", "")
|
||||
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
|
||||
|
||||
# Update code for merge requests
|
||||
mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all
|
||||
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
|
||||
|
||||
# Close merge requests
|
||||
mrs = self.merge_requests.opened.where(target_branch: branch_name).all
|
||||
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
|
||||
mrs.each { |merge_request| merge_request.merge!(user.id) }
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def valid_repo?
|
||||
repo
|
||||
rescue
|
||||
errors.add(:path, "Invalid repository path")
|
||||
false
|
||||
end
|
||||
|
||||
def empty_repo?
|
||||
!repository || repository.empty?
|
||||
end
|
||||
|
||||
def satellite
|
||||
@satellite ||= Gitlab::Satellite::Satellite.new(self)
|
||||
end
|
||||
|
||||
def repo
|
||||
repository.raw
|
||||
end
|
||||
|
||||
def url_to_repo
|
||||
gitolite.url_to_repo(path_with_namespace)
|
||||
end
|
||||
|
||||
def namespace_dir
|
||||
namespace.try(:path) || ''
|
||||
end
|
||||
|
||||
def update_repository
|
||||
gitolite.update_repository(self)
|
||||
end
|
||||
|
||||
def destroy_repository
|
||||
gitolite.remove_repository(self)
|
||||
end
|
||||
|
||||
def repo_exists?
|
||||
@repo_exists ||= (repository && repository.branches.present?)
|
||||
rescue
|
||||
@repo_exists = false
|
||||
end
|
||||
|
||||
def open_branches
|
||||
if protected_branches.empty?
|
||||
self.repo.heads
|
||||
else
|
||||
pnames = protected_branches.map(&:name)
|
||||
self.repo.heads.reject { |h| pnames.include?(h.name) }
|
||||
end.sort_by(&:name)
|
||||
end
|
||||
|
||||
def root_ref?(branch)
|
||||
repository.root_ref == branch
|
||||
end
|
||||
|
||||
def ssh_url_to_repo
|
||||
url_to_repo
|
||||
end
|
||||
|
||||
def http_url_to_repo
|
||||
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
|
||||
end
|
||||
|
||||
# Check if current branch name is marked as protected in the system
|
||||
def protected_branch? branch_name
|
||||
protected_branches.map(&:name).include?(branch_name)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
|
||||
class ProtectedBranch < ActiveRecord::Base
|
||||
include GitHost
|
||||
include Gitolited
|
||||
|
||||
attr_accessible :name
|
||||
|
||||
|
@ -22,10 +22,10 @@ class ProtectedBranch < ActiveRecord::Base
|
|||
after_destroy :update_repository
|
||||
|
||||
def update_repository
|
||||
git_host.update_repository(project)
|
||||
gitolite.update_repository(project)
|
||||
end
|
||||
|
||||
def commit
|
||||
project.commit(self.name)
|
||||
project.repository.commit(self.name)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,15 +1,35 @@
|
|||
module Repository
|
||||
include GitHost
|
||||
class Repository
|
||||
# Repository directory name with namespace direcotry
|
||||
# Examples:
|
||||
# gitlab/gitolite
|
||||
# diaspora
|
||||
#
|
||||
attr_accessor :path_with_namespace
|
||||
|
||||
def valid_repo?
|
||||
# Grit repo object
|
||||
attr_accessor :repo
|
||||
|
||||
# Default branch in the repository
|
||||
attr_accessor :root_ref
|
||||
|
||||
def initialize(path_with_namespace, root_ref = 'master')
|
||||
@root_ref = root_ref || "master"
|
||||
@path_with_namespace = path_with_namespace
|
||||
|
||||
# Init grit repo object
|
||||
repo
|
||||
rescue
|
||||
errors.add(:path, "Invalid repository path")
|
||||
false
|
||||
end
|
||||
|
||||
def empty_repo?
|
||||
!repo_exists? || !has_commits?
|
||||
def raw
|
||||
repo
|
||||
end
|
||||
|
||||
def path_to_repo
|
||||
@path_to_repo ||= File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git")
|
||||
end
|
||||
|
||||
def repo
|
||||
@repo ||= Grit::Repo.new(path_to_repo)
|
||||
end
|
||||
|
||||
def commit(commit_id = nil)
|
||||
|
@ -40,10 +60,6 @@ module Repository
|
|||
Commit.commits_between(repo, from, to)
|
||||
end
|
||||
|
||||
def satellite
|
||||
@satellite ||= Gitlab::Satellite::Satellite.new(self)
|
||||
end
|
||||
|
||||
def has_post_receive_file?
|
||||
!!hook_file
|
||||
end
|
||||
|
@ -88,36 +104,6 @@ module Repository
|
|||
[branch_names + tag_names].flatten
|
||||
end
|
||||
|
||||
def repo
|
||||
@repo ||= Grit::Repo.new(path_to_repo)
|
||||
end
|
||||
|
||||
def url_to_repo
|
||||
git_host.url_to_repo(path_with_namespace)
|
||||
end
|
||||
|
||||
def path_to_repo
|
||||
File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git")
|
||||
end
|
||||
|
||||
def namespace_dir
|
||||
namespace.try(:path) || ''
|
||||
end
|
||||
|
||||
def update_repository
|
||||
git_host.update_repository(self)
|
||||
end
|
||||
|
||||
def destroy_repository
|
||||
git_host.remove_repository(self)
|
||||
end
|
||||
|
||||
def repo_exists?
|
||||
@repo_exists ||= (repo && !repo.branches.empty?)
|
||||
rescue
|
||||
@repo_exists = false
|
||||
end
|
||||
|
||||
def heads
|
||||
@heads ||= repo.heads
|
||||
end
|
||||
|
@ -128,13 +114,14 @@ module Repository
|
|||
path ? (tree / path) : tree
|
||||
end
|
||||
|
||||
def open_branches
|
||||
if protected_branches.empty?
|
||||
self.repo.heads
|
||||
else
|
||||
pnames = protected_branches.map(&:name)
|
||||
self.repo.heads.reject { |h| pnames.include?(h.name) }
|
||||
end.sort_by(&:name)
|
||||
def has_commits?
|
||||
!!commit
|
||||
rescue Grit::NoSuchPathError
|
||||
false
|
||||
end
|
||||
|
||||
def empty?
|
||||
!has_commits?
|
||||
end
|
||||
|
||||
# Discovers the default branch based on the repository's available branches
|
||||
|
@ -153,20 +140,6 @@ module Repository
|
|||
end
|
||||
end
|
||||
|
||||
def has_commits?
|
||||
!!commit
|
||||
rescue Grit::NoSuchPathError
|
||||
false
|
||||
end
|
||||
|
||||
def root_ref
|
||||
default_branch || "master"
|
||||
end
|
||||
|
||||
def root_ref?(branch)
|
||||
root_ref == branch
|
||||
end
|
||||
|
||||
# Archive Project to .tar.gz
|
||||
#
|
||||
# Already packed repo archives stored at
|
||||
|
@ -193,17 +166,4 @@ module Repository
|
|||
|
||||
file_path
|
||||
end
|
||||
|
||||
def ssh_url_to_repo
|
||||
url_to_repo
|
||||
end
|
||||
|
||||
def http_url_to_repo
|
||||
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
|
||||
end
|
||||
|
||||
# Check if current branch name is marked as protected in the system
|
||||
def protected_branch? branch_name
|
||||
protected_branches.map(&:name).include?(branch_name)
|
||||
end
|
||||
end
|
|
@ -20,4 +20,8 @@ class Service < ActiveRecord::Base
|
|||
has_one :service_hook
|
||||
|
||||
validates :project_id, presence: true
|
||||
|
||||
def activated?
|
||||
active
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,6 +19,6 @@ class SystemHook < WebHook
|
|||
end
|
||||
|
||||
def async_execute(data)
|
||||
Resque.enqueue(SystemHookWorker, id, data)
|
||||
Sidekiq::Client.enqueue(SystemHookWorker, id, data)
|
||||
end
|
||||
end
|
||||
|
|
118
app/models/team.rb
Normal file
118
app/models/team.rb
Normal file
|
@ -0,0 +1,118 @@
|
|||
class Team
|
||||
attr_accessor :project
|
||||
|
||||
def initialize(project)
|
||||
@project = project
|
||||
end
|
||||
|
||||
# Shortcut to add users
|
||||
#
|
||||
# Use:
|
||||
# @team << [@user, :master]
|
||||
# @team << [@users, :master]
|
||||
#
|
||||
def << args
|
||||
users = args.first
|
||||
|
||||
if users.respond_to?(:each)
|
||||
add_users(users, args.second)
|
||||
else
|
||||
add_user(users, args.second)
|
||||
end
|
||||
end
|
||||
|
||||
def add_user(user, access)
|
||||
add_users_ids([user.id], access)
|
||||
end
|
||||
|
||||
def add_users(users, access)
|
||||
add_users_ids(users.map(&:id), access)
|
||||
end
|
||||
|
||||
def add_users_ids(user_ids, access)
|
||||
UsersProject.add_users_into_projects(
|
||||
[project.id],
|
||||
user_ids,
|
||||
access
|
||||
)
|
||||
end
|
||||
|
||||
# Remove all users from project team
|
||||
def truncate
|
||||
UsersProject.truncate_team(project)
|
||||
end
|
||||
|
||||
def members
|
||||
project.users_projects
|
||||
end
|
||||
|
||||
def guests
|
||||
members.guests.map(&:user)
|
||||
end
|
||||
|
||||
def reporters
|
||||
members.reporters.map(&:user)
|
||||
end
|
||||
|
||||
def developers
|
||||
members.developers.map(&:user)
|
||||
end
|
||||
|
||||
def masters
|
||||
members.masters.map(&:user)
|
||||
end
|
||||
|
||||
def repository_readers
|
||||
repository_members[UsersProject::REPORTER]
|
||||
end
|
||||
|
||||
def repository_writers
|
||||
repository_members[UsersProject::DEVELOPER]
|
||||
end
|
||||
|
||||
def repository_masters
|
||||
repository_members[UsersProject::MASTER]
|
||||
end
|
||||
|
||||
def repository_members
|
||||
keys = Hash.new {|h,k| h[k] = [] }
|
||||
UsersProject.select("keys.identifier, project_access").
|
||||
joins(user: :keys).where(project_id: project.id).
|
||||
each {|row| keys[row.project_access] << [row.identifier] }
|
||||
|
||||
keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier)
|
||||
keys
|
||||
end
|
||||
|
||||
def import(source_project)
|
||||
target_project = project
|
||||
|
||||
source_team = source_project.users_projects.all
|
||||
target_team = target_project.users_projects.all
|
||||
target_user_ids = target_team.map(&:user_id)
|
||||
|
||||
source_team.reject! do |tm|
|
||||
# Skip if user already present in team
|
||||
target_user_ids.include?(tm.user_id)
|
||||
end
|
||||
|
||||
source_team.map! do |tm|
|
||||
new_tm = tm.dup
|
||||
new_tm.id = nil
|
||||
new_tm.project_id = target_project.id
|
||||
new_tm.skip_git = true
|
||||
new_tm
|
||||
end
|
||||
|
||||
UsersProject.transaction do
|
||||
source_team.each do |tm|
|
||||
tm.save
|
||||
end
|
||||
target_project.update_repository
|
||||
end
|
||||
|
||||
true
|
||||
rescue
|
||||
false
|
||||
end
|
||||
end
|
|
@ -1,12 +1,13 @@
|
|||
class Tree
|
||||
include Linguist::BlobHelper
|
||||
attr_accessor :path, :tree, :project, :ref
|
||||
|
||||
attr_accessor :path, :tree, :ref
|
||||
|
||||
delegate :contents, :basename, :name, :data, :mime_type,
|
||||
:mode, :size, :text?, :colorize, to: :tree
|
||||
|
||||
def initialize(raw_tree, project, ref = nil, path = nil)
|
||||
@project, @ref, @path = project, ref, path
|
||||
def initialize(raw_tree, ref = nil, path = nil)
|
||||
@ref, @path = ref, path
|
||||
@tree = if path.present?
|
||||
raw_tree / path
|
||||
else
|
||||
|
|
|
@ -34,8 +34,6 @@
|
|||
#
|
||||
|
||||
class User < ActiveRecord::Base
|
||||
include Account
|
||||
|
||||
devise :database_authenticatable, :token_authenticatable, :lockable,
|
||||
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
|
||||
|
||||
|
@ -51,7 +49,6 @@ class User < ActiveRecord::Base
|
|||
has_many :groups, class_name: "Group", foreign_key: :owner_id
|
||||
|
||||
has_many :keys, dependent: :destroy
|
||||
has_many :projects, through: :users_projects
|
||||
has_many :users_projects, dependent: :destroy
|
||||
has_many :issues, foreign_key: :author_id, dependent: :destroy
|
||||
has_many :notes, foreign_key: :author_id, dependent: :destroy
|
||||
|
@ -70,6 +67,8 @@ class User < ActiveRecord::Base
|
|||
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
|
||||
|
||||
|
||||
validate :namespace_uniq, if: ->(user) { user.username_changed? }
|
||||
|
||||
before_validation :generate_password, on: :create
|
||||
before_save :ensure_authentication_token
|
||||
alias_attribute :private_token, :authentication_token
|
||||
|
@ -77,11 +76,14 @@ class User < ActiveRecord::Base
|
|||
delegate :path, to: :namespace, allow_nil: true, prefix: true
|
||||
|
||||
# Scopes
|
||||
scope :not_in_project, ->(project) { where("id not in (:ids)", ids: project.users.map(&:id) ) }
|
||||
scope :admins, where(admin: true)
|
||||
scope :blocked, where(blocked: true)
|
||||
scope :active, where(blocked: false)
|
||||
scope :alphabetically, order('name ASC')
|
||||
|
||||
#
|
||||
# Class methods
|
||||
#
|
||||
class << self
|
||||
def filter filter_name
|
||||
case filter_name
|
||||
|
@ -93,6 +95,14 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def not_in_project(project)
|
||||
if project.users.present?
|
||||
where("id not in (:ids)", ids: project.users.map(&:id) )
|
||||
else
|
||||
scoped
|
||||
end
|
||||
end
|
||||
|
||||
def without_projects
|
||||
where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)')
|
||||
end
|
||||
|
@ -118,9 +128,158 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Instance methods
|
||||
#
|
||||
def generate_password
|
||||
if self.force_random_password
|
||||
self.password = self.password_confirmation = Devise.friendly_token.first(8)
|
||||
end
|
||||
end
|
||||
|
||||
def namespace_uniq
|
||||
namespace_name = self.username
|
||||
if Namespace.find_by_path(namespace_name)
|
||||
self.errors.add :username, "already exist"
|
||||
end
|
||||
end
|
||||
|
||||
# Namespaces user has access to
|
||||
def namespaces
|
||||
namespaces = []
|
||||
|
||||
# Add user account namespace
|
||||
namespaces << self.namespace if self.namespace
|
||||
|
||||
# Add groups you can manage
|
||||
namespaces += if admin
|
||||
Group.all
|
||||
else
|
||||
groups.all
|
||||
end
|
||||
namespaces
|
||||
end
|
||||
|
||||
# Groups where user is an owner
|
||||
def owned_groups
|
||||
groups
|
||||
end
|
||||
|
||||
# Groups user has access to
|
||||
def authorized_groups
|
||||
@authorized_groups ||= begin
|
||||
groups = Group.where(id: self.authorized_projects.pluck(:namespace_id)).all
|
||||
groups = groups + self.groups
|
||||
groups.uniq
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Projects user has access to
|
||||
def authorized_projects
|
||||
project_ids = users_projects.pluck(:project_id)
|
||||
project_ids = project_ids | owned_projects.pluck(:id)
|
||||
Project.where(id: project_ids)
|
||||
end
|
||||
|
||||
# Projects in user namespace
|
||||
def personal_projects
|
||||
Project.personal(self)
|
||||
end
|
||||
|
||||
# Projects where user is an owner
|
||||
def owned_projects
|
||||
Project.where("(projects.namespace_id IN (:namespaces)) OR
|
||||
(projects.namespace_id IS NULL AND projects.creator_id = :user_id)",
|
||||
namespaces: namespaces.map(&:id), user_id: self.id)
|
||||
end
|
||||
|
||||
# Team membership in personal projects
|
||||
def tm_in_personal_projects
|
||||
UsersProject.where(project_id: personal_projects.map(&:id), user_id: self.id)
|
||||
end
|
||||
|
||||
# Returns a string for use as a Gitolite user identifier
|
||||
#
|
||||
# Note that Gitolite 2.x requires the following pattern for users:
|
||||
#
|
||||
# ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$
|
||||
def identifier
|
||||
# Replace non-word chars with underscores, then make sure it starts with
|
||||
# valid chars
|
||||
email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '')
|
||||
end
|
||||
|
||||
def is_admin?
|
||||
admin
|
||||
end
|
||||
|
||||
def require_ssh_key?
|
||||
keys.count == 0
|
||||
end
|
||||
|
||||
def can_create_project?
|
||||
projects_limit > personal_projects.count
|
||||
end
|
||||
|
||||
def can_create_group?
|
||||
is_admin?
|
||||
end
|
||||
|
||||
def abilities
|
||||
@abilities ||= begin
|
||||
abilities = Six.new
|
||||
abilities << Ability
|
||||
abilities
|
||||
end
|
||||
end
|
||||
|
||||
def can? action, subject
|
||||
abilities.allowed?(self, action, subject)
|
||||
end
|
||||
|
||||
def first_name
|
||||
name.split.first unless name.blank?
|
||||
end
|
||||
|
||||
def cared_merge_requests
|
||||
MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id)
|
||||
end
|
||||
|
||||
# Remove user from all projects and
|
||||
# set blocked attribute to true
|
||||
def block
|
||||
users_projects.find_each do |membership|
|
||||
return false unless membership.destroy
|
||||
end
|
||||
|
||||
self.blocked = true
|
||||
save
|
||||
end
|
||||
|
||||
def projects_limit_percent
|
||||
return 100 if projects_limit.zero?
|
||||
(personal_projects.count.to_f / projects_limit) * 100
|
||||
end
|
||||
|
||||
def recent_push project_id = nil
|
||||
# Get push events not earlier than 2 hours ago
|
||||
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
|
||||
events = events.where(project_id: project_id) if project_id
|
||||
|
||||
# Take only latest one
|
||||
events = events.recent.limit(1).first
|
||||
end
|
||||
|
||||
def projects_sorted_by_activity
|
||||
authorized_projects.sorted_by_activity
|
||||
end
|
||||
|
||||
def several_namespaces?
|
||||
namespaces.size > 1
|
||||
end
|
||||
|
||||
def namespace_id
|
||||
namespace.try :id
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
|
||||
class UsersProject < ActiveRecord::Base
|
||||
include GitHost
|
||||
include Gitolited
|
||||
|
||||
GUEST = 10
|
||||
REPORTER = 20
|
||||
|
@ -23,87 +23,96 @@ class UsersProject < ActiveRecord::Base
|
|||
belongs_to :user
|
||||
belongs_to :project
|
||||
|
||||
after_save :update_repository
|
||||
after_destroy :update_repository
|
||||
attr_accessor :skip_git
|
||||
|
||||
after_save :update_repository, unless: :skip_git?
|
||||
after_destroy :update_repository, unless: :skip_git?
|
||||
|
||||
validates :user, presence: true
|
||||
validates :user_id, uniqueness: { :scope => [:project_id], message: "already exists in project" }
|
||||
validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
|
||||
validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true
|
||||
validates :project, presence: true
|
||||
|
||||
delegate :name, :email, to: :user, prefix: true
|
||||
|
||||
scope :guests, where(project_access: GUEST)
|
||||
scope :reporters, where(project_access: REPORTER)
|
||||
scope :developers, where(project_access: DEVELOPER)
|
||||
scope :masters, where(project_access: MASTER)
|
||||
scope :in_project, ->(project) { where(project_id: project.id) }
|
||||
|
||||
class << self
|
||||
def import_team(source_project, target_project)
|
||||
UsersProject.without_repository_callback do
|
||||
UsersProject.transaction do
|
||||
team = source_project.users_projects.all
|
||||
|
||||
team.each do |tm|
|
||||
# Skip if user already present in team
|
||||
next if target_project.users.include?(tm.user)
|
||||
# Add users to project teams with passed access option
|
||||
#
|
||||
# access can be an integer representing a access code
|
||||
# or symbol like :master representing role
|
||||
#
|
||||
# Ex.
|
||||
# add_users_into_projects(
|
||||
# project_ids,
|
||||
# user_ids,
|
||||
# UsersProject::MASTER
|
||||
# )
|
||||
#
|
||||
# add_users_into_projects(
|
||||
# project_ids,
|
||||
# user_ids,
|
||||
# :master
|
||||
# )
|
||||
#
|
||||
def add_users_into_projects(project_ids, user_ids, access)
|
||||
project_access = if roles_hash.has_key?(access)
|
||||
roles_hash[access]
|
||||
elsif roles_hash.values.include?(access.to_i)
|
||||
access
|
||||
else
|
||||
raise "Non valid access"
|
||||
end
|
||||
|
||||
new_tm = tm.dup
|
||||
new_tm.id = nil
|
||||
new_tm.project_id = target_project.id
|
||||
new_tm.save
|
||||
UsersProject.transaction do
|
||||
project_ids.each do |project_id|
|
||||
user_ids.each do |user_id|
|
||||
users_project = UsersProject.new(project_access: project_access, user_id: user_id)
|
||||
users_project.project_id = project_id
|
||||
users_project.skip_git = true
|
||||
users_project.save
|
||||
end
|
||||
end
|
||||
Gitlab::Gitolite.new.update_repositories(Project.where(id: project_ids))
|
||||
end
|
||||
|
||||
target_project.update_repository
|
||||
true
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
def without_repository_callback
|
||||
UsersProject.skip_callback(:destroy, :after, :update_repository)
|
||||
yield
|
||||
UsersProject.set_callback(:destroy, :after, :update_repository)
|
||||
end
|
||||
|
||||
def bulk_delete(project, user_ids)
|
||||
def truncate_teams(project_ids)
|
||||
UsersProject.transaction do
|
||||
UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
|
||||
users_projects = UsersProject.where(project_id: project_ids)
|
||||
users_projects.each do |users_project|
|
||||
users_project.skip_git = true
|
||||
users_project.destroy
|
||||
end
|
||||
Gitlab::Gitolite.new.update_repositories(Project.where(id: project_ids))
|
||||
end
|
||||
|
||||
true
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
def bulk_update(project, user_ids, project_access)
|
||||
UsersProject.transaction do
|
||||
UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
|
||||
users_project.project_access = project_access
|
||||
users_project.save
|
||||
end
|
||||
end
|
||||
def truncate_team project
|
||||
truncate_teams [project.id]
|
||||
end
|
||||
|
||||
def bulk_import(project, user_ids, project_access)
|
||||
UsersProject.transaction do
|
||||
user_ids.each do |user_id|
|
||||
users_project = UsersProject.new(
|
||||
project_access: project_access,
|
||||
user_id: user_id
|
||||
)
|
||||
users_project.project = project
|
||||
users_project.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def user_bulk_import(user, project_ids, project_access)
|
||||
UsersProject.transaction do
|
||||
project_ids.each do |project_id|
|
||||
users_project = UsersProject.new(
|
||||
project_access: project_access,
|
||||
)
|
||||
users_project.project_id = project_id
|
||||
users_project.user_id = user.id
|
||||
users_project.save
|
||||
end
|
||||
end
|
||||
def roles_hash
|
||||
{
|
||||
guest: GUEST,
|
||||
reporter: REPORTER,
|
||||
developer: DEVELOPER,
|
||||
master: MASTER
|
||||
}
|
||||
end
|
||||
|
||||
def access_roles
|
||||
|
@ -116,12 +125,8 @@ class UsersProject < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def role_access
|
||||
project_access
|
||||
end
|
||||
|
||||
def update_repository
|
||||
git_host.update_repository(project)
|
||||
gitolite.update_repository(project)
|
||||
end
|
||||
|
||||
def project_access_human
|
||||
|
@ -131,4 +136,8 @@ class UsersProject < ActiveRecord::Base
|
|||
def repo_access_human
|
||||
self.class.access_roles.invert[self.project_access]
|
||||
end
|
||||
|
||||
def skip_git?
|
||||
!!@skip_git
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,5 +50,4 @@ class Wiki < ActiveRecord::Base
|
|||
def set_slug
|
||||
self.slug = self.title.parameterize
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ class IssueObserver < ActiveRecord::Observer
|
|||
|
||||
def after_create(issue)
|
||||
if issue.assignee && issue.assignee != current_user
|
||||
Notify.new_issue_email(issue.id).deliver
|
||||
Notify.delay.new_issue_email(issue.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -16,7 +16,7 @@ class IssueObserver < ActiveRecord::Observer
|
|||
if status
|
||||
Note.create_status_change_note(issue, current_user, status)
|
||||
[issue.author, issue.assignee].compact.each do |recipient|
|
||||
Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id).deliver
|
||||
Notify.delay.issue_status_changed_email(recipient.id, issue.id, status, current_user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -27,7 +27,7 @@ class IssueObserver < ActiveRecord::Observer
|
|||
recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
|
||||
|
||||
recipient_ids.each do |recipient_id|
|
||||
Notify.reassigned_issue_email(recipient_id, issue.id, issue.assignee_id_was).deliver
|
||||
Notify.delay.reassigned_issue_email(recipient_id, issue.id, issue.assignee_id_was)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
class KeyObserver < ActiveRecord::Observer
|
||||
include GitHost
|
||||
include Gitolited
|
||||
|
||||
def after_save(key)
|
||||
git_host.set_key(key.identifier, key.key, key.projects)
|
||||
gitolite.set_key(key.identifier, key.key, key.projects)
|
||||
end
|
||||
|
||||
def after_destroy(key)
|
||||
return if key.is_deploy_key && !key.last_deploy?
|
||||
git_host.remove_key(key.identifier, key.projects)
|
||||
gitolite.remove_key(key.identifier, key.projects)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ class MergeRequestObserver < ActiveRecord::Observer
|
|||
|
||||
def after_create(merge_request)
|
||||
if merge_request.assignee && merge_request.assignee != current_user
|
||||
Notify.new_merge_request_email(merge_request.id).deliver
|
||||
Notify.delay.new_merge_request_email(merge_request.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -25,7 +25,7 @@ class MergeRequestObserver < ActiveRecord::Observer
|
|||
recipients_ids.delete current_user.id
|
||||
|
||||
recipients_ids.each do |recipient_id|
|
||||
Notify.reassigned_merge_request_email(recipient_id, merge_request.id, merge_request.assignee_id_was).deliver
|
||||
Notify.delay.reassigned_merge_request_email(recipient_id, merge_request.id, merge_request.assignee_id_was)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ class NoteObserver < ActiveRecord::Observer
|
|||
notify_team(note)
|
||||
elsif note.notify_author
|
||||
# Notify only author of resource
|
||||
Notify.note_commit_email(note.noteable.author_email, note.id).deliver
|
||||
Notify.delay.note_commit_email(note.noteable.author_email, note.id)
|
||||
else
|
||||
# Otherwise ignore it
|
||||
nil
|
||||
|
@ -26,7 +26,7 @@ class NoteObserver < ActiveRecord::Observer
|
|||
|
||||
if Notify.respond_to? notify_method
|
||||
team_without_note_author(note).map do |u|
|
||||
Notify.send(notify_method, u.id, note.id).deliver
|
||||
Notify.delay.send(notify_method, u.id, note.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ class UserObserver < ActiveRecord::Observer
|
|||
def after_create(user)
|
||||
log_info("User \"#{user.name}\" (#{user.email}) was created")
|
||||
|
||||
Notify.new_user_email(user.id, user.password).deliver
|
||||
Notify.delay.new_user_email(user.id, user.password)
|
||||
end
|
||||
|
||||
def after_destroy user
|
||||
|
@ -14,7 +14,7 @@ class UserObserver < ActiveRecord::Observer
|
|||
if user.namespace
|
||||
user.namespace.update_attributes(path: user.username)
|
||||
else
|
||||
user.create_namespace!(path: user.username, name: user.name)
|
||||
user.create_namespace!(path: user.username, name: user.username)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class UsersProjectObserver < ActiveRecord::Observer
|
||||
def after_commit(users_project)
|
||||
return if users_project.destroyed?
|
||||
Notify.project_access_granted_email(users_project.id).deliver
|
||||
Notify.delay.project_access_granted_email(users_project.id)
|
||||
end
|
||||
|
||||
def after_create(users_project)
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
module Account
|
||||
# Returns a string for use as a Gitolite user identifier
|
||||
#
|
||||
# Note that Gitolite 2.x requires the following pattern for users:
|
||||
#
|
||||
# ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$
|
||||
def identifier
|
||||
# Replace non-word chars with underscores, then make sure it starts with
|
||||
# valid chars
|
||||
email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '')
|
||||
end
|
||||
|
||||
def is_admin?
|
||||
admin
|
||||
end
|
||||
|
||||
def require_ssh_key?
|
||||
keys.count == 0
|
||||
end
|
||||
|
||||
def can_create_project?
|
||||
projects_limit > my_own_projects.count
|
||||
end
|
||||
|
||||
def can_create_group?
|
||||
is_admin?
|
||||
end
|
||||
|
||||
def abilities
|
||||
@abilities ||= begin
|
||||
abilities = Six.new
|
||||
abilities << Ability
|
||||
abilities
|
||||
end
|
||||
end
|
||||
|
||||
def can? action, subject
|
||||
abilities.allowed?(self, action, subject)
|
||||
end
|
||||
|
||||
def last_activity_project
|
||||
projects.first
|
||||
end
|
||||
|
||||
def first_name
|
||||
name.split.first unless name.blank?
|
||||
end
|
||||
|
||||
def cared_merge_requests
|
||||
MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id)
|
||||
end
|
||||
|
||||
def project_ids
|
||||
projects.map(&:id)
|
||||
end
|
||||
|
||||
# Remove user from all projects and
|
||||
# set blocked attribute to true
|
||||
def block
|
||||
users_projects.find_each do |membership|
|
||||
return false unless membership.destroy
|
||||
end
|
||||
|
||||
self.blocked = true
|
||||
save
|
||||
end
|
||||
|
||||
def projects_limit_percent
|
||||
return 100 if projects_limit.zero?
|
||||
(my_own_projects.count.to_f / projects_limit) * 100
|
||||
end
|
||||
|
||||
def recent_push project_id = nil
|
||||
# Get push events not earlier than 2 hours ago
|
||||
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
|
||||
events = events.where(project_id: project_id) if project_id
|
||||
|
||||
# Take only latest one
|
||||
events = events.recent.limit(1).first
|
||||
end
|
||||
|
||||
def projects_sorted_by_activity
|
||||
projects.sorted_by_activity
|
||||
end
|
||||
|
||||
def namespaces
|
||||
namespaces = []
|
||||
|
||||
# Add user account namespace
|
||||
namespaces << self.namespace if self.namespace
|
||||
|
||||
# Add groups you can manage
|
||||
namespaces += if admin
|
||||
Group.all
|
||||
else
|
||||
groups.all
|
||||
end
|
||||
namespaces
|
||||
end
|
||||
|
||||
def several_namespaces?
|
||||
namespaces.size > 1
|
||||
end
|
||||
|
||||
def namespace_id
|
||||
namespace.try :id
|
||||
end
|
||||
|
||||
def authorized_groups
|
||||
@authorized_groups ||= begin
|
||||
groups = Group.where(id: self.projects.pluck(:namespace_id)).all
|
||||
groups = groups + self.groups
|
||||
groups.uniq
|
||||
end
|
||||
end
|
||||
|
||||
def authorized_projects
|
||||
Project.authorized_for(self)
|
||||
end
|
||||
|
||||
def my_own_projects
|
||||
Project.personal(self)
|
||||
end
|
||||
end
|
|
@ -1,58 +0,0 @@
|
|||
module Authority
|
||||
# Compatible with all access rights
|
||||
# Should be rewrited for new access rights
|
||||
def add_access(user, *access)
|
||||
access = if access.include?(:admin)
|
||||
{ project_access: UsersProject::MASTER }
|
||||
elsif access.include?(:write)
|
||||
{ project_access: UsersProject::DEVELOPER }
|
||||
else
|
||||
{ project_access: UsersProject::REPORTER }
|
||||
end
|
||||
opts = { user: user }
|
||||
opts.merge!(access)
|
||||
users_projects.create(opts)
|
||||
end
|
||||
|
||||
def reset_access(user)
|
||||
users_projects.where(project_id: self.id, user_id: user.id).destroy if self.id
|
||||
end
|
||||
|
||||
def repository_readers
|
||||
keys = Key.joins({user: :users_projects}).
|
||||
where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::REPORTER)
|
||||
keys.map(&:identifier) + deploy_keys.map(&:identifier)
|
||||
end
|
||||
|
||||
def repository_writers
|
||||
keys = Key.joins({user: :users_projects}).
|
||||
where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::DEVELOPER)
|
||||
keys.map(&:identifier)
|
||||
end
|
||||
|
||||
def repository_masters
|
||||
keys = Key.joins({user: :users_projects}).
|
||||
where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::MASTER)
|
||||
keys.map(&:identifier)
|
||||
end
|
||||
|
||||
def allow_read_for?(user)
|
||||
!users_projects.where(user_id: user.id).empty?
|
||||
end
|
||||
|
||||
def guest_access_for?(user)
|
||||
!users_projects.where(user_id: user.id).empty?
|
||||
end
|
||||
|
||||
def report_access_for?(user)
|
||||
!users_projects.where(user_id: user.id, project_access: [UsersProject::REPORTER, UsersProject::DEVELOPER, UsersProject::MASTER]).empty?
|
||||
end
|
||||
|
||||
def dev_access_for?(user)
|
||||
!users_projects.where(user_id: user.id, project_access: [UsersProject::DEVELOPER, UsersProject::MASTER]).empty?
|
||||
end
|
||||
|
||||
def master_access_for?(user)
|
||||
!users_projects.where(user_id: user.id, project_access: [UsersProject::MASTER]).empty?
|
||||
end
|
||||
end
|
|
@ -1,5 +0,0 @@
|
|||
module GitHost
|
||||
def git_host
|
||||
Gitlab::Gitolite.new
|
||||
end
|
||||
end
|
|
@ -1,59 +0,0 @@
|
|||
module NamespacedProject
|
||||
def transfer(new_namespace)
|
||||
Project.transaction do
|
||||
old_namespace = namespace
|
||||
self.namespace = new_namespace
|
||||
|
||||
old_dir = old_namespace.try(:path) || ''
|
||||
new_dir = new_namespace.try(:path) || ''
|
||||
|
||||
old_repo = if old_dir.present?
|
||||
File.join(old_dir, self.path)
|
||||
else
|
||||
self.path
|
||||
end
|
||||
|
||||
if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present?
|
||||
raise TransferError.new("Project with same path in target namespace already exists")
|
||||
end
|
||||
|
||||
Gitlab::ProjectMover.new(self, old_dir, new_dir).execute
|
||||
|
||||
git_host.move_repository(old_repo, self)
|
||||
|
||||
save!
|
||||
end
|
||||
rescue Gitlab::ProjectMover::ProjectMoveError => ex
|
||||
raise TransferError.new(ex.message)
|
||||
end
|
||||
|
||||
def name_with_namespace
|
||||
@name_with_namespace ||= begin
|
||||
if namespace
|
||||
namespace.human_name + " / " + name
|
||||
else
|
||||
name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def namespace_owner
|
||||
namespace.try(:owner)
|
||||
end
|
||||
|
||||
def chief
|
||||
if namespace
|
||||
namespace_owner
|
||||
else
|
||||
owner
|
||||
end
|
||||
end
|
||||
|
||||
def path_with_namespace
|
||||
if namespace
|
||||
namespace.path + '/' + path
|
||||
else
|
||||
path
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
module NoteEvent
|
||||
def note_commit_id
|
||||
target.commit_id
|
||||
end
|
||||
|
||||
def note_short_commit_id
|
||||
note_commit_id[0..8]
|
||||
end
|
||||
|
||||
def note_commit?
|
||||
target.noteable_type == "Commit"
|
||||
end
|
||||
|
||||
def note_target
|
||||
target.noteable
|
||||
end
|
||||
|
||||
def note_target_id
|
||||
if note_commit?
|
||||
target.commit_id
|
||||
else
|
||||
target.noteable_id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def wall_note?
|
||||
target.noteable_type.blank?
|
||||
end
|
||||
|
||||
def note_target_type
|
||||
if target.noteable_type.present?
|
||||
target.noteable_type.titleize
|
||||
else
|
||||
"Wall"
|
||||
end.downcase
|
||||
end
|
||||
end
|
|
@ -1,100 +0,0 @@
|
|||
module PushEvent
|
||||
def valid_push?
|
||||
data[:ref]
|
||||
rescue => ex
|
||||
false
|
||||
end
|
||||
|
||||
def tag?
|
||||
data[:ref]["refs/tags"]
|
||||
end
|
||||
|
||||
def branch?
|
||||
data[:ref]["refs/heads"]
|
||||
end
|
||||
|
||||
def new_branch?
|
||||
commit_from =~ /^00000/
|
||||
end
|
||||
|
||||
def new_ref?
|
||||
commit_from =~ /^00000/
|
||||
end
|
||||
|
||||
def rm_ref?
|
||||
commit_to =~ /^00000/
|
||||
end
|
||||
|
||||
def md_ref?
|
||||
!(rm_ref? || new_ref?)
|
||||
end
|
||||
|
||||
def commit_from
|
||||
data[:before]
|
||||
end
|
||||
|
||||
def commit_to
|
||||
data[:after]
|
||||
end
|
||||
|
||||
def ref_name
|
||||
if tag?
|
||||
tag_name
|
||||
else
|
||||
branch_name
|
||||
end
|
||||
end
|
||||
|
||||
def branch_name
|
||||
@branch_name ||= data[:ref].gsub("refs/heads/", "")
|
||||
end
|
||||
|
||||
def tag_name
|
||||
@tag_name ||= data[:ref].gsub("refs/tags/", "")
|
||||
end
|
||||
|
||||
# Max 20 commits from push DESC
|
||||
def commits
|
||||
@commits ||= data[:commits].map { |commit| project.commit(commit[:id]) }.reverse
|
||||
end
|
||||
|
||||
def commits_count
|
||||
data[:total_commits_count] || commits.count || 0
|
||||
end
|
||||
|
||||
def ref_type
|
||||
tag? ? "tag" : "branch"
|
||||
end
|
||||
|
||||
def push_action_name
|
||||
if new_ref?
|
||||
"pushed new"
|
||||
elsif rm_ref?
|
||||
"deleted"
|
||||
else
|
||||
"pushed to"
|
||||
end
|
||||
end
|
||||
|
||||
def parent_commit
|
||||
project.commit(commit_from)
|
||||
rescue => ex
|
||||
nil
|
||||
end
|
||||
|
||||
def last_commit
|
||||
project.commit(commit_to)
|
||||
rescue => ex
|
||||
nil
|
||||
end
|
||||
|
||||
def push_with_commits?
|
||||
md_ref? && commits.any? && parent_commit && last_commit
|
||||
rescue Grit::NoSuchPathError
|
||||
false
|
||||
end
|
||||
|
||||
def last_push_to_non_root?
|
||||
branch? && project.default_branch != branch_name
|
||||
end
|
||||
end
|
|
@ -1,144 +0,0 @@
|
|||
# Includes methods for handling Git Push events
|
||||
#
|
||||
# Triggered by PostReceive job
|
||||
module PushObserver
|
||||
# This method will be called after each post receive and only if the provided
|
||||
# user is present in GitLab.
|
||||
#
|
||||
# All callbacks for post receive should be placed here.
|
||||
def trigger_post_receive(oldrev, newrev, ref, user)
|
||||
data = post_receive_data(oldrev, newrev, ref, user)
|
||||
|
||||
# Create push event
|
||||
self.observe_push(data)
|
||||
|
||||
if push_to_branch? ref, oldrev
|
||||
# Close merged MR
|
||||
self.update_merge_requests(oldrev, newrev, ref, user)
|
||||
|
||||
# Execute web hooks
|
||||
self.execute_hooks(data.dup)
|
||||
|
||||
# Execute project services
|
||||
self.execute_services(data.dup)
|
||||
end
|
||||
|
||||
# Create satellite
|
||||
self.satellite.create unless self.satellite.exists?
|
||||
|
||||
# Discover the default branch, but only if it hasn't already been set to
|
||||
# something else
|
||||
if default_branch.nil?
|
||||
update_attributes(default_branch: discover_default_branch)
|
||||
end
|
||||
end
|
||||
|
||||
def push_to_branch? ref, oldrev
|
||||
ref_parts = ref.split('/')
|
||||
|
||||
# Return if this is not a push to a branch (e.g. new commits)
|
||||
!(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000")
|
||||
end
|
||||
|
||||
def observe_push(data)
|
||||
Event.create(
|
||||
project: self,
|
||||
action: Event::Pushed,
|
||||
data: data,
|
||||
author_id: data[:user_id]
|
||||
)
|
||||
end
|
||||
|
||||
def execute_hooks(data)
|
||||
hooks.each { |hook| hook.execute(data) }
|
||||
end
|
||||
|
||||
def execute_services(data)
|
||||
services.each do |service|
|
||||
|
||||
# Call service hook only if it is active
|
||||
service.execute(data) if service.active
|
||||
end
|
||||
end
|
||||
|
||||
# Produce a hash of post-receive data
|
||||
#
|
||||
# data = {
|
||||
# before: String,
|
||||
# after: String,
|
||||
# ref: String,
|
||||
# user_id: String,
|
||||
# user_name: String,
|
||||
# repository: {
|
||||
# name: String,
|
||||
# url: String,
|
||||
# description: String,
|
||||
# homepage: String,
|
||||
# },
|
||||
# commits: Array,
|
||||
# total_commits_count: Fixnum
|
||||
# }
|
||||
#
|
||||
def post_receive_data(oldrev, newrev, ref, user)
|
||||
|
||||
push_commits = commits_between(oldrev, newrev)
|
||||
|
||||
# Total commits count
|
||||
push_commits_count = push_commits.size
|
||||
|
||||
# Get latest 20 commits ASC
|
||||
push_commits_limited = push_commits.last(20)
|
||||
|
||||
# Hash to be passed as post_receive_data
|
||||
data = {
|
||||
before: oldrev,
|
||||
after: newrev,
|
||||
ref: ref,
|
||||
user_id: user.id,
|
||||
user_name: user.name,
|
||||
repository: {
|
||||
name: name,
|
||||
url: web_url,
|
||||
description: description,
|
||||
homepage: web_url,
|
||||
},
|
||||
commits: [],
|
||||
total_commits_count: push_commits_count
|
||||
}
|
||||
|
||||
# For perfomance purposes maximum 20 latest commits
|
||||
# will be passed as post receive hook data.
|
||||
#
|
||||
push_commits_limited.each do |commit|
|
||||
data[:commits] << {
|
||||
id: commit.id,
|
||||
message: commit.safe_message,
|
||||
timestamp: commit.date.xmlschema,
|
||||
url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}",
|
||||
author: {
|
||||
name: commit.author_name,
|
||||
email: commit.author_email
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
def update_merge_requests(oldrev, newrev, ref, user)
|
||||
return true unless ref =~ /heads/
|
||||
branch_name = ref.gsub("refs/heads/", "")
|
||||
c_ids = self.commits_between(oldrev, newrev).map(&:id)
|
||||
|
||||
# Update code for merge requests
|
||||
mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all
|
||||
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
|
||||
|
||||
# Close merge requests
|
||||
mrs = self.merge_requests.opened.where(target_branch: branch_name).all
|
||||
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
|
||||
mrs.each { |merge_request| merge_request.merge!(user.id) }
|
||||
|
||||
true
|
||||
end
|
||||
end
|
|
@ -1,47 +0,0 @@
|
|||
# Provides an ActiveRecord-like interface to a model whose data is not persisted to a database.
|
||||
module StaticModel
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
# Used by ActiveRecord's polymorphic association to set object_id
|
||||
def primary_key
|
||||
'id'
|
||||
end
|
||||
|
||||
# Used by ActiveRecord's polymorphic association to set object_type
|
||||
def base_class
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# Used by AR for fetching attributes
|
||||
#
|
||||
# Pass it along if we respond to it.
|
||||
def [](key)
|
||||
send(key) if respond_to?(key)
|
||||
end
|
||||
|
||||
def to_param
|
||||
id
|
||||
end
|
||||
|
||||
def new_record?
|
||||
false
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false
|
||||
end
|
||||
|
||||
def destroyed?
|
||||
false
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
if other.is_a? StaticModel
|
||||
id == other.id
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,52 +0,0 @@
|
|||
module Team
|
||||
def team_member_by_name_or_email(name = nil, email = nil)
|
||||
user = users.where("name like ? or email like ?", name, email).first
|
||||
users_projects.where(user: user) if user
|
||||
end
|
||||
|
||||
# Get Team Member record by user id
|
||||
def team_member_by_id(user_id)
|
||||
users_projects.find_by_user_id(user_id)
|
||||
end
|
||||
|
||||
# Add user to project
|
||||
# with passed access role
|
||||
def add_user_to_team(user, access_role)
|
||||
add_user_id_to_team(user.id, access_role)
|
||||
end
|
||||
|
||||
# Add multiple users to project
|
||||
# with same access role
|
||||
def add_users_to_team(users, access_role)
|
||||
add_users_ids_to_team(users.map(&:id), access_role)
|
||||
end
|
||||
|
||||
# Add user to project
|
||||
# with passed access role by user id
|
||||
def add_user_id_to_team(user_id, access_role)
|
||||
users_projects.create(
|
||||
user_id: user_id,
|
||||
project_access: access_role
|
||||
)
|
||||
end
|
||||
|
||||
# Add multiple users to project
|
||||
# with same access role by user ids
|
||||
def add_users_ids_to_team(users_ids, access_role)
|
||||
UsersProject.bulk_import(self, users_ids, access_role)
|
||||
self.update_repository
|
||||
end
|
||||
|
||||
# Update multiple project users
|
||||
# to same access role by user ids
|
||||
def update_users_ids_to_role(users_ids, access_role)
|
||||
UsersProject.bulk_update(self, users_ids, access_role)
|
||||
self.update_repository
|
||||
end
|
||||
|
||||
# Delete multiple users from project by user ids
|
||||
def delete_users_ids_from_team(users_ids)
|
||||
UsersProject.bulk_delete(self, users_ids)
|
||||
self.update_repository
|
||||
end
|
||||
end
|
|
@ -1,33 +0,0 @@
|
|||
module Votes
|
||||
|
||||
# Return the number of -1 comments (downvotes)
|
||||
def downvotes
|
||||
notes.select(&:downvote?).size
|
||||
end
|
||||
|
||||
def downvotes_in_percent
|
||||
if votes_count.zero?
|
||||
0
|
||||
else
|
||||
100.0 - upvotes_in_percent
|
||||
end
|
||||
end
|
||||
|
||||
# Return the number of +1 comments (upvotes)
|
||||
def upvotes
|
||||
notes.select(&:upvote?).size
|
||||
end
|
||||
|
||||
def upvotes_in_percent
|
||||
if votes_count.zero?
|
||||
0
|
||||
else
|
||||
100.0 / votes_count * upvotes
|
||||
end
|
||||
end
|
||||
|
||||
# Return the total number of votes
|
||||
def votes_count
|
||||
upvotes + downvotes
|
||||
end
|
||||
end
|
|
@ -1,49 +1,13 @@
|
|||
# encoding: utf-8
|
||||
|
||||
class AttachmentUploader < CarrierWave::Uploader::Base
|
||||
|
||||
# Include RMagick or ImageScience support:
|
||||
# include CarrierWave::RMagick
|
||||
# include CarrierWave::MiniMagick
|
||||
# include CarrierWave::ImageScience
|
||||
|
||||
# Choose what kind of storage to use for this uploader:
|
||||
storage :file
|
||||
# storage :fog
|
||||
|
||||
# Override the directory where uploaded files will be stored.
|
||||
# This is a sensible default for uploaders that are meant to be mounted:
|
||||
def store_dir
|
||||
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
|
||||
end
|
||||
|
||||
# Provide a default URL as a default if there hasn't been a file uploaded:
|
||||
# def default_url
|
||||
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
|
||||
# end
|
||||
|
||||
# Process files as they are uploaded:
|
||||
# process scale: [200, 300]
|
||||
#
|
||||
# def scale(width, height)
|
||||
# # do something
|
||||
# end
|
||||
|
||||
# Create different versions of your uploaded files:
|
||||
# version :thumb do
|
||||
# process scale: [50, 50]
|
||||
# end
|
||||
|
||||
# Add a white list of extensions which are allowed to be uploaded.
|
||||
# For images you might use something like this:
|
||||
# def extension_white_list
|
||||
# %w(jpg jpeg gif png)
|
||||
# end
|
||||
|
||||
# Override the filename of the uploaded files:
|
||||
# Avoid using model.id or version_name here, see uploader/store.rb for details.
|
||||
# def filename
|
||||
# "something.jpg" if original_filename
|
||||
# end
|
||||
|
||||
def image?
|
||||
%w(png jpg jpeg).include?(file.extension)
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue