Merge branch 'master' into file-store-4_git_task

Conflicts:
	Gemfile
This commit is contained in:
Alexander Machehin 2012-11-02 23:08:38 +06:00
commit 345e4f698b
263 changed files with 7241 additions and 923 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ public/downloads/*
*.tmproj
.sass-cache/
dump.rdb
crash.log

View File

@ -4,7 +4,7 @@ gem 'rails', '3.2.8' #, :git => 'git://github.com/rails/rails.git'
gem 'pg', '~> 0.14.0'
# gem 'silent-postgres', :git => 'git://github.com/dolzenko/silent-postgres.git' #'~> 0.1.1'
gem 'redhillonrails_core', :git => 'git://github.com/chipiga/redhillonrails_core.git', :branch => 'rails31' # '~> 2.0.0.pre' # deprecated
gem 'redhillonrails_core', :git => 'git://github.com/warpc/redhillonrails_core.git', :branch => 'rails31' # '~> 2.0.0.pre' # deprecated
# gem 'schema_plus', '~> 0.2.1' # buggy shit!
gem 'devise', '~> 2.1.2'
@ -32,7 +32,7 @@ gem 'diff-display', '~> 0.0.1'
# Wiki
gem "gollum", '~> 2.1.3'
gem "redcarpet", "1.17.2"
gem "redcarpet", "~> 2.1.1"
gem 'creole'
gem 'rdiscount'
# gem 'org-ruby'
@ -44,7 +44,7 @@ gem 'trinidad', '~> 1.0.2', :platforms => :jruby
gem 'newrelic_rpm', '~> 3.4.1', :platforms => [:mri, :rbx]
gem 'whenever', '~> 0.7.3', :require => false
gem 'jbuilder', '~> 0.4.0'
gem 'jbuilder', '~> 0.8.2'
gem 'rails3-jquery-autocomplete', '~> 1.0.7'
gem 'will_paginate', '~> 3.0.3'
gem 'meta-tags', '~> 1.2.5', :require => 'meta_tags'
@ -53,6 +53,7 @@ gem 'jquery-rails', '~> 2.0.2'
gem 'ruby-haml-js', '~> 0.0.3'
gem 'rails-backbone', '~> 0.7.2'
gem 'rack-throttle'
gem 'rest-client', '~> 1.6.6'
group :assets do

View File

@ -1,11 +1,3 @@
GIT
remote: git://github.com/chipiga/redhillonrails_core.git
revision: 5f58167c41882890c223168b0a5521d99e8d92aa
branch: rails31
specs:
redhillonrails_core (2.0.0.pre)
activerecord (>= 3.1.0.rc)
GIT
remote: git://github.com/rdblue/grack.git
revision: 020be3fef3fb308b9d214252522aa5945bf6584a
@ -21,6 +13,14 @@ GIT
mime-types (~> 1.15)
posix-spawn (~> 0.3.6)
GIT
remote: git://github.com/warpc/redhillonrails_core.git
revision: c0945a4c6ad4bae4ca2750b105efcff162408b15
branch: rails31
specs:
redhillonrails_core (2.0.0.pre)
activerecord (>= 3.1.0.rc)
GEM
remote: http://rubygems.org/
specs:
@ -145,9 +145,8 @@ GEM
hike (1.2.1)
hirb (0.7.0)
i18n (0.6.0)
jbuilder (0.4.3)
jbuilder (0.8.2)
activesupport (>= 3.0.0)
blankslate (>= 2.1.2.4)
journey (1.0.4)
jquery-rails (2.0.2)
railties (>= 3.2.0, < 5.0)
@ -218,6 +217,8 @@ GEM
rack
rack-test (0.6.1)
rack (>= 1.0)
rack-throttle (0.3.0)
rack (>= 1.0.0)
rails (3.2.8)
actionmailer (= 3.2.8)
actionpack (= 3.2.8)
@ -246,7 +247,7 @@ GEM
rdiscount (1.6.8)
rdoc (3.12)
json (~> 1.4)
redcarpet (1.17.2)
redcarpet (2.1.1)
redis (3.0.1)
redis-namespace (1.2.1)
redis (~> 3.0.0)
@ -381,7 +382,7 @@ DEPENDENCIES
haml-rails (~> 0.3.4)
highline (~> 1.6.11)
hirb
jbuilder (~> 0.4.0)
jbuilder (~> 0.8.2)
jquery-rails (~> 2.0.2)
mailcatcher
meta-tags (~> 1.2.5)
@ -391,12 +392,13 @@ DEPENDENCIES
paperclip (~> 3.1.4)
perform_later (~> 1.3.0)
pg (~> 0.14.0)
rack-throttle
rails (= 3.2.8)
rails-backbone (~> 0.7.2)
rails3-generators
rails3-jquery-autocomplete (~> 1.0.7)
rdiscount
redcarpet (= 1.17.2)
redcarpet (~> 2.1.1)
redhillonrails_core!
resque (~> 1.21.0)
resque-status (~> 0.3.3)

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

View File

@ -81,4 +81,10 @@ $(document).ready(function() {
}
return false;
});
window.CodeMirrorRun = function(code) {
CodeMirror.runMode(code.innerHTML.replace(/&amp;/gi, '&').replace(/&lt;/gi, '<').replace(/&gt;/gi, '>'), code.className, code);
}
$('.md_and_cm code').each(function (code) { CodeMirrorRun(this); });
});

View File

@ -0,0 +1,101 @@
$(document).ready(function() {
var new_comment = $('#open-comment.comment.hidden.new_line_comment');
$(document).on('click', '.buttons a.edit_comment', function() {
$(this).parents('div.activity').hide()
.next().show();
return false;
});
$(document).on('click', '.cancel_edit_comment.button', function() {
$(this).parents('#open-comment.comment').hide()
.prev().show();
return false;
});
$(document).on('submit', 'form.edit_comment', function() {
var form = $(this);
form.parent().find('.flash').remove();
$.ajax({
type: 'POST',
url: form.attr("action"),
data: form.serialize(),
success: function(data){
var cancel_button = form.find('.cancel_edit_comment.button');
var id = cancel_button.attr('id').match(/\d+$/)[0];
cancel_button.click();
$('#comment'+id+', #diff-comment'+id).parent().find('.cm-s-default.md_and_cm').html(data).find('code').each(function (code) { CodeMirrorRun(this); })
},
error: function(data){
form.before(data.responseText);
}
});
return false;
});
$('.new_inline_comment.button').on('click', function() {
$(this).parents('tr').prev('tr').find("a[href='"+$(this).attr('href')+"']").click();
return false;
});
$('.add_line-comment').on('click', function() {
function ProcessData(data) {
if(yield) {
var str = "<tr class='inline-comments'><td class='line_numbers' colspan='2'></td>"+"<td>"+data+"</td></tr>";
par.after(str);
par = par.next();
}
else {
par.find('td:last').append(data);
}
par.find('#md_tabs.nav.nav-tabs').each(function(i) {
$(this).find('a:first').tab('show');
$(this).parent().find('#new_line_edit_input').focus();
});
}
var line = $(this);
var tmp = line.parents('tr');
var yield = false;
var par = null;
if(tmp.hasClass('inline-comments')) {
par = tmp;
}
else {
par = tmp.next('tr.inline-comments');
}
if(par.length == 0) {
par = tmp;
yield = true;
}
// Hide visible new comment form
$('#open-comment.new_line_comment').parents('.inline-comments').each(function(i) {
if($(this).find('.line-comments').length > 0) {
$(this).find('#open-comment.new_line_comment').remove();
$(this).find('.new_inline_comment.button').show();
}
else {
$(this).remove();
}
});
par.find('.new_inline_comment.button').hide();
$.get(line.attr('href'), null, ProcessData);
return false;
});
$(document).on('click', '.cancel_inline_comment.button', function() {
var tr = $(this).parents('.inline-comments');
if(tr.find('.line-comments').length > 0) {
tr.find('#open-comment.new_line_comment').remove();
tr.find('.new_inline_comment.button').show();
}
else {
tr.remove();
}
return false;
});
});

View File

@ -0,0 +1,17 @@
$(document).ready(function() {
$(".div-filter-labels").live('click', function() {
var flag = this.id;
flag = flag.replace("label-","flag-");
var bg = $("#"+flag).css("background-color");
var checkbox = $(this).find(':checkbox');
if ($(this).css("background-color") != bg) {
$(this).css("background-color",bg).css("color","#FFFFFF");
checkbox.attr('checked', 'checked');
} else {
$(this).css("background-color","rgb(247, 247, 247)").css("color","#565657");
checkbox.removeAttr('checked');
}
});
});

View File

@ -0,0 +1,23 @@
$(document).ready(function() {
var preview_url = $('#preview_url').val();
$('#md_tabs.nav.nav-tabs').each(function(i) { $(this).find('a:first').tab('show') });
$(document).on('shown','#md_tabs a[data-toggle="tab"]', function (e) {
if(e.relatedTarget) { var hash = e.relatedTarget.hash; }
else { var hash = e.currentTarget.hash; }
var el = $(hash+'_input');
var el_dup = $(hash+'_input_dup');
var preview = $(e.target.hash+' > .formatted.cm-s-default');
if(el.val() != el_dup.val() || preview.val() === '') {
el_dup.val(el.val());
$.ajax({
type: 'POST',
url: preview_url,
data: el_dup.serialize(),
success: function(data){
preview.html(data).find('code').each(function (code) { CodeMirrorRun(this); })
}
});
};
});
});

View File

@ -0,0 +1,32 @@
$(document).ready(function() {
var upd_action = $('#update_action').val();
var form = $('#new_pull_request');
$('#pull_request_to_project').on('autocompleteselect', function(event, data){
var ref = $('#to_ref');
ref.parent().load(data.item.get_refs_url+' #to_ref', {"selected": ref.val()});
});
$('#pull_request_to_project, input#to_refs, input#from_refs').on('autocompleteselect', function(event, data){
form.attr('action', upd_action)
.attr('method', 'get');
$('#update_pull').fadeIn('fast');
$('#create_pull').fadeOut('fast');
});
$('#pull_tabs a').on('click', function (e) {
var href = $(this).attr("href");
if ( window.history && history.pushState ) {
history.pushState("", "", href);
history.replaceState("", "", href);
} else {
location.hash = href;
}
});
var diff_tab = $('#pull_tabs a[href="#diff"]');
$('.link_to_full_changes').on('click', function(){
diff_tab.tab('show');
});
});

View File

@ -30,19 +30,6 @@ $(document).ready(function() {
});
$("div.div-tracker-labels").live('click', function() {
var flag = this.id;
flag = flag.replace("label-","flag-");
var bg = $("#"+flag).css("background-color");
var checkbox = $(this).find(':checkbox');
if ($(this).css("background-color") != bg) {
$(this).css("background-color",bg);
$(this).css("color","#FFFFFF");
checkbox.attr('checked', 'checked');
} else {
$(this).css("background-color","rgb(247, 247, 247)");
$(this).css("color","#565657");
checkbox.removeAttr('checked');
}
return send_index_tracker_request('GET');
});
@ -50,7 +37,7 @@ $(document).ready(function() {
return send_index_tracker_request('GET');
});
$('#search_issue').live('submit', function() {
$('.ajax_search_form').live('submit', function() {
return send_index_tracker_request('GET', $(this).attr("action"), $(this).serialize());
});
@ -224,6 +211,7 @@ $(document).ready(function() {
$('.edit_form.issue').live('submit', function() {
var form = $(this);
form.parent().find('.flash').remove();
$.ajax({
type: 'POST',
url: form.attr("action"),
@ -232,10 +220,10 @@ $(document).ready(function() {
form.fadeOut('slow');
$('#edit_issue_content').fadeIn('slow');
$('h3.issue_title').html(form.find('#issue_title').attr('value'));
$('.fulltext.view.issue_body').html(data);
$('.fulltext.view.issue_body').html(data).find('code').each(function (code) { CodeMirrorRun(this); })
},
error: function(data){
alert('error'); // TODO remove
form.before(data.responseText);
}
});
return false;

View File

@ -38,7 +38,7 @@ div.description-top div.name input {
height: 100%;
}
article div.activity {
article div.activity, .commits_activity {
border: 1px solid #D6D6D6;
border-radius: 5px 5px 5px 5px;
color: #333333;
@ -46,6 +46,18 @@ article div.activity {
padding: 6px;
}
.commits_activity table {
border-collapse: collapse;
width: 100%;
img { margin: 2px 0 0 2px; }
.date { width: 140px; }
.name { width: 250px; }
tr:nth-child(2n) {
.md_and_cm, .data-expander { background-color: #E2E4FF; }
background-color: #E2E4FF;
}
}
article div.messages div.activity {
margin-top: 0;
margin-bottom: 10px;
@ -70,6 +82,7 @@ article div.activity .top div.text {
float: left;
font-size: 12px;
padding-left: 10px;
max-width: 579px;
}
article div.activity .top .imaged {
@ -101,21 +114,22 @@ article div.activity .fulltext.hidden {
display: none;
}
div.activity .data-expander {
div.activity, .commits_activity {
.data-expander {
margin-left: 10px;
display: inline-block;
width: 12px;
cursor: pointer;
}
div.activity .data-expander.collapsed {
.data-expander.collapsed {
background: #FFF image-url('expand-gray.png') no-repeat;
background-position: 0 2px;
}
div.activity .data-expander.expanded {
.data-expander.expanded {
background: #FFF image-url('expand-gray2.png') no-repeat;
background-position: 0 2px;
}
}
div.activity .fulltext p {
margin: 5px;
@ -698,48 +712,19 @@ div.toolbar a.button {
float: left;
}
div.toolbar div.legend {
float: left;
margin-left: 50px;
padding: 4px 0 6px;
div.toolbar, table.dataTable {
> .group_owner, > .user_owner, > .group, > .user { margin-right: 15px; }
.group_owner, .user_owner, .group, .user { padding-left: 20px; }
.user_owner { background: image-url('user16g.png') no-repeat 0 0 transparent; }
.group_owner { background: image-url('group16g.png') no-repeat 0 0 transparent; }
.user { background: image-url('user16.png') no-repeat 0 0 transparent; }
.group { background: image-url('group16.png') no-repeat 0 0 transparent; }
}
table.dataTable tr td.rights span.group,
div.toolbar div.legend.rights span.group {
background: image-url('group16.png') no-repeat 0 0 transparent;
}
table.dataTable tr td.rights span.user,
div.toolbar div.legend.rights span.user {
background: image-url('user16.png') no-repeat 0 0 transparent;
}
table.dataTable tr td.rights span.group_owner,
div.toolbar div.legend.rights span.group_owner {
background: image-url('group16g.png') no-repeat 0 0 transparent;
}
table.dataTable tr td.rights span.user_owner,
div.toolbar div.legend.rights span.user_owner {
background: image-url('user16g.png') no-repeat 0 0 transparent;
}
table.dataTable tr td.rights span.group_owner,
div.toolbar div.legend.rights span.group_owner,
table.dataTable tr td.rights span.user_owner,
div.toolbar div.legend.rights span.user_owner,
table.dataTable tr td.rights span.group,
div.toolbar div.legend.rights span.group,
table.dataTable tr td.rights span.user,
div.toolbar div.legend.rights span.user {
padding-left: 20px;
}
div.toolbar div.legend.rights span.group_owner,
div.toolbar div.legend.rights span.user_owner,
div.toolbar div.legend.rights span.group,
div.toolbar div.legend.rights span.user {
margin: 0 10px;
.div-filter-labels {
.group { background: image-url('group16b.png') no-repeat 0 0 transparent; }
.user { background: image-url('user16b.png') no-repeat 0 0 transparent; }
.user, .group { padding-left: 20px; }
}
.root {
@ -948,6 +933,14 @@ div#git_help_data p {
vertical-align: top;
}
.tab-content.pull_diff_fix {
display: block;
}
table.diff {
font-size: 12px;
}
/* Mass build forms */
form.mass_build input[type="checkbox"] {
width: 10px;
@ -1325,6 +1318,15 @@ hr.bootstrap {
max-height: 280px;
}
.label-bootstrap.label-info.font14 {
padding: 4px;
line-height: 1.8;
a { color: #FFFFFF; }
}
.line_numbers a {
color: #999999;
}
/* Flash Notifies */
.flash_notify {
@ -1491,3 +1493,219 @@ table.tablesorter.platform-maintainers.static-search thead tr.search th input[ty
.build_for_pl { font-weight: bold; }
.offset25 { padding-left: 10px }
}
.md_and_cm {
overflow: auto;
margin: 10px 0 10px;
padding: 5px;
border-radius: 4px 4px 4px 4px;
background: #FFF;
pre {
background-color: #F8F8F8;
border: 1px solid #D6D6D6;
border-radius: 5px 5px 5px 5px;
color: #333333;
padding: 6px 10px;
overflow: auto;
code {
background-color: transparent;
border: none;
}
}
code {
background-color: #F8F8F8;
border: 1px solid #D6D6D6;
border-radius: 5px 5px 5px 5px;
margin: 0 2px;
padding: 0px 5px;
}
blockquote {
border-left: 4px solid #EDEDED;
padding: 0 15px;
}
p {
margin-top: 10px;
}
}
.md_and_cm > :first-child {
margin-top: 0 !important;
}
#md_tabs {
margin: 0;
}
#md_tabs.nav-tabs > li > a {
line-height: 5px;
}
#open-comment {
textarea {
background: none repeat scroll 0 0 #FFFFFF;
border: 1px solid #DEDEDE;
border-radius: 4px 4px 4px 4px;
color: #575756;
font-size: 12px;
height: 110px;
padding: 5px;
width: 98%;
max-width: 98%;
margin: 10px 0 10px;
resize: vertical;
}
textarea#issue_title, textarea#pull_request_issue_attributes_title {
height: 16px;
margin: 0 0 10px;
}
.tab-pane textarea {
min-height: 112px;
}
.edit_comment .comment-left {
margin-top: 0;
}
}
#open-comment > h3.tmargin0 {
margin-bottom: 0;
}
#md_help {
width: 800px;
top: 30%;
left: 42%;
.modal-body {
max-height: 560px;
padding-top: 0;
}
.modal-header {
padding-bottom: 0;
}
.col {
width: 245px;
float: left;
padding: 0;
margin-right: 10px;
h3 {
margin: 6px 0 0 0;
}
pre {
background: none repeat scroll 0 0 #EDEDED;
box-shadow: 0 3px 3px -1px rgba(18, 86, 135, 0.2);
border: 1px solid #EDEDED;
padding: 5px;
margin-top: 3px;
}
}
}
#repo-wrapper .add_line-comment {
position: absolute;
width: 25px;
height: 18px;
margin-left: -103px;
margin-top: 0;
cursor: pointer;
opacity: 0;
filter: alpha(Opacity=0);
-moz-opacity:0;
}
#repo-wrapper tr:hover .add_line-comment {
opacity: 1;
filter: alpha(Opacity=100);
-moz-opacity:1;
}
div.file .inline-comments {
.top {
height:auto;
background: none;
box-shadow: none;
}
.activity {
border-radius: 0;
max-width: 752px;
margin-left: 10px;
}
}
tr.inline-comments td {
border: solid #DDD;
border-width: 1px 0;
}
.inline-comments #open-comment {
max-width: 754px;
margin: 10px;
}
#repo-wrapper .edit_form.issue div.comment div.wrapper {
background: 0;
border: 0;
border-radius: 0;
height: 30px;
margin: 0;
padding: 0;
}
form#new_pull_request {
.leftlist {
width: 50px;
margin: 5px 0 20px 0;
}
.leftlist.big-list {
margin-top: 0;
width: 150px;
}
.l {
float: left;
margin-right: 10px;
input {
height: 21px;
width: 380px;
}
select {
width: 380px;
}
}
}
#repo-wrapper form#new_pull_request .wrapper {
background: none;
border: none;
height: 36px;
margin: 0;
padding: 0;
}
.new_inline_comment.button {
margin: 10px 0 10px 10px;
}
.niceRadio input {
display:none;
}
table#myTable thead tr.search th form.button_to div input {
margin-top: 2px;
}

View File

@ -362,26 +362,29 @@ article div.all.verybigpadding {
width: 545px;
padding: 0px 40px 20px 200px;
text-align: left;
}
article div.all.verybigpadding div.left {
float: left;
.info, .avatar {
display: inline-block;
}
article div.all.verybigpadding div.left img{
.avatar { margin: 0 20px 20px 0; }
.info {
width: 380px;
}
.content {
width: 550px;
display: inline-block;
.pagination { position: absolute; }
}
img {
padding-right: 40px;
margin-top: 20px;
}
article div.all.verybigpadding div.left h3, article div.all.verybigpadding div.left h4, article div.all.verybigpadding div.left p{
width: 420px;
}
article div.all.verybigpadding div.left .tmargin5 {
h3, h4, p { width: 420px; }
tmargin5 {
padding-top: 5px;
position: relative;
}
}
/* Left part of page markup */
@ -1931,7 +1934,7 @@ a.button.width100 {
width: 100px;
}
.div-tracker-labels {
.div-filter-labels {
margin: 2px 13px 2px 0px;
cursor: pointer;
border-radius: 4px;

View File

@ -1,5 +1,6 @@
# -*- encoding : utf-8 -*-
class Admin::UsersController < Admin::BaseController
include AvatarHelper
prepend_before_filter :find_user
def index
@ -28,10 +29,7 @@ class Admin::UsersController < Admin::BaseController
def update
@user.role = params[:role]
if @user.update_without_password(params[:user])
if @user.avatar && params[:delete_avatar] == '1'
@user.avatar = nil
@user.save
end
update_avatar(@user, params)
flash[:notice] = t('flash.user.saved')
redirect_to admin_users_path
else

View File

@ -5,8 +5,6 @@ class AdvisoriesController < ApplicationController
load_resource :find_by => :advisory_id
authorize_resource
before_filter :fetch_packages_info, :only => [:show]
def index
@advisories = @advisories.scoped(:include => :platforms)
@advisories = @advisories.search_by_id(params[:q]) if params[:q]
@ -18,6 +16,7 @@ class AdvisoriesController < ApplicationController
end
def show
@packages_info = @advisory.fetch_packages_info
end
def search
@ -27,24 +26,4 @@ class AdvisoriesController < ApplicationController
format.json { render @advisory }
end
end
protected
# this method fetches and structurize packages attached to current advisory.
def fetch_packages_info
@packages_info = Hash.new { |h, k| h[k] = {} } # maaagic, it's maaagic ;)
@advisory.build_lists.find_in_batches(:include => [:save_to_platform, :packages, :project]) do |batch|
batch.each do |build_list|
tmp = build_list.packages.inject({:srpm => nil, :rpm => []}) do |h, p|
p.package_type == 'binary' ? h[:rpm] << p.fullname : h[:srpm] = p.fullname
h
end
h = { build_list.project => tmp }
@packages_info[build_list.save_to_platform].merge!(h) do |pr, old, new|
{:srpm => new[:srpm], :rpm => old[:rpm].concat(new[:rpm]).uniq}
end
end
end
end
end

View File

@ -0,0 +1,44 @@
# -*- encoding : utf-8 -*-
class Api::V1::AdvisoriesController < Api::V1::BaseController
before_filter :authenticate_user!
skip_before_filter :authenticate_user!, :only => [:index, :show] if APP_CONFIG['anonymous_access']
load_resource :advisory, :find_by => :advisory_id
before_filter :find_and_authorize_build_list, :only => [:create, :update]
authorize_resource :build_list, :only => [:create, :update]
def index
@advisories = @advisories.scoped(:include => :platforms).
paginate(paginate_params)
end
def show
@packages_info = @advisory.fetch_packages_info
end
def create
if @build_list.can_attach_to_advisory? &&
@build_list.associate_and_create_advisory(params[:advisory]) &&
@build_list.save
render_json_response @advisory, 'Advisory has been created successfully'
else
render_validation_error @advisory, error_message(@build_list, 'Advisory has not been created')
end
end
def update
if @advisory && @build_list.can_attach_to_advisory? &&
@advisory.attach_build_list(@build_list) && @build_list.save
render_json_response @advisory, "Build list '#{@build_list.id}' has been attached to advisory successfully"
else
render_validation_error @advisory, error_message(@build_list, 'Build list has not been attached to advisory')
end
end
protected
def find_and_authorize_build_list
@build_list = BuildList.find params[:build_list_id]
authorize! :update, @build_list.save_to_platform
end
end

View File

@ -0,0 +1,9 @@
# -*- encoding : utf-8 -*-
class Api::V1::ArchesController < Api::V1::BaseController
before_filter :authenticate_user! unless APP_CONFIG['anonymous_access']
def index
@arches = Arch.order(:id).paginate(paginate_params)
end
end

View File

@ -0,0 +1,114 @@
# -*- encoding : utf-8 -*-
class Api::V1::BaseController < ApplicationController
#respond_to :json
helper_method :member_path
rescue_from CanCan::AccessDenied do |exception|
respond_to do |format|
format.json { render :json => {:message => t("flash.exception_message")}.to_json, :status => 403 }
end
end
protected
def set_locale
I18n.locale = :en
end
def error_message(subject, message)
[message, subject.errors.full_messages].flatten.join('. ')
end
def create_subject(subject)
class_name = subject.class.name
if subject.save
render_json_response subject, "#{class_name} has been created successfully"
else
render_validation_error subject, "#{class_name} has not been created"
end
end
def update_member_in_subject(subject, relation = :relations)
role = params[:role]
class_name = subject.class.name.downcase
if member.present? && role.present? && subject.respond_to?(:owner) && subject.owner != member &&
subject.send(relation).by_actor(member).update_all(:role => role)
render_json_response subject, "Role for #{member.class.name.downcase} '#{member.id} has been updated in #{class_name} successfully"
else
render_validation_error subject, "Role for member has not been updated in #{class_name}"
end
end
def add_member_to_subject(subject, role = 'admin')
class_name = subject.class.name.downcase
if member.present? && subject.add_member(member, role)
render_json_response subject, "#{member.class.to_s} '#{member.id}' has been added to #{class_name} successfully"
else
render_validation_error subject, "Member has not been added to #{class_name}"
end
end
def remove_member_from_subject(subject)
class_name = subject.class.name.downcase
if member.present? && subject.remove_member(member)
render_json_response subject, "#{member.class.to_s} '#{member.id}' has been removed from #{class_name} successfully"
else
render_validation_error subject, "Member has not been removed from #{class_name}"
end
end
def destroy_subject(subject)
subject.destroy # later with resque
render_json_response subject, "#{subject.class.name} has been destroyed successfully"
end
def update_subject(subject)
class_name = subject.class.name
if subject.update_attributes(params[class_name.downcase.to_sym] || {})
render_json_response subject, "#{class_name} has been updated successfully"
else
render_validation_error subject, "#{class_name} has not been updated"
end
end
def paginate_params
per_page = params[:per_page].to_i
per_page = 20 if per_page < 1
per_page = 100 if per_page >100
{:page => params[:page], :per_page => per_page}
end
def render_json_response(subject, message, status = 200)
id = status != 200 ? nil : subject.id
render :json => {
subject.class.name.underscore.to_sym => {
:id => id,
:message => message
}
}.to_json, :status => status
end
def render_validation_error(subject, message)
render_json_response(subject, error_message(subject, message), 422)
end
def member_path(subject)
if subject.is_a?(User)
api_v1_user_path(subject.id, :format => :json)
else
api_v1_group_path(subject.id, :format => :json)
end
end
private
def member
if @member.blank? && %w(User Group).include?(params[:type])
@member = params[:type].constantize.where(:id => params[:member_id]).first
end
@member
end
end

View File

@ -0,0 +1,62 @@
# -*- encoding : utf-8 -*-
class Api::V1::BuildListsController < Api::V1::BaseController
before_filter :authenticate_user!
skip_before_filter :authenticate_user!, :only => [:show, :index] if APP_CONFIG['anonymous_access']
load_and_authorize_resource :project, :only => :index
load_and_authorize_resource :build_list, :only => [:show, :create, :cancel, :publish, :reject_publish]
def index
filter = BuildList::Filter.new(nil, current_user, params[:filter] || {})
@build_lists = filter.find.scoped(:include => [:save_to_platform, :project, :user, :arch])
@build_lists = @build_lists.recent.paginate(paginate_params)
end
def create
bl_params = params[:build_list] || {}
project = Project.where(:id => bl_params[:project_id]).first
save_to_repository = Repository.where(:id => bl_params[:save_to_repository_id]).first
if project && save_to_repository
bl_params[:save_to_platform_id] = save_to_repository.platform_id
bl_params[:auto_publish] = false unless save_to_repository.publish_without_qa?
@build_list = project.build_lists.build(bl_params)
@build_list.user = current_user
@build_list.priority = current_user.build_priority # User builds more priority than mass rebuild with zero priority
if @build_list.save
render :action => 'show'
else
render :json => {:message => "Validation Failed", :errors => @build_list.errors.messages}.to_json, :status => 422
end
else
render :json => {:message => "Bad Request"}.to_json, :status => 400
end
end
def cancel
render_json :cancel
end
def publish
render_json :publish
end
def reject_publish
render_json :reject_publish
end
private
def render_json(action_name)
if @build_list.send(action_name)
render :json => {:"is_#{action_name}ed" => true, :url => api_v1_build_list_path(@build_list, :format => :json), :message => t("layout.build_lists.#{action_name}_success")}
else
render :json => {:"is_#{action_name}ed" => false, :url => api_v1_build_list_path(@build_list, :format => :json), :message => t("layout.build_lists.#{action_name}_fail")}
end
end
end

View File

@ -0,0 +1,50 @@
# -*- encoding : utf-8 -*-
class Api::V1::GroupsController < Api::V1::BaseController
before_filter :authenticate_user!
skip_before_filter :authenticate_user!, :only => [:show] if APP_CONFIG['anonymous_access']
load_and_authorize_resource
def index
# accessible_by(current_ability)
@groups = current_user.groups.paginate(paginate_params)
end
def show
end
def members
@members = @group.members.
where('actor_id != ?', @group.owner_id).
order('name').paginate(paginate_params)
end
def update
update_subject @group
end
def destroy
destroy_subject @group
end
def create
@group = current_user.own_groups.new params[:group]
create_subject @group
end
def add_member
params[:type] = 'User'
add_member_to_subject @group, (params[:role] || 'admin')
end
def remove_member
params[:type] = 'User'
remove_member_from_subject @group
end
def update_member
params[:type] = 'User'
update_member_in_subject @group, :actors
end
end

View File

@ -0,0 +1,67 @@
# -*- encoding : utf-8 -*-
class Api::V1::PlatformsController < Api::V1::BaseController
before_filter :authenticate_user!
skip_before_filter :authenticate_user!, :only => [:show, :platforms_for_build, :members] if APP_CONFIG['anonymous_access']
load_and_authorize_resource
def index
@platforms = @platforms.accessible_by(current_ability, :related).
by_type(params[:type]).paginate(paginate_params)
end
def show
end
def platforms_for_build
@platforms = Platform.main.opened.paginate(paginate_params)
render :index
end
def create
platform_params = params[:platform] || {}
owner = User.where(:id => platform_params[:owner_id]).first
@platform.owner = owner || get_owner
create_subject @platform
end
def update
platform_params = params[:platform] || {}
owner = User.where(:id => platform_params[:owner_id]).first
platform_params[:owner] = owner if owner
update_subject @platform
end
def members
@members = @platform.members.order('name').paginate(paginate_params)
end
def add_member
add_member_to_subject @platform
end
def remove_member
remove_member_from_subject @platform
end
def clone
platform_params = params[:platform] || {}
platform_params[:owner] = current_user
@cloned = @platform.full_clone(platform_params)
if @cloned.persisted?
render_json_response @platform, 'Platform has been cloned successfully'
else
render_validation_error @platform, 'Platform has not been cloned'
end
end
def clear
@platform.clear
render_json_response @platform, 'Platform has been cleared successfully'
end
def destroy
destroy_subject @platform
end
end

View File

@ -0,0 +1,75 @@
# -*- encoding : utf-8 -*-
class Api::V1::ProjectsController < Api::V1::BaseController
before_filter :authenticate_user!
skip_before_filter :authenticate_user!, :only => [:get_id, :show, :refs] if APP_CONFIG['anonymous_access']
load_and_authorize_resource :project
def index
@projects = Project.accessible_by(current_ability, :membered).
paginate(paginate_params)
end
def get_id
if @project = Project.find_by_owner_and_name(params[:owner], params[:name])
authorize! :show, @project
else
raise ActiveRecord::RecordNotFound
end
end
def show
end
def refs_list
end
def update
update_subject @project
end
def destroy
destroy_subject @project
end
def create
p_params = params[:project] || {}
owner_type = p_params[:owner_type]
if owner_type.present? && %w(User Group).include?(owner_type)
@project.owner = owner_type.constantize.
where(:id => p_params[:owner_id]).first
else
@project.owner = nil
end
authorize! :update, @project.owner if @project.owner != current_user
create_subject @project
end
def members
@members = @project.collaborators.order('uname').paginate(paginate_params)
end
def add_member
add_member_to_subject @project, params[:role]
end
def remove_member
remove_member_from_subject @project
end
def update_member
update_member_in_subject @project
end
def fork
owner = (Group.find params[:group_id] if params[:group].present?) || current_user
authorize! :update, owner if owner.class == Group
if forked = @project.fork(owner) and forked.valid?
render_json_response forked, 'Project has been forked successfully'
else
render_validation_error forked, 'Project has not been forked'
end
end
end

View File

@ -0,0 +1,65 @@
# -*- encoding : utf-8 -*-
class Api::V1::RepositoriesController < Api::V1::BaseController
before_filter :authenticate_user!
skip_before_filter :authenticate_user!, :only => [:show, :projects] if APP_CONFIG['anonymous_access']
load_and_authorize_resource :repository, :through => :platform, :shallow => true
def show
end
def projects
@projects = @repository.projects.
recent.paginate(paginate_params)
end
def update
update_subject @repository
end
def add_member
add_member_to_subject @repository
end
def remove_member
remove_member_from_subject @repository
end
def destroy
destroy_subject @repository
end
def add_project
project = Project.where(:id => params[:project_id]).first
if project
begin
@repository.projects << project
render_json_response @repository, "Project '#{project.id}' has been added to repository successfully"
rescue ActiveRecord::RecordInvalid
render_validation_error @repository, t('flash.repository.project_not_added')
end
else
render_validation_error @repository, "Project has not been added to repository"
end
end
def remove_project
project_id = params[:project_id]
ProjectToRepository.where(:project_id => project_id, :repository_id => @repository.id).destroy_all
render_json_response @repository, "Project '#{project_id}' has been removed from repository successfully"
end
def signatures
key_pair = @repository.key_pair
key_pair.destroy if key_pair
key_pair = @repository.build_key_pair(params[:repository])
key_pair.user_id = current_user.id
if key_pair.save
render_json_response @repository, 'Signatures have been updated for repository successfully'
else
render_json_response @repository, error_message(key_pair, 'Signatures have not been updated for repository'), 422
end
end
end

View File

@ -0,0 +1,48 @@
# -*- encoding : utf-8 -*-
class Api::V1::UsersController < Api::V1::BaseController
before_filter :authenticate_user!
skip_before_filter :authenticate_user!, :only => [:show] if APP_CONFIG['anonymous_access']
load_and_authorize_resource :user, :only => :show
before_filter :set_current_user, :except => :show
def show
end
def show_current_user
render :show
end
def update
user_params = params[:user] || {}
send_confirmation = user_params[:email] != @user.email
if @user.update_without_password(user_params)
if send_confirmation
@user.confirmed_at, @user.confirmation_sent_at = nil
@user.send_confirmation_instructions
end
render_json_response @user, 'User has been updated successfully'
else
render_validation_error @user, "#{class_name} has not been updated"
end
end
def notifiers
if request.put?
if @user.notifier.update_attributes(params[:notifiers])
render_json_response @user, 'User notification settings have been updated successfully'
else
render_json_response @user, error_message(@user.notifier, 'User notification settings have not been updated'), 422
end
else
render :notifiers
end
end
protected
def set_current_user
@user = current_user
end
end

View File

@ -1,5 +1,8 @@
# -*- encoding : utf-8 -*-
class ApplicationController < ActionController::Base
AIRBRAKE_IGNORE = [ActionController::InvalidAuthenticityToken,
AbstractController::ActionNotFound]
protect_from_forgery
layout :layout_by_resource
@ -14,13 +17,42 @@ class ApplicationController < ActionController::Base
helper_method :get_owner
unless Rails.env.development?
rescue_from Exception, :with => :render_500
rescue_from ActiveRecord::RecordNotFound,
ActionController::RoutingError,
ActionController::UnknownController,
AbstractController::ActionNotFound, :with => :render_404
end
rescue_from CanCan::AccessDenied do |exception|
redirect_to forbidden_url, :alert => t("flash.exception_message")
end
rescue_from Grit::NoSuchPathError, :with => :not_found
protected
def render_404
render_error 404
end
def render_500(e)
#check for exceptions Airbrake ignores by default and exclude them from manual Airbrake notification
if Rails.env.production? && !AIRBRAKE_IGNORE.include?(e.class)
notify_airbrake(e)
end
render_error 500
end
def render_error(status)
respond_to do |format|
format.json { render :json => {:status => status, :message => t("flash.#{status}_message")}.to_json, :status => status }
format.html { redirect_to "/#{status}.html", :alert => t("flash.#{status}_message") }
end
end
def set_locale
I18n.locale = check_locale( get_user_locale ||
(request.env['HTTP_ACCEPT_LANGUAGE'] ? request.env['HTTP_ACCEPT_LANGUAGE'][0,2].downcase : nil ))

View File

@ -0,0 +1,7 @@
# -*- encoding : utf-8 -*-
class AutocompletesController < ApplicationController
before_filter :authenticate_user!
autocomplete :group, :uname
autocomplete :user, :uname
end

View File

@ -29,28 +29,22 @@ class Groups::MembersController < Groups::BaseController
def remove
all_user_ids = []
params['user_remove'].each do |user_id, remove|
all_user_ids << user_id if remove == ["1"] && parent.owner.id.to_s != user_id
all_user_ids << user_id if remove == ["1"]
end if params['user_remove']
all_user_ids.each do |user_id|
u = User.find(user_id)
Relation.by_actor(u).by_target(parent).each {|r| r.destroy}
User.where(:id => all_user_ids).each do |user|
parent.remove_member(user)
end
redirect_to group_members_path(parent)
end
def add
if params['user_id'] and !params['user_id'].empty?
if params['user_id'].present?
@user = User.find_by_uname(params['user_id'])
unless parent.actors.exists? :actor_id => @user.id, :actor_type => 'User'
relation = parent.actors.build(:actor_id => @user.id, :actor_type => 'User', :role => params[:role])
if relation.save
if parent.add_member(@user, params[:role])
flash[:notice] = t("flash.members.successfully_added")
else
flash[:error] = t("flash.members.error_in_adding")
end
else
flash[:error] = t("flash.members.already_added")
end
end
redirect_to group_members_path(parent)
end

View File

@ -1,8 +1,8 @@
# -*- encoding : utf-8 -*-
class Groups::ProfileController < Groups::BaseController
include AvatarHelper
load_and_authorize_resource :class => Group, :instance_name => 'group'
autocomplete :group, :uname
skip_before_filter :authenticate_user!, :only => :show if APP_CONFIG['anonymous_access']
def index
@groups = current_user.groups.paginate(:page => params[:group_page]) # accessible_by(current_ability)
@ -10,7 +10,9 @@ class Groups::ProfileController < Groups::BaseController
end
def show
@projects = @group.projects.by_visibilities(['open'])
@projects = @group.projects.by_visibilities(['open']).
search(params[:search]).search_order.
paginate(:page => params[:page], :per_page => 25)
end
def new
@ -20,8 +22,7 @@ class Groups::ProfileController < Groups::BaseController
end
def create
@group = Group.new params[:group]
@group.owner = current_user
@group = current_user.own_groups.new params[:group]
if @group.save
flash[:notice] = t('flash.group.saved')
redirect_to group_path(@group)
@ -34,6 +35,7 @@ class Groups::ProfileController < Groups::BaseController
def update
if @group.update_attributes(params[:group])
update_avatar(@group, params)
flash[:notice] = t('flash.group.saved')
redirect_to group_path(@group)
else

View File

@ -5,8 +5,6 @@ class Platforms::PlatformsController < Platforms::BaseController
skip_before_filter :authenticate_user!, :only => [:advisories, :members, :show] if APP_CONFIG['anonymous_access']
load_and_authorize_resource
autocomplete :user, :uname
def index
@platforms = @platforms.accessible_by(current_ability, :related).paginate(:page => params[:page], :per_page => 20)
end

View File

@ -123,16 +123,19 @@ class Platforms::RepositoriesController < Platforms::BaseController
:per_page => params[:iDisplayLength].present? ? params[:iDisplayLength] : 25
)
@total_projects_count = @projects.count
@total_projects = @projects.count
@projects = @projects.search(params[:sSearch]).search_order if params[:sSearch].present?
@projects = @projects.order(order)
respond_to do |format|
format.json {
render :partial => (params[:added] == "true") ? 'project' : 'proj_ajax', :layout => false
}
end
end
def remove_project
@project = Project.find(params[:project_id])
ProjectToRepository.where(:project_id => @project.id, :repository_id => @repository.id).destroy_all
ProjectToRepository.where(:project_id => params[:project_id], :repository_id => @repository.id).destroy_all
redirect_to platform_repository_path(@platform, @repository), :notice => t('flash.repository.project_removed')
end

View File

@ -57,8 +57,6 @@ class Projects::BuildListsController < Projects::BaseController
Arch.where(:id => params[:arches]).each do |arch|
Platform.main.where(:id => build_for_platforms).each do |build_for_platform|
@build_list = @project.build_lists.build(params[:build_list])
@build_list.commit_hash = @project.repo.commits(@build_list.project_version.match(/^latest_(.+)/).to_a.last ||
@build_list.project_version).first.id if @build_list.project_version
@build_list.build_for_platform = build_for_platform; @build_list.arch = arch; @build_list.user = current_user
@build_list.include_repos = @build_list.include_repos.select {|ir| @build_list.build_for_platform.repository_ids.include? ir.to_i}
@build_list.priority = current_user.build_priority # User builds more priority than mass rebuild with zero priority
@ -198,23 +196,13 @@ class Projects::BuildListsController < Projects::BaseController
if params[:attach_advisory] == 'new'
# create new advisory
unless @build_list.build_advisory(params[:build_list][:advisory]) do |a|
a.update_type = @build_list.update_type
a.projects << @build_list.project
a.platforms << @build_list.save_to_platform unless a.platforms.include? @build_list.save_to_platform
end.save
unless @build_list.associate_and_create_advisory(params[:build_list][:advisory])
redirect_to :back, :notice => t('layout.build_lists.publish_fail') and return
end
else
# attach existing advisory
a = Advisory.where(:advisory_id => params[:attach_advisory]).limit(1).first
if a.update_type != @build_list.update_type
redirect_to :back, :notice => t('layout.build_lists.publish_fail') and return
end
a.platforms << @build_list.save_to_platform unless a.platforms.include? @build_list.save_to_platform
a.projects << @build_list.project unless a.projects.include? @build_list.project
@build_list.advisory = a
unless a.save
a = Advisory.where(:advisory_id => params[:attach_advisory]).first
unless (a && a.attach_build_list(@build_list))
redirect_to :back, :notice => t('layout.build_lists.publish_fail') and return
end
end

View File

@ -4,31 +4,34 @@ class Projects::CommentsController < Projects::BaseController
load_and_authorize_resource :project
before_filter :find_commentable
before_filter :find_or_build_comment
load_and_authorize_resource #:through => :commentable
load_and_authorize_resource :new => :new_line
include CommentsHelper
def create
if @comment.save
anchor = ''
if !@comment.set_additional_data params
flash[:error] = I18n.t("flash.comment.save_error")
elsif @comment.save
flash[:notice] = I18n.t("flash.comment.saved")
redirect_to project_commentable_path(@project, @commentable)
anchor = view_context.comment_anchor(@comment)
else
flash[:error] = I18n.t("flash.comment.save_error")
render :action => 'new'
flash[:warning] = @comment.errors.full_messages.join('. ')
end
redirect_to "#{project_commentable_path(@project, @commentable)}##{anchor}"
end
def edit
end
def update
if @comment.update_attributes(params[:comment])
flash[:notice] = I18n.t("flash.comment.saved")
redirect_to project_commentable_path(@project, @commentable)
status, message = if @comment.update_attributes(params[:comment])
[200, view_context.markdown(@comment.body)]
else
flash[:error] = I18n.t("flash.comment.save_error")
render :action => 'new'
[400, view_context.local_alert(@comment.errors.full_messages.join('. '))]
end
render :inline => message, :status => status
end
def destroy
@ -37,6 +40,11 @@ class Projects::CommentsController < Projects::BaseController
redirect_to project_commentable_path(@project, @commentable)
end
def new_line
@path = view_context.project_commentable_comments_path(@project, @commentable)
render :layout => false
end
protected
def find_commentable

View File

@ -24,7 +24,9 @@ class Projects::Git::BlobsController < Projects::Git::BaseController
end
def raw
send_data @blob.data, :type => @blob.content_type, :disposition => @blob.disposition
repo = Grit::GitRuby::Repository.new(@project.repo.path)
raw = repo.get_raw_object_by_sha1(@blob.id)
send_data raw.content, :type => @blob.content_type, :disposition => @blob.disposition
end
protected

View File

@ -9,7 +9,9 @@ class Projects::Git::CommitsController < Projects::Git::BaseController
end
def show
@commit = @project.repo.commit(params[:id])
@commit = @commentable = @project.repo.commit(params[:id]) || raise(ActiveRecord::RecordNotFound)
@comments = Comment.for_commit(@commit)
respond_to do |format|
format.html
format.diff { render :text => (@commit.diffs.map(&:diff).join("\n") rescue ''), :content_type => "text/plain" }

View File

@ -1,6 +1,7 @@
# -*- encoding : utf-8 -*-
class Projects::Git::TreesController < Projects::Git::BaseController
before_filter lambda{redirect_to @project if params[:treeish] == @project.default_branch and params[:path].blank?}, :only => 'show'
before_filter lambda{redirect_to @project if params[:treeish] == @project.default_branch and params[:path].blank?}, :only => :show
skip_before_filter :set_branch_and_tree, :only => :archive
def show
@tree = @tree / @path if @path.present?
@ -9,13 +10,18 @@ class Projects::Git::TreesController < Projects::Git::BaseController
end
def archive
@commit = @project.repo.log(@treeish, nil, :max_count => 1).first
format = params[:format]
if (@treeish =~ /^#{@project.owner.uname}-#{@project.name}-/) && !(@treeish =~ /[\s]+/) && (format =~ /^(zip|tar\.gz)$/)
@treeish = @treeish.gsub(/^#{@project.owner.uname}-#{@project.name}-/, '')
@commit = @project.repo.commits(@treeish, 1).first
end
raise Grit::NoSuchPathError unless @commit
name = "#{@project.owner.uname}-#{@project.name}#{@project.repo.tags.include?(@treeish) ? "-#{@treeish}" : ''}-#{@commit.id[0..19]}"
fullname = "#{name}.#{params[:format] == 'tar' ? 'tar.gz' : 'zip'}"
name = "#{@project.owner.uname}-#{@project.name}-#{@treeish}"
fullname = "#{name}.#{format == 'zip' ? 'zip' : 'tar.gz'}"
file = Tempfile.new fullname, 'tmp'
system("cd #{@project.path}; git archive --format=#{params[:format]} --prefix=#{name}/ #{@treeish} #{params[:format] == 'tar' ? ' | gzip -9' : ''} > #{file.path}")
system("cd #{@project.path}; git archive --format=#{format == 'zip' ? 'zip' : 'tar'} --prefix=#{name}/ #{@treeish} #{format == 'zip' ? '' : ' | gzip -9'} > #{file.path}")
file.close
send_file file.path, :disposition => 'attachment', :type => "application/#{params[:format] == 'tar' ? 'x-tar-gz' : 'zip'}", :filename => fullname
send_file file.path, :disposition => 'attachment', :type => "application/#{format == 'zip' ? 'zip' : 'x-tar-gz'}", :filename => fullname
end
end

View File

@ -10,20 +10,24 @@ class Projects::IssuesController < Projects::BaseController
layout false, :only => [:update, :search_collaborators]
def index(status = 200)
@is_assigned_to_me = params[:filter] == 'to_me'
@status = params[:status] == 'closed' ? 'closed' : 'open'
@labels = params[:labels] || []
@issues = @project.issues
@issues = @issues.where(:assignee_id => current_user.id) if @is_assigned_to_me
@issues = @project.issues.without_pull_requests
@issues = @issues.where(:assignee_id => current_user.id) if @is_assigned_to_me = params[:filter] == 'to_me'
@issues = @issues.joins(:labels).where(:labels => {:name => @labels}) unless @labels == []
# Using mb_chars for correct transform to lowercase ('Русский Текст'.downcase => "Русский Текст")
@issues = @issues.where('issues.title ILIKE ?', "%#{params[:search_issue].mb_chars.downcase}%") if params[:search_issue]
@issues = @issues.search(params[:search_issue])
@opened_issues, @closed_issues = @issues.opened.count, @issues.closed.count
@issues = @issues.where(:status => @status)
.includes(:assignee, :user).order('serial_id desc').uniq.paginate :per_page => 10, :page => params[:page]
@opened_issues, @closed_issues = @issues.not_closed_or_merged.count, @issues.closed_or_merged.count
if params[:status] == 'closed'
@issues, @status = @issues.closed_or_merged, params[:status]
else
@issues, @status = @issues.not_closed_or_merged, 'open'
end
@issues = @issues.includes(:assignee, :user, :pull_request).def_order.uniq
.paginate :per_page => 10, :page => params[:page]
if status == 200
render 'index', :layout => request.xhr? ? 'issues' : 'application'
render 'index', :layout => request.xhr? ? 'with_sidebar' : 'application'
else
render :status => status, :nothing => true
end
@ -46,15 +50,23 @@ class Projects::IssuesController < Projects::BaseController
end
end
def show
redirect_to project_pull_request_path(@project, @issue.pull_request) if @issue.pull_request
end
def update
@issue.labelings.destroy_all if params[:update_labels]
if params[:issue] && status = params[:issue][:status]
@issue.set_close(current_user) if status == 'closed'
@issue.set_open if status == 'open'
render :partial => 'status', :status => (@issue.save ? 200 : 500)
render :partial => 'status', :status => (@issue.save ? 200 : 400)
elsif params[:issue]
status = @issue.update_attributes(params[:issue]) ? 200 : 500
render :inline => ActionController::Base.helpers.simple_format(params[:issue][:body]), :status => status
status, message = if @issue.update_attributes(params[:issue])
[200, view_context.markdown(@issue.body)]
else
[400, view_context.local_alert(@issue.errors.full_messages.join('. '))]
end
render :inline => message, :status => status
else
render :nothing => true, :status => 200
end

View File

@ -1,5 +1,6 @@
# -*- encoding : utf-8 -*-
class Projects::ProjectsController < Projects::BaseController
include ProjectsHelper
before_filter :authenticate_user!
load_and_authorize_resource :id_param => :project_name # to force member actions load
@ -7,8 +8,17 @@ class Projects::ProjectsController < Projects::BaseController
@projects = Project.accessible_by(current_ability, :membered)
respond_to do |format|
format.html { @projects = @projects.recent.paginate(:page => params[:page], :per_page => 25) }
format.json { @projects = prepare_list(@projects) }
format.html {
@all_projects = @projects
@groups = current_user.groups
@owners = User.where(:id => @projects.where(:owner_type => 'User').uniq.pluck(:owner_id))
@projects = @projects.recent.paginate(:page => params[:page], :per_page => 25)
}
format.json {
selected_groups = params[:groups] || []
selected_owners = params[:users] || []
@projects = prepare_list(@projects, selected_groups, selected_owners)
}
end
end
@ -93,9 +103,19 @@ class Projects::ProjectsController < Projects::BaseController
render :json => items
end
def preview
render :inline => view_context.markdown(params[:text] || ''), :layout => false
end
def refs_list
refs = @project.repo.branches_and_tags.map(&:name)
@selected = (refs.include? params[:selected]) ? params[:selected] : @project.default_branch
render :layout => false
end
protected
def prepare_list(projects)
def prepare_list(projects, groups, owners)
res = {}
colName = ['name']
@ -104,7 +124,13 @@ class Projects::ProjectsController < Projects::BaseController
order = "#{colName[sort_col.to_i]} #{sort_dir}"
res[:total_count] = projects.count
if groups.present? || owners.present?
projects = projects.by_owners(groups, owners)
end
projects = projects.search(params[:sSearch]).search_order if params[:sSearch].present?
res[:filtered_count] = projects.count
projects = projects.order(order)

View File

@ -0,0 +1,155 @@
# -*- encoding : utf-8 -*-
class Projects::PullRequestsController < Projects::BaseController
before_filter :authenticate_user!
skip_before_filter :authenticate_user!, :only => [:index, :show] if APP_CONFIG['anonymous_access']
load_and_authorize_resource :project
load_resource :issue, :through => :project, :find_by => :serial_id, :parent => false, :except => [:index, :autocomplete_to_project]
load_and_authorize_resource :instance_name => :pull, :through => :issue, :singleton => true, :except => [:index, :autocomplete_to_project]
def new
to_project = find_destination_project(false)
authorize! :read, to_project
@pull = to_project.pull_requests.new
@pull.issue = to_project.issues.new
set_attrs
if PullRequest.check_ref(@pull, 'to', @pull.to_ref) && PullRequest.check_ref(@pull, 'from', @pull.from_ref) || @pull.uniq_merge
flash.now[:warning] = @pull.errors.full_messages.join('. ')
else
@pull.check(false) # don't make event transaction
if @pull.already?
@pull.destroy
flash.now[:warning] = I18n.t('projects.pull_requests.up_to_date', :to_ref => @pull.to_ref, :from_ref => @pull.from_ref)
else
load_diff_commits_data
end
end
end
def create
unless pull_params
raise 'expect pull_request params' # for debug
redirect :back
end
to_project = find_destination_project
authorize! :read, to_project
@pull = to_project.pull_requests.new pull_params
@pull.issue.user, @pull.issue.project, @pull.from_project = current_user, to_project, @project
if @pull.valid? # FIXME more clean/clever logics
@pull.save # set pull id
@pull.check(false) # don't make event transaction
if @pull.already?
@pull.destroy
flash.now[:error] = I18n.t('projects.pull_requests.up_to_date', :to_ref => @pull.to_ref, :from_ref => @pull.from_ref)
render :new
else
@pull.send(@pull.status == 'blocked' ? 'block' : @pull.status)
redirect_to project_pull_request_path(@pull.to_project, @pull)
end
else
flash.now[:error] = t('flash.pull_request.save_error')
flash.now[:warning] = @pull.errors.full_messages.join('. ')
if @pull.errors.try(:messages) && @pull.errors.messages[:to_ref].nil? && @pull.errors.messages[:from_ref].nil?
@pull.check(false) # don't make event transaction
load_diff_commits_data
end
render :new
end
end
def update
if (action = params[:pull_request_action]) && %w(close reopen).include?(params[:pull_request_action])
if @pull.send("can_#{action}?")
@pull.set_user_and_time current_user
@pull.send(action)
@pull.check if @pull.open?
end
end
redirect_to project_pull_request_path(@pull.to_project, @pull)
end
def merge
@pull.check
unless @pull.merge!(current_user)
flash.now[:error] = t('flash.pull_request.save_error')
flash.now[:warning] = @pull.errors.full_messages.join('. ')
end
redirect_to project_pull_request_path(@pull.to_project, @pull)
end
def show
load_diff_commits_data
end
def index(status = 200)
@issues_with_pull_request = @project.issues.joins(:pull_request)
@issues_with_pull_request = @issues_with_pull_request.search(params[:search_pull_request])
@opened_issues, @closed_issues = @issues_with_pull_request.not_closed_or_merged.count, @issues_with_pull_request.closed_or_merged.count
if params[:status] == 'closed'
@issues_with_pull_request, @status = @issues_with_pull_request.closed_or_merged, params[:status]
else
@issues_with_pull_request, @status = @issues_with_pull_request.not_closed_or_merged, 'open'
end
@issues_with_pull_request = @issues_with_pull_request.
includes(:assignee, :user, :pull_request).def_order.uniq.
paginate :per_page => 10, :page => params[:page]
if status == 200
render 'index', :layout => request.xhr? ? 'with_sidebar' : 'application'
else
render :status => status, :nothing => true
end
end
def autocomplete_to_project
items = Project.accessible_by(current_ability, :membered) | @project.ancestors
items.select! {|e| Regexp.new(params[:term].downcase).match(e.name_with_owner.downcase) && e.repo.branches.count > 0}
render :json => json_for_autocomplete_base(items)
end
protected
def pull_params
@pull_params ||= params[:pull_request].presence
end
def json_for_autocomplete_base items
items.collect do |project|
hash = {"id" => project.id.to_s, "label" => project.name_with_owner, "value" => project.name_with_owner}
hash[:get_refs_url] = project_refs_list_path(project)
hash
end
end
def load_diff_commits_data
@commits = @pull.repo.commits_between(@pull.to_commit, @pull.from_commit)
@total_commits = @commits.count
@commits = @commits.last(100)
@stats = @pull.diff_stats
@comments, @commentable = @issue.comments, @issue
end
def find_destination_project bang=true
args = params[:to_project].try(:split, '/') || []
project = (args.length == 2) ? Project.find_by_owner_and_name(*args) : nil
raise ActiveRecord::RecordNotFound if bang && !project
project || @project
end
def set_attrs
if pull_params && pull_params[:issue_attributes]
@pull.issue.title = pull_params[:issue_attributes][:title].presence
@pull.issue.body = pull_params[:issue_attributes][:body].presence
end
@pull.from_project = @project
@pull.to_ref = (pull_params[:to_ref].presence if pull_params) || @pull.to_project.default_branch
@pull.from_ref = params[:treeish].presence || (pull_params[:from_ref].presence if pull_params) || @pull.from_project.default_branch
end
end

View File

@ -1,8 +1,10 @@
# -*- encoding : utf-8 -*-
class Users::ProfileController < Users::BaseController
autocomplete :user, :uname
skip_before_filter :authenticate_user!, :only => :show if APP_CONFIG['anonymous_access']
def show
@projects = @user.projects.by_visibilities(['open'])
@projects = @user.projects.by_visibilities(['open']).
search(params[:search]).search_order.
paginate(:page => params[:page], :per_page => 25)
end
end

View File

@ -1,15 +1,13 @@
# -*- encoding : utf-8 -*-
class Users::SettingsController < Users::BaseController
include AvatarHelper
before_filter :set_current_user
def profile
if request.put?
send_confirmation = params[:user][:email] != @user.email
if @user.update_without_password(params[:user])
if @user.avatar && params[:delete_avatar] == '1'
@user.avatar = nil
@user.save
end
update_avatar(@user, params)
if send_confirmation
@user.confirmed_at, @user.confirmation_sent_at = nil
@user.send_confirmation_instructions

View File

@ -39,4 +39,19 @@ module ApplicationHelper
else object.class.name
end
end
def markdown(text)
unless @redcarpet
html_options = {filter_html: true, hard_wrap: true, with_toc_data: true}
options = {no_intraemphasis: true, tables: true, fenced_code_blocks: true, autolink: true, strikethrough: true, lax_html_blocks: true}
@redcarpet = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(html_options), options)
end
@redcarpet.render(text).html_safe
end
def local_alert(text, type = 'error')
html = "<div class='flash'><div class='alert #{type}'> #{text}"
html << link_to('×', '#', :class => 'close close-alert', 'data-dismiss' => 'alert')
html << '</div></div>'
end
end

View File

@ -0,0 +1,8 @@
module AvatarHelper
def update_avatar(subject, params)
if subject.avatar && params[:delete_avatar] == '1'
subject.avatar = nil
subject.save
end
end
end

View File

@ -1,20 +1,30 @@
# -*- encoding : utf-8 -*-
module CommentsHelper
def project_commentable_comment_path(project, commentable, comment)
case
when Comment.issue_comment?(commentable.class)
if Comment.issue_comment?(commentable.class)
project_issue_comment_path(project, commentable, comment)
when Comment.commit_comment?(commentable.class)
elsif Comment.commit_comment?(commentable.class)
project_commit_comment_path(project, commentable, comment)
end
end
def project_commentable_path(project, commentable)
case
when Comment.issue_comment?(commentable.class)
polymorphic_path [project, commentable]
when Comment.commit_comment?(commentable.class)
if Comment.issue_comment?(commentable.class)
polymorphic_path [project, commentable.pull_request ? commentable.pull_request : commentable]
elsif Comment.commit_comment?(commentable.class)
commit_path project, commentable.id
end
end
def project_commentable_comments_path(project, commentable)
if commentable.is_a? Issue
project_issue_comments_path(@project, @commentable)
elsif commentable.is_a? Grit::Commit
project_commit_comments_path(@project, @commentable)
end
end
def comment_anchor c
"#{(c.data.present? && c.actual_inline_comment?) ? 'diff-' : ''}comment#{c.id}"
end
end

View File

@ -3,9 +3,10 @@ module CommitHelper
def render_commit_stats(stats)
res = ["<table class='commit_stats'>"]
ind=0
stats.files.each do |filename, adds, deletes, total|
res << "<tr>"
res << "<td><a href='##{h(filename)}'>#{h(filename)}</a></td>"
res << "<td><a href='#diff-#{ind}'>#{h(filename.rtruncate 120)}</a></td>"
res << "<td class='diffstat'>"
res << I18n.t("layout.projects.inline_changes_count", :count => total).strip +
" (" +
@ -14,6 +15,7 @@ module CommitHelper
I18n.t("layout.projects.inline_deletions_count", :count => deletes).strip +
")"
res << "</td>"
ind +=1
end
res << "</table>"

View File

@ -1,18 +1,267 @@
# -*- encoding : utf-8 -*-
module DiffHelper
def render_diff_stats(stats)
path = @pull.id ? polymorphic_path([@project, @pull]) : ''
res = ["<table class='commit_stats'>"]
stats.each_with_index do |stat, ind|
res << "<tr>"
res << "<td>#{link_to stat.filename.rtruncate(120), "#{path}#diff-#{ind}"}</td>"
res << "<td class='diffstat'>"
res << I18n.t("layout.projects.inline_changes_count", :count => stat.additions + stat.deletions).strip +
" (" +
I18n.t("layout.projects.inline_additions_count", :count => stat.additions).strip +
", " +
I18n.t("layout.projects.inline_deletions_count", :count => stat.deletions).strip +
")"
res << "</td>"
end
res << "</table>"
def render_diff(diff)
diff_display ||= Diff::Display::Unified.new(diff.diff)
res.join("\n").html_safe.default_encoding!
end
#res = "<a name='#{h(diff.a_path)}'></a>"
#include Git::Diff::InlineCallback
def render_diff(diff, args = {})#diff_counter, comments, opts = nil diffpath = nil)
if diff.respond_to?(:diff)
diff, filepath, in_discussion = diff.diff, diff.a_path, false
comments = (args[:comments] || []).select{|c| c.data.try('[]', :path) == filepath}
else
filepath, in_discussion, comments = args[:diffpath], true, args[:comments]
end
diff_display ||= Diff::Display::Unified.new(diff)
url = if @pull
@pull.id ? polymorphic_path([@project, @pull]) : ''
elsif @commit
commit_path @project, @commit
end
prepare(args.merge({:filepath => filepath, :comments => comments, :in_discussion => in_discussion}))
res = "<table class='diff inline' cellspacing='0' cellpadding='0'>"
res += "<tbody>"
res += diff_display.render(Git::Diff::InlineCallback.new)
res += renderer diff_display.data #diff_display.render(Git::Diff::InlineCallback.new comments, path)
res += tr_line_comments(comments) if in_discussion
res += "</tbody>"
res += "</table>"
res.html_safe
end
########################################################
# FIXME: Just to dev, remove to lib. Really need it?
########################################################
def prepare(args)
@url, @diff_counter, @in_discussion = args[:url], args[:diff_counter], args[:in_discussion]
@filepath, @line_comments, @in_wiki = args[:filepath], args[:comments], args[:in_wiki]
@add_reply_id, @num_line = if @in_discussion
[@line_comments[0].id, @line_comments[0].data[:line].to_i - @line_comments[0].data[:strings].lines.count.to_i-1]
else
[nil, -1]
end
end
def headerline(line)
set_line_number
"<tr class='header'>
<td class='line_numbers'>...</td>
<td class='line_numbers'>...</td>
<td class='header'>#{line}</td>
</tr>"
end
def addline(line)
set_line_number
"<tr class='changes'>
<td class='line_numbers'></td>
#{td_line_link "diff-F#{@diff_counter}R#{line.new_number}", line.new_number}
<td class='code ins'>
#{line_comment}
<pre>#{render_line(line)}</pre>
</td>
</tr>
#{render_line_comments}"
end
def remline(line)
set_line_number
"<tr class='changes'>
#{td_line_link "diff-F#{@diff_counter}L#{line.old_number}", line.old_number}
<td class='line_numbers'></td>
<td class='code del'>
#{line_comment}
<pre>#{render_line(line)}</pre>
</td>
</tr>
#{render_line_comments}"
end
def modline(line)
set_line_number
"<tr clas='chanes line'>
#{td_line_link "diff-F#{@diff_counter}L#{line.old_number}", line.old_number}
#{td_line_link "diff-F#{@diff_counter}R#{line.new_number}", line.new_number}
<td class='code unchanged modline'>
#{line_comment}
<pre>#{render_line(line)}</pre>
</td>
</tr>
#{render_line_comments}"
end
def unmodline(line)
set_line_number
"<tr class='changes unmodline'>
#{td_line_link "diff-F#{@diff_counter}L#{line.old_number}", line.old_number}
#{td_line_link "diff-F#{@diff_counter}R#{line.new_number}", line.new_number}
<td class='code unchanged unmodline'>
#{line_comment}
<pre>#{render_line(line)}</pre>
</td>
</tr>
#{render_line_comments}"
end
def sepline(line)
"<tr class='changes hunk-sep'>
<td class='line_numbers line_num_cut'>&hellip;</td>
<td class='line_numbers line_num_cut'>&hellip;</td>
<td class='code cut-line'></td>
</tr>"
end
def nonewlineline(line)
set_line_number
"<tr class='changes'>
#{td_line_link "diff-F#{@diff_counter}L#{line.old_number}", line.old_number}
#{td_line_link "diff-F#{@diff_counter}R#{line.new_number}", line.new_number}
<td class='code modline unmodline'>
#{line_comment}
<pre>#{render_line(line)}</pre>
</td>
</tr>
#{render_line_comments}"
end
def before_headerblock(block)
end
def after_headerblock(block)
end
def before_unmodblock(block)
end
def before_modblock(block)
end
def before_remblock(block)
end
def before_addblock(block)
end
def before_sepblock(block)
end
def before_nonewlineblock(block)
end
def after_unmodblock(block)
end
def after_modblock(block)
end
def after_remblock(block)
end
def after_addblock(block)
end
def after_sepblock(block)
end
def after_nonewlineblock(block)
end
def new_line
""
end
def renderer(data)
result = []
data.each do |block|
result << send("before_" + classify(block), block)
result << block.map { |line| send(classify(line), line) }
result << send("after_" + classify(block), block)
end
result.compact.join(new_line)
end
protected
def classify(object)
object.class.name[/\w+$/].downcase
end
def escape(str)
str.to_s.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;').gsub('"', '&#34;')
end
def render_line(line)
res = '<span class="diff-content">'
if line.inline_changes?
prefix, changed, postfix = line.segments.map{|segment| escape(segment) }
res += "#{prefix}<span class='idiff'>#{changed}</span>#{postfix}"
else
res += escape(line)
end
res += '</span>'
res
end
def set_line_number
@num_line = @num_line.succ
end
def line_comment
return if @in_wiki || (@in_discussion && @add_reply_id && @line_comments[0].data[:line].to_i != @num_line)
link_to image_tag('line_comment.png', :alt => t('layout.comments.new_header')), new_comment_path, :class => 'add_line-comment'
end
def render_line_comments
unless @in_wiki || @in_discussion
comments = @line_comments.select do |c|
c.data.try('[]', :line) == @num_line.to_s && c.actual_inline_comment?
end
tr_line_comments(comments) if comments.count > 0
end
end
def td_line_link id, num
"<td class='line_numbers' id='#{id}'><a href='#{@url}##{id}'>#{num}</a></td>"
end
def tr_line_comments comments
return if @in_wiki
res="<tr class='inline-comments'>
<td class='line_numbers' colspan='2'>#{comments.count}</td>
<td>"
comments.each do |comment|
res << "<div class='line-comments'>
#{render 'projects/comments/comment', :comment => comment, :data => {:project => @project, :commentable => @commentable, :add_anchor => 'inline', :in_discussion => @in_discussion}}
</div>"
end
res << link_to(t('layout.comments.new_inline'), new_comment_path, :class => 'new_inline_comment button')
res << "</td></tr>"
end
def new_comment_path
hash = {:path => @filepath, :line => @num_line}
if @commentable.is_a? Issue
project_new_line_pull_comment_path(@project, @commentable, hash.merge({:in_reply => @add_reply_id}))
elsif @commentable.is_a? Grit::Commit
new_line_commit_comment_path(@project, @commentable, hash)
end
end
end

View File

@ -80,4 +80,3 @@ module GitHelper
end
end
end

View File

@ -1,5 +1,25 @@
# -*- encoding : utf-8 -*-
module ProjectsHelper
def options_for_filters(all_projects, groups, owners)
projects_count_by_groups = all_projects.where(:owner_id => groups, :owner_type => 'Group').
group(:owner_id).count
projects_count_by_owners = all_projects.where(:owner_id => owners, :owner_type => 'User').
group(:owner_id).count
(groups + owners).map do |o|
class_name = o.class.name
{
:id => "#{class_name.downcase}-#{o.id}",
:color => '0054a6',
:selected => false,
:check_box_name => class_name.downcase.pluralize,
:check_box_value => o.id,
:name => content_tag(:div, content_tag(:span, o.uname, :class => class_name.downcase)),
:uname => o.uname, # only for sorting
:count => o.is_a?(User) ? projects_count_by_owners[o.id] : projects_count_by_groups[o.id]
}
end.sort_by{ |f| f[:uname] }
end
def git_repo_url(name)
if current_user
"#{request.protocol}#{current_user.uname}@#{request.host_with_port}/#{name}.git"

View File

@ -0,0 +1,54 @@
# -*- encoding : utf-8 -*-
module PullRequestHelper
def merge_activity comments, commits
common_comments, pull_comments = comments.partition {|c| c.data.blank?}
common_comments = common_comments.map{ |c| [c.created_at, c] }
pull_comments = pull_comments.group_by(&:data).map{|data, c| [c.first.created_at, [data || {}, [c].flatten]]}
commits = commits.map{ |c| [(c.committed_date || c.authored_date), c] }
(common_comments + pull_comments + commits).sort_by{ |c| c[0] }.map{ |c| c[1] }
end
def pull_status_label pull
statuses = {'ready' => 'success', 'closed' => 'important', 'merged' => 'important', 'blocked' => 'warning'}
content_tag :span, t("projects.pull_requests.statuses.#{pull.status}"), :class => "label-bootstrap label-#{statuses[pull.status]}"
end
def pull_status pull
if %w(blocked merged closed ready open).include? pull.status
t("projects.pull_requests.#{pull.status}", :user => pull.issue.closer.try(:uname), :to_ref => show_ref(pull, 'to'),
:from_ref => show_ref(pull, 'from'), :time => pull.issue.closed_at).html_safe
else
raise "pull id (#{pull.id}) wrong status #{pull.status} "
end
end
def pull_header pull
str = "#{t '.header'} #{t 'from'} <span class='label-bootstrap label-info font14'> \
#{show_ref pull, 'from'}</span> \
#{t 'into'} <span class='label-bootstrap label-info font14'> \
#{show_ref pull, 'to'}</span>"
str << " #{t 'by'} #{link_to pull.user.uname, user_path(pull.user)}" if pull.persisted?
str.html_safe
end
#helper for helpers
def show_ref pull, which, limit = 30
project, ref = pull.send("#{which}_project"), pull.send("#{which}_ref")
link_to "#{project.owner.uname.truncate limit}/#{project.name.truncate limit}: #{ref.truncate limit}", ref_path(project, ref)
end
def ref_path project, ref
return tree_path(project, ref) if project.repo.branches_and_tags.map(&:name).include? ref
return commit_path(project, ref) if project.repo.commit ref
'#'
end
def ref_selector_options(project, current)
res = []
value = Proc.new {|t| [t.name.truncate(40)]}
res << [I18n.t('layout.git.repositories.branches'), project.repo.branches.map(&value)]
res << [I18n.t('layout.git.repositories.tags'), project.repo.tags.map(&value)]
grouped_options_for_select(res, current)
end
end

View File

@ -5,12 +5,13 @@ module UsersHelper
avatar_url(User.where(:email => email).first || User.new(:email => email), size)
end
def avatar_url(user, size = :small)
return image_path('group32.png') if user.kind_of? Group
if user.try('avatar?')
user.avatar.url(size)
def avatar_url(subject, size = :small)
if subject.try('avatar?')
subject.avatar.url(size)
elsif subject.kind_of? Group
image_path('ava-big.png')
else
gravatar_url(user.email, user.avatar.styles[size].geometry.split('x').first)
gravatar_url(subject.email, subject.avatar.styles[size].geometry.split('x').first)
end
end

View File

@ -4,6 +4,7 @@ class FeedbackMailer < ActionMailer::Base
default :to => FBM_CONFIG['email'],
:cc => FBM_CONFIG['cc'],
:bcc => FBM_CONFIG['bcc']
default_url_options.merge!(:protocol => 'https') if APP_CONFIG['mailer_https_url']
include Resque::Mailer # send email async

View File

@ -2,6 +2,7 @@
class UserMailer < ActionMailer::Base
default :from => APP_CONFIG['do-not-reply-email']
default_url_options.merge!(:protocol => 'https') if APP_CONFIG['mailer_https_url']
include Resque::Mailer # send email async

View File

@ -14,7 +14,10 @@ class Ability
# Shared rights between guests and registered users
can [:show, :archive], Project, :visibility => 'open'
can :get_id, Project, :visibility => 'open' # api
can :archive, Project, :visibility => 'open'
can :read, Issue, :project => {:visibility => 'open'}
can :read, PullRequest, :to_project => {:visibility => 'open'}
can :search, BuildList
can [:read, :log, :everything], BuildList, :project => {:visibility => 'open'}
can :read, ProductBuildList#, :product => {:platform => {:visibility => 'open'}} # double nested hash don't work
@ -24,10 +27,14 @@ class Ability
can [:publish_build, :status_build, :pre_build, :post_build, :circle_build, :new_bbdt], BuildList
# Platforms block
can [:show, :members, :advisories], Platform, :visibility == 'open'
can [:read, :projects_list], Repository, :platform => {:visibility => 'open'}
can [:show, :members, :advisories], Platform, :visibility => 'open'
can :platforms_for_build, Platform, :visibility => 'open', :platform_type => 'main'
can [:read, :projects_list, :projects], Repository, :platform => {:visibility => 'open'}
can :read, Product, :platform => {:visibility => 'open'}
can :show, Group
can :show, User
if user.guest? # Guest rights
# can [:new, :create], RegisterRequest
else # Registered user rights
@ -44,10 +51,8 @@ class Ability
end
if user.user?
can [:show, :autocomplete_user_uname], User
can [:read, :create, :autocomplete_group_uname], Group
can [:update, :manage_members], Group do |group|
can [:read, :create], Group
can [:update, :manage_members, :members, :add_member, :remove_member, :update_member], Group do |group|
group.actors.exists?(:actor_type => 'User', :actor_id => user.id, :role => 'admin') # or group.owner_id = user.id
end
can :destroy, Group, :owner_id => user.id
@ -57,14 +62,18 @@ class Ability
can :read, Project, :visibility => 'open'
can [:read, :archive], Project, :owner_type => 'User', :owner_id => user.id
can [:read, :archive], Project, :owner_type => 'Group', :owner_id => user.group_ids
can([:read, :membered], Project, read_relations_for('projects')) {|project| local_reader? project}
can([:read, :membered, :get_id], Project, read_relations_for('projects')) {|project| local_reader? project}
can(:write, Project) {|project| local_writer? project} # for grack
can([:update, :sections, :manage_collaborators, :autocomplete_maintainers], Project) {|project| local_admin? project}
can [:update, :sections, :manage_collaborators, :autocomplete_maintainers, :add_member, :remove_member, :update_member, :members], Project do |project|
local_admin? project
end
can(:fork, Project) {|project| can? :read, project}
can(:fork, Project) {|project| project.owner_type == 'Group' and can? :update, project.owner}
can(:destroy, Project) {|project| owner? project}
can(:destroy, Project) {|project| project.owner_type == 'Group' and project.owner.actors.exists?(:actor_type => 'User', :actor_id => user.id, :role => 'admin')}
can :remove_user, Project
can :preview, Project
can(:refs_list, Project) {|project| can? :read, project}
can [:read, :log, :owned, :everything], BuildList, :user_id => user.id
can [:read, :log, :related, :everything], BuildList, :project => {:owner_type => 'User', :owner_id => user.id}
@ -73,28 +82,27 @@ class Ability
can([:create, :update], BuildList) {|build_list| build_list.project.is_package && can?(:write, build_list.project)}
can(:publish, BuildList) do |build_list|
build_list.can_publish? and build_list.save_to_repository.publish_without_qa ? can?(:write, build_list.project) : local_admin?(build_list.save_to_platform)
build_list.save_to_repository.publish_without_qa ? can?(:write, build_list.project) : local_admin?(build_list.save_to_platform)
end
can(:reject_publish, BuildList) do |build_list|
build_list.can_reject_publish? and not build_list.save_to_repository.publish_without_qa and local_admin?(build_list.save_to_platform)
local_admin?(build_list.save_to_platform)
end
can(:cancel, BuildList) {|build_list| build_list.can_cancel? && can?(:write, build_list.project)}
can(:cancel, BuildList) {|build_list| can?(:write, build_list.project)}
can [:read, :owned, :related, :members], Platform, :owner_type => 'User', :owner_id => user.id
can [:read, :related, :members], Platform, :owner_type => 'Group', :owner_id => user.group_ids
can([:read, :related, :members], Platform, read_relations_for('platforms')) {|platform| local_reader? platform}
can([:update, :members], Platform) {|platform| local_admin? platform}
can([:destroy, :members, :add_member, :remove_member, :remove_members] , Platform) {|platform| owner?(platform) || local_admin?(platform) }
can [:autocomplete_user_uname], Platform
can([:failed_builds_list, :create], MassBuild) {|mass_build| (owner?(mass_build.platform) || local_admin?(mass_build.platform)) && mass_build.platform.main? }
can(:cancel, MassBuild) {|mass_build| (owner?(mass_build.platform) || local_admin?(mass_build.platform)) && !mass_build.stop_build && mass_build.platform.main?}
can [:read, :projects_list], Repository, :platform => {:owner_type => 'User', :owner_id => user.id}
can [:read, :projects_list], Repository, :platform => {:owner_type => 'Group', :owner_id => user.group_ids}
can([:read, :projects_list], Repository, read_relations_for('repositories', 'platforms')) {|repository| local_reader? repository.platform}
can([:create, :edit, :update, :destroy, :projects_list, :add_project, :remove_project], Repository) {|repository| local_admin? repository.platform}
can([:remove_members, :remove_member, :add_member], Repository) {|repository| owner?(repository.platform) || local_admin?(repository.platform)}
can [:read, :projects_list, :projects], Repository, :platform => {:owner_type => 'User', :owner_id => user.id}
can [:read, :projects_list, :projects], Repository, :platform => {:owner_type => 'Group', :owner_id => user.group_ids}
can([:read, :projects_list, :projects], Repository, read_relations_for('repositories', 'platforms')) {|repository| local_reader? repository.platform}
can([:create, :edit, :update, :destroy, :projects_list, :projects, :add_project, :remove_project], Repository) {|repository| local_admin? repository.platform}
can([:remove_members, :remove_member, :add_member, :signatures], Repository) {|repository| owner?(repository.platform) || local_admin?(repository.platform)}
can([:add_project, :remove_project], Repository) {|repository| repository.members.exists?(:id => user.id)}
can(:clear, Platform) {|platform| local_admin?(platform) && platform.personal?}
can([:change_visibility, :settings, :destroy, :edit, :update], Repository) {|repository| owner? repository.platform}
@ -116,12 +124,20 @@ class Ability
can :read, Issue, :project => {:owner_type => 'Group', :owner_id => user.group_ids}
can(:read, Issue, read_relations_for('issues', 'projects')) {|issue| can? :read, issue.project rescue nil}
can(:create, Issue) {|issue| can? :read, issue.project}
can([:update, :destroy], Issue) {|issue| issue.user_id == user.id or local_admin?(issue.project)}
can(:update, Issue) {|issue| issue.user_id == user.id or local_admin?(issue.project)}
cannot :manage, Issue, :project => {:has_issues => false} # switch off issues
can(:create, Comment) {|comment| can? :read, comment.project}
can(:update, Comment) {|comment| comment.user == user or comment.project.owner == user or local_admin?(comment.project)}
cannot :manage, Comment, :commentable_type => 'Issue', :commentable => {:project => {:has_issues => false}} # switch off issues
can :read, PullRequest, :to_project => {:owner_type => 'User', :owner_id => user.id}
can :read, PullRequest, :to_project => {:owner_type => 'Group', :owner_id => user.group_ids}
can(:read, PullRequest, read_relations_for('pull_requests', 'to_projects')) {|pull| can? :read, pull.to_project rescue nil}
can :create, PullRequest
can([:update, :merge], PullRequest) {|pull| pull.user_id == user.id or local_admin?(pull.to_project)}
can([:create, :new_line], Comment) {|comment| can? :read, comment.project}
can([:update, :destroy], Comment) {|comment| comment.user == user or comment.project.owner == user or local_admin?(comment.project)}
cannot :manage, Comment do |c|
c.commentable_type == 'Issue' && !c.project.has_issues && !c.commentable.pull_request # when switch off issues
end
end
# Shared cannot rights for all users (registered, admin)

View File

@ -71,6 +71,8 @@ class ActivityFeedObserver < ActiveRecord::Observer
when 'GitHook'
return unless record.project
PullRequest.where("from_project_id = ? OR to_project_id = ?", record.project, record.project).needed_checking.each {|pull| pull.check}
change_type = record.change_type
branch_name = record.refname.split('/').last

View File

@ -21,6 +21,32 @@ class Advisory < ActiveRecord::Base
advisory_id
end
def attach_build_list(build_list)
return false if update_type != build_list.update_type
self.platforms << build_list.save_to_platform unless platforms.include? build_list.save_to_platform
self.projects << build_list.project unless projects.include? build_list.project
build_list.advisory = self
save
end
# this method fetches and structurize packages attached to current advisory.
def fetch_packages_info
packages_info = Hash.new { |h, k| h[k] = {} } # maaagic, it's maaagic ;)
build_lists.find_in_batches(:include => [:save_to_platform, :packages, :project]) do |batch|
batch.each do |build_list|
tmp = build_list.packages.inject({:srpm => nil, :rpm => []}) do |h, p|
p.package_type == 'binary' ? h[:rpm] << p.fullname : h[:srpm] = p.fullname
h
end
h = { build_list.project => tmp }
packages_info[build_list.save_to_platform].merge!(h) do |pr, old, new|
{:srpm => new[:srpm], :rpm => old[:rpm].concat(new[:rpm]).uniq}
end
end
end
packages_info
end
protected
def generate_advisory_id

17
app/models/avatar.rb Normal file
View File

@ -0,0 +1,17 @@
# -*- encoding : utf-8 -*-
class Avatar < ActiveRecord::Base
self.abstract_class = true
MAX_AVATAR_SIZE = 5.megabyte
has_attached_file :avatar, :styles =>
{ :micro => { :geometry => "16x16#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
:small => { :geometry => "30x30#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
:medium => { :geometry => "40x40#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
:big => { :geometry => "81x81#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'}
}
validates_inclusion_of :avatar_file_size, :in => (0..MAX_AVATAR_SIZE), :allow_nil => true
attr_accessible :avatar
end

View File

@ -27,6 +27,16 @@ class BuildList < ActiveRecord::Base
validate lambda {
errors.add(:save_to_repository, I18n.t('flash.build_list.wrong_repository')) unless save_to_repository_id.in? save_to_platform.repositories.map(&:id)
}
validate lambda {
include_repos.each {|ir|
errors.add(:save_to_repository, I18n.t('flash.build_list.wrong_include_repos')) unless build_for_platform.repository_ids.include? ir.to_i
}
}
validate lambda {
if commit_hash.blank? || project.repo.commit(commit_hash).blank?
errors.add :commit_hash, I18n.t('flash.build_list.wrong_commit_hash', :commit_hash => commit_hash)
end
}
LIVE_TIME = 4.week # for unpublished
MAX_LIVE_TIME = 3.month # for published
@ -105,6 +115,7 @@ class BuildList < ActiveRecord::Base
after_commit :place_build
after_destroy :delete_container
before_validation :set_commit_and_version
@queue = :clone_and_build
@ -127,6 +138,10 @@ class BuildList < ActiveRecord::Base
after_transition :on => :published, :do => [:set_version_and_tag, :actualize_packages]
after_transition :on => [:published, :fail_publish, :build_error], :do => :notify_users
after_transition :on => :build_success, :do => :notify_users,
:unless => lambda { |build_list| build_list.auto_publish? }
event :place_build do
transition :waiting_for_response => :build_pending, :if => lambda { |build_list|
build_list.add_to_queue == BuildServer::SUCCESS
@ -288,8 +303,36 @@ class BuildList < ActiveRecord::Base
#[WAITING_FOR_RESPONSE, BuildServer::BUILD_PENDING, BuildServer::BUILD_STARTED].include?(status)
end
def associate_and_create_advisory(params)
build_advisory(params){ |a| a.update_type = update_type }
advisory.attach_build_list(self)
end
def can_attach_to_advisory?
!save_to_repository.publish_without_qa &&
save_to_platform.main? &&
save_to_platform.released &&
status == BUILD_PUBLISHED
end
protected
def notify_users
unless mass_build_id
users = []
if project # find associated users
users = project.all_members.
select{ |user| user.notifier.can_notify? && user.notifier.new_associated_build? }
end
if user.notifier.can_notify? && user.notifier.new_build?
users = users | [user]
end
users.each do |user|
UserMailer.build_list_notification(self, user).deliver
end
end
end # notify_users
def delete_container
if can_cancel?
BuildServer.delete_build_list bs_id
@ -306,4 +349,13 @@ class BuildList < ActiveRecord::Base
yield p
end
end
def set_commit_and_version
if project_version.present? && commit_hash.blank?
self.commit_hash = project.repo.commits(project_version.match(/^latest_(.+)/).to_a.last ||
project_version).try(:first).try(:id)
elsif project_version.blank? && commit_hash.present?
self.project_version = commit_hash
end
end
end

View File

@ -68,7 +68,9 @@ class BuildList::Filter
end
def build_date_from_params(field_name, params)
if params["#{field_name}(1i)"].present? || params["#{field_name}(2i)"].present? || params["#{field_name}(3i)"].present?
if params[field_name].present?
Time.at(params[field_name].to_i)
elsif params["#{field_name}(1i)"].present? || params["#{field_name}(2i)"].present? || params["#{field_name}(3i)"].present?
Date.civil((params["#{field_name}(1i)"].presence || Date.today.year).to_i,
(params["#{field_name}(2i)"].presence || Date.today.month).to_i,
(params["#{field_name}(3i)"].presence || Date.today.day).to_i)

View File

@ -1,7 +1,4 @@
class BuildListObserver < ActiveRecord::Observer
PUBLICATION_STATUSES = [BuildList::BUILD_PUBLISHED, BuildList::FAILED_PUBLISH]
STATUSES = [BuildServer::BUILD_ERROR, BuildServer::SUCCESS] + PUBLICATION_STATUSES
observe :build_list
def before_update(record)
@ -18,28 +15,7 @@ class BuildListObserver < ActiveRecord::Observer
record.project.update_attributes({ :average_build_time => new_av_time, :build_count => build_count + 1 }, :without_protection => true)
end
end
BuildListObserver.notify_users(record)
end
end # before_update
private
def self.notify_users(build_list)
if !build_list.mass_build_id &&
( (build_list.auto_publish? && PUBLICATION_STATUSES.include?(build_list.status)) ||
(!build_list.auto_publish? && STATUSES.include?(build_list.status)) )
users = []
if build_list.project # find associated users
users = build_list.project.all_members.
select{ |user| user.notifier.can_notify? && user.notifier.new_associated_build? }
end
if build_list.user.notifier.can_notify? && build_list.user.notifier.new_build?
users = users | [build_list.user]
end
users.each do |user|
UserMailer.build_list_notification(build_list, user).deliver
end
end
end # notify_users
end

View File

@ -3,6 +3,7 @@ class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
belongs_to :user
belongs_to :project
serialize :data
validates :body, :user_id, :commentable_id, :commentable_type, :project_id, :presence => true
@ -12,7 +13,7 @@ class Comment < ActiveRecord::Base
after_create :subscribe_on_reply, :unless => lambda {|c| c.commit_comment?}
after_create :subscribe_users
attr_accessible :body
attr_accessible :body, :data
def commentable
# raise commentable_id.inspect
@ -53,6 +54,80 @@ class Comment < ActiveRecord::Base
User.find(subscribe.user).notifier.new_comment && User.find(subscribe.user).notifier.can_notify
end
def actual_inline_comment?(diff = nil, force = false)
unless force
raise "This is not inline comment!" if data.blank? # for debug
return data[:actual] unless data[:actual].nil?
return false if diff.nil?
end
filepath, line_number = data[:path], data[:line]
diff_path = (diff || commentable.diffs ).select {|d| d.a_path == data[:path]}
comment_line = data[:line].to_i
# NB! also dont create a comment to the diff header
return data[:actual] = false if diff_path.blank? || comment_line == 0
return data[:actual] = true if commentable_type == 'Grit::Commit'
res, ind = true, 0
diff_path[0].diff.each_line do |line|
if self.persisted? && (comment_line-2..comment_line+2).include?(ind) && data.try('[]', "line#{ind-comment_line}") != line.chomp
break res = false
end
ind = ind + 1
end
if ind < comment_line
return data[:actual] = false
else
return data[:actual] = res
end
end
def inline_diff
data[:strings] + data['line0']
end
def pull_comment?
return true if commentable.is_a?(Issue) && commentable.pull_request.present?
end
def set_additional_data params
return true if params[:path].blank? && params[:line].blank? # not inline comment
if params[:in_reply].present? && reply = Comment.where(:id => params[:in_reply]).first
self.data = reply.data
return true
end
self.data = {:path => params[:path], :line => params[:line]}
if commentable.is_a?(Issue) && pull = commentable.pull_request
diff_path = pull.diff.select {|d| d.a_path == params[:path]}
return false unless actual_inline_comment?(pull.diff, true)
comment_line, line_number, strings = params[:line].to_i, -1, []
diff_path[0].diff.each_line do |line|
line_number = line_number.succ
# Save 2 lines above and bottom of the diff comment line
break if line_number > comment_line + 2
if (comment_line-2..comment_line+2).include? line_number
data["line#{line_number-comment_line}"] = line.chomp
end
# Save lines from the closest header for rendering in the discussion
if line_number < comment_line
# Header is the line like "@@ -47,9 +50,8 @@ def initialize(user)"
if line =~ Diff::Display::Unified::Generator::LINE_NUM_RE
strings = [line]
else
strings << line
end
end
end
## Bug with numbers of diff lines, now store all diff
data[:strings] = strings.join
# Limit stored diff to 10 lines (see inline_diff)
#data[:strings] = ((strings.count) <= 9 ? strings : [strings[0]] + strings.last(8)).join
##
data[:view_path] = h(diff_path[0].renamed_file ? "#{diff_path[0].a_path.rtruncate 60} -> #{diff_path[0].b_path.rtruncate 60}" : diff_path[0].a_path.rtruncate(120))
end
return true
end
protected
def subscribe_on_reply

View File

@ -1,5 +1,5 @@
# -*- encoding : utf-8 -*-
class Group < ActiveRecord::Base
class Group < Avatar
belongs_to :owner, :class_name => 'User'
has_many :relations, :as => :actor, :dependent => :destroy, :dependent => :destroy
@ -39,6 +39,14 @@ class Group < ActiveRecord::Base
uname
end
def add_member(member, role = 'admin')
Relation.add_member(member, self, role, :actors)
end
def remove_member(member)
Relation.remove_member(member, self)
end
protected
def add_owner_to_members

View File

@ -11,6 +11,7 @@ class Issue < ActiveRecord::Base
has_many :subscribes, :as => :subscribeable, :dependent => :destroy
has_many :labelings, :dependent => :destroy
has_many :labels, :through => :labelings, :uniq => true
has_one :pull_request, :dependent => :destroy
validates :title, :body, :project_id, :presence => true
@ -21,8 +22,16 @@ class Issue < ActiveRecord::Base
attr_accessible :labelings_attributes, :title, :body, :assignee_id
accepts_nested_attributes_for :labelings, :allow_destroy => true
scope :opened, where(:status => 'open', :closed_by => nil, :closed_at => nil)
scope :closed, where(:status => 'closed').where("closed_by is not null and closed_at is not null")
scope :opened, where(:status => 'open')
scope :closed, where(:status => 'closed')
scope :needed_checking, where(:issues => {:status => ['open', 'blocked', 'ready', 'already']})
scope :not_closed_or_merged, needed_checking
scope :closed_or_merged, where(:issues => {:status => ['closed', 'merged']})
# Using mb_chars for correct transform to lowercase ('Русский Текст'.downcase => "Русский Текст")
scope :search, lambda {|q| where('issues.title ILIKE ?', "%#{q.mb_chars.downcase}%") if q.present?}
scope :def_order, order('issues.serial_id desc')
scope :without_pull_requests, where('NOT EXISTS (select null from pull_requests as pr where pr.issue_id = issues.id)')
def assign_uname
assignee.uname if assignee
@ -43,7 +52,7 @@ class Issue < ActiveRecord::Base
end
def set_close(closed_by)
self.closed_at = Time.now
self.closed_at = Time.now.utc
self.closer = closed_by
self.status = 'closed'
end

View File

@ -20,9 +20,15 @@ class Platform < ActiveRecord::Base
has_many :mass_builds
validates :description, :presence => true
validates :owner, :presence => true
validates :visibility, :presence => true, :inclusion => {:in => VISIBILITIES}
validates :name, :uniqueness => {:case_sensitive => false}, :presence => true, :format => { :with => /^[a-zA-Z0-9_\-\.]+$/ }
validates :distrib_type, :presence => true, :inclusion => {:in => APP_CONFIG['distr_types']}
validate lambda {
if released_was && !released
errors.add(:released, I18n.t('flash.platform.released_status_can_not_be_changed'))
end
}
before_create :create_directory, :if => lambda {Thread.current[:skip]} # TODO remove this when core will be ready
before_create :xml_rpc_create, :unless => lambda {Thread.current[:skip]}
@ -39,8 +45,9 @@ class Platform < ActiveRecord::Base
scope :by_visibilities, lambda {|v| where(:visibility => v)}
scope :opened, where(:visibility => 'open')
scope :hidden, where(:visibility => 'hidden')
scope :main, where(:platform_type => 'main')
scope :personal, where(:platform_type => 'personal')
scope :by_type, lambda {|type| where(:platform_type => type) if type.present?}
scope :main, by_type('main')
scope :personal, by_type('personal')
attr_accessible :name, :distrib_type, :parent_platform_id, :platform_type, :owner, :visibility, :description, :released
attr_readonly :name, :distrib_type, :parent_platform_id, :platform_type
@ -166,7 +173,7 @@ class Platform < ActiveRecord::Base
def update_owner_relation
if owner_id_was != owner_id
r = relations.where(:actor_id => owner_id_was, :actor_type => owner_type_was)[0]
r = relations.where(:actor_id => owner_id_was, :actor_type => owner_type_was).first
r.update_attributes(:actor_id => owner_id, :actor_type => owner_type)
end
end

View File

@ -8,6 +8,7 @@ class Project < ActiveRecord::Base
belongs_to :maintainer, :class_name => "User"
has_many :issues, :dependent => :destroy
has_many :pull_requests, :dependent => :destroy, :foreign_key => 'to_project_id'
has_many :labels, :dependent => :destroy
has_many :build_lists, :dependent => :destroy
@ -36,7 +37,7 @@ class Project < ActiveRecord::Base
scope :recent, order("name ASC")
scope :search_order, order("CHAR_LENGTH(name) ASC")
scope :search, lambda {|q| by_name("%#{q.to_s.strip}%")}
scope :by_name, lambda {|name| where('projects.name ILIKE ?', name)}
scope :by_name, lambda {|name| where('projects.name ILIKE ?', name) if name.present?}
scope :by_visibilities, lambda {|v| where(:visibility => v)}
scope :opened, where(:visibility => 'open')
scope :package, where(:is_package => true)
@ -49,7 +50,11 @@ class Project < ActiveRecord::Base
WHERE (ptr.repository_id = #{ repository_id })
)
) }
scope :by_owners, lambda { |group_owner_ids, user_owner_ids|
where("(projects.owner_id in (?) AND projects.owner_type = 'Group') OR (projects.owner_id in (?) AND projects.owner_type = 'User')", group_owner_ids, user_owner_ids)
}
before_validation :truncate_name, :on => :create
before_create :set_maintainer
after_save :attach_to_personal_repository
@ -84,6 +89,14 @@ class Project < ActiveRecord::Base
collaborators | groups.map(&:members).flatten
end
def add_member(member, role = 'admin')
Relation.add_member(member, self, role)
end
def remove_member(member)
Relation.remove_member(member, self)
end
def platforms
@platforms ||= repositories.map(&:platform).uniq
end
@ -172,6 +185,10 @@ class Project < ActiveRecord::Base
protected
def truncate_name
self.name = name.strip if name
end
def attach_to_personal_repository
owner_rep = self.owner.personal_repository
if is_package
@ -182,7 +199,9 @@ class Project < ActiveRecord::Base
end
def set_maintainer
if maintainer_id.blank?
self.maintainer_id = (owner_type == 'User') ? self.owner_id : self.owner.owner_id
end
end
end

222
app/models/pull_request.rb Normal file
View File

@ -0,0 +1,222 @@
class PullRequest < ActiveRecord::Base
STATUSES = %w(ready already blocked merged closed)
belongs_to :issue, :autosave => true, :dependent => :destroy, :touch => true, :validate => true
belongs_to :to_project, :class_name => 'Project', :foreign_key => 'to_project_id'
belongs_to :from_project, :class_name => 'Project', :foreign_key => 'from_project_id'
delegate :user, :user_id, :title, :body, :serial_id, :assignee, :status, :to_param,
:created_at, :updated_at, :comments, :status=, :to => :issue, :allow_nil => true
validate :uniq_merge
validates_each :from_ref, :to_ref do |record, attr, value|
check_ref record, attr, value
end
before_create :clean_dir
after_destroy :clean_dir
accepts_nested_attributes_for :issue
attr_accessible :issue_attributes, :to_ref, :from_ref
scope :needed_checking, includes(:issue).where(:issues => {:status => ['open', 'blocked', 'ready']})
state_machine :status, :initial => :open do
event :ready do
transition [:ready, :open, :blocked] => :ready
end
event :already do
transition [:ready, :open, :blocked] => :already
end
event :block do
transition [:ready, :open, :blocked] => :blocked
end
event :merging do
transition :ready => :merged
end
event :close do
transition [:ready, :open, :blocked] => :closed
end
event :reopen do
transition :closed => :open
end
end
def check(do_transaction = true)
res = merge
new_status = case res
when /Already up-to-date/
'already'
when /Merge made by/
system("cd #{path} && git reset --hard HEAD^") # remove merge commit
'ready'
when /Automatic merge failed/
system("cd #{path} && git reset --hard HEAD") # clean git index
'block'
else
raise res
end
if do_transaction
new_status == 'already' ? (ready; merging) : send(new_status)
self.update_inline_comments
else
self.status = new_status == 'block' ? 'blocked' : new_status
end
end
def merge!(who)
return false unless can_merging?
Dir.chdir(path) do
system "git config user.name \"#{who.uname}\" && git config user.email \"#{who.email}\""
if merge
system("git push origin HEAD")
system("git reset --hard HEAD^") # for diff maybe FIXME
set_user_and_time who
merging
end
end
end
def path
filename = [id, from_project.owner.uname, from_project.name].compact.join('-')
File.join(APP_CONFIG['root_path'], 'pull_requests', to_project.owner.uname, to_project.name, filename)
end
def from_branch
if to_project != from_project
"head_#{from_ref}"
else
from_ref
end
end
def common_ancestor
return @common_ancestor if @common_ancestor
base_commit = repo.commits(to_ref).first
@common_ancestor = repo.commit(repo.git.merge_base({}, base_commit, from_commit)) || base_commit
end
alias_method :to_commit, :common_ancestor
def diff_stats
stats = []
Dir.chdir(path) do
lines = repo.git.native(:diff, {:numstat => true, :M => true}, "#{to_commit.id}...#{from_commit.id}").split("\n")
while !lines.empty?
files = []
while lines.first =~ /^([-\d]+)\s+([-\d]+)\s+(.+)/
additions, deletions, filename = lines.shift.gsub(' => ', '=>').split
additions, deletions = additions.to_i, deletions.to_i
stat = Grit::DiffStat.new filename, additions, deletions
stats << stat
end
end
stats
end
end
# FIXME maybe move to warpc/grit?
def diff
return @diff if @diff.present?
diff = repo.git.native('diff', {:M => true}, "#{to_commit.id}...#{from_commit.id}")
if diff =~ /diff --git a/
diff = diff.sub(/.*?(diff --git a)/m, '\1')
else
diff = ''
end
@diff = Grit::Diff.list_from_string(repo, diff)
end
def set_user_and_time user
issue.closed_at = Time.now.utc
issue.closer = user
end
def self.check_ref(record, attr, value)
project = attr == :from_ref ? record.from_project : record.to_project
record.errors.add attr, I18n.t('projects.pull_requests.wrong_ref') unless project.repo.branches_and_tags.map(&:name).include?(value)
end
def uniq_merge
if to_project.pull_requests.needed_checking.where(:from_project_id => from_project, :to_ref => to_ref, :from_ref => from_ref).where('pull_requests.id <> :id or :id is null', :id => id).count > 0
errors.add(:base_branch, I18n.t('projects.pull_requests.duplicate', :from_ref => from_ref))
end
end
def repo
return @repo if @repo.present? #&& !id_changed?
@repo = Grit::Repo.new path
end
def from_commit
repo.commits(from_branch).first
end
protected
def merge
clone
message = "Merge pull request ##{serial_id} from #{from_project.name_with_owner}:#{from_ref}\r\n #{title}"
%x(cd #{path} && git checkout #{to_ref} && git merge --no-ff #{from_branch} -m '#{message}')
end
def clone
git = Grit::Git.new(path)
unless git.exist?
#~ FileUtils.mkdir_p(path)
#~ system("git clone --local --no-hardlinks #{to_project.path} #{path}")
options = {:bare => false, :shared => false, :branch => to_ref} # shared?
git.fs_mkdir('..')
git.clone(options, to_project.path, path)
if to_project != from_project
Dir.chdir(path) do
system 'git', 'remote', 'add', 'head', from_project.path
end
end
clean # Need testing
end
Dir.chdir(path) do
system 'git', 'checkout', to_ref
system 'git', 'pull', 'origin', to_ref
if to_project == from_project
system 'git', 'checkout', from_ref
system 'git', 'pull', 'origin', from_ref
else
system 'git', 'fetch', 'head', "+#{from_ref}:#{from_branch}"
end
end
# TODO catch errors
end
def clean
Dir.chdir(path) do
to_project.repo.branches.each {|branch| system 'git', 'checkout', branch.name}
system 'git', 'checkout', to_ref
to_project.repo.branches.each do |branch|
system 'git', 'branch', '-D', branch.name unless [to_ref, from_branch].include? branch.name
end
to_project.repo.tags.each do |tag|
system 'git', 'tag', '-d', tag.name unless [to_ref, from_branch].include? tag.name
end
end
end
def clean_dir
FileUtils.rm_rf path
end
def update_inline_comments
self.comments.each do |c|
if c.data.present? # maybe need add new column 'actual'?
c.actual_inline_comment? diff, true
c.save
end
end
end
end

View File

@ -22,17 +22,18 @@ class Relation < ActiveRecord::Base
r.save
end
def self.add_member(member, target, role)
if target.relations.exists?(:actor_id => member.id, :actor_type => member.class.to_s) || @platform.try(:owner) == member
def self.add_member(member, target, role, relation = :relations)
if target.send(relation).exists?(:actor_id => member.id, :actor_type => member.class.to_s) || (target.respond_to?(:owner) && target.owner == member)
true
else
rel = target.relations.build(:role => role)
rel = target.send(relation).build(:role => role)
rel.actor = member
rel.save
end
end
def self.remove_member(member, target)
return false if target.respond_to?(:owner) && target.owner == member
Relation.by_actor(member).by_target(target).each{|r| r.destroy}
end

View File

@ -1,19 +1,11 @@
# -*- encoding : utf-8 -*-
class User < ActiveRecord::Base
class User < Avatar
ROLES = ['', 'admin', 'banned']
LANGUAGES_FOR_SELECT = [['Russian', 'ru'], ['English', 'en']]
LANGUAGES = LANGUAGES_FOR_SELECT.map(&:last)
MAX_AVATAR_SIZE = 5.megabyte
devise :database_authenticatable, :registerable, :omniauthable, :token_authenticatable,# :encryptable, :timeoutable
:recoverable, :rememberable, :validatable, :lockable, :confirmable#, :reconfirmable, :trackable
has_attached_file :avatar, :styles =>
{ :micro => { :geometry => "16x16#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
:small => { :geometry => "30x30#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
:medium => { :geometry => "40x40#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
:big => { :geometry => "81x81#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'}
}
validates_inclusion_of :avatar_file_size, :in => (0..MAX_AVATAR_SIZE), :allow_nil => true
has_one :notifier, :class_name => 'SettingsNotifier', :dependent => :destroy #:notifier
@ -43,7 +35,7 @@ class User < ActiveRecord::Base
validates :language, :inclusion => {:in => LANGUAGES}, :allow_blank => true
attr_accessible :email, :password, :password_confirmation, :current_password, :remember_me, :login, :name, :uname, :language,
:site, :company, :professional_experience, :location, :avatar
:site, :company, :professional_experience, :location
attr_readonly :uname
attr_accessor :login

View File

@ -9,7 +9,7 @@ class CommentPresenter < ApplicationPresenter
@user = comment.user
@options = opts
@content = simple_format(@comment.body, {}, :sanitize => true).html_safe
@content = @comment.body
end
def expandable?
@ -27,23 +27,17 @@ class CommentPresenter < ApplicationPresenter
def caption?
false
end
def buttons
project = options[:project]
commentable = options[:commentable]
(ep, dp) = if Comment.issue_comment?(commentable.class)
[edit_project_issue_comment_path(project, commentable, comment),
project_issue_comment_path(project, commentable, comment)]
elsif Comment.commit_comment?(commentable.class)
[edit_project_commit_comment_path(project, commentable, comment),
project_commit_comment_path(project, commentable, comment)]
end
res = []
def buttons
project, commentable = options[:project], options[:commentable]
path = helpers.project_commentable_comment_path(project, commentable, comment)
res = [link_to(t("layout.link"), "#{helpers.project_commentable_path(project, commentable)}##{comment_anchor}", :class => "#{@options[:in_discussion].present? ? 'in_discussion_' : ''}link_to_comment").html_safe]
if controller.can? :update, @comment
res << link_to(t("layout.edit"), ep).html_safe
res << link_to(t("layout.edit"), path, :id => "comment-#{comment.id}", :class => "edit_comment").html_safe
end
if controller.can? :delete, @comment
res << link_to(t("layout.delete"), dp, :method => "delete",
if controller.can? :destroy, @comment
res << link_to(t("layout.delete"), path, :method => "delete",
:confirm => t("layout.comments.confirm_delete")).html_safe
end
res
@ -69,4 +63,15 @@ class CommentPresenter < ApplicationPresenter
def comment_id
@comment.id
end
def comment_anchor
# check for pull diff inline comment
before = if @options[:add_anchor].present? && !@options[:in_discussion]
'diff-'
else
''
end
"#{before}comment#{@comment.id}"
end
end

View File

@ -11,7 +11,7 @@
- else
= image_tag("lock.png")
%td
= link_to "#{project.owner.uname}/#{project.name}", project_path(project)
= link_to project.name_with_owner, project_path(project)
%tr
%td
\ 

View File

@ -4,7 +4,7 @@
.text
%span
= raw t("notifications.bodies.new_comment_notification.title", :user_link => link_to(user_name, user_path(user_id)) )
= raw t("notifications.bodies.new_comment_notification.commit_content", {:commit_link => link_to(commit_message, commit_path(project_owner, project_name, commit_id) + "#comment##{comment_id}")})
= raw t("notifications.bodies.new_comment_notification.commit_content", {:commit_link => link_to(commit_message, commit_path(project_owner, project_name, commit_id) + "#comment#{comment_id}")})
= raw t("notifications.bodies.project", :project_link => link_to("#{project_owner}/#{project_name}", project_path(project_owner, project_name)) )
.both
%span.date= activity_feed.created_at

View File

@ -4,7 +4,7 @@
.text
%span
= raw t("notifications.bodies.new_comment_notification.title", {:user_link => link_to(user_name, user_path(user_id))})
= raw t("notifications.bodies.new_comment_notification.content", {:issue_link => link_to(issue_title, project_issue_path(project_owner, project_name, issue_serial_id) + "#comment##{comment_id}")})
= raw t("notifications.bodies.new_comment_notification.content", {:issue_link => link_to(issue_title, project_issue_path(project_owner, project_name, issue_serial_id) + "#comment#{comment_id}")})
= raw t("notifications.bodies.project", :project_link => link_to("#{project_owner}/#{project_name}", project_path(project_owner, project_name)) )
.both
%span.date= activity_feed.created_at

View File

@ -24,9 +24,14 @@
%td= request.more
%td= request.created_at
%td
= link_to t("layout.approve"), approve_admin_register_request_path(request) if can? :approve, request
|
= link_to t("layout.reject"), reject_admin_register_request_path(request) if can? :reject, request
- links = []
- if can? :approve, request
- links << link_to(t("layout.approve"), approve_admin_register_request_path(request))
- if can? :reject, request
- links << link_to(t("layout.reject"), reject_admin_register_request_path(request))
- if request.token
- links << link_to('Link', new_user_registration_url(:invitation_token => request.token))
= raw links.join('|')
.actions
%input#approve_registration{:type => 'button', :value => "Approve Selected"}

View File

@ -0,0 +1,12 @@
json.id advisory.advisory_id
json.(advisory, :description)
json.platforms advisory.platforms do |json_platform, platform|
json_platform.(platform, :id, :released)
json_platform.url api_v1_platform_path(platform.id, :format => :json)
end
json.projects advisory.projects do |json_project, project|
json_project.(project, :id, :name)
json_project.fullname project.name_with_owner
json_project.url api_v1_project_path(project.id, :format => :json)
end
json.url api_v1_advisory_path(advisory.advisory_id, :format => :json)

View File

@ -0,0 +1,4 @@
json.advisories @advisories do |json, advisory|
json.partial! 'advisory', :advisory => advisory, :json => json
end
json.url api_v1_advisories_path(:format => :json)

View File

@ -0,0 +1,27 @@
json.advisory do |json|
json.partial! 'advisory', :advisory => @advisory, :json => json
json.created_at @advisory.created_at.to_i
json.updated_at @advisory.updated_at.to_i
json.(@advisory, :update_type)
json.references @advisory.references.split('\n')
json.build_lists @advisory.build_lists do |json_build_list, build_list|
json_build_list.(build_list, :id)
json_build_list.url api_v1_build_list_path(build_list.id, :format => :json)
end
json.affected_in @packages_info do |json_platform, package_info|
json.partial! 'api/v1/platforms/platform',
:platform => package_info[0], :json => json_platform
json_platform.projects package_info[1] do |json_project, info|
json.partial! 'api/v1/projects/project',
:project => info[0], :json => json_project
packages = info[1]
json_project.srpm packages[:srpm]
json_project.rpm packages[:rpm]
end
end
end

View File

@ -0,0 +1,3 @@
json.architectures @arches do |json, arch|
json.(arch, :id, :name)
end

View File

@ -0,0 +1,7 @@
json.build_lists @build_lists do |json, build_list|
json.(build_list, :id, :name, :status)
json.url api_v1_build_list_path(build_list, :format => :json)
end
json.url api_v1_build_lists_path(:format => :json, :params => {:filter => params[:filter] } )

View File

@ -0,0 +1,62 @@
json.build_list do |json|
json.(@build_list, :id, :name, :container_path, :status, :duration)
json.(@build_list, :is_circle, :update_type, :build_requires, :priority)
json.(@build_list, :advisory, :mass_build)
json.(@build_list, :auto_publish, :package_version, :commit_hash)
json.build_log_url log_build_list_path(@build_list)
json.arch do |json_arch|
json_arch.(@build_list.arch, :id, :name)
end
json.created_at @build_list.created_at.to_i
json.updated_at @build_list.updated_at.to_i
json.project do |json_project|
json.partial! 'api/v1/projects/project',
:project => @build_list.project, :json => json_project
end
json.save_to_repository do |json_save_to_repository|
json.partial! 'api/v1/repositories/repository',
:repository => @build_list.save_to_repository,
:json => json_save_to_repository
json_save_to_repository.platform do |json_str_platform|
json.partial! 'api/v1/platforms/platform',
:platform => @build_list.save_to_repository.platform,
:json => json_str_platform
end
end
json.build_for_platform do |json_build_for_platform|
json.partial! 'api/v1/platforms/platform',
:platform => @build_list.build_for_platform,
:json => json_build_for_platform
end
json.partial! 'api/v1/shared/owner', :owner => @build_list.project.owner
inc_repos = Repository.includes(:platform).where(:id => @build_list.include_repos)
json.include_repos inc_repos do |json_include_repos, repo|
json.partial! 'api/v1/repositories/repository',
:repository => repo,
:json => json_include_repos
json_include_repos.platform do |json_str_platform|
json.partial! 'api/v1/platforms/platform',
:platform => repo.platform,
:json => json_str_platform
end
end
json.advisory do |json_advisory|
json_advisory.name @build_list.advisory.advisory_id
json_advisory.(@build_list.advisory, :description)
end if @build_list.advisory
json.mass_build do |json_mass_build|
json_mass_build.(@build_list.mass_build, :id, :name)
end if @build_list.mass_build
json.url api_v1_build_list_path(@build_list, :format => :json)
end

View File

@ -0,0 +1,11 @@
json.groups @groups do |json, group|
json.(group, :id, :uname, :own_projects_count, :description)
json.created_at group.created_at.to_i
json.updated_at group.updated_at.to_i
json.partial! 'api/v1/shared/owner', :owner => group.owner
json.avatar_url avatar_url(group, :big)
json.url api_v1_group_path(group.id, :format => :json)
json.html_url group_path(group.uname)
end
json.url api_v1_groups_path(:format => :json)

View File

@ -0,0 +1,5 @@
json.group do |json|
json.(@group, :id)
json.partial! 'api/v1/shared/members'
end
json.url members_api_v1_group_path(@group.id, :format => :json)

View File

@ -0,0 +1,9 @@
json.group do |json|
json.(@group, :id, :uname, :own_projects_count, :description)
json.created_at @group.created_at.to_i
json.updated_at @group.updated_at.to_i
json.partial! 'api/v1/shared/owner', :owner => @group.owner
json.avatar_url avatar_url(@group, :big)
json.url api_v1_group_path(@group.id, :format => :json)
json.html_url group_path(@group.uname)
end

View File

@ -0,0 +1,2 @@
json.(platform, :id, :name)
json.url api_v1_platform_path(platform.id, :format => :json)

View File

@ -0,0 +1,11 @@
json.platforms @platforms do |json, platform|
json.partial! 'platform', :platform => platform, :json => json
json.(platform, :platform_type, :visibility)
json.partial! 'api/v1/shared/owner', :owner => platform.owner
json.repositories platform.repositories do |json_repos, repo|
json_repos.(repo, :id, :name)
json_repos.url api_v1_repository_path(repo.id, :format => :json)
end
end
json.url api_v1_platforms_path(:format => :json)

View File

@ -0,0 +1,5 @@
json.platform do |json|
json.partial! 'platform', :platform => @platform, :json => json
json.partial! 'api/v1/shared/members'
end
json.url members_api_v1_platform_path(@platform.id, :format => :json)

View File

@ -0,0 +1,11 @@
json.platform do |json|
json.partial! 'platform', :platform => @platform, :json => json
json.(@platform, :description, :parent_platform_id, :released, :visibility, :platform_type, :distrib_type)
json.created_at @platform.created_at.to_i
json.updated_at @platform.updated_at.to_i
json.partial! 'api/v1/shared/owner', :owner => @platform.owner
json.repositories @platform.repositories do |json_repos, repo|
json_repos.(repo, :id, :name)
json_repos.url api_v1_repository_path(repo.id, :format => :json)
end
end

View File

@ -0,0 +1,3 @@
json.(project, :id, :name)
json.fullname project.name_with_owner
json.url api_v1_project_path(project.id, :format => :json)

View File

@ -0,0 +1,5 @@
json.project do |json|
json.partial! 'project', :project => @project, :json => json
json.(@project, :visibility)
json.partial! 'api/v1/shared/owner', :owner => @project.owner
end

View File

@ -0,0 +1,9 @@
json.projects @projects do |json, project|
json.partial! 'project', :project => project, :json => json
json.(project, :visibility, :description, :ancestry, :has_issues, :has_wiki, :default_branch, :is_package, :average_build_time)
json.created_at project.created_at.to_i
json.updated_at project.updated_at.to_i
json.partial! 'api/v1/shared/owner', :owner => project.owner
end
json.url api_v1_projects_path(:format => :json)

View File

@ -0,0 +1,5 @@
json.project do |json|
json.partial! 'project', :project => @project, :json => json
json.partial! 'api/v1/shared/members'
end
json.url members_api_v1_project_path(@project.id, :format => :json)

View File

@ -0,0 +1,8 @@
json.refs_list (@project.repo.branches + @project.repo.tags) do |json_grit, grit|
json_grit.ref grit.name
json_grit.object do |json_object|
json_object.type (grit.class.name =~ /Tag/ ? 'tag' : 'commit')
json_object.sha grit.commit.id
end
end
json.url refs_list_api_v1_project_path(@project.id, :format => :json)

View File

@ -0,0 +1,18 @@
json.project do |json|
json.partial! 'project', :project => @project, :json => json
json.(@project, :visibility, :description, :ancestry, :has_issues, :has_wiki, :default_branch, :is_package, :average_build_time)
json.created_at @project.created_at.to_i
json.updated_at @project.updated_at.to_i
json.partial! 'api/v1/shared/owner', :owner => @project.owner
json.maintainer do |json_maintainer|
json.partial! 'api/v1/shared/member', :member => @project.maintainer, :tag => json_maintainer
end
json.repositories @project.repositories do |json_repos, repo|
json_repos.(repo, :id, :name)
json_repos.url api_v1_repository_path(repo.name, :format => :json)
json_repos.platform do |json_platform|
json_platform.(repo.platform, :id, :name)
json_platform.url api_v1_platform_path(repo.platform, :format => :json)
end
end
end

View File

@ -0,0 +1,2 @@
json.(repository, :id, :name)
json.url api_v1_repository_path(repository.id, :format => :json)

View File

@ -0,0 +1,8 @@
json.repository do |json|
json.partial! 'repository', :repository => @repository, :json => json
json.projects @projects do |json_project, project|
json.partial! 'api/v1/projects/project',
:project => project, :json => json_project
end
end
json.url projects_api_v1_repository_path(@repository.id, :format => :json)

View File

@ -0,0 +1,10 @@
json.repository do |json|
json.partial! 'repository', :repository => @repository, :json => json
json.(@repository, :description, :publish_without_qa)
json.created_at @repository.created_at.to_i
json.updated_at @repository.updated_at.to_i
json.platform do |json_platform|
json_platform.(@repository.platform, :id, :name)
json_platform.url api_v1_platform_path(@repository.platform, :format => :json)
end
end

View File

@ -0,0 +1,3 @@
tag.(member, :id, :name)
tag.type member.class.name
tag.url member_path(member)

View File

@ -0,0 +1,3 @@
json.members @members do |json_members, member|
json.partial! 'api/v1/shared/member', :member => member, :tag => json_members
end

View File

@ -0,0 +1,3 @@
json.owner do |json_owner|
json.partial! 'api/v1/shared/member', :member => owner, :tag => json_owner
end

View File

@ -0,0 +1,8 @@
json.user do |json|
json.(@user, :id)
json.notifiers do |json_notifiers|
json_notifiers.(@user.notifier, :can_notify, :new_comment, :new_comment_reply, :new_issue, :issue_assign, :new_comment_commit_owner, :new_comment_commit_repo_owner, :new_comment_commit_commentor, :new_build, :new_associated_build)
end
end
json.url notifiers_api_v1_user_path(:json)

View File

@ -0,0 +1,8 @@
json.user do |json|
json.(@user, :id, :name, :email, :uname,:language, :own_projects_count, :professional_experience, :site, :company, :location, :build_priority)
json.created_at @user.created_at.to_i
json.updated_at @user.updated_at.to_i
json.avatar_url avatar_url(@user,:big)
json.url api_v1_user_path(@user.id, :format => :json)
json.html_url user_path(@user.uname)
end

View File

@ -25,7 +25,7 @@
.hr.top
= form_tag add_group_members_path(parent) do
.admin-search= autocomplete_field_tag 'user_id', params[:user_id], autocomplete_user_uname_users_path#, :id_element => '#member_id_field'
.admin-search= autocomplete_field_tag 'user_id', params[:user_id], autocomplete_user_uname_autocompletes_path#, :id_element => '#member_id_field'
.admin-role
.lineForm= select_tag 'role', options_for_collaborators_roles_select
.both

Some files were not shown because too many files have changed in this diff Show More