diff --git a/.gitignore b/.gitignore index 0511a9ffc..3fdbd8aa8 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ public/downloads/* *.tmproj .sass-cache/ dump.rdb +crash.log diff --git a/Gemfile b/Gemfile index 947caac82..7a5bf7ceb 100644 --- a/Gemfile +++ b/Gemfile @@ -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 diff --git a/Gemfile.lock b/Gemfile.lock index e9278a16a..5683760b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/assets/images/group16b.png b/app/assets/images/group16b.png new file mode 100644 index 000000000..4cdbc44bb Binary files /dev/null and b/app/assets/images/group16b.png differ diff --git a/app/assets/images/line_comment.png b/app/assets/images/line_comment.png new file mode 100644 index 000000000..544cb0714 Binary files /dev/null and b/app/assets/images/line_comment.png differ diff --git a/app/assets/images/user16b.png b/app/assets/images/user16b.png new file mode 100644 index 000000000..195d96379 Binary files /dev/null and b/app/assets/images/user16b.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 6b311c6f5..69d7c1b0c 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -34,7 +34,7 @@ $(document).ready(function() { var current = $(this); current.parent().find('input.user_role_chbx').each(function(i,el) { if ($(el).attr('id') != current.attr('id')) { - $(el).removeAttr('checked'); + $(el).removeAttr('checked'); } }); }); @@ -81,4 +81,10 @@ $(document).ready(function() { } return false; }); + + window.CodeMirrorRun = function(code) { + CodeMirror.runMode(code.innerHTML.replace(/&/gi, '&').replace(/</gi, '<').replace(/>/gi, '>'), code.className, code); + } + + $('.md_and_cm code').each(function (code) { CodeMirrorRun(this); }); }); diff --git a/app/assets/javascripts/extra/comment.js b/app/assets/javascripts/extra/comment.js new file mode 100644 index 000000000..da683f382 --- /dev/null +++ b/app/assets/javascripts/extra/comment.js @@ -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 = ""+""+data+""; + 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; + }); +}); + diff --git a/app/assets/javascripts/extra/filter_labels.js b/app/assets/javascripts/extra/filter_labels.js new file mode 100644 index 000000000..5dd23a47e --- /dev/null +++ b/app/assets/javascripts/extra/filter_labels.js @@ -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'); + } + }); + +}); diff --git a/app/assets/javascripts/extra/preview.js b/app/assets/javascripts/extra/preview.js new file mode 100644 index 000000000..4eb397844 --- /dev/null +++ b/app/assets/javascripts/extra/preview.js @@ -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); }) + } + }); + }; + }); +}); diff --git a/app/assets/javascripts/extra/pull.js b/app/assets/javascripts/extra/pull.js new file mode 100644 index 000000000..b802f251a --- /dev/null +++ b/app/assets/javascripts/extra/pull.js @@ -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'); + }); +}); diff --git a/app/assets/javascripts/extra/tracker.js b/app/assets/javascripts/extra/tracker.js index 07b22c78f..6ffbf2bad 100644 --- a/app/assets/javascripts/extra/tracker.js +++ b/app/assets/javascripts/extra/tracker.js @@ -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; diff --git a/app/assets/stylesheets/design/custom.scss b/app/assets/stylesheets/design/custom.scss index 7dd03070a..188c8c508 100644 --- a/app/assets/stylesheets/design/custom.scss +++ b/app/assets/stylesheets/design/custom.scss @@ -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; @@ -64,12 +76,13 @@ article div.activity .top .buttons { display: block; position: relative; float: right; -} + } article div.activity .top div.text { float: left; font-size: 12px; padding-left: 10px; + max-width: 579px; } article div.activity .top .imaged { @@ -101,20 +114,21 @@ article div.activity .fulltext.hidden { display: none; } -div.activity .data-expander { - margin-left: 10px; - display: inline-block; - width: 12px; -} - -div.activity .data-expander.collapsed { - background: #FFF image-url('expand-gray.png') no-repeat; - background-position: 0 2px; -} - -div.activity .data-expander.expanded { - background: #FFF image-url('expand-gray2.png') no-repeat; - background-position: 0 2px; +div.activity, .commits_activity { + .data-expander { + margin-left: 10px; + display: inline-block; + width: 12px; + cursor: pointer; + } + .data-expander.collapsed { + background: #FFF image-url('expand-gray.png') no-repeat; + background-position: 0 2px; + } + .data-expander.expanded { + background: #FFF image-url('expand-gray2.png') no-repeat; + background-position: 0 2px; + } } div.activity .fulltext p { @@ -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 { @@ -760,7 +745,7 @@ div.tos ol li { } div.tos_sidebar ul { - list-style: none outside none; + list-style: none outside none; margin: 20px 0 0; padding: 0; text-align: left; @@ -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; +} + diff --git a/app/assets/stylesheets/design/main.scss b/app/assets/stylesheets/design/main.scss index 63a3fc533..a0e76e21f 100644 --- a/app/assets/stylesheets/design/main.scss +++ b/app/assets/stylesheets/design/main.scss @@ -362,27 +362,30 @@ 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; + } + .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; + } + h3, h4, p { width: 420px; } + tmargin5 { + padding-top: 5px; + position: relative; + } } -article div.all.verybigpadding div.left 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 { - padding-top: 5px; - position: relative; -} - - /* Left part of page markup */ aside div.bordered { @@ -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; diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 9b947578d..498d11926 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -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 diff --git a/app/controllers/advisories_controller.rb b/app/controllers/advisories_controller.rb index 9bbf15f6c..912509508 100644 --- a/app/controllers/advisories_controller.rb +++ b/app/controllers/advisories_controller.rb @@ -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 diff --git a/app/controllers/api/v1/advisories_controller.rb b/app/controllers/api/v1/advisories_controller.rb new file mode 100644 index 000000000..201c4cd98 --- /dev/null +++ b/app/controllers/api/v1/advisories_controller.rb @@ -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 diff --git a/app/controllers/api/v1/arches_controller.rb b/app/controllers/api/v1/arches_controller.rb new file mode 100644 index 000000000..641869742 --- /dev/null +++ b/app/controllers/api/v1/arches_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb new file mode 100644 index 000000000..af29763ac --- /dev/null +++ b/app/controllers/api/v1/base_controller.rb @@ -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 diff --git a/app/controllers/api/v1/build_lists_controller.rb b/app/controllers/api/v1/build_lists_controller.rb new file mode 100644 index 000000000..d2c4f947e --- /dev/null +++ b/app/controllers/api/v1/build_lists_controller.rb @@ -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 diff --git a/app/controllers/api/v1/groups_controller.rb b/app/controllers/api/v1/groups_controller.rb new file mode 100644 index 000000000..09b3e9b4d --- /dev/null +++ b/app/controllers/api/v1/groups_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/api/v1/platforms_controller.rb b/app/controllers/api/v1/platforms_controller.rb new file mode 100644 index 000000000..37cdf20de --- /dev/null +++ b/app/controllers/api/v1/platforms_controller.rb @@ -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 diff --git a/app/controllers/api/v1/projects_controller.rb b/app/controllers/api/v1/projects_controller.rb new file mode 100644 index 000000000..927a35bed --- /dev/null +++ b/app/controllers/api/v1/projects_controller.rb @@ -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 diff --git a/app/controllers/api/v1/repositories_controller.rb b/app/controllers/api/v1/repositories_controller.rb new file mode 100644 index 000000000..63a1ffffe --- /dev/null +++ b/app/controllers/api/v1/repositories_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb new file mode 100644 index 000000000..8915a301c --- /dev/null +++ b/app/controllers/api/v1/users_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b191227aa..0107fa98d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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 )) diff --git a/app/controllers/autocompletes_controller.rb b/app/controllers/autocompletes_controller.rb new file mode 100644 index 000000000..cbffc074a --- /dev/null +++ b/app/controllers/autocompletes_controller.rb @@ -0,0 +1,7 @@ +# -*- encoding : utf-8 -*- +class AutocompletesController < ApplicationController + before_filter :authenticate_user! + + autocomplete :group, :uname + autocomplete :user, :uname +end diff --git a/app/controllers/groups/members_controller.rb b/app/controllers/groups/members_controller.rb index 4f9d53706..e6d95a95c 100644 --- a/app/controllers/groups/members_controller.rb +++ b/app/controllers/groups/members_controller.rb @@ -29,27 +29,21 @@ 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 - flash[:notice] = t("flash.members.successfully_added") - else - flash[:error] = t("flash.members.error_in_adding") - end + if parent.add_member(@user, params[:role]) + flash[:notice] = t("flash.members.successfully_added") else - flash[:error] = t("flash.members.already_added") + flash[:error] = t("flash.members.error_in_adding") end end redirect_to group_members_path(parent) diff --git a/app/controllers/groups/profile_controller.rb b/app/controllers/groups/profile_controller.rb index ff35cac4a..9124a458f 100644 --- a/app/controllers/groups/profile_controller.rb +++ b/app/controllers/groups/profile_controller.rb @@ -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 diff --git a/app/controllers/platforms/platforms_controller.rb b/app/controllers/platforms/platforms_controller.rb index a35f64b5c..cd2de9147 100644 --- a/app/controllers/platforms/platforms_controller.rb +++ b/app/controllers/platforms/platforms_controller.rb @@ -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 diff --git a/app/controllers/platforms/repositories_controller.rb b/app/controllers/platforms/repositories_controller.rb index 7afc34a21..fcb064849 100644 --- a/app/controllers/platforms/repositories_controller.rb +++ b/app/controllers/platforms/repositories_controller.rb @@ -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) - render :partial => (params[:added] == "true") ? 'project' : 'proj_ajax', :layout => false + 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 diff --git a/app/controllers/projects/build_lists_controller.rb b/app/controllers/projects/build_lists_controller.rb index 742530992..08788ebd9 100644 --- a/app/controllers/projects/build_lists_controller.rb +++ b/app/controllers/projects/build_lists_controller.rb @@ -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 diff --git a/app/controllers/projects/comments_controller.rb b/app/controllers/projects/comments_controller.rb index 2a8b82685..e1f7f1073 100644 --- a/app/controllers/projects/comments_controller.rb +++ b/app/controllers/projects/comments_controller.rb @@ -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 diff --git a/app/controllers/projects/git/blobs_controller.rb b/app/controllers/projects/git/blobs_controller.rb index 96dc586dc..68e4734c8 100644 --- a/app/controllers/projects/git/blobs_controller.rb +++ b/app/controllers/projects/git/blobs_controller.rb @@ -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 diff --git a/app/controllers/projects/git/commits_controller.rb b/app/controllers/projects/git/commits_controller.rb index 31816996c..1bd9aa966 100644 --- a/app/controllers/projects/git/commits_controller.rb +++ b/app/controllers/projects/git/commits_controller.rb @@ -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" } diff --git a/app/controllers/projects/git/trees_controller.rb b/app/controllers/projects/git/trees_controller.rb index 45dae2aa1..f32414efd 100644 --- a/app/controllers/projects/git/trees_controller.rb +++ b/app/controllers/projects/git/trees_controller.rb @@ -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 diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index ef624f79d..594aed8cb 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -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 diff --git a/app/controllers/projects/projects_controller.rb b/app/controllers/projects/projects_controller.rb index 455b49ad4..4c76cbbb4 100644 --- a/app/controllers/projects/projects_controller.rb +++ b/app/controllers/projects/projects_controller.rb @@ -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) diff --git a/app/controllers/projects/pull_requests_controller.rb b/app/controllers/projects/pull_requests_controller.rb new file mode 100644 index 000000000..c4d9f0771 --- /dev/null +++ b/app/controllers/projects/pull_requests_controller.rb @@ -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 diff --git a/app/controllers/users/profile_controller.rb b/app/controllers/users/profile_controller.rb index 25aa15e31..892ee0717 100644 --- a/app/controllers/users/profile_controller.rb +++ b/app/controllers/users/profile_controller.rb @@ -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 diff --git a/app/controllers/users/settings_controller.rb b/app/controllers/users/settings_controller.rb index cae985b96..3ff416963 100644 --- a/app/controllers/users/settings_controller.rb +++ b/app/controllers/users/settings_controller.rb @@ -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 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 26ed84332..970715327 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -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 = "
#{text}" + html << link_to('×', '#', :class => 'close close-alert', 'data-dismiss' => 'alert') + html << '
' + end end diff --git a/app/helpers/avatar_helper.rb b/app/helpers/avatar_helper.rb new file mode 100644 index 000000000..b292c065e --- /dev/null +++ b/app/helpers/avatar_helper.rb @@ -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 \ No newline at end of file diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index 6bfdd3f70..5e1fe450e 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -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 diff --git a/app/helpers/commit_helper.rb b/app/helpers/commit_helper.rb index d6019a772..b9ef51752 100644 --- a/app/helpers/commit_helper.rb +++ b/app/helpers/commit_helper.rb @@ -3,9 +3,10 @@ module CommitHelper def render_commit_stats(stats) res = [""] + ind=0 stats.files.each do |filename, adds, deletes, total| res << "" - res << "" + res << "" res << "" + ind +=1 end res << "
#{h(filename)}#{h(filename.rtruncate 120)}" 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 << "
" diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 5cd707e1a..dc354f0f4 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -1,18 +1,267 @@ # -*- encoding : utf-8 -*- module DiffHelper + def render_diff_stats(stats) + path = @pull.id ? polymorphic_path([@project, @pull]) : '' + res = [""] + stats.each_with_index do |stat, ind| + res << "" + res << "" + res << "" + end + res << "
#{link_to stat.filename.rtruncate(120), "#{path}#diff-#{ind}"}" + 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 << "
" - def render_diff(diff) - diff_display ||= Diff::Display::Unified.new(diff.diff) + res.join("\n").html_safe.default_encoding! + end - #res = "" + #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 = "" res += "" - 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 += "" res += "
" - 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 + " + ... + ... + #{line} + " + end + + def addline(line) + set_line_number + " + + #{td_line_link "diff-F#{@diff_counter}R#{line.new_number}", line.new_number} + + #{line_comment} +
#{render_line(line)}
+ + + #{render_line_comments}" + end + + def remline(line) + set_line_number + " + #{td_line_link "diff-F#{@diff_counter}L#{line.old_number}", line.old_number} + + + #{line_comment} +
#{render_line(line)}
+ + + #{render_line_comments}" + end + + def modline(line) + set_line_number + " + #{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} + + #{line_comment} +
#{render_line(line)}
+ + + #{render_line_comments}" + end + + def unmodline(line) + set_line_number + " + #{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} + + #{line_comment} +
#{render_line(line)}
+ + + #{render_line_comments}" + end + + def sepline(line) + " + … + … + + " + end + + def nonewlineline(line) + set_line_number + " + #{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} + + #{line_comment} +
#{render_line(line)}
+ + + #{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('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"') + end + + def render_line(line) + res = '' + if line.inline_changes? + prefix, changed, postfix = line.segments.map{|segment| escape(segment) } + res += "#{prefix}#{changed}#{postfix}" + else + res += escape(line) + end + res += '' + + 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 + "#{num}" + end + + def tr_line_comments comments + return if @in_wiki + res=" + #{comments.count} + " + comments.each do |comment| + res << "
+ #{render 'projects/comments/comment', :comment => comment, :data => {:project => @project, :commentable => @commentable, :add_anchor => 'inline', :in_discussion => @in_discussion}} +
" + end + res << link_to(t('layout.comments.new_inline'), new_comment_path, :class => 'new_inline_comment button') + res << "" + 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 diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb index 853c69b8a..d9e1851e8 100644 --- a/app/helpers/git_helper.rb +++ b/app/helpers/git_helper.rb @@ -68,7 +68,7 @@ module GitHelper [ ['Branches', project.repo.branches.map{|b| "latest_#{b.name}"}], ['Tags', project.repo.tags.map(&:name)] ] end - + def split_commits_by_date(commits) commits.sort{|x, y| y.authored_date <=> x.authored_date}.inject({}) do |h, commit| dt = commit.authored_date @@ -80,4 +80,3 @@ module GitHelper end end end - diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 40ddb89a2..a04f1c531 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -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" diff --git a/app/helpers/pull_request_helper.rb b/app/helpers/pull_request_helper.rb new file mode 100644 index 000000000..db6ee030d --- /dev/null +++ b/app/helpers/pull_request_helper.rb @@ -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'} \ + #{show_ref pull, 'from'} \ + #{t 'into'} \ + #{show_ref pull, 'to'}" + 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 diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 94d80dcd0..1424c819f 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -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 diff --git a/app/mailers/feedback_mailer.rb b/app/mailers/feedback_mailer.rb index 1d38f4e4d..272849100 100644 --- a/app/mailers/feedback_mailer.rb +++ b/app/mailers/feedback_mailer.rb @@ -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 diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index e66f06b79..057c735cc 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -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 diff --git a/app/models/ability.rb b/app/models/ability.rb index 11a0e84b0..2cf367642 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -14,20 +14,27 @@ 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 can :read, Advisory - + # Core callbacks 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) diff --git a/app/models/activity_feed_observer.rb b/app/models/activity_feed_observer.rb index 7ba79d38c..715450e03 100644 --- a/app/models/activity_feed_observer.rb +++ b/app/models/activity_feed_observer.rb @@ -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 diff --git a/app/models/advisory.rb b/app/models/advisory.rb index 4410ba621..951a8ec8c 100644 --- a/app/models/advisory.rb +++ b/app/models/advisory.rb @@ -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 @@ -38,4 +64,4 @@ class Advisory < ActiveRecord::Base end end -Advisory.include_root_in_json = false +Advisory.include_root_in_json = false \ No newline at end of file diff --git a/app/models/avatar.rb b/app/models/avatar.rb new file mode 100644 index 000000000..2fbefa5e8 --- /dev/null +++ b/app/models/avatar.rb @@ -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 diff --git a/app/models/build_list.rb b/app/models/build_list.rb index 86244a552..c3976f623 100644 --- a/app/models/build_list.rb +++ b/app/models/build_list.rb @@ -27,8 +27,18 @@ 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 + LIVE_TIME = 4.week # for unpublished MAX_LIVE_TIME = 3.month # for published # The kernel does not send these statuses directly @@ -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 @@ -195,7 +210,7 @@ class BuildList < ActiveRecord::Base def set_version_and_tag pkg = self.packages.where(:package_type => 'source', :project_id => self.project_id).first # TODO: remove 'return' after deployment ABF kernel 2.0 - return if pkg.nil? # For old client that does not sends data about packages + return if pkg.nil? # For old client that does not sends data about packages self.package_version = "#{pkg.platform.name}-#{pkg.version}-#{pkg.release}" system("cd #{self.project.repo.path} && git tag #{self.package_version} #{self.commit_hash}") # TODO REDO through grit save @@ -284,12 +299,40 @@ class BuildList < ActiveRecord::Base end def in_work? - status == BuildServer::BUILD_STARTED + status == BuildServer::BUILD_STARTED #[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 diff --git a/app/models/build_list/filter.rb b/app/models/build_list/filter.rb index 345a138dc..f3a4f78f2 100644 --- a/app/models/build_list/filter.rb +++ b/app/models/build_list/filter.rb @@ -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) diff --git a/app/models/build_list_observer.rb b/app/models/build_list_observer.rb index e236e64fc..2d12b85d0 100644 --- a/app/models/build_list_observer.rb +++ b/app/models/build_list_observer.rb @@ -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 diff --git a/app/models/comment.rb b/app/models/comment.rb index 6960860cc..ed6b638cb 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -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 diff --git a/app/models/group.rb b/app/models/group.rb index c09af034f..ecc8898fb 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -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 diff --git a/app/models/issue.rb b/app/models/issue.rb index 51a5ebac8..3e9e929be 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -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 diff --git a/app/models/platform.rb b/app/models/platform.rb index 49a84e898..802a2461a 100644 --- a/app/models/platform.rb +++ b/app/models/platform.rb @@ -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 diff --git a/app/models/project.rb b/app/models/project.rb index 535409d9a..58a7c0446 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -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 - self.maintainer_id = (owner_type == 'User') ? self.owner_id : self.owner.owner_id + if maintainer_id.blank? + self.maintainer_id = (owner_type == 'User') ? self.owner_id : self.owner.owner_id + end end end diff --git a/app/models/pull_request.rb b/app/models/pull_request.rb new file mode 100644 index 000000000..d43e3f325 --- /dev/null +++ b/app/models/pull_request.rb @@ -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 diff --git a/app/models/relation.rb b/app/models/relation.rb index 76829a849..2ce5d0fa4 100644 --- a/app/models/relation.rb +++ b/app/models/relation.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb index 027175d5c..fe76e0b3e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/presenters/comment_presenter.rb b/app/presenters/comment_presenter.rb index bab0b3343..ac9564c31 100644 --- a/app/presenters/comment_presenter.rb +++ b/app/presenters/comment_presenter.rb @@ -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 diff --git a/app/views/activity_feeds/_sidebar.html.haml b/app/views/activity_feeds/_sidebar.html.haml index baf06577f..6115f796f 100644 --- a/app/views/activity_feeds/_sidebar.html.haml +++ b/app/views/activity_feeds/_sidebar.html.haml @@ -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 \  diff --git a/app/views/activity_feeds/partials/_new_comment_commit_notification.haml b/app/views/activity_feeds/partials/_new_comment_commit_notification.haml index 003ffd448..2f276d098 100644 --- a/app/views/activity_feeds/partials/_new_comment_commit_notification.haml +++ b/app/views/activity_feeds/partials/_new_comment_commit_notification.haml @@ -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 diff --git a/app/views/activity_feeds/partials/_new_comment_notification.haml b/app/views/activity_feeds/partials/_new_comment_notification.haml index 567aae9a3..bc626889d 100644 --- a/app/views/activity_feeds/partials/_new_comment_notification.haml +++ b/app/views/activity_feeds/partials/_new_comment_notification.haml @@ -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 diff --git a/app/views/admin/register_requests/index.html.haml b/app/views/admin/register_requests/index.html.haml index a562479a3..90c609fc0 100644 --- a/app/views/admin/register_requests/index.html.haml +++ b/app/views/admin/register_requests/index.html.haml @@ -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"} diff --git a/app/views/api/v1/advisories/_advisory.json.jbuilder b/app/views/api/v1/advisories/_advisory.json.jbuilder new file mode 100644 index 000000000..5205ddc29 --- /dev/null +++ b/app/views/api/v1/advisories/_advisory.json.jbuilder @@ -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) \ No newline at end of file diff --git a/app/views/api/v1/advisories/index.json.jbuilder b/app/views/api/v1/advisories/index.json.jbuilder new file mode 100644 index 000000000..c0248c828 --- /dev/null +++ b/app/views/api/v1/advisories/index.json.jbuilder @@ -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) \ No newline at end of file diff --git a/app/views/api/v1/advisories/show.json.jbuilder b/app/views/api/v1/advisories/show.json.jbuilder new file mode 100644 index 000000000..740d12afe --- /dev/null +++ b/app/views/api/v1/advisories/show.json.jbuilder @@ -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 diff --git a/app/views/api/v1/arches/index.json.jbuilder b/app/views/api/v1/arches/index.json.jbuilder new file mode 100644 index 000000000..b51119646 --- /dev/null +++ b/app/views/api/v1/arches/index.json.jbuilder @@ -0,0 +1,3 @@ +json.architectures @arches do |json, arch| + json.(arch, :id, :name) +end \ No newline at end of file diff --git a/app/views/api/v1/build_lists/index.json.jbuilder b/app/views/api/v1/build_lists/index.json.jbuilder new file mode 100644 index 000000000..3b59be879 --- /dev/null +++ b/app/views/api/v1/build_lists/index.json.jbuilder @@ -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] } ) + diff --git a/app/views/api/v1/build_lists/show.json.jbuilder b/app/views/api/v1/build_lists/show.json.jbuilder new file mode 100644 index 000000000..7819f429e --- /dev/null +++ b/app/views/api/v1/build_lists/show.json.jbuilder @@ -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 diff --git a/app/views/api/v1/groups/index.json.jbuilder b/app/views/api/v1/groups/index.json.jbuilder new file mode 100644 index 000000000..fec845836 --- /dev/null +++ b/app/views/api/v1/groups/index.json.jbuilder @@ -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) \ No newline at end of file diff --git a/app/views/api/v1/groups/members.json.jbuilder b/app/views/api/v1/groups/members.json.jbuilder new file mode 100644 index 000000000..eb4ec3391 --- /dev/null +++ b/app/views/api/v1/groups/members.json.jbuilder @@ -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) \ No newline at end of file diff --git a/app/views/api/v1/groups/show.json.jbuilder b/app/views/api/v1/groups/show.json.jbuilder new file mode 100644 index 000000000..ebe02b9ec --- /dev/null +++ b/app/views/api/v1/groups/show.json.jbuilder @@ -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 \ No newline at end of file diff --git a/app/views/api/v1/platforms/_platform.json.jbuilder b/app/views/api/v1/platforms/_platform.json.jbuilder new file mode 100644 index 000000000..d16180771 --- /dev/null +++ b/app/views/api/v1/platforms/_platform.json.jbuilder @@ -0,0 +1,2 @@ +json.(platform, :id, :name) +json.url api_v1_platform_path(platform.id, :format => :json) \ No newline at end of file diff --git a/app/views/api/v1/platforms/index.json.jbuilder b/app/views/api/v1/platforms/index.json.jbuilder new file mode 100644 index 000000000..7e5e5d71d --- /dev/null +++ b/app/views/api/v1/platforms/index.json.jbuilder @@ -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) diff --git a/app/views/api/v1/platforms/members.json.jbuilder b/app/views/api/v1/platforms/members.json.jbuilder new file mode 100644 index 000000000..7cfc6e9a1 --- /dev/null +++ b/app/views/api/v1/platforms/members.json.jbuilder @@ -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) \ No newline at end of file diff --git a/app/views/api/v1/platforms/show.json.jbuilder b/app/views/api/v1/platforms/show.json.jbuilder new file mode 100644 index 000000000..c8c90a945 --- /dev/null +++ b/app/views/api/v1/platforms/show.json.jbuilder @@ -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 \ No newline at end of file diff --git a/app/views/api/v1/projects/_project.json.jbuilder b/app/views/api/v1/projects/_project.json.jbuilder new file mode 100644 index 000000000..a84d904f9 --- /dev/null +++ b/app/views/api/v1/projects/_project.json.jbuilder @@ -0,0 +1,3 @@ +json.(project, :id, :name) +json.fullname project.name_with_owner +json.url api_v1_project_path(project.id, :format => :json) \ No newline at end of file diff --git a/app/views/api/v1/projects/get_id.json.jbuilder b/app/views/api/v1/projects/get_id.json.jbuilder new file mode 100644 index 000000000..bc4ab3598 --- /dev/null +++ b/app/views/api/v1/projects/get_id.json.jbuilder @@ -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 diff --git a/app/views/api/v1/projects/index.json.jbuilder b/app/views/api/v1/projects/index.json.jbuilder new file mode 100644 index 000000000..4914044c4 --- /dev/null +++ b/app/views/api/v1/projects/index.json.jbuilder @@ -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) \ No newline at end of file diff --git a/app/views/api/v1/projects/members.json.jbuilder b/app/views/api/v1/projects/members.json.jbuilder new file mode 100644 index 000000000..258b81ca7 --- /dev/null +++ b/app/views/api/v1/projects/members.json.jbuilder @@ -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) \ No newline at end of file diff --git a/app/views/api/v1/projects/refs_list.json.jbuilder b/app/views/api/v1/projects/refs_list.json.jbuilder new file mode 100644 index 000000000..7b913f98b --- /dev/null +++ b/app/views/api/v1/projects/refs_list.json.jbuilder @@ -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) \ No newline at end of file diff --git a/app/views/api/v1/projects/show.json.jbuilder b/app/views/api/v1/projects/show.json.jbuilder new file mode 100644 index 000000000..dc27a12a2 --- /dev/null +++ b/app/views/api/v1/projects/show.json.jbuilder @@ -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 \ No newline at end of file diff --git a/app/views/api/v1/repositories/_repository.json.jbuilder b/app/views/api/v1/repositories/_repository.json.jbuilder new file mode 100644 index 000000000..ecd0ec545 --- /dev/null +++ b/app/views/api/v1/repositories/_repository.json.jbuilder @@ -0,0 +1,2 @@ +json.(repository, :id, :name) +json.url api_v1_repository_path(repository.id, :format => :json) \ No newline at end of file diff --git a/app/views/api/v1/repositories/projects.json.jbuilder b/app/views/api/v1/repositories/projects.json.jbuilder new file mode 100644 index 000000000..f911d6645 --- /dev/null +++ b/app/views/api/v1/repositories/projects.json.jbuilder @@ -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) \ No newline at end of file diff --git a/app/views/api/v1/repositories/show.json.jbuilder b/app/views/api/v1/repositories/show.json.jbuilder new file mode 100644 index 000000000..fc6d54f77 --- /dev/null +++ b/app/views/api/v1/repositories/show.json.jbuilder @@ -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 \ No newline at end of file diff --git a/app/views/api/v1/shared/_member.json.jbuilder b/app/views/api/v1/shared/_member.json.jbuilder new file mode 100644 index 000000000..25d2d1cf5 --- /dev/null +++ b/app/views/api/v1/shared/_member.json.jbuilder @@ -0,0 +1,3 @@ +tag.(member, :id, :name) +tag.type member.class.name +tag.url member_path(member) \ No newline at end of file diff --git a/app/views/api/v1/shared/_members.json.jbuilder b/app/views/api/v1/shared/_members.json.jbuilder new file mode 100644 index 000000000..00c8b9a93 --- /dev/null +++ b/app/views/api/v1/shared/_members.json.jbuilder @@ -0,0 +1,3 @@ +json.members @members do |json_members, member| + json.partial! 'api/v1/shared/member', :member => member, :tag => json_members +end \ No newline at end of file diff --git a/app/views/api/v1/shared/_owner.json.jbuilder b/app/views/api/v1/shared/_owner.json.jbuilder new file mode 100644 index 000000000..e6983d550 --- /dev/null +++ b/app/views/api/v1/shared/_owner.json.jbuilder @@ -0,0 +1,3 @@ +json.owner do |json_owner| + json.partial! 'api/v1/shared/member', :member => owner, :tag => json_owner +end \ No newline at end of file diff --git a/app/views/api/v1/users/notifiers.json.jbuilder b/app/views/api/v1/users/notifiers.json.jbuilder new file mode 100644 index 000000000..273ebbacc --- /dev/null +++ b/app/views/api/v1/users/notifiers.json.jbuilder @@ -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) \ No newline at end of file diff --git a/app/views/api/v1/users/show.json.jbuilder b/app/views/api/v1/users/show.json.jbuilder new file mode 100644 index 000000000..ff20c6f3b --- /dev/null +++ b/app/views/api/v1/users/show.json.jbuilder @@ -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 \ No newline at end of file diff --git a/app/views/groups/members/index.html.haml b/app/views/groups/members/index.html.haml index 9f80db048..25b05239d 100644 --- a/app/views/groups/members/index.html.haml +++ b/app/views/groups/members/index.html.haml @@ -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 diff --git a/app/views/groups/profile/_form.html.haml b/app/views/groups/profile/_form.html.haml index f30cb0ab4..4ac4eaf82 100644 --- a/app/views/groups/profile/_form.html.haml +++ b/app/views/groups/profile/_form.html.haml @@ -8,6 +8,8 @@ .rightlist.nomargin= f.text_area :description .both %br += render 'shared/avatar_form', :subject => @group, :f => f +%br .leftlist \  .rightlist= submit_tag t("layout.save") diff --git a/app/views/groups/profile/show.html.haml b/app/views/groups/profile/show.html.haml index a67abf0e8..890b9c2c9 100644 --- a/app/views/groups/profile/show.html.haml +++ b/app/views/groups/profile/show.html.haml @@ -1,12 +1,4 @@ -set_meta_tags :title => title_object(@group) -.all.verybigpadding - %h3= @group.name - %h4= t("activerecord.attributes.group.description") + ":" - %p= @group.description - %h4= t("layout.groups.public_projects_list") + ":" - %p - - @projects.each do |project| - = link_to project.name, project - %br - %br - = link_to t("layout.edit"), edit_group_path(@group), :class => 'button' if can? :edit, @group + +- edit_link = can?(:edit, @group) ? link_to(t("layout.edit"), edit_group_path(@group), :class => 'button') : nil += render 'shared/profile', :uname => @group.name, :group => @group, :search_path => group_path, :projects => @projects, :edit_link => edit_link diff --git a/app/views/layouts/menu/_bottom.html.haml b/app/views/layouts/menu/_bottom.html.haml index e50bebcf7..4858cfde7 100644 --- a/app/views/layouts/menu/_bottom.html.haml +++ b/app/views/layouts/menu/_bottom.html.haml @@ -17,3 +17,6 @@ %li = image_tag 'square.png' = link_to t('bottom_menu.support'), contact_url + %li + = image_tag 'square.png' + = link_to t('bottom_menu.developer_api'), t('bottom_menu.developer_api_url') diff --git a/app/views/layouts/issues.html.haml b/app/views/layouts/with_sidebar.html.haml similarity index 92% rename from app/views/layouts/issues.html.haml rename to app/views/layouts/with_sidebar.html.haml index 75987198b..87bd1860c 100644 --- a/app/views/layouts/issues.html.haml +++ b/app/views/layouts/with_sidebar.html.haml @@ -1,4 +1,4 @@ - if content_for?(:sidebar) %aside= yield :sidebar .right= yield -.both \ No newline at end of file +.both diff --git a/app/views/platforms/platforms/_form.html.haml b/app/views/platforms/platforms/_form.html.haml index ce9d9cb37..4508e03b6 100644 --- a/app/views/platforms/platforms/_form.html.haml +++ b/app/views/platforms/platforms/_form.html.haml @@ -22,7 +22,7 @@ .both .leftlist= label_tag "", t("layout.platforms.admin_id"), :class => :label - .rightlist= autocomplete_field_tag 'admin_id', @admin_uname, autocomplete_user_uname_platforms_path, :id_element => '#admin_id_field' + .rightlist= autocomplete_field_tag 'admin_id', @admin_uname, autocomplete_user_uname_autocompletes_path, :id_element => '#admin_id_field' = hidden_field_tag 'admin_id', @admin_id, :id => 'admin_id_field' .both diff --git a/app/views/platforms/repositories/_proj_list.html.haml b/app/views/platforms/repositories/_proj_list.html.haml index b5cbd50e9..a470d9dd2 100644 --- a/app/views/platforms/repositories/_proj_list.html.haml +++ b/app/views/platforms/repositories/_proj_list.html.haml @@ -7,7 +7,7 @@ :filtered_label => t("datatables.filtered_label"), :table_dom_id => 'datatable', :auto_width => 'false', - :ajax_source => "#{url_for :controller => :repositories, :action => :projects_list, :id => @repository.id, :added => "#{controller.action_name.to_sym == :show}"}" }) + :ajax_source => "#{url_for :controller => :repositories, :action => :projects_list, :id => @repository.id, :added => "#{controller.action_name.to_sym == :show}", :format => :json}" }) %table#datatable.tablesorter.repo-projects{:cellspacing => 0, :cellpadding => 0} %thead diff --git a/app/views/platforms/repositories/_project.html.haml b/app/views/platforms/repositories/_project.html.haml deleted file mode 100644 index 2048b04a2..000000000 --- a/app/views/platforms/repositories/_project.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -%tr{:id => "Row#{project_counter}", :class => cycle('odd', 'even')} - %td - = link_to project do - .table-sort-left= image_tag visibility_icon(project.visibility) - .table-sort-right #{project.owner.uname} / #{project.name} - %td.td2 - %span= project.description - %td.buttons - = link_to remove_project_platform_repository_path(@platform, @repository, :project_id => project.id), :method => :delete, :confirm => t("layout.confirm") do - %span.delete   diff --git a/app/views/projects/base/_repo_block.html.haml b/app/views/projects/base/_repo_block.html.haml index f301d63b3..d8788f420 100644 --- a/app/views/projects/base/_repo_block.html.haml +++ b/app/views/projects/base/_repo_block.html.haml @@ -6,8 +6,9 @@ =image_tag 'zip.png', :alt => 'ZIP' %b.caret %ul.dropdown-menu - %li=link_to "tar.gz", archive_path(project, @treeish, 'tar') - %li=link_to "zip", archive_path(project, @treeish, 'zip') + - file_name = "#{@project.owner.uname}-#{@project.name}-#{@commit.id}" + %li=link_to "tar.gz", archive_path(project, file_name, 'tar.gz') + %li=link_to "zip", archive_path(project, file_name, 'zip') = text_field_tag :url, git_repo_url(project.git_repo_name), :class => 'name', :spellcheck => 'false', :readonly => true .git_help ? diff --git a/app/views/projects/base/_submenu.html.haml b/app/views/projects/base/_submenu.html.haml index 607ee2ef6..67b6fb37d 100644 --- a/app/views/projects/base/_submenu.html.haml +++ b/app/views/projects/base/_submenu.html.haml @@ -11,6 +11,7 @@ %li= link_to t("project_menu.builds"), project_build_lists_path(@project), :class => (contr == :build_lists ? 'active' : nil) - if @project.has_issues %li= link_to t("project_menu.tracker"), project_issues_path(@project), :class => (contr == :issues ? 'active' : nil) + %li=link_to t("project_menu.pull_requests"), project_pull_requests_path(@project), :class => (contr == :pull_requests ? 'active' : nil) - if @project.has_wiki %li= link_to t("project_menu.wiki"), project_wiki_index_path(@project), :class => (contr == :wiki ? 'active' : nil) %li=# link_to t("project_menu.readme"), "#" #pending diff --git a/app/views/projects/collaborators/edit.html.haml b/app/views/projects/collaborators/edit.html.haml index f17ec9645..0474cec64 100644 --- a/app/views/projects/collaborators/edit.html.haml +++ b/app/views/projects/collaborators/edit.html.haml @@ -7,7 +7,7 @@ = form_tag add_project_collaborators_path(@project) do .admin-search - = autocomplete_field_tag 'member_id', params[:member_id], autocomplete_user_uname_users_path, :id_element => '#member_id_field' + = autocomplete_field_tag 'member_id', params[:member_id], autocomplete_user_uname_autocompletes_path, :id_element => '#member_id_field' .admin-role .lineForm = select_tag 'role', options_for_collaborators_roles_select @@ -54,7 +54,7 @@ = form_tag add_project_collaborators_path(@project) do .admin-search - = autocomplete_field_tag 'group_id', params[:group_id], autocomplete_group_uname_groups_path, :id_element => '#group_id_field' + = autocomplete_field_tag 'group_id', params[:group_id], autocomplete_group_uname_autocompletes_path, :id_element => '#group_id_field' .admin-role .lineForm = select_tag 'role', options_for_collaborators_roles_select, :id => 'group_role' diff --git a/app/views/projects/comments/_add.html.haml b/app/views/projects/comments/_add.html.haml index e78fe2ac3..132f81541 100644 --- a/app/views/projects/comments/_add.html.haml +++ b/app/views/projects/comments/_add.html.haml @@ -1,4 +1,5 @@ #open-comment.comment.view + =render 'projects/comments/button_md_help' %h3.tmargin0= t("layout.comments.new_header") - if Comment.issue_comment?(commentable.class) - new_path = project_issue_comments_path(project, commentable) @@ -10,7 +11,7 @@ - subscribe_path = is_subscribed ? unsubscribe_commit_path(project, commentable) : subscribe_commit_path(project, commentable) = form_for :comment, :url => new_path, :method => :post, :html => { :class => :form } do |f| - = render "projects/comments/form", :f => f + = render "projects/comments/form", :f => f, :id => 'new' .comment-left = t("layout.comments.notifications_are") %span.bold diff --git a/app/views/projects/comments/_body.html.haml b/app/views/projects/comments/_body.html.haml new file mode 100644 index 000000000..4b57f37b1 --- /dev/null +++ b/app/views/projects/comments/_body.html.haml @@ -0,0 +1,13 @@ +%ul.nav.nav-tabs#md_tabs + %li + %a{"data-toggle" => "tab", :href => "##{id}_edit"}=t 'layout.edit' + %li + %a{"data-toggle" => "tab", :href => "##{id}_preview"}=t 'layout.preview' + +.tab-content + .tab-pane.active{:id => "#{id}_edit"} + =f.text_area :body, :cols => 80, :id => "#{id}_edit_input" + =hidden_field_tag :body_dup, nil, :name => 'text', :id => "#{id}_edit_input_dup" + .tab-pane{:id => "#{id}_preview"} + .formatted.cm-s-default.md_and_cm + diff --git a/app/views/projects/comments/_button_md_help.html.haml b/app/views/projects/comments/_button_md_help.html.haml new file mode 100644 index 000000000..3cc040988 --- /dev/null +++ b/app/views/projects/comments/_button_md_help.html.haml @@ -0,0 +1,2 @@ +=link_to t('layout.comments.md_cheatsheet_header'), '#md_help', 'data-toggle' => 'modal', :style => 'float:right;' + diff --git a/app/views/projects/comments/_comment.html.haml b/app/views/projects/comments/_comment.html.haml new file mode 100644 index 000000000..0c25b2ac3 --- /dev/null +++ b/app/views/projects/comments/_comment.html.haml @@ -0,0 +1,10 @@ +- CommentPresenter.present(comment, data) do |presenter| + = render 'shared/feed_message', :presenter => presenter +#open-comment.comment.hidden{:class => "comment-#{comment.id}"} + =render 'projects/comments/button_md_help' + %h3.tmargin0= t("layout.comments.edit_header") + = form_for comment, :url => project_commentable_comment_path(data[:project], data[:commentable], comment), :html => { :class => 'form edit_comment' } do |f| + = render "projects/comments/form", :f => f, :id => "#{data[:add_id]}edit_#{comment.id}" + .comment-left + =link_to t('layout.cancel'), '#', :id => "comment-#{comment.id}", :class => 'cancel_edit_comment button' + .both diff --git a/app/views/projects/comments/_form.html.haml b/app/views/projects/comments/_form.html.haml index d7814ddad..f37286e42 100644 --- a/app/views/projects/comments/_form.html.haml +++ b/app/views/projects/comments/_form.html.haml @@ -1,2 +1,2 @@ -.wrapper= f.text_area :body, :cols => 80 -.comment-right= submit_tag t("layout.save") \ No newline at end of file +=render 'projects/comments/body', :f => f, :id => id +.comment-right= submit_tag t("layout.save") diff --git a/app/views/projects/comments/_list.html.haml b/app/views/projects/comments/_list.html.haml index acbead95f..e63505aa7 100644 --- a/app/views/projects/comments/_list.html.haml +++ b/app/views/projects/comments/_list.html.haml @@ -1,6 +1,6 @@ %a{ :name => "comments" } .hr -%h3= t("layout.comments.comments_header") +%h3#block-list= t("layout.comments.comments_header") - list.each do |comment| - - CommentPresenter.present(comment, :project => project, :commentable => commentable) do |presenter| - = render 'shared/feed_message', :presenter => presenter \ No newline at end of file + = render 'projects/comments/comment', :comment => comment, :data => {:project => project, :commentable => commentable} += render "projects/comments/markdown_help" diff --git a/app/views/projects/comments/_markdown_help.html.haml b/app/views/projects/comments/_markdown_help.html.haml new file mode 100644 index 000000000..28cf08dd8 --- /dev/null +++ b/app/views/projects/comments/_markdown_help.html.haml @@ -0,0 +1,86 @@ +#md_help.modal.hidden + .modal-header + %button.close{"aria-hidden" => "true", "data-dismiss" => "modal", :type => "button"} × + %h3#myModalLabel=t('layout.comments.md_cheatsheet_header') + .modal-body + .mod + .col + %h3=t 'layout.comments.md_cheatsheet.format_text' + %p=t 'layout.comments.md_cheatsheet.headers' + %pre + -[1, 3, 6].each do |i| + ="#{'#'*i} This is an tag" + %p=t 'layout.comments.md_cheatsheet.text_styles' + %pre + :preserve + *This text will be italic* + _This will also be italic_ + **This text will be bold** + __This will also be bold__ + .col + %h3=t 'layout.comments.md_cheatsheet.lists' + %p=t 'layout.comments.md_cheatsheet.unordered' + %pre + :preserve + * Item 1 + * Item 2 + * Item 2a + * Item 2b + %p=t 'layout.comments.md_cheatsheet.ordered' + %pre + :preserve + 1. Item 1 + 2. Item 2 + 3. Item 3 + * Item 3a + * Item 3b + .col + %h3=t 'layout.comments.md_cheatsheet.miscellaneous' + %p=t 'layout.comments.md_cheatsheet.images' + %pre + :preserve + ![avatar](/assets/ava.png) + Format: ![Alt Text](url) + %p=t 'layout.comments.md_cheatsheet.links' + %pre + :preserve + https://abf.rosalinux.ru + [ABF](https://abf.rosalinux.ru/) + %p=t 'layout.comments.md_cheatsheet.blockquotes' + %pre + :preserve + As Kanye West said: + + > We're living the future so + > the present is our past. + .both + %hr.bootstrap + %h3=t 'layout.comments.md_cheatsheet.code_examples' + .col + %p + =t 'layout.comments.md_cheatsheet.syntax_highlighting' + %pre + :preserve + ```javascript + function fancyAlert(arg) { + if(arg) { + $.facebox({div:'#foo'}) + } + } + ``` + .col + %p=t 'layout.comments.md_cheatsheet.indent_code' + %pre + :preserve + Here is a Python code example + without syntax highlighting: + + def foo: + if not bar: + return true + .col + %p=t 'layout.comments.md_cheatsheet.inline_code' + %pre + :preserve + I think you should use an + `[addr]` element here instead. diff --git a/app/views/projects/comments/edit.html.haml b/app/views/projects/comments/edit.html.haml index a27158367..0fd4907c1 100644 --- a/app/views/projects/comments/edit.html.haml +++ b/app/views/projects/comments/edit.html.haml @@ -8,4 +8,6 @@ = t("layout.comments.edit_header") .inner = form_for @comment, :url => project_commentable_comment_path(@project, @commentable, @comment), :html => {:class => :form} do |f| - = render "form", :f => f + = render "form", :f => f, :id => "edit_#{@comment.id}" +=hidden_field_tag :preview_url, project_md_preview_path(@project) + diff --git a/app/views/projects/comments/new_line.html.haml b/app/views/projects/comments/new_line.html.haml new file mode 100644 index 000000000..15dc609be --- /dev/null +++ b/app/views/projects/comments/new_line.html.haml @@ -0,0 +1,11 @@ +#open-comment.comment.view.new_line_comment + =render 'projects/comments/button_md_help' + %h3.tmargin0= t("layout.comments.new_header") + = form_for :comment, :url => @path, :method => :post, :html => { :class => :form } do |f| + = render "projects/comments/form", :f => f, :id => 'new_line' + .comment-left + =link_to t('layout.cancel'), '#', :class => 'cancel_inline_comment button' + =hidden_field_tag :path, params[:path] + =hidden_field_tag :line, params[:line] + =hidden_field_tag :in_reply, params[:in_reply] if params[:in_reply].present? + .both diff --git a/app/views/projects/git/base/_choose_fork.html.haml b/app/views/projects/git/base/_choose_fork.html.haml index 4288bf9a8..e627dd77c 100644 --- a/app/views/projects/git/base/_choose_fork.html.haml +++ b/app/views/projects/git/base/_choose_fork.html.haml @@ -11,4 +11,4 @@ :javascript $('#create_fork').click(function () { $(this).button('loading'); - }) \ No newline at end of file + }) diff --git a/app/views/projects/git/base/_fork.html.haml b/app/views/projects/git/base/_fork.html.haml index 1e733798f..be9cf54d4 100644 --- a/app/views/projects/git/base/_fork.html.haml +++ b/app/views/projects/git/base/_fork.html.haml @@ -1,3 +1,7 @@ +- if can? :write, @project + .r{:style => "display: block"} + =link_to t("projects.pull_requests.show.pull"), new_project_pull_request_path(@project, :treeish => @treeish), :id => 'send_pull_request', :class => 'button' + - if can? :fork, @project .r#fork-and-edit= link_to t('layout.projects.fork_and_edit'), '#forkModal', :class => 'button', 'data-toggle' => 'modal' #forkModal.modal.fork-modal{:style => 'display: none;'} diff --git a/app/views/projects/git/commits/_commit_diff.html.haml b/app/views/projects/git/commits/_commit_diff.html.haml index 46f639af9..c02a04395 100644 --- a/app/views/projects/git/commits/_commit_diff.html.haml +++ b/app/views/projects/git/commits/_commit_diff.html.haml @@ -1,10 +1,10 @@ - commit_id = commit_diff.deleted_file ? @commit.parents.first.id : @commit.id .file - %a{:name => h(commit_diff.a_path)} + %a{:name => "diff-#{commit_diff_counter}"} .top - .l= h(commit_diff.a_path) + .l= h(commit_diff.a_path.rtruncate 120) - if commit_diff.b_path.present? .r= link_to "view file @ #{short_hash_id(commit_id)}", blob_path(@project, commit_id, commit_diff.b_path) .clear - - .diff_data= render_diff(commit_diff) unless (@project.repo.tree(commit_id) / commit_diff.b_path).binary? \ No newline at end of file + -unless (@project.repo.tree(commit_id) / commit_diff.b_path).binary? + .diff_data=render_diff(commit_diff, :diff_counter => commit_diff_counter, :comments => @comments) diff --git a/app/views/projects/git/commits/_commits.html.haml b/app/views/projects/git/commits/_commits.html.haml index 9651c7060..cb79b9ffa 100644 --- a/app/views/projects/git/commits/_commits.html.haml +++ b/app/views/projects/git/commits/_commits.html.haml @@ -15,4 +15,4 @@ - GitPresenters::CommitAsMessagePresenter.present(commit, :branch => @branch, :project => @project) do |presenter| = render 'shared/feed_message', :presenter => presenter, :item_no => counter - counter += 1 - .both \ No newline at end of file + .both diff --git a/app/views/projects/git/commits/_commits_small.html.haml b/app/views/projects/git/commits/_commits_small.html.haml new file mode 100644 index 000000000..528f9b3ff --- /dev/null +++ b/app/views/projects/git/commits/_commits_small.html.haml @@ -0,0 +1,22 @@ +%div.commits_activity + %table + %tbody + - commits.each do |commit| + - item_no = commit.id + - GitPresenters::CommitAsMessagePresenter.present(commit, :branch => @branch, :project => @project) do |presenter| + %tr + %td + %img{:height => 16, :alt => "avatar", :src => presenter.image} + %td.date + = presenter.date + %td.name + = presenter.header + %td.subject + - if presenter.caption? + = presenter.caption + - if presenter.expandable? and presenter.content? + %span.data-expander.collapsed{:id => "expand#{item_no}"}   + - if presenter.content? + .fulltext{:class => "#{presenter.expandable? ? "hidden" : ''} #{presenter.caption? ? "" : "alone"}", + :id => presenter.expandable? ? "content-expand#{item_no}" : ''} + .cm-s-default.md_and_cm=markdown presenter.content diff --git a/app/views/projects/git/commits/show.html.haml b/app/views/projects/git/commits/show.html.haml index 28a008ac9..7da6e3afb 100644 --- a/app/views/projects/git/commits/show.html.haml +++ b/app/views/projects/git/commits/show.html.haml @@ -2,15 +2,16 @@ = render 'submenu' = render 'about_block', :project => @project - %h3= t("layout.projects.last_commit") - GitPresenters::CommitAsMessagePresenter.present(@commit, :branch => @branch, :project => @project) do |presenter| = render :partial => 'shared/feed_message', :locals => {:presenter => presenter, :item_no => 1} .both - #repo-wrapper = render 'show' - - = render "projects/comments/list", :list => Comment.for_commit(@commit), :project => @project, :commentable => @commit + -comments = @comments.select {|c| c.data.blank? } # dont work @comments.where(:data => nil) + = render "projects/comments/list", :list => comments, :project => @project, :commentable => @commit = render "projects/comments/add", :project => @project, :commentable => @commit if current_user + +=hidden_field_tag :preview_url, project_md_preview_path(@project) + diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 72c9f4cc7..01b6a0a1a 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,8 +1,4 @@ -.leftlist= t('activerecord.attributes.issue.title') + ':' -.rightlist= f.text_field :title -.leftlist= t('activerecord.attributes.issue.body') + ':' -.rightlist= f.text_area :body -.both +=render 'title_body', :f => f, :id => 'new' .leftlist= t('activerecord.attributes.issue.assignee') + ':' .rightlist %span#people-span.small-text= t('layout.issues.choose_user_on_left') diff --git a/app/views/projects/issues/_header.html.haml b/app/views/projects/issues/_header.html.haml new file mode 100644 index 000000000..3393fb965 --- /dev/null +++ b/app/views/projects/issues/_header.html.haml @@ -0,0 +1,21 @@ +%h3.issue_title=@issue.title +.activity + .top + .image + =image_tag(avatar_url(@issue.user, :medium), :alt => 'avatar') if @issue.user + .text + %span.name=link_to(@issue.user.fullname, user_path(@issue.user)) if @issue.user + %br/ + %span.date=@issue.created_at.to_s(:long) + %br/ + .both + .fulltext.view.issue_body.formatted.cm-s-default.md_and_cm=markdown @issue.body +.both +%br +- if can? :update, @issue + =link_to t('layout.edit'), '#', :id => 'edit_issue_content', :class => 'button' + =form_for :issue, :url => [@project, @issue], :method => :put, :html => { :class => 'edit_form issue', :style => 'display:none;' } do |f| + =render 'projects/issues/title_body', :f => f, :id => 'update' + =f.submit t('layout.update'), :id => 'update_issue_content' + =link_to t('layout.issues.cancel_button'), '#', :id => 'cancel_edit_issue_content', :class => 'button' +%br diff --git a/app/views/projects/issues/_index_sidebar.html.haml b/app/views/projects/issues/_index_sidebar.html.haml index d1ccfc387..03f8fb427 100644 --- a/app/views/projects/issues/_index_sidebar.html.haml +++ b/app/views/projects/issues/_index_sidebar.html.haml @@ -7,12 +7,12 @@ %tr %td.width18=radio_button_tag :myradio, 'all', !@is_assigned_to_me, {:id => 'myradio1', :class => 'niceRadio', :name => 'filter'} %td.width135=t("layout.issues.all") - %td.width30.right=@project.issues.count + %td.width30.right=@project.issues.without_pull_requests.count %tr %td=radio_button_tag :myradio, 'to_me', @is_assigned_to_me, {:id => 'myradio1', :class => 'niceRadio', :name => 'filter'} %td=t("layout.issues.to_me") - %td.width30.right=@project.issues.where(:assignee_id => current_user.id).count - =form_tag project_issues_path(@project), :id => 'search_issue', :method => :get do + %td.width30.right=@project.issues.without_pull_requests.where(:assignee_id => current_user.id).count + =form_tag project_issues_path(@project), :id => 'search_issue', :class => 'ajax_search_form', :method => :get do .bordered.bpadding20 =tracker_search_field(:search_issue, t('layout.issues.search')) - if can? :new, @project.issues.new diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 5f0c13ec4..f71bf84e1 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -1,9 +1,11 @@ +-path = polymorphic_path [@project, issue.pull_request ? issue.pull_request : issue] + %tr#row1{:name => "row", :class => issue.labels.map(&:name).compact} %td.td0 %span{:style => "display: none;"}=issue.serial_id %td.td1=issue.serial_id %td - %a{:href => project_issue_path(@project, issue)} + %a{:href => path} %div.issue_title=issue.title .smalltext =issue.created_at.to_s(:long) @@ -14,9 +16,12 @@ .label.selected.tracker.left .labeltext.selected{:style => "background: ##{label.color};"}=label.name %td.td3 + -if issue.pull_request + %a{:href => path} + .code # .avatar =link_to image_tag(avatar_url(issue.assignee), :alt => 'avatar'), user_path(issue.assignee) if issue.assignee - %a{:href => "#{project_issue_path @project, issue}#block-list"} + %a{:href => "#{path}#block-list"} .answers .pic= image_tag 'answers.png' .count=issue.comments.count diff --git a/app/views/projects/issues/_issues_table.html.haml b/app/views/projects/issues/_issues_table.html.haml new file mode 100644 index 000000000..4b62218c7 --- /dev/null +++ b/app/views/projects/issues/_issues_table.html.haml @@ -0,0 +1,18 @@ +#closed-switcher.blue-switcher + =hidden_field_tag :issues_status, @status, :id => 'issues_status' + .open + ="#{t('layout.issues.statuses.open')} (#{@opened_issues})" + #closed-tasks.closed + ="#{t('layout.issues.statuses.closed')} (#{@closed_issues})" + #blue-switch-select.selected{:style => "margin-left: #{@status == 'open' ? '0' : '130'}px;"} + .both +.both +#table1 + %table#myTable.tablesorter.tracker{:cellpadding => "0", :cellspacing => "0"} + %thead + %tr + %th.th1{:colspan => "2"}=t('layout.issues.number') + %th{:colspan => "2"}=t('layout.issues.description') + %tbody + = render :partial => 'projects/issues/issue', :collection => issues + = will_paginate issues diff --git a/app/views/projects/issues/_labels.html.haml b/app/views/projects/issues/_labels.html.haml index 81effb206..fec7aba5f 100644 --- a/app/views/projects/issues/_labels.html.haml +++ b/app/views/projects/issues/_labels.html.haml @@ -3,16 +3,15 @@ #labels-stock =form_tag project_issues_path(@project), :id => 'filter_labels', :method => :get do - @project.labels.each_with_index do |label, index| - .div-tracker-labels{:id => "label-#{label.name}", :style => @labels.include?(label.name) ? "background-color:##{label.color};color:#FFF" : ''} - .div-label-left - .label - .flag{:id => "flag-#{label.name}", :style => "background-color: ##{label.color};"} - .labeltext=label.name - =check_box_tag 'labels[]', label.name, @labels.include?(label.name), :style => 'display:none' - .both - .div-label-right=Labeling.joins(:label).where(:labels => {:name => label.name, :project_id => @project.id}).count - .both - .both + =render 'projects/shared/filter_label', + :id => label.name.parameterize, + :selected => @labels.include?(label.name), + :extra_classes => 'div-tracker-labels', + :color => label.color, + :check_box_name => 'labels', + :check_box_value => label.name, + :name => label.name, + :count => Labeling.joins(:label).where(:labels => {:name => label.name, :project_id => @project.id}).count - if can? :write, @project %a#manage-labels.button.tmargin10{:href => "#labels-stock"}=t('layout.issues.label_manage') #labels-edit{:style => "display: none;"} diff --git a/app/views/projects/issues/_title_body.html.haml b/app/views/projects/issues/_title_body.html.haml new file mode 100644 index 000000000..7f35fe7d6 --- /dev/null +++ b/app/views/projects/issues/_title_body.html.haml @@ -0,0 +1,8 @@ +#open-comment.comment.view + %h3.tmargin0= t 'activerecord.attributes.issue.title' + .wrapper= f.text_area :title, :cols => 80, :rows => 1 +#open-comment.comment.view + =render 'projects/comments/button_md_help' + %h3.tmargin0= t 'activerecord.attributes.issue.body' + =render 'projects/comments/body', :f => f, :id => id +.both diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 02c05d53f..7b116f361 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -2,21 +2,4 @@ -render 'submenu' -render 'index_sidebar' -#closed-switcher.blue-switcher - =hidden_field_tag :issues_status, @status, :id => 'issues_status' - .open - ="#{t('layout.issues.statuses.open')} (#{@opened_issues})" - #closed-tasks.closed - ="#{t('layout.issues.statuses.closed')} (#{@closed_issues})" - #blue-switch-select.selected{:style => "margin-left: #{@status == 'open' ? '0' : '130'}px;"} - .both -.both -#table1 - %table#myTable.tablesorter.tracker{:cellpadding => "0", :cellspacing => "0"} - %thead - %tr - %th.th1{:colspan => "2"}=t('layout.issues.number') - %th{:colspan => "2"}=t('layout.issues.description') - %tbody - = render :partial => 'issue', :collection => @issues - = will_paginate @issues \ No newline at end of file += render 'issues_table', :issues => @issues diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml index e3c4948e7..a59cc7a10 100644 --- a/app/views/projects/issues/new.html.haml +++ b/app/views/projects/issues/new.html.haml @@ -5,3 +5,6 @@ %h3.bpadding10= t("layout.issues.create_header") = form_for :issue, :url => project_issues_path(@project), :html => { :class => 'form issue' } do |f| = render "form", :f => f +=hidden_field_tag :preview_url, project_md_preview_path(@project) += render "projects/comments/markdown_help" + diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 6e9cc015b..6d8b4d06e 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -3,33 +3,12 @@ -render 'manage_sidebar' -content_for :right_nopadding do dummy -%h3.issue_title=@issue.title -.activity - .top - .image - =image_tag(avatar_url(@issue.user, :medium), :alt => 'avatar') if @issue.user - .text - %span.name=link_to(@issue.user.fullname, user_path(@issue.user)) if @issue.user - %br/ - %span.date=@issue.created_at.to_s(:long) - %br/ - .both - .fulltext.view.issue_body=simple_format @issue.body -.both -%br -- if can? :update, @issue - =link_to t('layout.edit'), '#', :id => 'edit_issue_content', :class => 'button' - =form_for :issue, :url => [@project, @issue], :method => :put, :html => { :class => 'edit_form issue', :style => 'display:none;' } do |f| - .leftlist= t('activerecord.attributes.issue.title') + ':' - .rightlist= f.text_field :title - .leftlist= t('activerecord.attributes.issue.body') + ':' - .rightlist= f.text_area :body - .both - =f.submit t('layout.update'), :id => 'update_issue_content' - =link_to t('layout.issues.cancel_button'), '#', :id => 'cancel_edit_issue_content', :class => 'button' -%br +=render 'header' =render 'status' = render "projects/comments/list", :list => @issue.comments, :project => @project, :commentable => @issue %br = render "projects/comments/add", :project => @project, :commentable => @issue if current_user + +=hidden_field_tag :preview_url, project_md_preview_path(@project) + diff --git a/app/views/projects/projects/_filters.html.haml b/app/views/projects/projects/_filters.html.haml new file mode 100644 index 000000000..12577ac68 --- /dev/null +++ b/app/views/projects/projects/_filters.html.haml @@ -0,0 +1,12 @@ +-content_for :sidebar do + - if current_user + =form_tag projects_path, :id => 'filter_projects', :method => :get do + .bordered.bpadding20 + =tracker_search_field(:search, t('layout.find_project')) + - if can?(:create, Project) + .bordered.bpadding20 + = link_to t('layout.projects.new'), new_project_path, :class => 'button' + .bordered.bpadding20 + %h3=t('layout.relations.filters') + - options_for_filters(@all_projects, @groups, @owners).each do |options| + =render 'projects/shared/filter_label', options diff --git a/app/views/projects/projects/index.html.haml b/app/views/projects/projects/index.html.haml index 01c48b8bd..920b10daa 100644 --- a/app/views/projects/projects/index.html.haml +++ b/app/views/projects/projects/index.html.haml @@ -1,12 +1,8 @@ -set_meta_tags :title => t('layout.projects.list_header') +-render 'filters' .toolbar - = link_to t('layout.projects.new'), new_project_path, :class => 'button' if can?(:create, Project) - - .legend.rights - %span.user_owner= t("layout.relations.user_owner") - %span.group_owner= t("layout.relations.group_owner") - %span.user= t("layout.relations.user") - %span.group= t("layout.relations.group") + -%w(user_owner group_owner user group).each do |el| + %span{:class => el}=t "layout.relations.#{el}" .both - columns = [{:type => 'html'}, {:type => 'html', :sortable => false, :searchable => false}, @@ -29,15 +25,11 @@ %th.th2= t("activerecord.attributes.project.description") %th.th3= t("layout.projects.role") %th.th4= t("layout.projects.remove_user") - %tr.search - %th{:colspan => 4} - - %tbody= render :partial => 'projects/projects/project', :collection => @projects :javascript $(document).ready(function() { - + var isUpdateDataTable = null; var JsonParser = function (json) { var firstColumn = function(row) { @@ -99,16 +91,17 @@ #{ format_columns_for_datatable(columns) } ], "fnServerData": function ( sSource, aoData, fnCallback ) { - $.getJSON( sSource, aoData, function (json) { + if (isUpdateDataTable != null) { isUpdateDataTable.abort(); } + _.each($('#filter_projects').serializeArray(), function(dv) { aoData.push(dv); }); + isUpdateDataTable = $.getJSON( sSource, aoData, function (json) { json.aaData = JsonParser(json); fnCallback(json); } ); } }); - $('#datatable_wrapper').append("
"); - var $search = $('tr.search input[type="text"]'); + var $search = $('#search'); $search.live('blur', function() { $this = $(this); if ($this.val() == '') { @@ -128,6 +121,12 @@ $search.live('keyup', function() { oTable.fnFilter(this.value); }); + $(".div-filter-labels").live('click', function() { + oTable.dataTable().fnDraw(); + }); + $('#filter_projects').live('submit', function() { + return false; + }); }); =# will_paginate diff --git a/app/views/projects/projects/refs_list.html.haml b/app/views/projects/projects/refs_list.html.haml new file mode 100644 index 000000000..43ea5f649 --- /dev/null +++ b/app/views/projects/projects/refs_list.html.haml @@ -0,0 +1 @@ +=render 'projects/pull_requests/ref_select', :kind => 'to', :project => @project, :current => @selected diff --git a/app/views/projects/pull_requests/_activity.html.haml b/app/views/projects/pull_requests/_activity.html.haml new file mode 100644 index 000000000..3f868e607 --- /dev/null +++ b/app/views/projects/pull_requests/_activity.html.haml @@ -0,0 +1,23 @@ +.hr +-commits_queue = [] +-merge_activity(@comments, @commits).each do |item| # + -if item.is_a? Comment + =render 'projects/git/commits/commits_small', :commits => commits_queue if commits_queue.present? + -commits_queue.clear + =render 'projects/comments/comment', :comment => item, :data => {:project => @project, :commentable => @commentable} + -elsif item.is_a? Grit::Commit + -commits_queue << item + -elsif item.is_a? Array + =render 'projects/git/commits/commits_small', :commits => commits_queue if commits_queue.present? + -commits_queue.clear + -unless item[1].first.data[:actual] + -exp_id = "expand-comment#{item[1].first.id}" + .activity + =t '.show_outdated_diff' + %span.data-expander.collapsed{:id => exp_id}   + .hidden{:id => "content-#{exp_id}"} + =render 'projects/pull_requests/discussion_comments', :item => item, :add_id => nil + -else + =render 'projects/pull_requests/discussion_comments', :item => item, :add_id => nil +=render 'projects/git/commits/commits_small', :commits => commits_queue if commits_queue.present? + diff --git a/app/views/projects/pull_requests/_diff_commits_tabs.html.haml b/app/views/projects/pull_requests/_diff_commits_tabs.html.haml new file mode 100644 index 000000000..117b84635 --- /dev/null +++ b/app/views/projects/pull_requests/_diff_commits_tabs.html.haml @@ -0,0 +1,21 @@ +#diff.tab-pane + .leftside + -total_additions = @stats.inject(0) {|sum, n| sum + n.additions} + -total_deletions = @stats.inject(0) {|sum, n| sum + n.deletions} + %h5= t("layout.projects.diff_show_header", + :files => t("layout.projects.commit_files_count", :count => @stats.count), + :additions => t("layout.projects.commit_additions_count", :count => total_additions), + :deletions => t("layout.projects.commit_deletions_count", :count => total_deletions)) + .both + -begin + = render_diff_stats(@stats) + = render :partial => 'pull_diff', :collection => @pull.diff + - rescue => ex + -if ex.try(:message) == 'Grit::Git::GitTimeout' + %p= t 'layout.git.repositories.commit_diff_too_big' + -else + -raise ex +#commits.tab-pane + - if @total_commits > @commits.count + %div= t("projects.pull_requests.is_big", :count => @commits.count) + = render :partial => 'projects/git/commits/commits', :object => @commits diff --git a/app/views/projects/pull_requests/_discussion_comments.html.haml b/app/views/projects/pull_requests/_discussion_comments.html.haml new file mode 100644 index 000000000..e42f7eb69 --- /dev/null +++ b/app/views/projects/pull_requests/_discussion_comments.html.haml @@ -0,0 +1,15 @@ +-comment = item[1].first +.file + .top + .l=comment.data[:view_path] + -if comment.actual_inline_comment? @pull.diff + .r=link_to t("layout.pull_requests.view_full_changes"), + "#{project_commentable_path(@project, @commentable)}##{comment_anchor(comment)}", + :class => 'link_to_full_changes' + -else + .r + =t'projects.pull_requests.outdated_diff' + =image_tag 'x.png' + .clear + .diff_data=render_diff(comment.inline_diff, :diff_counter => 0, :comments => item[1], :filepath => comment.data[:path]) + diff --git a/app/views/projects/pull_requests/_index_sidebar.html.haml b/app/views/projects/pull_requests/_index_sidebar.html.haml new file mode 100644 index 000000000..6b1af739c --- /dev/null +++ b/app/views/projects/pull_requests/_index_sidebar.html.haml @@ -0,0 +1,4 @@ +-content_for :sidebar do + =form_tag project_pull_requests_path(@project), :id => 'filter_pull_requests', :method => :get, :class => 'ajax_search_form' do + .bordered.bpadding20 + =tracker_search_field(:search_pull_request, t('layout.pull_requests.search')) diff --git a/app/views/projects/pull_requests/_nav_tabs.html.haml b/app/views/projects/pull_requests/_nav_tabs.html.haml new file mode 100644 index 000000000..5d244eb0c --- /dev/null +++ b/app/views/projects/pull_requests/_nav_tabs.html.haml @@ -0,0 +1,22 @@ +%ul.nav.nav-tabs#pull_tabs + %li + %a{"data-toggle" => "tab", :href => "#discussion"}=t 'pull_requests.tabs.discussion' + -if @stats + %li + %a{"data-toggle" => "tab", :href => "#diff"}="#{t'pull_requests.tabs.diff'} (#{@stats.count})" + -if @commits + %li + - commits_message = @commits.count.to_s + - commits_message << '+' if @total_commits > @commits.count + %a{"data-toggle" => "tab", :href => "#commits"}="#{t'pull_requests.tabs.commits'} (#{commits_message})" + +:javascript + $(document).ready(function() { + var tab = 'discussion'; + if(document.location.href.match(/(.*)#diff(.*)/)) { + tab = 'diff'; + } else if(document.location.href.match(/(.*)#commits(.*)/)) { + tab = 'commits'; + }; + $('#pull_tabs a[href="#'+tab+'"]').tab('show'); + }); diff --git a/app/views/projects/pull_requests/_pull_diff.html.haml b/app/views/projects/pull_requests/_pull_diff.html.haml new file mode 100644 index 000000000..60fbe55ff --- /dev/null +++ b/app/views/projects/pull_requests/_pull_diff.html.haml @@ -0,0 +1,10 @@ +- commit_id = pull_diff.deleted_file ? @pull.to_commit.id : @pull.from_commit.id +.file + %a{:name => "diff-#{pull_diff_counter}"} + .top + .l= h(pull_diff.renamed_file ? "#{pull_diff.a_path.rtruncate 60} -> #{pull_diff.b_path.rtruncate 60}" : pull_diff.a_path.rtruncate(120)) + - if pull_diff.b_path.present? + .r= link_to "view file @ #{short_hash_id(commit_id)}", blob_path(@project, commit_id, pull_diff.b_path) + .clear + -if pull_diff.diff.present? && !(@pull.repo.tree(commit_id) / pull_diff.b_path).binary? + .diff_data=render_diff(pull_diff, :diff_counter => pull_diff_counter, :comments => @comments) diff --git a/app/views/projects/pull_requests/_ref_select.html.haml b/app/views/projects/pull_requests/_ref_select.html.haml new file mode 100644 index 000000000..8a979ecf8 --- /dev/null +++ b/app/views/projects/pull_requests/_ref_select.html.haml @@ -0,0 +1,3 @@ +-ref="#{kind}_ref" +.l=select_tag ref, ref_selector_options(project, current), :id => ref, :class => 'sel80', :name => "pull_request[#{ref}]" + diff --git a/app/views/projects/pull_requests/_status.html.haml b/app/views/projects/pull_requests/_status.html.haml new file mode 100644 index 000000000..de98651d4 --- /dev/null +++ b/app/views/projects/pull_requests/_status.html.haml @@ -0,0 +1,14 @@ +- if can?(:merge, @pull) && @pull.can_merging? + %br + =form_for PullRequest.new, :url => merge_project_pull_request_path(@project, @pull), :html => { :method => :put, :class => :form } do |f| + =f.submit t 'projects.pull_requests.ready' +-else + .flash + %div{:class => @pull.ready? ? 'notice' : 'alert'} + =pull_status @pull +-if can? :update, @pull + -if action = @pull.can_close? ? 'close' : ('reopen' if @pull.can_reopen?) + %br + =form_for :pull, :url => [@project, @pull], :html => { :id => 'do_pull_action',:method => :put, :class => :form } do |f| + =hidden_field_tag "pull_request_action", action + =f.submit t ".#{action}" diff --git a/app/views/projects/pull_requests/index.html.haml b/app/views/projects/pull_requests/index.html.haml new file mode 100644 index 000000000..5aa20c682 --- /dev/null +++ b/app/views/projects/pull_requests/index.html.haml @@ -0,0 +1,6 @@ +-ar = 'activerecord.attributes.pull_requests' +-set_meta_tags :title => [title_object(@project), t('.title')] +-render 'submenu' +-render 'index_sidebar' + += render 'projects/issues/issues_table', :issues => @issues_with_pull_request diff --git a/app/views/projects/pull_requests/new.html.haml b/app/views/projects/pull_requests/new.html.haml new file mode 100644 index 000000000..6ad43d4de --- /dev/null +++ b/app/views/projects/pull_requests/new.html.haml @@ -0,0 +1,42 @@ +-ar = 'activerecord.attributes.pull_requests' +-set_meta_tags :title => [title_object(@project), t('.title')] += render :partial => 'submenu' +%h3.bpadding10=pull_header @pull +#repo-wrapper + =render 'nav_tabs' + .tab-content.pull_diff_fix + #discussion.tab-pane.active + =hidden_field_tag :update_action, new_project_pull_request_path + =form_for @pull, :url => (@pull.already? ? new_project_pull_request_path : project_pull_requests_path), :html => {:class => 'well well-large', :method => (@pull.already? ? :get : :post)} do |f| + + .leftlist=f.label :from_project, t("#{ar}.from_project"), :class => :label + %div + .l=text_field_tag :from_project, @pull.from_project.name_with_owner, :readonly => :readonly, :style => 'background-color: #DDD;' + =render 'ref_select', :kind => 'from', :project => @pull.from_project, :current => @pull.from_ref + .both + .leftlist=f.label :to_project, t("#{ar}.to_project"), :class => :label + %div + .l=f.autocomplete_field :to_project, autocomplete_to_project_project_pull_requests_path, :value => @pull.to_project.name_with_owner, :id_element => 'pull_request_to_project_id', :name => 'to_project' + =render 'ref_select', :kind => 'to', :project => @pull.to_project, :current => @pull.to_ref + .both + .leftlist.big-list + .rightlist=f.submit t('.update'), :class => 'btn btn-primary disabled', 'data-loading-text' => t('layout.processing'), :style => @pull.already? ? '' : 'display: none;', :id => 'update_pull' + .both + -unless @pull.already? + =f.fields_for :issue do |issue| + =render 'projects/issues/title_body', :f => issue, :id => 'new' + .leftlist.big-list=f.label :title, t('activerecord.attributes.issue.status'), :class => :label + .rightlist + .flash + %div{:class => @pull.ready? ? 'notice' : 'alert'} + =pull_status @pull + .both + .leftlist.big-list + .rightlist + =f.submit t('.submit'), :class => 'btn btn-primary disabled', 'data-loading-text' => t('layout.processing'), :id => 'create_pull' unless @pull.already? + .both + =render 'diff_commits_tabs' if !@pull.already? && @stats != nil + +=hidden_field_tag :preview_url, project_md_preview_path(@project) += render "projects/comments/markdown_help" + diff --git a/app/views/projects/pull_requests/show.html.haml b/app/views/projects/pull_requests/show.html.haml new file mode 100644 index 000000000..0e525d1cd --- /dev/null +++ b/app/views/projects/pull_requests/show.html.haml @@ -0,0 +1,19 @@ +-ar = 'activerecord.attributes.pull_requests' +-set_meta_tags :title => [title_object(@project), t('.title', :name => @pull.title.truncate(40), :user => @pull.user.uname)] += render :partial => 'submenu' +%h3.bpadding10 + =pull_status_label @pull + =pull_header @pull +#repo-wrapper + =render 'nav_tabs' + .tab-content.pull_diff_fix + #discussion.tab-pane.active + =render 'projects/issues/header' + =render 'activity' + %br + =render "projects/comments/add", :project => @project, :commentable => @issue if current_user + .pull_status + =render 'status' + =render 'diff_commits_tabs' unless @pull.already? +=hidden_field_tag :preview_url, project_md_preview_path(@project) += render "projects/comments/markdown_help" diff --git a/app/views/projects/shared/_filter_label.html.haml b/app/views/projects/shared/_filter_label.html.haml new file mode 100644 index 000000000..14fa61277 --- /dev/null +++ b/app/views/projects/shared/_filter_label.html.haml @@ -0,0 +1,11 @@ +- extra_classes ||= '' +.div-filter-labels{:id => "label-#{id}", :class => extra_classes, :style => selected ? "background-color:##{color};color:#FFF" : ''} + .div-label-left + .label + .flag{:id => "flag-#{id}", :style => "background-color: ##{color};"} + .labeltext=name + =check_box_tag "#{check_box_name}[]", check_box_value, selected, :style => 'display:none' + .both + .div-label-right=count + .both +.both diff --git a/app/views/projects/wiki/_diff_data.html.haml b/app/views/projects/wiki/_diff_data.html.haml index 1c00ff781..952b6ea45 100644 --- a/app/views/projects/wiki/_diff_data.html.haml +++ b/app/views/projects/wiki/_diff_data.html.haml @@ -1,4 +1,7 @@ -.blob_header - .size= h(diff.deleted_file ? diff.a_path : diff.b_path) - .clear -.diff_data.highlight= render_diff(diff) \ No newline at end of file +.file + %a{:name => "diff-#{diff_counter}"} + .top + .l= h((diff.deleted_file ? diff.a_path : diff.b_path).rtruncate 100) + .clear + .diff_data.highlight= render_diff(diff, :diff_counter => diff_counter, :in_wiki => true) + diff --git a/app/views/shared/_avatar_form.html.haml b/app/views/shared/_avatar_form.html.haml new file mode 100644 index 000000000..9729a6b9b --- /dev/null +++ b/app/views/shared/_avatar_form.html.haml @@ -0,0 +1,10 @@ +.leftlist= f.label :avatar, t("layout.avatars.avatar_with_size", :max => number_to_human_size(Avatar::MAX_AVATAR_SIZE)) +.rightlist= image_tag(avatar_url(subject, :medium)) +.leftlist +.rightlist + .check + %span#niceCheckbox1.niceCheck-main= check_box_tag "delete_avatar", 1, false, :class => 'niceCheckbox1' + .forcheck= t('layout.avatars.delete_avatar') + .both + = f.file_field :avatar +.both \ No newline at end of file diff --git a/app/views/shared/_feed_message.html.haml b/app/views/shared/_feed_message.html.haml index 42b5d942a..414b4cc9b 100644 --- a/app/views/shared/_feed_message.html.haml +++ b/app/views/shared/_feed_message.html.haml @@ -1,4 +1,4 @@ -.activity{:id => presenter.comment_id? ? "comment##{presenter.comment_id}" : ''} +.activity{:id => presenter.comment_id? ? presenter.comment_anchor : ''} .top - if presenter.buttons? %span.buttons= raw presenter.buttons.join(' | ').html_safe @@ -19,5 +19,5 @@ - if presenter.content? .fulltext{:class => "#{presenter.expandable? ? "hidden" : ''} #{presenter.caption? ? "" : "alone"}", :id => presenter.expandable? ? "content-expand#{item_no}" : ''} - = presenter.content + .cm-s-default.md_and_cm=markdown presenter.content .both diff --git a/app/views/shared/_members_table.html.haml b/app/views/shared/_members_table.html.haml index 2b9ca7177..306e9e8aa 100644 --- a/app/views/shared/_members_table.html.haml +++ b/app/views/shared/_members_table.html.haml @@ -33,7 +33,7 @@ .hr.top = form_tag add_member_path do .admin-search - = autocomplete_field_tag 'member_id', params[:member_id], autocomplete_user_uname_users_path, :id_element => '#member_id_field' + = autocomplete_field_tag 'member_id', params[:member_id], autocomplete_user_uname_autocompletes_path, :id_element => '#member_id_field' = hidden_field_tag 'member_id', nil, :id => 'member_id_field' = submit_tag t("layout.add"), :class => 'button' .both diff --git a/app/views/shared/_profile.html.haml b/app/views/shared/_profile.html.haml new file mode 100644 index 000000000..6f8c14778 --- /dev/null +++ b/app/views/shared/_profile.html.haml @@ -0,0 +1,38 @@ +- edit_link ||= nil +- user ||= nil +- group ||= nil +- name ||= uname +.avatar + = image_tag avatar_url(user || group, :big) + - if edit_link + %br + = edit_link +.info + %h3= title uname + = name + - if user + %br + = mail_to user.email, user.email, :encode => "javascript" + %br + - if user + %h4= t("activerecord.attributes.user.professional_experience") + ":" + %p= user.professional_experience + - else + %h4= t("activerecord.attributes.group.description") + ":" + %p= group.description +.content + %h4= t("layout.projects.public_projects_list") + ":" + %p + =form_tag search_path, :id => 'filter_projects', :method => :get do + =tracker_search_field(:search, t('layout.find_project')) + %br + %p + - projects.each do |project| + = link_to project.name, project + %br + %br + = will_paginate projects + %br + +:javascript + $('article .all').addClass('verybigpadding'); diff --git a/app/views/user_mailer/_footer.html.haml b/app/views/user_mailer/_footer.html.haml new file mode 100644 index 000000000..b4d481d03 --- /dev/null +++ b/app/views/user_mailer/_footer.html.haml @@ -0,0 +1,7 @@ +%hr + %p{:style => "font-size:small;color:#666"} + = t("notifications.footers.notifiers") + = link_to t("notifications.footers.notification_center"), notifiers_settings_url + %p{:style => "font-size:small;color:#666"} + = t("notifications.footers.support_team") + diff --git a/app/views/user_mailer/build_list_notification.en.haml b/app/views/user_mailer/build_list_notification.en.haml index 9f58dafe3..afd0752d6 100644 --- a/app/views/user_mailer/build_list_notification.en.haml +++ b/app/views/user_mailer/build_list_notification.en.haml @@ -12,4 +12,4 @@ More detailed information you can get by link: = link_to "task [№ #{@build_list.bs_id ? @build_list.bs_id : t("layout.build_lists.bs_id_not_set")}]", build_list_url(@build_list) -%p== Support team «ROSA Build System» += render 'footer' diff --git a/app/views/user_mailer/build_list_notification.ru.haml b/app/views/user_mailer/build_list_notification.ru.haml index 8a0970053..21401beaa 100644 --- a/app/views/user_mailer/build_list_notification.ru.haml +++ b/app/views/user_mailer/build_list_notification.ru.haml @@ -12,4 +12,4 @@ Более подробную информацию можно получить по ссылке: = link_to "задание [№ #{@build_list.bs_id ? @build_list.bs_id : t("layout.build_lists.bs_id_not_set")}]", build_list_url(@build_list) -%p== Команда поддержки «ROSA Build System» += render 'footer' diff --git a/app/views/user_mailer/invite_approve_notification.en.haml b/app/views/user_mailer/invite_approve_notification.en.haml index f3ff83e13..27a4ca11a 100644 --- a/app/views/user_mailer/invite_approve_notification.en.haml +++ b/app/views/user_mailer/invite_approve_notification.en.haml @@ -1,7 +1,7 @@ %p== Hello, #{@register_request.name || @register_request.email}. %p - You have been invited to project ABF. Please click on the following link for registration: - = link_to 'link', new_user_registration_url(:invitation_token => @register_request.token) + You have been invited to project ABF. Please click on the following + = link_to 'link for registration', new_user_registration_url(:invitation_token => @register_request.token) %p== Support team «ROSA Build System» diff --git a/app/views/user_mailer/invite_approve_notification.ru.haml b/app/views/user_mailer/invite_approve_notification.ru.haml index 8d4bcc6bd..e75296fe2 100644 --- a/app/views/user_mailer/invite_approve_notification.ru.haml +++ b/app/views/user_mailer/invite_approve_notification.ru.haml @@ -1,7 +1,7 @@ %p== Здравствуйте, #{@register_request.name || @register_request.email}. %p - Вы приглашены в проект ABF. Чтобы зарегистрироваться перейдите по - = link_to 'ссылке', new_user_registration_url(:invitation_token => @register_request.token) + Вы приглашены в проект ABF. Пожалуйста, перейдите по + = link_to 'ссылке для регистрации', new_user_registration_url(:invitation_token => @register_request.token) %p== Команда поддержки «ROSA Build System» diff --git a/app/views/user_mailer/issue_assign_notification.en.haml b/app/views/user_mailer/issue_assign_notification.en.haml index c913bea95..3aa4a049a 100644 --- a/app/views/user_mailer/issue_assign_notification.en.haml +++ b/app/views/user_mailer/issue_assign_notification.en.haml @@ -4,4 +4,4 @@ %p You have been assigned to issue #{ link_to @issue.title, project_issue_url(@issue.project, @issue) } -%p== Support team «ROSA Build System» += render 'footer' diff --git a/app/views/user_mailer/issue_assign_notification.ru.haml b/app/views/user_mailer/issue_assign_notification.ru.haml index da59dc6cc..5b5dcd7d5 100644 --- a/app/views/user_mailer/issue_assign_notification.ru.haml +++ b/app/views/user_mailer/issue_assign_notification.ru.haml @@ -4,4 +4,4 @@ %p Вам была назначена задача #{ link_to @issue.title, project_issue_url(@issue.project, @issue) } -%p== Команда поддержки «ROSA Build System» += render 'footer' diff --git a/app/views/user_mailer/new_comment_notification.en.haml b/app/views/user_mailer/new_comment_notification.en.haml index 7c6c93175..00d903a37 100644 --- a/app/views/user_mailer/new_comment_notification.en.haml +++ b/app/views/user_mailer/new_comment_notification.en.haml @@ -11,4 +11,4 @@ %p "#{ @comment.body }" -%p== Support team «ROSA Build System» += render 'footer' diff --git a/app/views/user_mailer/new_comment_notification.ru.haml b/app/views/user_mailer/new_comment_notification.ru.haml index 1c7b933e5..da038b0e6 100644 --- a/app/views/user_mailer/new_comment_notification.ru.haml +++ b/app/views/user_mailer/new_comment_notification.ru.haml @@ -11,4 +11,4 @@ %p "#{ @comment.body }" -%p== Команда поддержки «ROSA Build System» += render 'footer' diff --git a/app/views/user_mailer/new_issue_notification.en.haml b/app/views/user_mailer/new_issue_notification.en.haml index 713c7ed36..b8be5a8f7 100644 --- a/app/views/user_mailer/new_issue_notification.en.haml +++ b/app/views/user_mailer/new_issue_notification.en.haml @@ -4,4 +4,4 @@ %p To project #{ link_to @issue.project.name, project_url(@issue.project) } has been added an issue #{ link_to @issue.title, project_issue_url(@issue.project, @issue) } -%p== Support team «ROSA Build System» += render 'footer' diff --git a/app/views/user_mailer/new_issue_notification.ru.haml b/app/views/user_mailer/new_issue_notification.ru.haml index 302a13a28..9cbb20188 100644 --- a/app/views/user_mailer/new_issue_notification.ru.haml +++ b/app/views/user_mailer/new_issue_notification.ru.haml @@ -4,4 +4,4 @@ %p К проекту #{ link_to @issue.project.name, project_url(@issue.project) } была добавлена задача #{ link_to @issue.title, project_issue_url(@issue.project, @issue) } -%p== Команда поддержки «ROSA Build System» += render 'footer' diff --git a/app/views/user_mailer/new_user_notification.en.haml b/app/views/user_mailer/new_user_notification.en.haml index 4afbf64cb..307e7c6a5 100644 --- a/app/views/user_mailer/new_user_notification.en.haml +++ b/app/views/user_mailer/new_user_notification.en.haml @@ -7,4 +7,4 @@ %p ==Your email : #{@user.email} -%p== Support team «ROSA Build System» += render 'footer' diff --git a/app/views/user_mailer/new_user_notification.ru.haml b/app/views/user_mailer/new_user_notification.ru.haml index cb7929fab..4ae928374 100644 --- a/app/views/user_mailer/new_user_notification.ru.haml +++ b/app/views/user_mailer/new_user_notification.ru.haml @@ -7,4 +7,4 @@ %p ==Ваш email : #{@user.email} -%p== Команда поддержки «ROSA Build System» += render 'footer' diff --git a/app/views/users/base/_form.html.haml b/app/views/users/base/_form.html.haml index 4baa5218e..e661a9191 100644 --- a/app/views/users/base/_form.html.haml +++ b/app/views/users/base/_form.html.haml @@ -28,16 +28,7 @@ .leftlist= f.label :location, t("activerecord.attributes.user.location") .rightlist= f.text_field :location .both -.leftlist= f.label :avatar, t("layout.users.avatar_with_size", :max => number_to_human_size(User::MAX_AVATAR_SIZE)) -.rightlist= image_tag(avatar_url(@user, :medium)) -.leftlist -.rightlist - .check - %span#niceCheckbox1.niceCheck-main= check_box_tag "delete_avatar", 1, false, :class => 'niceCheckbox1' - .forcheck= t('layout.users.delete_avatar') - .both - = f.file_field :avatar -.both += render 'shared/avatar_form', :subject => @user, :f => f .leftlist= f.label :professional_experience, t("activerecord.attributes.user.professional_experience") .rightlist= f.text_area :professional_experience .both diff --git a/app/views/users/profile/show.html.haml b/app/views/users/profile/show.html.haml index 3ab79e99f..5c16bf2fa 100644 --- a/app/views/users/profile/show.html.haml +++ b/app/views/users/profile/show.html.haml @@ -1,20 +1,4 @@ -.left - = image_tag avatar_url(@user, :big) - %br - = link_to t("layout.users.settings"), current_user == @user ? profile_settings_path : edit_admin_user_path(@user), :class => 'button width81' if can? :edit, @user -.left - %h3= title @user.uname - = @user.name - %br - = mail_to @user.email, @user.email, :encode => "javascript" - %br - %h4= t("activerecord.attributes.user.professional_experience") + ":" - %p= @user.professional_experience - %h4= t("layout.users.public_projects_list") + ":" - %p - - @projects.each do |project| - = link_to project.name, project - %br +-set_meta_tags :title => title_object(@user) -:javascript - $('article .all').addClass('verybigpadding'); +- edit_link = can?(:edit, @user) ? link_to(t("layout.users.settings"), current_user == @user ? profile_settings_path : edit_admin_user_path(@user), :class => 'button width81') : nil += render 'shared/profile', :uname => @user.uname, :name => @user.name, :user => @user, :search_path => user_path, :projects => @projects, :edit_link => edit_link \ No newline at end of file diff --git a/app/views/users/settings/profile.html.haml b/app/views/users/settings/profile.html.haml index bcf053457..d09263464 100644 --- a/app/views/users/settings/profile.html.haml +++ b/app/views/users/settings/profile.html.haml @@ -7,7 +7,7 @@ .notify %p= t('layout.users.public_data_edit_warning') .notify - %p= t('layout.users.avatar_notice') + %p= t('layout.avatars.avatar_notice') :javascript $('article .right').addClass('middlepadding'); diff --git a/config/application.rb b/config/application.rb index 3cfd1ce68..b6e82755f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -2,6 +2,7 @@ require File.expand_path('../boot', __FILE__) require 'rails/all' +require './lib/api_defender' # If you have a Gemfile, require the gems listed there, including any gems # you've limited to :test, :development, or :production. @@ -14,7 +15,9 @@ end module Rosa class Application < Rails::Application - + # Rate limit + config.middleware.insert_after Rack::Lock, ApiDefender + config.action_view.javascript_expansions[:defaults] = %w(jquery rails) config.autoload_paths += %W(#{config.root}/lib) diff --git a/config/environments/production.rb b/config/environments/production.rb index b3ca044d1..2ab9938fd 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -38,6 +38,7 @@ Rosa::Application.configure do # Disable delivery errors, bad email addresses will be ignored # config.action_mailer.raise_delivery_errors = false config.action_mailer.default_url_options = { :host => 'abf.rosalinux.ru' } + config.delivery_method = :sendmail # Enable threaded mode # config.threadsafe! diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index e9b053ba5..c421b2c2e 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -12,7 +12,8 @@ Mime::Type.register "text/plain", 'patch' [["text/x-python", ['py'], '8bit'], ["text/x-rpm-spec", ['spec'], '8bit'], ["text/x-csrc", ['h', 'c'], '8bit'], - ["text/x-c++src", ['cpp'], '8bit'] + ["text/x-c++src", ['cpp'], '8bit'], + ["text/x-diff", ['diff'], '8bit'] ].each do |type| MIME::Types.add MIME::Type.from_array(type) end diff --git a/config/initializers/rack_throttle.rb b/config/initializers/rack_throttle.rb new file mode 100644 index 000000000..dc0572042 --- /dev/null +++ b/config/initializers/rack_throttle.rb @@ -0,0 +1,7 @@ +class Rack::Throttle::Limiter + def http_error(code, message = nil, headers = {}) + [code, {'Content-Type' => 'application/json; charset=utf-8'}.merge(headers), + Array(({'message' => http_status(code) + " | " + message}.to_json))] + end +end + diff --git a/config/locales/en.yml b/config/locales/en.yml index de81c3bb8..ce2f6a1be 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -47,6 +47,8 @@ en: processing: working ... invalid_content_type: incorrect type atom_link_tag_title: Private feed for %{nickname} | %{app_name} + preview: Preview + link: Link settings: label: Settings @@ -114,7 +116,7 @@ en: empty: "Repository is empty. You need to wait some time if you have forked project or imported package" source: Source commits: Commits - commit_diff_too_big: Sorry, commit too big! + commit_diff_too_big: Sorry, diff too big! tags: Tags branches: Branches project_versions: Versions @@ -136,6 +138,9 @@ en: exception_message: Access violation to this page! + 500_message: Error 500. Something went wrong. We've been notified about this issue and we'll take a look at it shortly. + 404_message: Error 404. Resource not found! + collaborators: successfully_changed: Collaborators list successfully changed error_in_changing: Collaborators list changing error @@ -185,3 +190,12 @@ en: private_user: login: Login password: Password + + arch: + name: Name + created_at: Created + updated_at: Updated + + into: into + from: from + by: by diff --git a/config/locales/menu.en.yml b/config/locales/menu.en.yml index 091cacd7e..d940e3c51 100644 --- a/config/locales/menu.en.yml +++ b/config/locales/menu.en.yml @@ -18,6 +18,8 @@ en: tos: Terms Of Service tos_url: http://www.rosalab.com/about support: Support + developer_api: Developer API + developer_api_url: http://abf-doc.rosalinux.ru project_menu: project: Project commits: Commits @@ -26,6 +28,7 @@ en: wiki: Wiki readme: Readme settings: Settings + pull_requests: Pull Requests feed_menu: all: All code: Code diff --git a/config/locales/menu.ru.yml b/config/locales/menu.ru.yml index d2ab87bad..35783e139 100644 --- a/config/locales/menu.ru.yml +++ b/config/locales/menu.ru.yml @@ -18,6 +18,8 @@ ru: tos: Условия использования tos_url: http://www.rosalab.ru/about support: Служба поддержки + developer_api: API для разработчиков + developer_api_url: http://abf-doc.rosalinux.ru project_menu: project: Проект commits: Коммиты @@ -26,6 +28,7 @@ ru: wiki: Wiki readme: Readme settings: Настройки + pull_requests: Пул реквесты feed_menu: all: Все code: Код diff --git a/config/locales/models/activity_feed.en.yml b/config/locales/models/activity_feed.en.yml index 8df4770b3..b00b2e762 100644 --- a/config/locales/models/activity_feed.en.yml +++ b/config/locales/models/activity_feed.en.yml @@ -44,3 +44,8 @@ en: success: completed successfully failed: completed with error "%{error}" pending: build is pending + + footers: + support_team: Support team «ROSA Build System». + notifiers: You can configure notifications in + notification_center: Notification Center. diff --git a/config/locales/models/activity_feed.ru.yml b/config/locales/models/activity_feed.ru.yml index 60911a6c8..9cc3f9640 100644 --- a/config/locales/models/activity_feed.ru.yml +++ b/config/locales/models/activity_feed.ru.yml @@ -45,3 +45,8 @@ ru: success: успешно собрано failed: завершилось с ошибкой "%{error}" pending: ожидает сборки + + footers: + support_team: Команда поддержки «ROSA Build System». + notifiers: Вы можете настроить уведомления в + notification_center: Центре уведомлений. diff --git a/config/locales/models/avatar.en.yml b/config/locales/models/avatar.en.yml new file mode 100644 index 000000000..eda4a76ab --- /dev/null +++ b/config/locales/models/avatar.en.yml @@ -0,0 +1,6 @@ +en: + layout: + avatars: + avatar_notice: Without uploaded avatar will be used avatar from gravar web service. + delete_avatar: Delete avatar + avatar_with_size: Avatar (less than %{max}) \ No newline at end of file diff --git a/config/locales/models/avatar.ru.yml b/config/locales/models/avatar.ru.yml new file mode 100644 index 000000000..4fac6e7a5 --- /dev/null +++ b/config/locales/models/avatar.ru.yml @@ -0,0 +1,6 @@ +ru: + layout: + avatars: + avatar_notice: При отсутствии загруженного аватара будет использован Ваш аватар на сервисе gravatar. + delete_avatar: Удалить аватар + avatar_with_size: Аватар (менее %{max}) \ No newline at end of file diff --git a/config/locales/models/build_list.en.yml b/config/locales/models/build_list.en.yml index f29c7d7cb..962155821 100644 --- a/config/locales/models/build_list.en.yml +++ b/config/locales/models/build_list.en.yml @@ -29,6 +29,7 @@ en: started_at: Build started at duration: Build duration in seconds mass_build_id: Mass build + commit_hash: Commit hash build_list/item: name: Name @@ -60,7 +61,9 @@ en: show: Show cancel: Cancel build cancel_success: 'Build canceled' + publish_success: 'Build published' cancel_fail: 'Errors during build cancelation!' + publish_fail: 'Errors during build publishing!' publish_success: 'Build is queued for publishing' reject_publish_success: 'Publishing rejected' publish_fail: 'Errors during build publishing!' @@ -121,7 +124,7 @@ en: build_log: Build Log not_available: Log not available yet. download: Download log - autoreload: Update log every + autoreload: Update log every load_lines: Load last %{count} lines reload_times: @@ -144,3 +147,5 @@ en: cannot_write: You can't build project to this repository. can_not_published: Build can only be published with status "Build complete" frozen_platform: In case of a repository for package storage with frozen platform allowed only bugfix and security updates + wrong_include_repos: Include repos have to belongs to build for platform + wrong_commit_hash: Unable find commit '%{commit_hash}' in project diff --git a/config/locales/models/build_list.ru.yml b/config/locales/models/build_list.ru.yml index 2f81ba2d0..4953d08bd 100644 --- a/config/locales/models/build_list.ru.yml +++ b/config/locales/models/build_list.ru.yml @@ -28,6 +28,7 @@ ru: preferences: Настройки duration: Длительность билда в секундах mass_build_id: Массовая сборка + commit_hash: Хэш коммита build_list/item: name: Название @@ -120,9 +121,9 @@ ru: build_log: Лог сборки not_available: В настоящий момент лог недоступен. download: Загрузить лог - autoreload: Обновлять лог каждые + autoreload: Обновлять лог каждые load_lines: Загружать последние %{count} строк - + reload_times: 10000: "10 сек" 30000: "30 сек" @@ -143,3 +144,5 @@ ru: cannot_write: Вы не можете собирать пакет в этот репозиторий. can_not_published: Опубликовать сборку можно только со статусом "Собран" frozen_platform: В случае выбора репозитория для сохранения пакетов из замороженнной платформы разрешены только bugfix и security обновления + wrong_include_repos: Включаемые репозитории должны принадлежать платформе для сборки + wrong_commit_hash: Невозможно найти коммит '%{commit_hash}' в проекте diff --git a/config/locales/models/comment.en.yml b/config/locales/models/comment.en.yml index a1bcd69ec..8f3e5a4a6 100644 --- a/config/locales/models/comment.en.yml +++ b/config/locales/models/comment.en.yml @@ -3,11 +3,28 @@ en: comments: confirm_delete: Are you sure you want to delete the comment? new_header: New comment + new_inline: Add a line comment edit_header: Editing a comment - has_commented: "added a note" - notifications_are: "Notifications for new comments are" - comments_header: "Comments" - back: 'Back' + has_commented: added a note + notifications_are: Notifications for new comments are + comments_header: Comments + back: Back + md_cheatsheet_header: Markdown Cheat Sheet + md_cheatsheet: + format_text: Format Text + headers: Headers + text_styles: Text styles + lists: Lists + unordered: Unordered + ordered: Ordered + miscellaneous: Miscellaneous + images: Images + links: Links + blockquotes: Blockquotes + code_examples: Code Examples in Markdown + syntax_highlighting: Syntax highlighting + indent_code: indent your code 4 spaces + inline_code: Inline code for comments flash: comment: diff --git a/config/locales/models/comment.ru.yml b/config/locales/models/comment.ru.yml index ff9e15dd3..f481d904c 100644 --- a/config/locales/models/comment.ru.yml +++ b/config/locales/models/comment.ru.yml @@ -3,18 +3,35 @@ ru: comments: confirm_delete: Вы уверены, что хотите удалить комментарий? new_header: Новый комментарий + new_inline: Добавить комментарий к строке edit_header: Редактирование комментария - has_commented: "оставил комментарий" - notifications_are: "Уведомления о последующих комментариях" - comments_header: "Комментарии" - back: 'Назад' + has_commented: оставил комментарий + notifications_are: Уведомления о последующих комментариях + comments_header: Комментарии + back: Назад + md_cheatsheet_header: Шпаргалка по Markdown + md_cheatsheet: + format_text: Формат текста + headers: Заголовки + text_styles: Стиль текста + lists: Списки + unordered: Маркированный + ordered: Нумерованный + miscellaneous: Разное + images: Изображения + links: Ссылки + blockquotes: Цитирование + code_examples: Примеры кода в Markdown + syntax_highlighting: Подсветка синтаксиса + indent_code: Отступ кода на 4 пробела + inline_code: Встроенный код в строке flash: comment: saved: Комментарий успешно сохранен save_error: Ошибка сохранения комментария destroyed: Комментарий удален - + activerecord: attributes: comment: diff --git a/config/locales/models/group.en.yml b/config/locales/models/group.en.yml index 1cb6a06dc..f8e015de3 100644 --- a/config/locales/models/group.en.yml +++ b/config/locales/models/group.en.yml @@ -16,7 +16,6 @@ en: description: Descripton leave_group: Leave group projects_list: Projects list - public_projects_list: Public projects list public_profile: Public profile delete_warning: Attention! Deleted group can not be restored! diff --git a/config/locales/models/group.ru.yml b/config/locales/models/group.ru.yml index c43e33e2a..01c5aaf7a 100644 --- a/config/locales/models/group.ru.yml +++ b/config/locales/models/group.ru.yml @@ -16,7 +16,6 @@ ru: description: Описание leave_group: Покинуть группу projects_list: Список проектов - public_projects_list: Список публичных проектов public_profile: Публичный профиль delete_warning: Внимание! Удаленная группа восстановлению не подлежит. diff --git a/config/locales/models/platform.en.yml b/config/locales/models/platform.en.yml index 9a828c31c..afb83a510 100644 --- a/config/locales/models/platform.en.yml +++ b/config/locales/models/platform.en.yml @@ -49,6 +49,7 @@ en: flash: platform: + released_status_can_not_be_changed: Released status can't be changed if platform has been released saved: Platform saved created: Platform created save_error: Platform save error diff --git a/config/locales/models/platform.ru.yml b/config/locales/models/platform.ru.yml index a71e49bdc..a379f293b 100644 --- a/config/locales/models/platform.ru.yml +++ b/config/locales/models/platform.ru.yml @@ -49,6 +49,7 @@ ru: flash: platform: + released_status_can_not_be_changed: Released статус платформы не может быть изменен, если платформа уже выпущена saved: Платформа успешно сохранена created: Платформа успешно добавлена save_error: Не удалось сохранить платформу diff --git a/config/locales/models/project.en.yml b/config/locales/models/project.en.yml index a538a25f9..19f76d2ae 100644 --- a/config/locales/models/project.en.yml +++ b/config/locales/models/project.en.yml @@ -2,6 +2,7 @@ en: layout: projects: add: Add + public_projects_list: Public projects list edit: Settings fork_and_edit: Fork fork_to: Fork to %{to} diff --git a/config/locales/models/project.ru.yml b/config/locales/models/project.ru.yml index 321fb777d..0a38bafb1 100644 --- a/config/locales/models/project.ru.yml +++ b/config/locales/models/project.ru.yml @@ -2,6 +2,7 @@ ru: layout: projects: add: Добавить + public_projects_list: Список публичных проектов edit: Настройки fork_and_edit: Клонировать fork_to: Клонировать в %{to} diff --git a/config/locales/models/pull_request.en.yml b/config/locales/models/pull_request.en.yml new file mode 100644 index 000000000..8c1842f18 --- /dev/null +++ b/config/locales/models/pull_request.en.yml @@ -0,0 +1,58 @@ +en: + projects: + pull_requests: + new: + header: 'Create a pull request' + submit: Send pull request + update: Update commits + show: + pull: Pull Request + header: 'Pull Request' + status: + close: Close Pull Request + reopen: Reopen Pull Request + merge: Merge + duplicate: 'There is already a pull request for %{from_ref}' + up_to_date: 'The %{to_ref} branch is already up-to-date with %{from_ref}' + wrong_ref: Wrong branch or tag + blocked: This pull request cannot be automatically merged. + ready: This pull request can be automatically merged. + merged: | + %{user} merged into %{to_ref} + from %{from_ref} at %{time} + closed: '%{user} closed this pull request at %{time}' + is_big: This pull request is big! We're only showing the most recent %{count} commits. + open: '' + statuses: + blocked: Blocked + ready: Ready + merged: Merged + closed: Closed + outdated_diff: Outdated diff + activity: + show_outdated_diff: Show outdated_diff + pull_requests: + tabs: + discussion: Discussion + diff: Diff + commits: Commits + + flash: + pull_request: + new_error: Unable to create pull request + saved: Pull request saved + save_error: Unable to save pull request + + activerecord: + attributes: + pull_requests: + to_ref: Source + from_ref: Receiver + refs: 'branch · tag' + to_project: Into + from_project: From + + layout: + pull_requests: + search: Find pull request... + view_full_changes: View full changes diff --git a/config/locales/models/pull_request.ru.yml b/config/locales/models/pull_request.ru.yml new file mode 100644 index 000000000..65eb34bfd --- /dev/null +++ b/config/locales/models/pull_request.ru.yml @@ -0,0 +1,58 @@ +ru: + projects: + pull_requests: + new: + header: Создать пул реквест + submit: Создать пул реквест + update: Обновить коммиты + show: + pull: Пул реквест + header: Пул реквест + status: + close: Закрыть пул реквест + reopen: Переоткрыть пул реквест + merge: Мерж + duplicate: 'Уже существует пул реквест %{from_ref}' + up_to_date: 'Ветка %{to_ref} на данный момент уже содержит последние изменения %{from_ref}' + wrong_ref: Неправильная ветка или тег + blocked: Невозможно автоматически смержить данный пул реквест. + ready: Данный пул реквест можно смержить автоматически. + merged: | + %{user} смержил %{to_ref} + с %{from_ref} в %{time}' + closed: '%{user} закрыл пул реквест в %{time}' + is_big: Этот пул реквест слишком большой! Мы показываем только последние %{count} коммитов. + open: '' + statuses: + blocked: Заблокирован + ready: Готов к мержу + merged: Смержен + closed: Закрыт + outdated_diff: Устаревшие изменения + activity: + show_outdated_diff: Отобразить устаревшие изменения + pull_requests: + tabs: + discussion: Дискуссия + diff: Изменения + commits: Коммиты + + flash: + pull_request: + new_error: Не удалось создать пул реквест + saved: Пул реквест успешно сохранен + save_error: Не удалось сохранить пул реквест + + activerecord: + attributes: + pull_requests: + to_ref: Приемник + from_ref: Источник + refs: 'ветка · тег' + to_project: Куда + from_project: Откуда + + layout: + pull_requests: + search: Найти пул реквест... + view_full_changes: Посмотреть все изменения diff --git a/config/locales/models/relation.en.yml b/config/locales/models/relation.en.yml index 620780bf0..8a5af138e 100644 --- a/config/locales/models/relation.en.yml +++ b/config/locales/models/relation.en.yml @@ -1,6 +1,7 @@ en: layout: relations: + filters: Filters user_owner: I'm owner group_owner: I'm member of owner group user: I'm collaborator diff --git a/config/locales/models/relation.ru.yml b/config/locales/models/relation.ru.yml index 51202e88e..8160c72b5 100644 --- a/config/locales/models/relation.ru.yml +++ b/config/locales/models/relation.ru.yml @@ -1,6 +1,7 @@ ru: layout: relations: + filters: Фильтры user_owner: Я - владелец group_owner: Я состою в группе-владельце user: Я - участник diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 5d35021ab..927c12501 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -47,6 +47,8 @@ ru: processing: Обрабатывается... invalid_content_type: имеет неверный тип atom_link_tag_title: Приватная лента для %{nickname} | %{app_name} + preview: Предосмотр + link: Ссылка settings: label: 'Настройки' @@ -114,7 +116,7 @@ ru: empty: "Репозиторий пуст. Если вы клонировали(Fork) проект или импортировали пакет, данные скоро появятся" source: Source commits: Коммиты - commit_diff_too_big: Извините, коммит слишком большой! + commit_diff_too_big: Извините, изменений слишком много! tags: Теги branches: Ветки project_versions: Версии @@ -136,6 +138,9 @@ ru: exception_message: У Вас нет доступа к этой странице! + 500_message: Ошибка 500. Что-то пошло не так. Мы уже в курсе данной проблемы и постараемся поскорее ее решить. + 404_message: Ошибка 404. Страница не найдена! + collaborators: successfully_changed: Список коллабораторов успешно изменен error_in_changing: Ошибка изменения списка коллабораторов @@ -186,3 +191,12 @@ ru: private_user: login: Логин password: Пароль + + arch: + name: Название + created_at: Создана + updated_at: Обновлена + + into: в + from: из + by: от diff --git a/config/locales/title.en.yml b/config/locales/title.en.yml index 191ba53f6..01c37f6b7 100644 --- a/config/locales/title.en.yml +++ b/config/locales/title.en.yml @@ -22,7 +22,14 @@ en: title: 'Compare Revisions' searching: title: 'Search in Wiki' + pull_requests: + index: + title: 'Pull Requests' + new: + title: 'Create a Pull Request' + show: + title: 'Pull Request: %{name} by %{user}' platforms: product_build_lists: index: - title: 'Products Monitoring' \ No newline at end of file + title: 'Products Monitoring' diff --git a/config/locales/title.ru.yml b/config/locales/title.ru.yml index b37f512f0..cbb65e5ca 100644 --- a/config/locales/title.ru.yml +++ b/config/locales/title.ru.yml @@ -22,7 +22,14 @@ ru: title: 'Сравнение версий' searching: title: 'Поиск в вики' + pull_requests: + index: + title: 'Пул реквесты' + new: + title: 'Создать пул реквест' + show: + title: 'Пул реквест %{name} от %{user}' platforms: product_build_lists: index: - title: 'Мониторинг продуктов' \ No newline at end of file + title: 'Мониторинг продуктов' diff --git a/config/locales/users.en.yml b/config/locales/users.en.yml index 837632146..4dc842606 100644 --- a/config/locales/users.en.yml +++ b/config/locales/users.en.yml @@ -15,16 +15,12 @@ en: own_projects: My projects part_projects: Participate projects filter_header: Filter - public_projects_list: Public projects list public_data_edit_warning: This data is able for public profile. If you don't want to show some data, just don't fill them. user_private_settings: Settings settings_notifier: Notification center delete_header: Delete aacount delete_warning: Warning! Deleted account can not be recovered. private_settings_header: Password change - avatar_notice: Without uploaded avatar will be used avatar from gravar web service. - delete_avatar: Delete avatar - avatar_with_size: Avatar (less than %{max}) users_filter: all: All admin: Admins diff --git a/config/locales/users.ru.yml b/config/locales/users.ru.yml index 7cdff25f5..f85f57523 100644 --- a/config/locales/users.ru.yml +++ b/config/locales/users.ru.yml @@ -15,16 +15,12 @@ ru: own_projects: Мои проекты part_projects: Участвую в проектах filter_header: Фильтр - public_projects_list: Список публичных проектов public_data_edit_warning: Данные доступны для публичного профиля. Если вы не хотите показывать какие-либо данные, просто не заполняйте их. user_private_settings: Настройки settings_notifier: Центр подписки delete_header: Удалить аккаунт delete_warning: Внимание! Удаленный аккаунт восстановлению не подлежит. private_settings_header: Изменение пароля - avatar_notice: При отсутствии загруженного аватара будет использован Ваш аватар на сервисе gravatar. - delete_avatar: Удалить аватар - avatar_with_size: Аватар (менее %{max}) users_filter: all: Все admin: Админы diff --git a/config/routes.rb b/config/routes.rb index 82c553f74..4e6795e81 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,6 +10,69 @@ Rosa::Application.routes.draw do end devise_for :users, :controllers => {:omniauth_callbacks => 'users/omniauth_callbacks'} + namespace :api do + namespace :v1 do + resources :advisories, :only => [:index, :show, :create, :update] + resources :build_lists, :only => [:index, :create, :show] do + member { + get :publish + get :reject_publish + get :cancel + } + end + resources :arches, :only => [:index] + resources :platforms, :only => [:index, :show, :update, :destroy, :create] do + collection { + get :platforms_for_build + } + member { + get :members + put :add_member + delete :remove_member + post :clone + put :clear + } + end + resources :repositories, :only => [:show, :update, :destroy] do + member { + get :projects + put :add_member + delete :remove_member + put :add_project + delete :remove_project + put :signatures + } + end + resources :projects, :only => [:index, :show, :update, :create, :destroy] do + collection { get :get_id } + member { + post :fork + get :refs_list + get :members + put :add_member + delete :remove_member + put :update_member + } + end + resources :users, :only => [:show] + get 'user' => 'users#show_current_user' + resource :user, :only => [:update] do + member { + get :notifiers + put :notifiers + } + end + resources :groups, :only => [:index, :show, :update, :create, :destroy] do + member { + get :members + put :add_member + delete :remove_member + put :update_member + } + end + end + end + resources :search, :only => [:index] get '/forbidden' => 'pages#forbidden', :as => 'forbidden' @@ -70,7 +133,6 @@ Rosa::Application.routes.draw do end end - get :autocomplete_user_uname, :on => :collection resources :repositories do member do get :add_project @@ -93,6 +155,13 @@ Rosa::Application.routes.draw do match 'product_status', :to => 'product_build_lists#status_build' end + resources :autocompletes, :only => [] do + collection do + get :autocomplete_user_uname + get :autocomplete_group_uname + end + end + scope :module => 'users' do resources :settings, :only => [] do collection do @@ -104,9 +173,7 @@ Rosa::Application.routes.draw do put :notifiers end end - resources :users, :controller => 'profile', :only => [] do - get :autocomplete_user_uname, :on => :collection - end + resources :register_requests, :only => [:new, :create], :format => /ru|en/ #view support only two languages end @@ -114,7 +181,6 @@ Rosa::Application.routes.draw do get '/groups/new' => 'profile#new' # need to force next route exclude :id => 'new' get '/groups/:id' => redirect("/%{id}"), :as => :profile_group # override default group show route resources :groups, :controller => 'profile' do - get :autocomplete_group_uname, :on => :collection delete :remove_user, :on => :member resources :members, :only => [:index] do collection do @@ -185,7 +251,15 @@ Rosa::Application.routes.draw do resources :collaborators do get :find, :on => :collection end + resources :pull_requests, :except => :destroy do + get :autocomplete_to_project, :on => :collection + put :merge, :on => :member + end + post '/preview' => 'projects#preview', :as => 'md_preview' + post 'refs_list' => 'projects#refs_list', :as => 'refs_list' + get '/pull_requests/:issue_id/add_line_comments(.:format)' => "comments#new_line", :as => :new_line_pull_comment end + # Resource get '/autocomplete_maintainers' => 'projects#autocomplete_maintainers', :as => :autocomplete_maintainers get '/modify' => 'projects#edit', :as => :edit_project @@ -209,6 +283,7 @@ Rosa::Application.routes.draw do get '/commit/:commit_id/comments/:id(.:format)' => "comments#edit", :as => :edit_project_commit_comment put '/commit/:commit_id/comments/:id(.:format)' => "comments#update", :as => :project_commit_comment delete '/commit/:commit_id/comments/:id(.:format)' => "comments#destroy" + get '/commit/:commit_id/add_line_comments(.:format)' => "comments#new_line", :as => :new_line_commit_comment # Commit subscribes post '/commit/:commit_id/subscribe' => "commit_subscribes#create", :as => :subscribe_commit delete '/commit/:commit_id/unsubscribe' => "commit_subscribes#destroy", :as => :unsubscribe_commit @@ -222,7 +297,7 @@ Rosa::Application.routes.draw do # Raw get '/raw/:treeish/*path' => "git/blobs#raw", :as => :raw, :format => false # Archive - get '/archive/:treeish.:format' => "git/trees#archive", :as => :archive, :format => /zip|tar/ + get '/archive/:treeish.:format' => "git/trees#archive", :as => :archive, :format => /zip|tar\.gz/ end end end diff --git a/db/migrate/20120412173938_create_pull_requests.rb b/db/migrate/20120412173938_create_pull_requests.rb new file mode 100644 index 000000000..618ccef61 --- /dev/null +++ b/db/migrate/20120412173938_create_pull_requests.rb @@ -0,0 +1,11 @@ +class CreatePullRequests < ActiveRecord::Migration + def change + create_table :pull_requests do |t| + t.integer :issue_id, :null => false + t.integer :base_project_id, :null => false + t.integer :head_project_id, :null => false + t.string :base_ref, :null => false + t.string :head_ref, :null => false + end + end +end diff --git a/db/migrate/20120627101821_add_index_to_pull_request.rb b/db/migrate/20120627101821_add_index_to_pull_request.rb new file mode 100644 index 000000000..4a05dfbd7 --- /dev/null +++ b/db/migrate/20120627101821_add_index_to_pull_request.rb @@ -0,0 +1,7 @@ +class AddIndexToPullRequest < ActiveRecord::Migration + def change + add_index :pull_requests, :base_project_id + add_index :pull_requests, :head_project_id + add_index :pull_requests, :issue_id + end +end diff --git a/db/migrate/20121003081546_rename_base_head_in_pull_requests.rb b/db/migrate/20121003081546_rename_base_head_in_pull_requests.rb new file mode 100644 index 000000000..73cdb4e8f --- /dev/null +++ b/db/migrate/20121003081546_rename_base_head_in_pull_requests.rb @@ -0,0 +1,8 @@ +class RenameBaseHeadInPullRequests < ActiveRecord::Migration + def change + rename_column :pull_requests, :base_project_id, :to_project_id + rename_column :pull_requests, :base_ref, :to_ref + rename_column :pull_requests, :head_project_id, :from_project_id + rename_column :pull_requests, :head_ref, :from_ref + end +end diff --git a/db/migrate/20121003154246_add_avatar_to_groups.rb b/db/migrate/20121003154246_add_avatar_to_groups.rb new file mode 100644 index 000000000..96a835ca8 --- /dev/null +++ b/db/migrate/20121003154246_add_avatar_to_groups.rb @@ -0,0 +1,11 @@ +class AddAvatarToGroups < ActiveRecord::Migration + def change + change_table :groups do |t| + t.has_attached_file :avatar + end + end + + def self.down + drop_attached_file :groups, :avatar + end +end diff --git a/db/migrate/20121005100158_add_data_to_comments.rb b/db/migrate/20121005100158_add_data_to_comments.rb new file mode 100644 index 000000000..c33ed6a8b --- /dev/null +++ b/db/migrate/20121005100158_add_data_to_comments.rb @@ -0,0 +1,5 @@ +class AddDataToComments < ActiveRecord::Migration + def change + add_column :comments, :data, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 420636893..bd9ee3cdb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,14 +11,14 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20120914160741) do +ActiveRecord::Schema.define(:version => 20121005100158) do create_table "activity_feeds", :force => true do |t| t.integer "user_id", :null => false t.string "kind" t.text "data" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "advisories", :force => true do |t| @@ -53,8 +53,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do create_table "arches", :force => true do |t| t.string "name", :null => false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end add_index "arches", ["name"], :name => "index_arches_on_name", :unique => true @@ -63,8 +63,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.integer "user_id" t.string "provider" t.string "uid" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end add_index "authentications", ["provider", "uid"], :name => "index_authentications_on_provider_and_uid", :unique => true @@ -75,8 +75,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.integer "level" t.integer "status" t.integer "build_list_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "version" end @@ -110,8 +110,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.integer "project_id" t.integer "arch_id" t.datetime "notified_at" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.boolean "is_circle", :default => false t.text "additional_repos" t.string "name" @@ -142,10 +142,11 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.string "commentable_type" t.integer "user_id" t.text "body" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.decimal "commentable_id", :precision => 50, :scale => 0 t.integer "project_id" + t.text "data" end create_table "event_logs", :force => true do |t| @@ -160,8 +161,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.string "controller" t.string "action" t.text "message" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "flash_notifies", :force => true do |t| @@ -175,11 +176,15 @@ ActiveRecord::Schema.define(:version => 20120914160741) do create_table "groups", :force => true do |t| t.integer "owner_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "uname" - t.integer "own_projects_count", :default => 0, :null => false + t.integer "own_projects_count", :default => 0, :null => false t.text "description" + t.string "avatar_file_name" + t.string "avatar_content_type" + t.integer "avatar_file_size" + t.datetime "avatar_updated_at" end create_table "issues", :force => true do |t| @@ -189,8 +194,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.string "title" t.text "body" t.string "status", :default => "open" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "user_id" t.datetime "closed_at" t.integer "closed_by" @@ -250,14 +255,14 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.string "description" t.string "name", :null => false t.integer "parent_platform_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.boolean "released", :default => false, :null => false t.integer "owner_id" t.string "owner_type" t.string "visibility", :default => "open", :null => false t.string "platform_type", :default => "main", :null => false - t.string "distrib_type", :null => false + t.string "distrib_type" end add_index "platforms", ["name"], :name => "index_platforms_on_name", :unique => true, :case_sensitive => false @@ -266,16 +271,16 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.integer "platform_id" t.string "login" t.string "password" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "user_id" end create_table "product_build_lists", :force => true do |t| t.integer "product_id" t.integer "status", :default => 2, :null => false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end add_index "product_build_lists", ["product_id"], :name => "index_product_build_lists_on_product_id" @@ -283,8 +288,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do create_table "products", :force => true do |t| t.string "name", :null => false t.integer "platform_id", :null => false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.text "build_script" t.text "counter" t.text "ks" @@ -303,8 +308,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.string "name" t.string "version" t.datetime "file_mtime" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "platform_id" end @@ -313,25 +318,25 @@ ActiveRecord::Schema.define(:version => 20120914160741) do create_table "project_to_repositories", :force => true do |t| t.integer "project_id" t.integer "repository_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "projects", :force => true do |t| t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "owner_id" t.string "owner_type" t.string "visibility", :default => "open" t.text "description" t.string "ancestry" t.boolean "has_issues", :default => true + t.boolean "has_wiki", :default => false t.string "srpm_file_name" + t.string "srpm_content_type" t.integer "srpm_file_size" t.datetime "srpm_updated_at" - t.string "srpm_content_type" - t.boolean "has_wiki", :default => false t.string "default_branch", :default => "master" t.boolean "is_package", :default => true, :null => false t.integer "average_build_time", :default => 0, :null => false @@ -339,7 +344,19 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.integer "maintainer_id" end - add_index "projects", ["owner_id"], :name => "index_projects_on_name_and_owner_id_and_owner_type", :unique => true, :case_sensitive => false + add_index "projects", ["owner_id", "name", "owner_type"], :name => "index_projects_on_name_and_owner_id_and_owner_type", :unique => true, :case_sensitive => false + + create_table "pull_requests", :force => true do |t| + t.integer "issue_id", :null => false + t.integer "to_project_id", :null => false + t.integer "from_project_id", :null => false + t.string "to_ref", :null => false + t.string "from_ref", :null => false + end + + add_index "pull_requests", ["from_project_id"], :name => "index_pull_requests_on_head_project_id" + add_index "pull_requests", ["issue_id"], :name => "index_pull_requests_on_issue_id" + add_index "pull_requests", ["to_project_id"], :name => "index_pull_requests_on_base_project_id" create_table "register_requests", :force => true do |t| t.string "name" @@ -347,8 +364,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.string "token" t.boolean "approved", :default => false t.boolean "rejected", :default => false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "interest" t.text "more" t.string "language" @@ -362,16 +379,16 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.string "actor_type" t.integer "target_id" t.string "target_type" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "role" end create_table "repositories", :force => true do |t| t.string "description", :null => false t.integer "platform_id", :null => false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "name", :null => false t.boolean "publish_without_qa", :default => true end @@ -383,8 +400,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.boolean "new_comment_reply", :default => true t.boolean "new_issue", :default => true t.boolean "issue_assign", :default => true - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.boolean "new_comment_commit_owner", :default => true t.boolean "new_comment_commit_repo_owner", :default => true t.boolean "new_comment_commit_commentor", :default => true @@ -395,8 +412,8 @@ ActiveRecord::Schema.define(:version => 20120914160741) do create_table "subscribes", :force => true do |t| t.string "subscribeable_type" t.integer "user_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.boolean "status", :default => true t.integer "project_id" t.decimal "subscribeable_id", :precision => 50, :scale => 0 @@ -404,18 +421,21 @@ ActiveRecord::Schema.define(:version => 20120914160741) do create_table "users", :force => true do |t| t.string "name" - t.string "email", :default => "", :null => false - t.string "encrypted_password", :limit => 128, :default => "", :null => false + t.string "email", :default => "", :null => false + t.string "encrypted_password", :default => "", :null => false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.text "ssh_key" t.string "uname" t.string "role" - t.string "language", :default => "en" - t.integer "own_projects_count", :default => 0, :null => false + t.string "language", :default => "en" + t.integer "own_projects_count", :default => 0, :null => false + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" t.text "professional_experience" t.string "site" t.string "company" @@ -424,14 +444,11 @@ ActiveRecord::Schema.define(:version => 20120914160741) do t.string "avatar_content_type" t.integer "avatar_file_size" t.datetime "avatar_updated_at" - t.integer "failed_attempts", :default => 0 + t.integer "failed_attempts", :default => 0 t.string "unlock_token" t.datetime "locked_at" - t.string "confirmation_token" - t.datetime "confirmed_at" - t.datetime "confirmation_sent_at" t.string "authentication_token" - t.integer "build_priority", :default => 50 + t.integer "build_priority", :default => 50 end add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token" diff --git a/lib/api_defender.rb b/lib/api_defender.rb new file mode 100644 index 000000000..210295d8c --- /dev/null +++ b/lib/api_defender.rb @@ -0,0 +1,53 @@ +require 'rack/throttle' +require 'redis' + +# Limit hourly API usage +# http://martinciu.com/2011/08/how-to-add-api-throttle-to-your-rails-app.html +class ApiDefender < Rack::Throttle::Hourly + + def initialize(app) + options = { + :cache => Redis.new(:thread_safe => true), + :key_prefix => :throttle, + + # only 500 request per hour + :max => 500 + } + @app, @options = app, options + end + + # this method checks if request needs throttling. + # If so, it increases usage counter and compare it with maximum + # allowed API calls. Returns true if a request can be handled. + def allowed?(request) + need_defense?(request) ? cache_incr(request) <= max_per_window : true + end + + def call(env) + status, heders, body = super + request = Rack::Request.new(env) + # just to be nice for our clients we inform them how many + # requests remaining does they have + if need_defense?(request) + heders['X-RateLimit-Limit'] = max_per_window.to_s + heders['X-RateLimit-Remaining'] = ([0, max_per_window - (cache_get(cache_key(request)).to_i rescue 1)].max).to_s + end + [status, heders, body] + end + + # key increase and key expiration + def cache_incr(request) + key = cache_key(request) + count = cache.incr(key) + cache.expire(key, 1.day) if count == 1 + count + end + + protected + + # only API calls should be throttled + def need_defense?(request) + request.env['PATH_INFO'] =~ /^\/api\/v1\// + end + +end \ No newline at end of file diff --git a/lib/ext/core/string.rb b/lib/ext/core/string.rb index 9ff9bde77..608acc628 100644 --- a/lib/ext/core/string.rb +++ b/lib/ext/core/string.rb @@ -21,4 +21,18 @@ class String # ensure # return self end + + # same as reverse.truncate.reverse + def rtruncate(length, options = {}) + text = self.dup + options[:omission] ||= "..." + options[:separator] ||= '/' + + length_with_room_for_omission = length - options[:omission].mb_chars.length + chars = text.mb_chars + stop = options[:separator] ? + (chars.index(options[:separator].mb_chars, length_with_room_for_omission) || length_with_room_for_omission) : length_with_room_for_omission + + (chars.length > length ? "#{options[:omission]}#{chars[-(stop+1)...-1]}" : text).to_s + end end diff --git a/lib/ext/git/grit.rb b/lib/ext/git/grit.rb index 3ec533062..3ea90a1b5 100644 --- a/lib/ext/git/grit.rb +++ b/lib/ext/git/grit.rb @@ -32,16 +32,16 @@ module Grit # def file_mime_type # @file_mime_type ||= data.file_type(:mime_type) # end - # + # # def text? # file_mime_type =~ /^text\// # not binary? # end - # + # # def binary? # not text? # file_mime_type !~ /^text\// # # s = data.split(//); ((s.size - s.grep(" ".."~").size) / s.size.to_f) > 0.30 # works only for latin chars # end - # + # # def image? # mime_type.match(/image/) # end diff --git a/lib/ext/git/inline_callback.rb b/lib/ext/git/inline_callback.rb deleted file mode 100644 index e57be6d65..000000000 --- a/lib/ext/git/inline_callback.rb +++ /dev/null @@ -1,86 +0,0 @@ -# -*- encoding : utf-8 -*- -module Git - module Diff - class InlineCallback < ::Diff::Renderer::Base - def before_headerblock(block) - end - - def after_headerblock(block) - end - - def headerline(line) - " - ... - ... - #{line} - " - end - - def addline(line) - " - - #{line.new_number} -
#{render_line(line)}
- " - end - - def remline(line) - " - #{line.old_number} - -
#{render_line(line)}
- " - end - - def modline(line) - " - #{line.old_number} - #{line.new_number} -
#{render_line(line)}
- " - end - - def unmodline(line) - " - #{line.old_number} - #{line.new_number} -
#{render_line(line)}
- " - end - - def sepline(line) - " - … - … - - " - end - - def nonewlineline(line) - " - #{line.old_number} - #{line.new_number} -
#{render_line(line)}
- " - end - - protected - def escape(str) - str.to_s.gsub('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"') - end - - def render_line(line) - res = '' - if line.inline_changes? - prefix, changed, postfix = line.segments.map{|segment| escape(segment) } - res += "#{prefix}#{changed}#{postfix}" - else - res += escape(line) - end - res += '' - - res - end - end - end -end diff --git a/lib/modules/models/git.rb b/lib/modules/models/git.rb index 4cf2baafc..299f15a8a 100644 --- a/lib/modules/models/git.rb +++ b/lib/modules/models/git.rb @@ -116,7 +116,7 @@ module Modules end def fork_git_repo - dummy = Grit::Repo.new(path) rescue parent.repo.fork_bare(path) + dummy = Grit::Repo.new(path) rescue parent.repo.fork_bare(path, :shared => false) write_hook end diff --git a/lib/tasks/add_branch.rake b/lib/tasks/add_branch.rake index 2e65145d4..47c0bf9ed 100644 --- a/lib/tasks/add_branch.rake +++ b/lib/tasks/add_branch.rake @@ -40,7 +40,7 @@ namespace :add_branch do open(source).readlines.each do |name| name.chomp!; name.strip! print "Fork branch for '#{name}'... " - if p = Project.find_by_name_and_owner_type_and_owner_id(name, owner.class.to_s, owner.id) + if p = Project.find_by_owner_and_name(owner.uname, name) # Rake::Task['add_branch:fork_branch'].execute(:path => p.path, :src_branch => src_branch, :dst_branch => dst_branch) system "bundle exec rake add_branch:fork_branch[#{p.path},#{src_branch},#{dst_branch}] -s RAILS_ENV=#{Rails.env} > /dev/null 2>&1" print 'Ok!' diff --git a/lib/tasks/git_detach_from_parent.rake b/lib/tasks/git_detach_from_parent.rake new file mode 100644 index 000000000..737208c87 --- /dev/null +++ b/lib/tasks/git_detach_from_parent.rake @@ -0,0 +1,13 @@ +namespace :project do + desc 'Break the dependency of a repository cloned with --shared on its source repository' + task :git_detach_from_parent => :environment do + projects = Project.where('ancestry IS NOT NULL') + say "Total count of the forked projects is #{projects.count}" + projects.each_with_index do |project, ind| + Dir.chdir(project.path) do + say "--Start work with #{project.name_with_owner} (#{ind+1}/#{projects.count})--" + say (system('git', 'repack', '-a') ? 'Ok!' : 'Something wrong!') + end + end + end +end diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index 15a979b57..d485d14ad 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -48,21 +48,21 @@ namespace :import do repo.projects << project else # check if project already added if project = repo.projects.find_by_name(name) || repo.projects.by_name(name).first # fallback to speedup - print "Found project '#{project.owner.uname}/#{project.name}' in '#{platform.name}/#{repo.name}'." + print "Found project '#{project.name_with_owner}' in '#{platform.name}/#{repo.name}'." elsif scoped = Project.where(:owner_id => owner.id, :owner_type => owner.class) and project = scoped.find_by_name(name) || scoped.by_name(name).first begin repo.projects << project rescue Exception => e - print "Add project '#{project.owner.uname}/#{project.name}' to '#{platform.name}/#{repo.name}' FAILED: #{e.message}." + print "Add project '#{project.name_with_owner}' to '#{platform.name}/#{repo.name}' FAILED: #{e.message}." else - print "Add project '#{project.owner.uname}/#{project.name}' to '#{platform.name}/#{repo.name}' OK." + print "Add project '#{project.name_with_owner}' to '#{platform.name}/#{repo.name}' OK." end else description = ::Iconv.conv('UTF-8//IGNORE', 'UTF-8', `rpm -q --qf '[%{Description}]' -p #{srpm_file}`) project = Project.create!(:name => name, :description => description) {|p| p.owner = owner} repo.projects << project - print "Create project #{project.owner.uname}/#{project.name} in #{platform.name}/#{repo.name} OK." + print "Create project #{project.name_with_owner} in #{platform.name}/#{repo.name} OK." end end project.import_srpm(srpm_file, platform.name) @@ -124,24 +124,24 @@ namespace :import do unless project = project_import.project if platform.personal? # search project through owner # used for testhat project = Project.find_or_create_by_name_and_owner_type_and_owner_id(name, owner.class.to_s, owner.id) - print "Use project #{project.owner.uname}/#{project.name}. " + print "Use project #{project.name_with_owner}. " else # search project through repository if project = repository.projects.find_by_name(name) || repository.projects.by_name(name).first # fallback to speedup - print "Found project #{project.owner.uname}/#{project.name} in #{platform.name}/#{repository.name}. " + print "Found project #{project.name_with_owner} in #{platform.name}/#{repository.name}. " elsif scoped = Project.where(:owner_id => owner.id, :owner_type => owner.class) and project = scoped.find_by_name(name) || scoped.by_name(name).first repository.projects << project - print "Add project #{project.owner.uname}/#{project.name} to #{platform.name}/#{repository.name}. " + print "Add project #{project.name_with_owner} to #{platform.name}/#{repository.name}. " else description = ::Iconv.conv('UTF-8//IGNORE', 'UTF-8', `rpm -q --qf '[%{Description}]' -p #{srpm_file}`) project = Project.create!(:name => name, :description => description) {|p| p.owner = owner} repository.projects << project - print "Create project #{project.owner.uname}/#{project.name} at #{platform.name}/#{repository.name}. " + print "Create project #{project.name_with_owner} at #{platform.name}/#{repository.name}. " end end end project.import_srpm(srpm_file, branch) - print "New version (#{version}) for #{project.owner.uname}/#{project.name} successfully imported to branch #{branch}! " + print "New version (#{version}) for #{project.name_with_owner} successfully imported to branch #{branch}! " project_import.project = project # project_import.platform = platform diff --git a/public/googleac324ed2f70faefc.html b/public/googleac324ed2f70faefc.html new file mode 100644 index 000000000..95843ef02 --- /dev/null +++ b/public/googleac324ed2f70faefc.html @@ -0,0 +1 @@ +google-site-verification: googleac324ed2f70faefc.html \ No newline at end of file diff --git a/spec/controllers/api/v1/advisories_controller_spec.rb b/spec/controllers/api/v1/advisories_controller_spec.rb new file mode 100644 index 000000000..bb7114c7f --- /dev/null +++ b/spec/controllers/api/v1/advisories_controller_spec.rb @@ -0,0 +1,144 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +shared_examples_for 'api advisories user with show rights' do + it 'should be able to perform show action' do + get :show, :id => @advisory.advisory_id, :format => :json + response.should be_success + end + + it 'should be able to perform index action' do + get :index, :format => :json + response.should be_success + end +end + +shared_examples_for 'api advisories user with admin rights' do + context 'api advisories user with create rights' do + let(:params) { {:build_list_id => @build_list.id, :advisory => {:description => 'test'}} } + it 'should be able to perform create action' do + post :create, params, :format => :json + response.should be_success + end + it 'ensures that advisory has been created' do + lambda { post :create, params, :format => :json }.should change{ Advisory.count }.by(1) + end + it 'ensures that build_list has been associated with advisory' do + post :create, params, :format => :json + @build_list.reload + @build_list.advisory.should_not be_nil + end + end + + context 'api advisories user with update rights' do + let(:params) { {:id => @advisory.advisory_id, :build_list_id => @build_list.id} } + it 'should be able to perform update action' do + put :update, params, :format => :json + response.should be_success + end + it 'ensures that advisory has not been created' do + lambda { put :update, params, :format => :json }.should_not change{ Advisory.count } + end + it 'ensures that build_list has been associated with advisory' do + put :update, params, :format => :json + @build_list.reload + @build_list.advisory.should_not be_nil + end + end +end + +shared_examples_for 'api advisories user without admin rights' do + context 'api advisories user without create rights' do + let(:params) { {:build_list_id => @build_list.id, :advisory => {:description => 'test'}} } + it 'should not be able to perform create action' do + post :create, params, :format => :json + response.should_not be_success + end + it 'ensures that advisory has not been created' do + lambda { post :create, params, :format => :json }.should_not change{ Advisory.count } + end + it 'ensures that build_list has not been associated with advisory' do + post :create, params, :format => :json + @build_list.reload + @build_list.advisory.should be_nil + end + end + + context 'api advisories user without update rights' do + let(:params) { {:id => @advisory.advisory_id, :build_list_id => @build_list.id} } + it 'should not be able to perform update action' do + put :update, params, :format => :json + response.should_not be_success + end + it 'ensures that advisory has not been created' do + lambda { put :update, params, :format => :json }.should_not change{ Advisory.count } + end + it 'ensures that build_list has not been associated with advisory' do + put :update, params, :format => :json + @build_list.reload + @build_list.advisory.should be_nil + end + end +end + +describe Api::V1::AdvisoriesController do + + before do + stub_symlink_methods + + @advisory = FactoryGirl.create(:advisory) + @build_list = FactoryGirl.create(:build_list_core) + @build_list.save_to_platform.update_column(:released, true) + @build_list.save_to_repository.update_column(:publish_without_qa, false) + @build_list.update_column(:status, BuildList::BUILD_PUBLISHED) + end + + context 'for guest' do + + if APP_CONFIG['anonymous_access'] + it_should_behave_like 'api advisories user with show rights' + end + + it 'should not be able to perform show action', :anonymous_access => false do + get :show, :id => @advisory.advisory_id, :format => :json + response.should_not be_success + end + + it 'should not be able to perform index action', :anonymous_access => false do + get :index, :format => :json + response.should_not be_success + end + it_should_behave_like 'api advisories user without admin rights' + end + + context 'for simple user' do + before do + @user = FactoryGirl.create(:user) + http_login(@user) + end + it_should_behave_like 'api advisories user with show rights' + it_should_behave_like 'api advisories user without admin rights' + end + + context 'for admin' do + before do + @admin = FactoryGirl.create(:admin) + http_login(@admin) + end + + it_should_behave_like 'api advisories user with show rights' + it_should_behave_like 'api advisories user with admin rights' + end + + context 'for user who has access to update build_list' do + before do + @user = FactoryGirl.create(:user) + @build_list.save_to_platform.relations.create(:role => 'admin', :actor => @user) + http_login(@user) + end + + it_should_behave_like 'api advisories user with show rights' + it_should_behave_like 'api advisories user with admin rights' + end + +end diff --git a/spec/controllers/api/v1/arches_controller_spec.rb b/spec/controllers/api/v1/arches_controller_spec.rb new file mode 100644 index 000000000..7adfe4c5d --- /dev/null +++ b/spec/controllers/api/v1/arches_controller_spec.rb @@ -0,0 +1,31 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +describe Api::V1::ArchesController do + + before { FactoryGirl.create(:arch) } + + context 'for guest' do + it "should be able to perform index action", :anonymous_access => true do + get :index, :format => :json + should render_template(:index) + end + + it 'should be able to perform get_id action', :anonymous_access => false do + get :index, :format => :json + response.status.should == 401 + end + end + + context 'for simple user' do + before do + stub_symlink_methods + http_login(FactoryGirl.create(:user)) + end + + it "should be able to perform index action" do + get :index, :format => :json + should render_template(:index) + end + end +end diff --git a/spec/controllers/api/v1/build_lists_controller_spec.rb b/spec/controllers/api/v1/build_lists_controller_spec.rb new file mode 100644 index 000000000..37466c139 --- /dev/null +++ b/spec/controllers/api/v1/build_lists_controller_spec.rb @@ -0,0 +1,599 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +shared_examples_for 'show build list via api' do + it 'should be able to perform show action' do + get :show, @show_params + response.should render_template("api/v1/build_lists/show") + end + + it 'should be able to perform index action' do + get :index, :format => :json + response.should render_template("api/v1/build_lists/index") + end +end + +shared_examples_for 'not show build list via api' do + it 'should not be able to perform show action' do + get :show, @show_params + response.body.should == {"message" => "Access violation to this page!"}.to_json + end + + pending 'should not be able to perform index action' do + get :index, :format => :json + response.body.should == {"message" => "Access violation to this page!"}.to_json + end +end + +shared_examples_for 'create build list via api' do + before { + #@project.update_attributes({:repositories => @platform.repositories}) + #test_git_commit(@project) + } + + it 'should create one more build list' do + lambda { post :create, @create_params }.should change{ BuildList.count }.by(1) + end + + it 'should save correct commit_hash for branch based build' do + post :create, @create_params + #@project.build_lists.last.commit_hash.should == @project.repo.commits('master').last.id + @project.build_lists.last.commit_hash.should == @params[:commit_hash] + end + + it 'should save correct commit_hash for tag based build' do + system("cd #{@project.repo.path} && git tag 4.7.5.3") # TODO REDO through grit + post :create, @create_params + #@project.build_lists.last.commit_hash.should == @project.repo.commits('4.7.5.3').last.id + @project.build_lists.last.commit_hash.should == @params[:commit_hash] + end + + it 'should not create without existing commit hash in project' do + lambda{ post :create, @create_params.deep_merge(:build_list => {:commit_hash => 'wrong'})}.should change{@project.build_lists.count}.by(0) + end +end + +shared_examples_for 'not create build list via api' do + before { + #@project.update_attributes({:repositories => @platform.repositories}) + #test_git_commit(@project) + } + + it 'should not be able to perform create action' do + post :create, @create_params + response.body.should == {"message" => "Access violation to this page!"}.to_json + end + + it 'should not create one more build list' do + lambda { post :create, @create_params }.should change{ BuildList.count }.by(0) + end +end + +describe Api::V1::BuildListsController do + before(:each) do + stub_symlink_methods + end + + context 'create and update abilities' do + context 'for user' do + before(:each) do + Arch.destroy_all + User.destroy_all + + @build_list = FactoryGirl.create(:build_list_core) + @params = @build_list.attributes.symbolize_keys + @project = @build_list.project + @platform = @build_list.save_to_platform + #@platform = FactoryGirl.create(:platform_with_repos) + + stub_symlink_methods + @user = FactoryGirl.create(:user) + @owner_user = @project.owner + @member_user = FactoryGirl.create(:user) + @project.relations.create(:role => 'reader', :actor => @member_user) + @build_list.save_to_platform.relations.create(:role => 'admin', :actor => @owner_user) # Why it's really need it?? + + # Create and show params: + @create_params = {:build_list => @build_list.attributes.symbolize_keys.except(:bs_id)} + @create_params = @create_params.merge(:arches => [@params[:arch_id]], :build_for_platforms => [@params[:build_for_platform_id]], :format => :json) + any_instance_of(Project, :versions => ['v1.0', 'v2.0']) + + http_login(@user) + end + + context "do cancel" do + def do_cancel + get :cancel, :id => @build_list, :format => :json + end + + context 'if user is project owner' do + before(:each) {http_login(@owner_user)} + + context "if it has :build_pending status" do + it "should return correct json message" do + @build_list.update_column(:status, BuildList::BUILD_PENDING) + do_cancel + response.body.should == {:is_canceled => true, :url => api_v1_build_list_path(@build_list, :format => :json), :message => I18n.t('layout.build_lists.cancel_success')}.to_json + end + + it "should cancel build list" do + @build_list.update_column(:status, BuildList::BUILD_PENDING) + do_cancel + @build_list.reload.status.should == BuildList::BUILD_CANCELED + end + end + + context "if it has another status" do + it "should return correct json error message" do + @build_list.update_column(:status, BuildServer::PROJECT_NOT_FOUND) + do_cancel + response.body.should == {:is_canceled => false, :url => api_v1_build_list_path(@build_list, :format => :json), :message => I18n.t('layout.build_lists.cancel_fail')}.to_json + end + + it "should not cancel build list" do + @build_list.update_column(:status, BuildServer::PROJECT_NOT_FOUND) + do_cancel + @build_list.reload.status.should == BuildServer::PROJECT_NOT_FOUND + end + end + end + + context 'if user is not project owner' do + before(:each) do + @build_list.update_column(:status, BuildList::BUILD_PENDING) + do_cancel + end + + it "should return access violation message" do + response.body.should == {"message" => "Access violation to this page!"}.to_json + end + + it "should not cancel build list" do + @build_list.reload.status.should == BuildList::BUILD_PENDING + end + end + end + + context "do publish" do + def do_publish + get :publish, :id => @build_list, :format => :json + end + + context 'if user is project owner' do + before(:each) do + http_login(@owner_user) + @build_list.update_column(:status, BuildList::FAILED_PUBLISH) + do_publish + end + + context "if it has :failed_publish status" do + it "should return correct json message" do + response.body.should == {:is_published => true, :url => api_v1_build_list_path(@build_list, :format => :json), :message => I18n.t('layout.build_lists.publish_success')}.to_json + end + + it "should cancel build list" do + @build_list.reload.status.should == BuildList::BUILD_PUBLISH + end + end + + context "if it has another status" do + before(:each) do + @build_list.update_column(:status, BuildServer::PROJECT_NOT_FOUND) + do_publish + end + + it "should return correct json error message" do + response.body.should == {:is_published => false, :url => api_v1_build_list_path(@build_list, :format => :json), :message => I18n.t('layout.build_lists.publish_fail')}.to_json + end + + it "should not cancel build list" do + @build_list.reload.status.should == BuildServer::PROJECT_NOT_FOUND + end + end + end + + context 'if user is not project owner' do + before(:each) do + @build_list.update_column(:status, BuildList::FAILED_PUBLISH) + do_publish + end + + it "should return access violation message" do + response.body.should == {"message" => "Access violation to this page!"}.to_json + end + + it "should not cancel build list" do + @build_list.reload.status.should == BuildList::FAILED_PUBLISH + end + end + end + + context "do reject_publish" do + before(:each) do + any_instance_of(BuildList, :current_duration => 100) + @build_list.save_to_repository.update_column(:publish_without_qa, false) + end + + def do_reject_publish + get :reject_publish, :id => @build_list, :format => :json + end + + context 'if user is project owner' do + before(:each) do + http_login(@owner_user) + @build_list.update_column(:status, BuildServer::SUCCESS) + @build_list.save_to_platform.update_column(:released, true) + do_reject_publish + end + + context "if it has :success status" do + it "should return correct json message" do + response.body.should == {:is_reject_published => true, :url => api_v1_build_list_path(@build_list, :format => :json), :message => I18n.t('layout.build_lists.reject_publish_success')}.to_json + end + + it "should reject publish build list" do + @build_list.reload.status.should == BuildList::REJECTED_PUBLISH + end + end + + context "if it has another status" do + before(:each) do + @build_list.update_column(:status, BuildServer::PROJECT_NOT_FOUND) + do_reject_publish + end + + it "should return correct json error message" do + response.body.should == {:is_reject_published => false, :url => api_v1_build_list_path(@build_list, :format => :json), :message => I18n.t('layout.build_lists.reject_publish_fail')}.to_json + end + + it "should not cancel build list" do + @build_list.reload.status.should == BuildServer::PROJECT_NOT_FOUND + end + end + end + + context 'if user is not project owner' do + before(:each) do + @build_list.update_column(:status, BuildServer::SUCCESS) + @build_list.save_to_platform.update_column(:released, true) + do_reject_publish + end + + it "should return access violation message" do + response.body.should == {"message" => "Access violation to this page!"}.to_json + end + + it "should not cancel build list" do + do_reject_publish + @build_list.reload.status.should == BuildServer::SUCCESS + end + end + end + + context 'for open project' do + it_should_behave_like 'not create build list via api' + + context 'if user is project owner' do + before(:each) {http_login(@owner_user)} + it_should_behave_like 'create build list via api' + end + + context 'if user is project read member' do + before(:each) {http_login(@member_user)} + end + end + + context 'for hidden project' do + before(:each) do + @project.update_column(:visibility, 'hidden') + end + + it_should_behave_like 'not create build list via api' + + context 'if user is project owner' do + before(:each) {http_login(@owner_user)} + + it_should_behave_like 'create build list via api' + end + + context 'if user is project read member' do + before(:each) {http_login(@member_user)} + it_should_behave_like 'not create build list via api' + end + end + end + + context 'for group' do + before(:each) do + Arch.destroy_all + User.destroy_all + + @build_list = FactoryGirl.create(:build_list_core) + @params = @build_list.attributes.symbolize_keys + @project = @build_list.project + @platform = @build_list.save_to_platform + + stub_symlink_methods + @user = FactoryGirl.create(:user) + @owner_user = FactoryGirl.create(:user) + @member_user = FactoryGirl.create(:user) + + # Create and show params: + @create_params = {:build_list => @build_list.attributes.symbolize_keys.except(:bs_id)} + @create_params = @create_params.merge(:arches => [@params[:arch_id]], :build_for_platforms => [@params[:build_for_platform_id]], :format => :json) + any_instance_of(Project, :versions => ['v1.0', 'v2.0']) + + # Groups: + @owner_group = FactoryGirl.create(:group, :owner => @owner_user) + @member_group = FactoryGirl.create(:group) + @member_group.actors.create :role => 'reader', :actor_id => @member_user.id, :actor_type => 'User' + + @group = FactoryGirl.create(:group) + @user = FactoryGirl.create(:user) + @group.actors.create :role => 'reader', :actor_id => @user.id, :actor_type => 'User' + + @project.owner = @owner_group + @project.save + + @project.relations.create :role => 'reader', :actor_id => @member_group.id, :actor_type => 'Group' + @project.relations.create :role => 'admin', :actor_id => @owner_group.id, :actor_type => 'Group' + @build_list.save_to_platform.relations.create(:role => 'admin', :actor => @owner_group) # Why it's really need it?? + @build_list.save_to_platform.relations.create(:role => 'reader', :actor => @member_group) # Why it's really need it?? + + http_login(@user) + end + + context 'for open project' do + it_should_behave_like 'not create build list via api' + + context 'if user is group owner' do + before(:each) {http_login(@owner_user)} + it_should_behave_like 'create build list via api' + end + + context 'if user is group read member' do + before(:each) {http_login(@member_user)} + it_should_behave_like 'not create build list via api' + end + end + + context 'for hidden project' do + before(:each) do + @build_list.project.update_column(:visibility, 'hidden') + end + + it_should_behave_like 'not create build list via api' + + context 'if user is group owner' do + before(:each) {http_login(@owner_user)} + it_should_behave_like 'create build list via api' + end + + context 'if user is group read member' do + before(:each) {http_login(@member_user)} + it_should_behave_like 'not create build list via api' + end + end + + end + end + + context 'read and accessible abilities' do + before(:each) do + Arch.destroy_all + User.destroy_all + + @user = FactoryGirl.create(:user) + + # Build Lists: + @build_list1 = FactoryGirl.create(:build_list_core) + + @build_list2 = FactoryGirl.create(:build_list_core) + @build_list2.project.update_column(:visibility, 'hidden') + + project = FactoryGirl.create(:project, :visibility => 'hidden', :owner => @user) + @build_list3 = FactoryGirl.create(:build_list_core, :project => project) + + @build_list4 = FactoryGirl.create(:build_list_core) + @build_list4.project.update_column(:visibility, 'hidden') + @build_list4.project.relations.create! :role => 'reader', :actor_id => @user.id, :actor_type => 'User' + + @filter_build_list1 = FactoryGirl.create(:build_list_core) + @filter_build_list2 = FactoryGirl.create(:build_list_core) + @filter_build_list3 = FactoryGirl.create(:build_list_core) + @filter_build_list4 = FactoryGirl.create(:build_list_core, :updated_at => (Time.now - 1.day), + :project => @build_list3.project, :save_to_platform => @build_list3.save_to_platform, + :arch => @build_list3.arch) + end + + context 'for guest' do + it 'should be able to perform index action', :anonymous_access => true do + get :index, :format => :json + response.should be_success + end + + it 'should not be able to perform index action', :anonymous_access => false do + get :index, :format => :json + response.status.should == 401 + end + end + + context 'for all build lists' do + before(:each) { + http_login(@user) + } + + it 'should be able to perform index action' do + get :index, :format => :json + response.should be_success + end + + it 'should show only accessible build_lists' do + get :index, :filter => {:ownership => 'index'}, :format => :json + assigns(:build_lists).should include(@build_list1) + assigns(:build_lists).should_not include(@build_list2) + assigns(:build_lists).should include(@build_list3) + assigns(:build_lists).should include(@build_list4) + assigns(:build_lists).count.should eq 7 + end + end + + context 'filter' do + before(:each) do + http_login FactoryGirl.create(:admin) + end + + it 'should filter by bs_id' do + get :index, :filter => {:bs_id => @filter_build_list1.bs_id, :project_name => 'fdsfdf', :any_other_field => 'do not matter'}, :format => :json + assigns[:build_lists].should include(@filter_build_list1) + assigns[:build_lists].should_not include(@filter_build_list2) + assigns[:build_lists].should_not include(@filter_build_list3) + end + + it 'should filter by project_name' do + get :index, :filter => {:project_name => @filter_build_list2.project.name, :ownership => 'index'}, :format => :json + assigns[:build_lists].should_not include(@filter_build_list1) + assigns[:build_lists].should include(@filter_build_list2) + assigns[:build_lists].should_not include(@filter_build_list3) + end + + it 'should filter by project_name and start_date' do + get :index, :filter => {:project_name => @filter_build_list3.project.name, :ownership => 'index', + :"updated_at_start(1i)" => @filter_build_list3.updated_at.year.to_s, + :"updated_at_start(2i)" => @filter_build_list3.updated_at.month.to_s, + :"updated_at_start(3i)" => @filter_build_list3.updated_at.day.to_s}, :format => :json + assigns[:build_lists].should_not include(@filter_build_list1) + assigns[:build_lists].should_not include(@filter_build_list2) + assigns[:build_lists].should include(@filter_build_list3) + assigns[:build_lists].should_not include(@filter_build_list4) + end + + end + + context "for user" do + before(:each) do + @build_list = FactoryGirl.create(:build_list_core) + @params = @build_list.attributes.symbolize_keys + @project = @build_list.project + + stub_symlink_methods + @owner_user = @project.owner + @member_user = FactoryGirl.create(:user) + @project.relations.create(:role => 'reader', :actor => @member_user) + @build_list.save_to_platform.relations.create(:role => 'admin', :actor => @owner_user) # Why it's really need it?? + + # Show params: + @show_params = {:id => @build_list.id, :format => :json} + end + + context 'for open project' do + context 'for simple user' do + before(:each) {http_login(@user)} + it_should_behave_like 'show build list via api' + end + + context 'if user is project owner' do + before(:each) {http_login(@owner_user)} + it_should_behave_like 'show build list via api' + end + + context 'if user is project read member' do + before(:each) {http_login(@member_user)} + it_should_behave_like 'show build list via api' + end + end + + context 'for hidden project' do + before(:each) do + @project.update_column(:visibility, 'hidden') + end + + context 'for simple user' do + before(:each) {http_login(@user)} + it_should_behave_like 'not show build list via api' + end + + context 'if user is project owner' do + before(:each) {http_login(@owner_user)} + it_should_behave_like 'show build list via api' + end + + context 'if user is project read member' do + before(:each) {http_login(@member_user)} + it_should_behave_like 'show build list via api' + end + end + end + + context "for group" do + before(:each) do + @platform = FactoryGirl.create(:platform_with_repos) + @build_list = FactoryGirl.create(:build_list_core, :save_to_platform => @platform) + @project = @build_list.project + @params = @build_list.attributes.symbolize_keys + + stub_symlink_methods + @owner_user = @project.owner#FactoryGirl.create(:user) + @member_user = FactoryGirl.create(:user) + #@project.relations.create(:role => 'reader', :actor => @member_user) + + # Show params: + @show_params = {:id => @build_list.id, :format => :json} + + # Groups: + @owner_group = FactoryGirl.create(:group, :owner => @owner_user) + @member_group = FactoryGirl.create(:group) + @member_group.actors.create :role => 'reader', :actor_id => @member_user.id, :actor_type => 'User' + @group = FactoryGirl.create(:group) + @group.actors.create :role => 'reader', :actor_id => @user.id, :actor_type => 'User' + + #@project = FactoryGirl.create(:project, :owner => @owner_group, :repositories => @platform.repositories) + + #@project.owner = @owner_group + #@project.save + @project.relations.create :role => 'reader', :actor_id => @member_group.id, :actor_type => 'Group' + #@build_list.save_to_platform.relations.create(:role => 'reader', :actor => @member_group) # Why it's really need it?? + #@build_list.save_to_platform.relations.create(:role => 'admin', :actor => @owner_group) # Why it's really need it?? + end + + context 'for open project' do + context 'for simple user' do + before(:each) {http_login(@user)} + it_should_behave_like 'show build list via api' + end + + context 'if user is group owner' do + before(:each) {http_login(@owner_user)} + it_should_behave_like 'show build list via api' + end + + context 'if user is group read member' do + before(:each) {http_login(@member_user)} + it_should_behave_like 'show build list via api' + end + end + + context 'for hidden project' do + before(:each) do + @build_list.project.update_column(:visibility, 'hidden') + end + + context 'for simple user' do + before(:each) {http_login(@user)} + it_should_behave_like 'not show build list via api' + end + + context 'if user is group owner' do + before(:each) { http_login(@owner_user) } + it_should_behave_like 'show build list via api' + end + + context 'if user is group read member' do + before(:each) {http_login(@member_user)} + it_should_behave_like 'show build list via api' + end + end + end + + end +end diff --git a/spec/controllers/api/v1/groups_controller_spec.rb b/spec/controllers/api/v1/groups_controller_spec.rb new file mode 100644 index 000000000..526ed825f --- /dev/null +++ b/spec/controllers/api/v1/groups_controller_spec.rb @@ -0,0 +1,263 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +shared_examples_for 'api group user with reader rights' do + it 'should be able to perform members action' do + get :members, :id => @group.id, :format => :json + response.should be_success + end + it_should_behave_like 'api group user with show rights' +end + +shared_examples_for 'api group user with show rights' do + it 'should be able to perform show action' do + get :show, :id => @group.id, :format => :json + response.should be_success + end + + it 'should be able to perform index action' do + get :index, :format => :json + response.should be_success + end +end + +shared_examples_for 'api group user without reader rights' do + it 'should not be able to perform members action' do + get :members, :id => @group.id, :format => :json + response.should_not be_success + end +end + +shared_examples_for 'api group user with admin rights' do + + context 'api group user with update rights' do + before do + put :update, {:group => {:description => 'new description'}, :id => @group.id}, :format => :json + end + + it 'should be able to perform update action' do + response.should be_success + end + it 'ensures that group has been updated' do + @group.reload + @group.description.should == 'new description' + end + end + + context 'api group user with add_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + put :add_member, {:member_id => member.id, :id => @group.id}, :format => :json + end + + it 'should be able to perform add_member action' do + response.should be_success + end + it 'ensures that new member has been added to group' do + @group.members.should include(member) + end + end + + context 'api group user with remove_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @group.add_member(member) + delete :remove_member, {:member_id => member.id, :id => @group.id}, :format => :json + end + + it 'should be able to perform remove_member action' do + response.should be_success + end + it 'ensures that member has been removed from group' do + @group.members.should_not include(member) + end + end + + context 'api group user with update_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @group.add_member(member) + put :update_member, {:member_id => member.id, :role => 'reader', :id => @group.id}, :format => :json + end + + it 'should be able to perform update_member action' do + response.should be_success + end + it 'ensures that member role has been updated in group' do + @group.actors.where(:actor_id => member, :actor_type => 'User').first. + role.should == 'reader' + end + end +end + +shared_examples_for 'api group user with owner rights' do + context 'api group user with destroy rights' do + it 'should be able to perform destroy action' do + delete :destroy, :id => @group.id, :format => :json + response.should be_success + end + it 'ensures that group has been destroyed' do + lambda { delete :destroy, :id => @group.id, :format => :json }.should change{ Group.count }.by(-1) + end + end +end + +shared_examples_for 'api group user without admin rights' do + context 'api group user without update_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @group.add_member(member) + put :update_member, {:member_id => member.id, :role => 'reader', :id => @group.id}, :format => :json + end + + it 'should not be able to perform update_member action' do + response.should_not be_success + end + it 'ensures that member role has not been updated in group' do + @group.actors.where(:actor_id => member, :actor_type => 'User').first. + role.should_not == 'reader' + end + end + + context 'api group user without update rights' do + before do + put :update, {:group => {:description => 'new description'}, :id => @group.id}, :format => :json + end + + it 'should not be able to perform update action' do + response.should_not be_success + end + it 'ensures that platform has not been updated' do + @group.reload + @group.description.should_not == 'new description' + end + end + + context 'api group user without add_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + put :add_member, {:member_id => member.id, :id => @group.id}, :format => :json + end + + it 'should not be able to perform add_member action' do + response.should_not be_success + end + it 'ensures that new member has not been added to group' do + @group.members.should_not include(member) + end + end + + context 'api group user without remove_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @group.add_member(member) + delete :remove_member, {:member_id => member.id, :id => @group.id}, :format => :json + end + + it 'should be able to perform update action' do + response.should_not be_success + end + it 'ensures that member has not been removed from group' do + @group.members.should include(member) + end + end + +end + +shared_examples_for 'api group user without owner rights' do + context 'api group user without destroy rights' do + it 'should not be able to perform destroy action' do + delete :destroy, :id => @group.id, :format => :json + response.should_not be_success + end + it 'ensures that group has not been destroyed' do + lambda { delete :destroy, :id => @group.id, :format => :json }.should_not change{ Group.count } + end + end +end + +describe Api::V1::GroupsController do + before do + stub_symlink_methods + + @group = FactoryGirl.create(:group) + @user = FactoryGirl.create(:user) + end + + context 'for guest' do + + it "should not be able to perform index action" do + get :index, :format => :json + response.status.should == 401 + end + + it "should not be able to perform show action", :anonymous_access => false do + get :show, :id => @group.id, :format => :json + response.status.should == 401 + end + + it "should be able to perform show action", :anonymous_access => true do + get :show, :id => @group.id, :format => :json + response.should be_success + end + + context 'api group user without create rights' do + let(:params) { {:group => {:uname => 'test_uname'}} } + it 'should not be able to perform create action' do + post :create, params, :format => :json + response.should_not be_success + end + it 'ensures that group has not been created' do + lambda { post :create, params, :format => :json }.should_not change{ Group.count } + end + end + + it_should_behave_like 'api group user without reader rights' + it_should_behave_like 'api group user without admin rights' + it_should_behave_like 'api group user without owner rights' + end + + context 'for global admin' do + before do + @admin = FactoryGirl.create(:admin) + http_login(@admin) + end + + it_should_behave_like 'api group user with reader rights' + it_should_behave_like 'api group user with admin rights' + it_should_behave_like 'api group user with owner rights' + end + + context 'for owner user' do + before do + @group = FactoryGirl.create(:group, :owner => @user) + http_login(@user) + end + + it_should_behave_like 'api group user with reader rights' + it_should_behave_like 'api group user with admin rights' + it_should_behave_like 'api group user with owner rights' + end + + context 'for admin user' do + before do + @group.add_member(@user) + http_login(@user) + end + + it_should_behave_like 'api group user with reader rights' + it_should_behave_like 'api group user with admin rights' + it_should_behave_like 'api group user without owner rights' + end + + context 'for simple user' do + before do + http_login(@user) + end + + it_should_behave_like 'api group user with show rights' + it_should_behave_like 'api group user without reader rights' + it_should_behave_like 'api group user without admin rights' + it_should_behave_like 'api group user without owner rights' + end +end diff --git a/spec/controllers/api/v1/platforms_controller_spec.rb b/spec/controllers/api/v1/platforms_controller_spec.rb new file mode 100644 index 000000000..682bce637 --- /dev/null +++ b/spec/controllers/api/v1/platforms_controller_spec.rb @@ -0,0 +1,327 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +shared_examples_for 'api platform user with reader rights' do + include_examples "api platform user with show rights" + + it 'should be able to perform index action' do + get :index, :format => :json + response.should render_template(:index) + end + + it 'should be able to perform members action' do + get :members, :id => @platform.id, :format => :json + response.should render_template(:members) + end +end + +shared_examples_for 'api platform user with writer rights' do + + context 'api platform user with update rights' do + before do + put :update, {:platform => {:description => 'new description'}, :id => @platform.id}, :format => :json + end + + it 'should be able to perform update action' do + response.should be_success + end + it 'ensures that platform has been updated' do + @platform.reload + @platform.description.should == 'new description' + end + end + + context 'api platform user with add_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + put :add_member, {:member_id => member.id, :type => 'User', :id => @platform.id}, :format => :json + end + + it 'should be able to perform add_member action' do + response.should be_success + end + it 'ensures that new member has been added to platform' do + @platform.members.should include(member) + end + end + + context 'api platform user with remove_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @platform.add_member(member) + delete :remove_member, {:member_id => member.id, :type => 'User', :id => @platform.id}, :format => :json + end + + it 'should be able to perform remove_member action' do + response.should be_success + end + it 'ensures that member has been removed from platform' do + @platform.members.should_not include(member) + end + end + + context 'api platform user with destroy rights for main platforms only' do + it 'should be able to perform destroy action for main platform' do + delete :destroy, :id => @platform.id, :format => :json + response.should be_success + end + it 'ensures that main platform has been destroyed' do + lambda { delete :destroy, :id => @platform.id, :format => :json }.should change{ Platform.count }.by(-1) + end + it 'should not be able to perform destroy action for personal platform' do + delete :destroy, :id => @personal_platform.id, :format => :json + response.should_not be_success + end + it 'ensures that personal platform has not been destroyed' do + lambda { delete :destroy, :id => @personal_platform.id, :format => :json }.should_not change{ Platform.count } + end + end +end + +shared_examples_for 'api platform user without writer rights' do + + context 'api platform user without update rights' do + before do + put :update, {:platform => {:description => 'new description'}, :id => @platform.id}, :format => :json + end + + it 'should not be able to perform update action' do + response.should_not be_success + end + it 'ensures that platform has not been updated' do + @platform.reload + @platform.description.should_not == 'new description' + end + end + + context 'api platform user without add_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + put :add_member, {:member_id => member.id, :type => 'User', :id => @platform.id}, :format => :json + end + + it 'should not be able to perform add_member action' do + response.should_not be_success + end + it 'ensures that new member has not been added to platform' do + @platform.members.should_not include(member) + end + end + + context 'api platform user without remove_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @platform.add_member(member) + delete :remove_member, {:member_id => member.id, :type => 'User', :id => @platform.id}, :format => :json + end + + it 'should be able to perform update action' do + response.should_not be_success + end + it 'ensures that member has not been removed from platform' do + @platform.members.should include(member) + end + end + + context 'should not be able to perform clear action' do + it 'for personal platform' do + put :clear, :id => @personal_platform.id, :format => :json + response.should_not be_success + end + it 'for main platform' do + put :clear, :id => @platform.id, :format => :json + response.should_not be_success + end + end + + context 'api platform user without destroy rights' do + it 'should not be able to perform destroy action for main platform' do + delete :destroy, :id => @platform.id, :format => :json + response.should_not be_success + end + it 'ensures that main platform has not been destroyed' do + lambda { delete :destroy, :id => @platform.id, :format => :json }.should_not change{ Platform.count } + end + it 'should not be able to perform destroy action for personal platform' do + delete :destroy, :id => @personal_platform.id, :format => :json + response.should_not be_success + end + it 'ensures that personal platform has not been destroyed' do + lambda { delete :destroy, :id => @personal_platform.id, :format => :json }.should_not change{ Platform.count } + end + end + + it_should_behave_like 'api platform user without global admin rights' +end + +shared_examples_for 'api platform user without global admin rights' do + context 'should not be able to perform clear action' do + it 'for personal platform' do + put :clear, :id => @personal_platform.id, :format => :json + response.should_not be_success + end + it 'for main platform' do + put :clear, :id => @platform.id, :format => :json + response.should_not be_success + end + end + + [:create, :clone].each do |action| + context "api platform user without #{action} rights" do + before { any_instance_of(Platform, :create_directory => true) } + it "should not be able to perform #{action} action" do + post action, clone_or_create_params, :format => :json + response.should_not be_success + end + it "ensures that platform has not been #{action}d" do + lambda { post action, clone_or_create_params, :format => :json }.should_not change{ Platform.count } + end + end + end +end + +shared_examples_for 'api platform user with reader rights for hidden platform' do + before(:each) do + @platform.update_column(:visibility, 'hidden') + end + + it_should_behave_like 'api platform user with show rights' +end + +shared_examples_for 'api platform user without reader rights for hidden platform' do + before(:each) do + @platform.update_column(:visibility, 'hidden') + end + + it_should_behave_like 'api platform user without show rights' +end + +shared_examples_for "api platform user with show rights" do + it 'should be able to perform show action' do + get :show, :id => @platform.id, :format => :json + response.should render_template(:show) + end + + it 'should be able to perform platforms_for_build action' do + get :platforms_for_build, :format => :json + response.should render_template(:index) + end +end + +shared_examples_for "api platform user without show rights" do + [:show, :members].each do |action| + it "should not be able to perform #{ action } action" do + get action, :id => @platform.id, :format => :json + response.body.should == {"message" => "Access violation to this page!"}.to_json + end + end +end + +describe Api::V1::PlatformsController do + let(:clone_or_create_params) { {:id => @platform.id, :platform => {:description => 'new description', :name => 'new_name', :owner_id => @user.id, :distrib_type => APP_CONFIG['distr_types'].first}} } + before do + stub_symlink_methods + + @platform = FactoryGirl.create(:platform, :visibility => 'open') + @personal_platform = FactoryGirl.create(:platform, :platform_type => 'personal') + @user = FactoryGirl.create(:user) + end + + context 'for guest' do + + it "should not be able to perform index action" do + get :index, :format => :json + response.status.should == 401 + end + + [:show, :platforms_for_build].each do |action| + it "should not be able to perform #{ action } action", :anonymous_access => false do + get action, :format => :json + response.status.should == 401 + end + end + + it 'should be able to perform members action', :anonymous_access => true do + get :members, :id => @platform.id, :format => :json + response.should render_template(:members) + end + + it_should_behave_like 'api platform user with show rights' if APP_CONFIG['anonymous_access'] + it_should_behave_like 'api platform user without reader rights for hidden platform' if APP_CONFIG['anonymous_access'] + it_should_behave_like 'api platform user without writer rights' + end + + context 'for global admin' do + before do + @admin = FactoryGirl.create(:admin) + http_login(@admin) + end + + it_should_behave_like 'api platform user with reader rights' + it_should_behave_like 'api platform user with reader rights for hidden platform' + it_should_behave_like 'api platform user with writer rights' + + [:clone, :create].each do |action| + context "with #{action} rights" do + before do + any_instance_of(Platform, :create_directory => true) + clone_or_create_params[:platform][:owner_id] = @admin.id + end + it "should be able to perform #{action} action" do + post action, clone_or_create_params, :format => :json + response.should be_success + end + it "ensures that platform has been #{action}d" do + lambda { post action, clone_or_create_params, :format => :json }.should change{ Platform.count }.by(1) + end + end + end + + end + + context 'for owner user' do + before do + http_login(@user) + @platform.owner = @user; @platform.save + @platform.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'admin') + end + + it_should_behave_like 'api platform user with reader rights' + it_should_behave_like 'api platform user with reader rights for hidden platform' + it_should_behave_like 'api platform user with writer rights' + it_should_behave_like 'api platform user without global admin rights' + end + + context 'for reader user' do + before do + http_login(@user) + @platform.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'reader') + @personal_platform.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'reader') + end + + context 'perform index action with type param' do + render_views + %w(main personal).each do |type| + it "ensures that filter by type = #{type} returns true result" do + get :index, :format => :json, :type => "#{type}" + JSON.parse(response.body)['platforms'].map{ |p| p['platform_type'] }. + uniq.should == ["#{type}"] + end + end + end + + it_should_behave_like 'api platform user with reader rights' + it_should_behave_like 'api platform user with reader rights for hidden platform' + it_should_behave_like 'api platform user without writer rights' + end + + context 'for simple user' do + before do + http_login(@user) + end + + it_should_behave_like 'api platform user with reader rights' + it_should_behave_like 'api platform user without reader rights for hidden platform' + it_should_behave_like 'api platform user without writer rights' + end +end diff --git a/spec/controllers/api/v1/projects_controller_spec.rb b/spec/controllers/api/v1/projects_controller_spec.rb new file mode 100644 index 000000000..012437bc1 --- /dev/null +++ b/spec/controllers/api/v1/projects_controller_spec.rb @@ -0,0 +1,512 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +shared_examples_for "api projects user with reader rights" do + include_examples "api projects user with show rights" +end + +shared_examples_for "api projects user with reader rights for hidden project" do + before(:each) do + @project.update_column(:visibility, 'hidden') + end + + it_should_behave_like 'api projects user with show rights' +end + +shared_examples_for "api projects user without reader rights for hidden project" do + before(:each) do + @project.update_column(:visibility, 'hidden') + end + + it_should_behave_like 'api projects user without show rights' +end + +shared_examples_for "api projects user without show rights" do + it "should show access violation instead of project data" do + get :show, :id => @project.id, :format => :json + response.should_not be_success + end + + it "should show access violation instead of project refs_list" do + get :refs_list, :id => @project.id, :format => :json + response.should_not be_success + end + + it "should access violation instead of project data by get_id" do + get :get_id, :name => @project.name, :owner => @project.owner.uname, :format => :json + response.should_not be_success + end + + it "should show access violation instead of project members data" do + get :members, :id => @project.id, :format => :json + response.should_not be_success + end + +end + +shared_examples_for 'api projects user without fork rights' do + it 'should not be able to perform fork action' do + post :fork, :id => @project.id, :format => :json + response.should_not be_success + end + it 'ensures that project has not been forked' do + lambda { post :fork, :id => @project.id, :format => :json }.should_not change{ Project.count } + end +end + +shared_examples_for 'api projects user with fork rights' do + it 'should be able to perform fork action' do + post :fork, :id => @project.id, :format => :json + response.should be_success + end + it 'ensures that project has been forked' do + lambda { post :fork, :id => @project.id, :format => :json }.should change{ Project.count }.by(1) + end +end + +shared_examples_for 'api projects user with fork rights for hidden project' do + before { @project.update_column(:visibility, 'hidden') } + it_should_behave_like 'api projects user with fork rights' +end + +shared_examples_for 'api projects user without fork rights for hidden project' do + before { @project.update_column(:visibility, 'hidden') } + it_should_behave_like 'api projects user without fork rights' +end + +shared_examples_for "api projects user with show rights" do + it "should show project data" do + get :show, :id => @project.id, :format => :json + render_template(:show) + end + + it "should show refs_list of project" do + get :refs_list, :id => @project.id, :format => :json + render_template(:refs_list) + end + + context 'project find by get_id' do + it "should find project by name and owner name" do + @project.reload + get :get_id, :name => @project.name, :owner => @project.owner.uname, :format => :json + assigns[:project].id.should == @project.id + end + + it "should not find project by non existing name and owner name" do + get :get_id, :name => 'NONE_EXISTING_NAME', :owner => @project.owner.uname, :format => :json + assigns[:project].should be_blank + end + + it "should render 404 for non existing name and owner name" do + get :get_id, :name => 'NONE_EXISTING_NAME', :owner => @project.owner.uname, :format => :json + response.body.should == {:status => 404, :message => I18n.t("flash.404_message")}.to_json + end + end +end + +shared_examples_for 'api projects user with admin rights' do + + it "should be able to perform members action" do + get :members, :id => @project.id, :format => :json + response.should be_success + end + + context 'api project user with update rights' do + before do + put :update, {:project => {:description => 'new description'}, :id => @project.id}, :format => :json + end + + it 'should be able to perform update action' do + response.should be_success + end + it 'ensures that group has been updated' do + @project.reload + @project.description.should == 'new description' + end + end + + context 'api project user with add_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + put :add_member, {:member_id => member.id, :type => 'User', :role => 'admin', :id => @project.id}, :format => :json + end + + it 'should be able to perform add_member action' do + response.should be_success + end + it 'ensures that new member has been added to project' do + @project.members.should include(member) + end + end + + context 'api project user with remove_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @project.add_member(member) + delete :remove_member, {:member_id => member.id, :type => 'User', :id => @project.id}, :format => :json + end + + it 'should be able to perform remove_member action' do + response.should be_success + end + it 'ensures that member has been removed from project' do + @project.members.should_not include(member) + end + end + + context 'api group user with update_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @project.add_member(member) + put :update_member, {:member_id => member.id, :type => 'User', :role => 'reader', :id => @project.id}, :format => :json + end + + it 'should be able to perform update_member action' do + response.should be_success + end + it 'ensures that member role has been updated in project' do + @project.relations.by_actor(member).first. + role.should == 'reader' + end + end +end + +shared_examples_for 'api projects user without admin rights' do + + it "should not be able to perform members action" do + get :members, :id => @project.id, :format => :json + response.should_not be_success + end + + context 'api project user without update_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @project.add_member(member) + put :update_member, {:member_id => member.id, :type => 'User', :role => 'reader', :id => @project.id}, :format => :json + end + + it 'should not be able to perform update_member action' do + response.should_not be_success + end + it 'ensures that member role has not been updated in project' do + @project.relations.by_actor(member).first. + role.should_not == 'reader' + end + end + + context 'api project user without update rights' do + before do + put :update, {:project => {:description => 'new description'}, :id => @project.id}, :format => :json + end + + it 'should not be able to perform update action' do + response.should_not be_success + end + it 'ensures that project has not been updated' do + @project.reload + @project.description.should_not == 'new description' + end + end + + context 'api project user without add_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + put :add_member, {:member_id => member.id, :type => 'User', :role => 'admin', :id => @project.id}, :format => :json + end + + it 'should not be able to perform add_member action' do + response.should_not be_success + end + it 'ensures that new member has not been added to project' do + @project.members.should_not include(member) + end + end + + context 'api project user without remove_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @project.add_member(member) + delete :remove_member, {:member_id => member.id, :type => 'User', :id => @project.id}, :format => :json + end + + it 'should be able to perform update action' do + response.should_not be_success + end + it 'ensures that member has not been removed from project' do + @project.members.should include(member) + end + end + +end + +shared_examples_for 'api projects user with owner rights' do + context 'api project user with destroy rights' do + it 'should be able to perform destroy action' do + delete :destroy, :id => @project.id, :format => :json + response.should be_success + end + it 'ensures that project has been destroyed' do + lambda { delete :destroy, :id => @project.id, :format => :json }.should change{ Project.count }.by(-1) + end + end +end + +shared_examples_for 'api projects user without owner rights' do + context 'api project user with destroy rights' do + it 'should not be able to perform destroy action' do + delete :destroy, :id => @project.id, :format => :json + response.should_not be_success + end + it 'ensures that project has not been destroyed' do + lambda { delete :destroy, :id => @project.id, :format => :json }.should_not change{ Project.count } + end + end +end + +describe Api::V1::ProjectsController do + + before(:each) do + stub_symlink_methods + + @project = FactoryGirl.create(:project) + @hidden_project = FactoryGirl.create(:project) + @another_user = FactoryGirl.create(:user) + end + + context 'for guest' do + + [:index, :members].each do |action| + it "should not be able to perform #{action} action" do + get action, :id => @project.id, :format => :json + response.should_not be_success + end + end + + if APP_CONFIG['anonymous_access'] + it_should_behave_like 'api projects user with show rights' + it_should_behave_like 'api projects user without reader rights for hidden project' + else + it_should_behave_like 'api projects user without show rights' + end + it_should_behave_like 'api projects user without fork rights' + it_should_behave_like 'api projects user without fork rights for hidden project' + it_should_behave_like 'api projects user without admin rights' + it_should_behave_like 'api projects user without owner rights' + end + + context 'for simple user' do + before(:each) do + @user = FactoryGirl.create(:user) + http_login(@user) + end + + it 'should be able to perform index action' do + get :index, :format => :json + response.should be_success + end + + context 'api project user with create rights' do + let(:params) { {:project => {:name => 'test_name', :owner_id => @user.id, :owner_type => 'User', :visibility => 'open'}} } + it 'should be able to perform create action' do + post :create, params, :format => :json + response.should be_success + end + it 'ensures that project has been created' do + lambda { post :create, params, :format => :json }.should change{ Project.count }.by(1) + end + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user without reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user without fork rights for hidden project' + it_should_behave_like 'api projects user without admin rights' + it_should_behave_like 'api projects user without owner rights' + end + + context 'for admin' do + before(:each) do + @admin = FactoryGirl.create(:admin) + http_login(@admin) + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user with fork rights for hidden project' + it_should_behave_like 'api projects user with admin rights' + it_should_behave_like 'api projects user with owner rights' + end + + context 'for owner user' do + before(:each) do + @user = FactoryGirl.create(:user) + http_login(@user) + @project = FactoryGirl.create(:project, :owner => @user) + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with reader rights for hidden project' + it_should_behave_like 'api projects user without fork rights' + it_should_behave_like 'api projects user without fork rights for hidden project' + it_should_behave_like 'api projects user with admin rights' + it_should_behave_like 'api projects user with owner rights' + end + + context 'for reader user' do + before(:each) do + @user = FactoryGirl.create(:user) + http_login(@user) + @project.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'reader') + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user with fork rights for hidden project' + it_should_behave_like 'api projects user without admin rights' + it_should_behave_like 'api projects user without owner rights' + end + + context 'for writer user' do + before(:each) do + @user = FactoryGirl.create(:user) + http_login(@user) + @project.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'writer') + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user with fork rights for hidden project' + it_should_behave_like 'api projects user without admin rights' + it_should_behave_like 'api projects user without owner rights' + end + + context 'for group' do + before(:each) do + @group = FactoryGirl.create(:group) + @group_user = FactoryGirl.create(:user) +# @project.relations.destroy_all + http_login(@group_user) + end + + context 'with no relations to project' do + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user without reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user without fork rights for hidden project' + it_should_behave_like 'api projects user without admin rights' + it_should_behave_like 'api projects user without owner rights' + end + + context 'owner of the project' do + before(:each) do + @project = FactoryGirl.create(:project, :owner => @group) + end + + context 'reader user' do + before(:each) do + @group.actors.create(:actor_id => @group_user.id, :actor_type => 'User', :role => 'reader') + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user with fork rights for hidden project' + it_should_behave_like 'api projects user without admin rights' + it_should_behave_like 'api projects user without owner rights' + end + + context 'admin user' do + before(:each) do + @group.actors.create(:actor_id => @group_user.id, :actor_type => 'User', :role => 'admin') + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user with fork rights for hidden project' + it_should_behave_like 'api projects user with admin rights' + it_should_behave_like 'api projects user with owner rights' + end + end + + context 'member of the project' do + context 'with admin rights' do + before(:each) do + @project.relations.create :actor_id => @group.id, :actor_type => @group.class.to_s, :role => 'admin' + end + + context 'reader user' do + before(:each) do + @group.actors.create(:actor_id => @group_user.id, :actor_type => 'User', :role => 'reader') + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user with fork rights for hidden project' + it_should_behave_like 'api projects user with admin rights' + it_should_behave_like 'api projects user without owner rights' + end + + context 'admin user' do + before(:each) do + @group.actors.create(:actor_id => @group_user.id, :actor_type => 'User', :role => 'admin') + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user with fork rights for hidden project' + it_should_behave_like 'api projects user with admin rights' + it_should_behave_like 'api projects user without owner rights' + end + end + + context 'with reader rights' do + before(:each) do + @project.relations.create :actor_id => @group.id, :actor_type => @group.class.to_s, :role => 'reader' + end + + context 'reader user' do + before(:each) do + @group.actors.create(:actor_id => @group_user.id, :actor_type => 'User', :role => 'reader') + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user with fork rights for hidden project' + it_should_behave_like 'api projects user without admin rights' + it_should_behave_like 'api projects user without owner rights' + + context 'user should has best role' do + before(:each) do + @project.relations.create :actor_id => @group_user.id, :actor_type => @group_user.class.to_s, :role => 'admin' + end + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user with fork rights for hidden project' + it_should_behave_like 'api projects user with admin rights' + it_should_behave_like 'api projects user without owner rights' + end + end + + context 'admin user' do + before(:each) do + @group.actors.create(:actor_id => @group_user.id, :actor_type => 'User', :role => 'admin') + end + + it_should_behave_like 'api projects user with reader rights' + it_should_behave_like 'api projects user with reader rights for hidden project' + it_should_behave_like 'api projects user with fork rights' + it_should_behave_like 'api projects user with fork rights for hidden project' + it_should_behave_like 'api projects user without admin rights' + it_should_behave_like 'api projects user without owner rights' + end + end + end + end +end diff --git a/spec/controllers/api/v1/repositories_controller_spec.rb b/spec/controllers/api/v1/repositories_controller_spec.rb new file mode 100644 index 000000000..ff46685f3 --- /dev/null +++ b/spec/controllers/api/v1/repositories_controller_spec.rb @@ -0,0 +1,310 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +shared_examples_for 'api repository user with reader rights' do + it_should_behave_like 'api repository user with show rights' +end + +shared_examples_for 'api repository user with reader rights for hidden platform' do + before(:each) do + @platform.update_column(:visibility, 'hidden') + end + + it_should_behave_like 'api repository user with show rights' +end + +shared_examples_for 'api repository user without reader rights for hidden platform' do + before(:each) do + @platform.update_column(:visibility, 'hidden') + end + + it_should_behave_like 'api repository user without show rights' +end + +shared_examples_for "api repository user with show rights" do + it 'should be able to perform show action' do + get :show, :id => @repository.id, :format => :json + response.should render_template(:show) + end + it 'should be able to perform projects action' do + get :projects, :id => @repository.id, :format => :json + response.should render_template(:projects) + end +end + +shared_examples_for "api repository user without show rights" do + it 'should not be able to perform show action' do + get :show, :id => @repository.id, :format => :json + response.body.should == {"message" => "Access violation to this page!"}.to_json + end +end + +shared_examples_for 'api repository user with writer rights' do + + context 'api repository user with update rights' do + before do + put :update, {:repository => {:description => 'new description'}, :id => @repository.id}, :format => :json + end + + it 'should be able to perform update action' do + response.should be_success + end + it 'ensures that repository has been updated' do + @repository.reload + @repository.description.should == 'new description' + end + end + + context 'api repository user with add_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + put :add_member, {:member_id => member.id, :type => 'User', :id => @repository.id}, :format => :json + end + + it 'should be able to perform add_member action' do + response.should be_success + end + it 'ensures that new member has been added to repository' do + @repository.members.should include(member) + end + end + + context 'api repository user with remove_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @repository.add_member(member) + delete :remove_member, {:member_id => member.id, :type => 'User', :id => @repository.id}, :format => :json + end + + it 'should be able to perform remove_member action' do + response.should be_success + end + it 'ensures that member has been removed from repository' do + @repository.members.should_not include(member) + end + end + + context 'api repository user with destroy rights' do + it 'should be able to perform destroy action for main platform' do + delete :destroy, :id => @repository.id, :format => :json + response.should be_success + end + it 'ensures that repository of main platform has been destroyed' do + lambda { delete :destroy, :id => @repository.id, :format => :json }.should change{ Repository.count }.by(-1) + end + it 'should not be able to perform destroy action for repository of personal platform' do + delete :destroy, :id => @personal_repository.id, :format => :json + response.should_not be_success + end + it 'ensures that repository of personal platform has not been destroyed' do + lambda { delete :destroy, :id => @personal_repository.id, :format => :json }.should_not change{ Repository.count } + end + end + + context 'api repository user with add_project rights' do + before { put :add_project, :id => @repository.id, :project_id => @project.id, :format => :json } + it 'should be able to perform add_project action' do + response.should be_success + end + it 'ensures that project has been added to repository' do + @repository.projects.should include(@project) + end + end + + context 'api repository user with remove_project rights' do + before do + @repository.projects << @project + delete :remove_project, :id => @repository.id, :project_id => @project.id, :format => :json + end + it 'should be able to perform remove_project action' do + response.should be_success + end + it 'ensures that project has been removed from repository' do + @repository.reload + @repository.projects.should_not include(@project) + end + end + + context 'api repository user with update signatures rights' do + before do + stub_key_pairs_calls + put :signatures, :id => @repository.id, :repository => {:public => 'iampublic', :secret => 'iamsecret'}, :format => :json + end + it 'should be able to perform signatures action' do + response.should be_success + end + it 'ensures that signatures has been updated' do + @repository.key_pair.should_not be_nil + end + end + +end + +shared_examples_for 'api repository user without writer rights' do + + context 'api repository user without update rights' do + before do + put :update, {:repository => {:description => 'new description'}, :id => @repository.id}, :format => :json + end + + it 'should not be able to perform update action' do + response.should_not be_success + end + it 'ensures that repository has not been updated' do + @repository.reload + @repository.description.should_not == 'new description' + end + end + + context 'api repository user without add_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + put :add_member, {:member_id => member.id, :type => 'User', :id => @repository.id}, :format => :json + end + + it 'should not be able to perform add_member action' do + response.should_not be_success + end + it 'ensures that new member has not been added to repository' do + @repository.members.should_not include(member) + end + end + + context 'api repository user without remove_member rights' do + let(:member) { FactoryGirl.create(:user) } + before do + @repository.add_member(member) + delete :remove_member, {:member_id => member.id, :type => 'User', :id => @repository.id}, :format => :json + end + + it 'should be able to perform update action' do + response.should_not be_success + end + it 'ensures that member has not been removed from repository' do + @repository.members.should include(member) + end + end + + context 'api repository user without destroy rights' do + it 'should not be able to perform destroy action for repository of main platform' do + delete :destroy, :id => @repository.id, :format => :json + response.should_not be_success + end + it 'ensures that repository of main platform has not been destroyed' do + lambda { delete :destroy, :id => @repository.id, :format => :json }.should_not change{ Repository.count } + end + it 'should not be able to perform destroy action for repository of personal platform' do + delete :destroy, :id => @personal_repository.id, :format => :json + response.should_not be_success + end + it 'ensures that repository of personal platform has not been destroyed' do + lambda { delete :destroy, :id => @personal_repository.id, :format => :json }.should_not change{ Repository.count } + end + end + + context 'api repository user without add_project rights' do + before { put :add_project, :id => @repository.id, :project_id => @project.id, :format => :json } + it 'should not be able to perform add_project action' do + response.should_not be_success + end + it 'ensures that project has not been added to repository' do + @repository.projects.should_not include(@project) + end + end + + context 'api repository user without remove_project rights' do + before do + @repository.projects << @project + delete :remove_project, :id => @repository.id, :project_id => @project.id, :format => :json + end + it 'should not be able to perform remove_project action' do + response.should_not be_success + end + it 'ensures that project has not been removed from repository' do + @repository.reload + @repository.projects.should include(@project) + end + end + + context 'api repository user without update signatures rights' do + before do + stub_key_pairs_calls + put :signatures, :id => @repository.id, :repository => {:public => 'iampublic', :secret => 'iamsecret'}, :format => :json + end + it 'should not be able to perform signatures action' do + response.should_not be_success + end + it 'ensures that signatures has not been updated' do + @repository.key_pair.should be_nil + end + end + +end + + +describe Api::V1::RepositoriesController do + before(:each) do + stub_symlink_methods + + @platform = FactoryGirl.create(:platform) + @repository = FactoryGirl.create(:repository, :platform => @platform) + @personal_repository = FactoryGirl.create(:personal_repository) + @project = FactoryGirl.create(:project) + @another_user = FactoryGirl.create(:user) + end + + context 'for guest' do + it "should not be able to perform show action", :anonymous_access => false do + get :show, :id => @repository.id, :format => :json + response.status.should == 401 + end + + if APP_CONFIG['anonymous_access'] + it_should_behave_like 'api repository user without reader rights for hidden platform' + it_should_behave_like 'api repository user with show rights' + end + it_should_behave_like 'api repository user without writer rights' + + it 'should not be able to perform projects action', :anonymous_access => false do + get :projects, :id => @repository.id, :format => :json + response.should_not be_success + end + end + + context 'for admin' do + before(:each) do + @admin = FactoryGirl.create(:admin) + http_login(@admin) + end + + it_should_behave_like 'api repository user with reader rights' + it_should_behave_like 'api repository user with reader rights for hidden platform' + it_should_behave_like 'api repository user with writer rights' + end + + context 'for platform owner user' do + before(:each) do + @user = FactoryGirl.create(:user) + http_login(@user) + platform = @repository.platform + platform.owner = @user; platform.save + @repository.platform.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'admin') + end + + it_should_behave_like 'api repository user with reader rights' + it_should_behave_like 'api repository user with reader rights for hidden platform' + it_should_behave_like 'api repository user with writer rights' + end + + context 'for user' do + before(:each) do + @user = FactoryGirl.create(:user) + http_login(@user) + end + + it_should_behave_like 'api repository user with reader rights' + it_should_behave_like 'api repository user without reader rights for hidden platform' + it_should_behave_like 'api repository user with show rights' + it_should_behave_like 'api repository user without writer rights' + end +end diff --git a/spec/controllers/api/v1/users_controller_spec.rb b/spec/controllers/api/v1/users_controller_spec.rb new file mode 100644 index 000000000..bad6ffc8e --- /dev/null +++ b/spec/controllers/api/v1/users_controller_spec.rb @@ -0,0 +1,94 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +describe Api::V1::UsersController do + before(:all) { User.destroy_all } + before do + stub_symlink_methods + @user = FactoryGirl.create(:user) + end + + context 'for guest' do + + [:show_current_user, :notifiers].each do |action| + it "should not be able to perform #{ action } action for a current user" do + get action, :format => :json + response.should_not be_success + end + end + + it 'should be able to perform show action for a single user', :anonymous_access => true do + get :show, :id => @user.id, :format => :json + response.should render_template(:show) + end + + it 'should not be able to perform show action for a single user', :anonymous_access => false do + get :show, :id => @user.id, :format => :json + response.should_not be_success + end + + context 'should not be able to perform update action for a current user' do + before do + put :update, {:user => {:company => 'test_company'}}, :format => :json + end + it { response.should_not be_success } + it 'ensures that user has not been updated' do + @user.reload + @user.company.should_not == 'test_company' + end + end + + context 'should not be able to perform notifiers action for a current user' do + before do + put :notifiers, {:notifiers => {:can_notify => false}}, :format => :json + end + it { response.should_not be_success } + it 'ensures that user notification settings have not been updated' do + @user.reload + @user.notifier.can_notify.should be_true + end + end + + end + + context 'for simple user' do + before do + http_login(@user) + end + + [:show_current_user, :notifiers].each do |action| + it "should be able to perform #{ action } action for a current user" do + get action, :format => :json + response.should be_success + end + end + + it 'should be able to perform show action for a single user' do + get :show, :id => @user.id, :format => :json + response.should render_template(:show) + end + + context 'should be able to perform update action for a current user' do + before do + put :update, {:user => {:company => 'test_company'}}, :format => :json + end + it { response.should be_success } + it 'ensures that user has been updated' do + @user.reload + @user.company.should == 'test_company' + end + end + + context 'should be able to perform notifiers action for a current user' do + before do + put :notifiers, {:notifiers => {:can_notify => false}}, :format => :json + end + it { response.should be_success } + it 'ensures that user notification settings have been updated' do + @user.reload + @user.notifier.can_notify.should be_false + end + end + + end +end diff --git a/spec/controllers/autocompletes_controller_spec.rb b/spec/controllers/autocompletes_controller_spec.rb new file mode 100644 index 000000000..6b0a4f3a4 --- /dev/null +++ b/spec/controllers/autocompletes_controller_spec.rb @@ -0,0 +1,40 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +describe AutocompletesController do + + context 'for user' do + before do + set_session_for(FactoryGirl.create(:user)) + end + + it 'should be able to perform autocomplete_group_uname action' do + get :autocomplete_group_uname + response.should be_success + end + + it 'should be able to perform autocomplete_user_uname action' do + get :autocomplete_user_uname + response.should be_success + end + + end + + context 'for guest' do + + before do + set_session_for(User.new) + end + + it 'should not be able to perform autocomplete_group_uname action' do + get :autocomplete_group_uname + response.should redirect_to(new_user_session_path) + end + + it 'should not be able to perform autocomplete_user_uname action' do + get :autocomplete_user_uname + response.should redirect_to(new_user_session_path) + end + + end +end diff --git a/spec/controllers/groups/profile_controller_spec.rb b/spec/controllers/groups/profile_controller_spec.rb index 8ea65d72b..08abcca95 100644 --- a/spec/controllers/groups/profile_controller_spec.rb +++ b/spec/controllers/groups/profile_controller_spec.rb @@ -1,6 +1,13 @@ # -*- encoding : utf-8 -*- require 'spec_helper' +shared_examples_for 'group user with project show rights' do + it 'should be able to perform show action' do + get :show, :id => @group + response.should render_template(:show) + end +end + shared_examples_for 'group user without update rights' do it 'should be not able to perform update action' do put :update, {:id => @group}.merge(@update_params) @@ -47,11 +54,6 @@ shared_examples_for 'no group user' do it 'should change objects count on create' do lambda { post :create, @create_params }.should change{ Group.count }.by(1) end - - it 'should be able to perform autocomplete_group_uname action' do - get :autocomplete_group_uname - response.should be_success - end end shared_examples_for 'group owner' do @@ -77,6 +79,16 @@ describe Groups::ProfileController do end context 'for guest' do + + if APP_CONFIG['anonymous_access'] + it_should_behave_like 'group user with project show rights' + else + it 'should not be able to perform show action' do + get :show, :id => @group + response.should redirect_to(new_user_session_path) + end + end + it 'should not be able to perform index action' do get :index response.should redirect_to(new_user_session_path) @@ -99,6 +111,7 @@ describe Groups::ProfileController do set_session_for(@admin) end + it_should_behave_like 'group user with project show rights' it_should_behave_like 'update_member_relation' it_should_behave_like 'group owner' @@ -120,6 +133,7 @@ describe Groups::ProfileController do @group.actors.create(:actor_type => 'User', :actor_id => @user.id, :role => 'admin') end + it_should_behave_like 'group user with project show rights' it_should_behave_like 'update_member_relation' it_should_behave_like 'group admin' it_should_behave_like 'group user without destroy rights' @@ -134,6 +148,7 @@ describe Groups::ProfileController do @group.actors.create(:actor_type => 'User', :actor_id => @user.id, :role => 'admin') end + it_should_behave_like 'group user with project show rights' it_should_behave_like 'update_member_relation' it_should_behave_like 'group owner' end @@ -154,6 +169,7 @@ describe Groups::ProfileController do lambda { delete :remove_user, :id => @group }.should change{ Relation.count }.by(-1) end + it_should_behave_like 'group user with project show rights' it_should_behave_like 'no group user' it_should_behave_like 'group user without destroy rights' it_should_behave_like 'group user without update rights' diff --git a/spec/controllers/projects/build_lists_controller_spec.rb b/spec/controllers/projects/build_lists_controller_spec.rb index 4e9575f9c..d3c6ed428 100644 --- a/spec/controllers/projects/build_lists_controller_spec.rb +++ b/spec/controllers/projects/build_lists_controller_spec.rb @@ -28,7 +28,9 @@ describe Projects::BuildListsController do end shared_examples_for 'create build list' do - before {test_git_commit(@project)} + before { + @project.update_attribute(:repositories, @platform.repositories) + } it 'should be able to perform new action' do get :new, :owner_name => @project.owner.uname, :project_name => @project.name @@ -50,9 +52,21 @@ describe Projects::BuildListsController do post :create, {:owner_name => @project.owner.uname, :project_name => @project.name}.merge(@create_params).deep_merge(:build_list => {:project_version => "4.7.5.3"}) @project.build_lists.last.commit_hash.should == @project.repo.commits('4.7.5.3').last.id end + + it 'should not be able to create with wrong project version' do + lambda{ post :create, {:owner_name => @project.owner.uname, :project_name => @project.name}.merge(@create_params).deep_merge(:build_list => {:project_version => "latest_wrong", :commit_hash => nil})}.should change{@project.build_lists.count}.by(0) + end + + it 'should not be able to create with wrong git hash' do + lambda{ post :create, {:owner_name => @project.owner.uname, :project_name => @project.name}.merge(@create_params).deep_merge(:build_list => {:commit_hash => 'wrong'})}.should change{@project.build_lists.count}.by(0) + end end shared_examples_for 'not create build list' do + before { + @project.update_attribute(:repositories, @platform.repositories) + } + it 'should not be able to perform new action' do get :new, :owner_name => @project.owner.uname, :project_name => @project.name response.should redirect_to(forbidden_url) @@ -68,31 +82,29 @@ describe Projects::BuildListsController do context 'crud' do before(:each) do - platform = FactoryGirl.create(:platform_with_repos) + @platform = FactoryGirl.create(:platform_with_repos) @create_params = { - :build_list => { + :build_list => { :project_version => 'latest_master', - :save_to_platform_id => platform.id, + :save_to_repository_id => @platform.repositories.first.id, :update_type => 'security', - :include_repos => [platform.repositories.first.id] + :include_repos => [@platform.repositories.first.id] }, :arches => [FactoryGirl.create(:arch).id], - :build_for_platforms => [platform.id] + :build_for_platforms => [@platform.id] } any_instance_of(Project, :versions => ['v1.0', 'v2.0']) end context 'for guest' do - if APP_CONFIG['anonymous_access'] - it 'should be able to perform index action' do - get :index - response.should be_success - end - else - it 'should not be able to perform index action' do - get :index - response.should redirect_to(new_user_session_path) - end + it 'should be able to perform index action', :anonymous_access => true do + get :index + response.should be_success + end + + it 'should not be able to perform index action', :anonymous_access => false do + get :index + response.should redirect_to(new_user_session_path) end end @@ -113,10 +125,16 @@ describe Projects::BuildListsController do context 'for all build lists' do before(:each) do @build_list1 = FactoryGirl.create(:build_list_core) - @build_list2 = FactoryGirl.create(:build_list_core, :project => FactoryGirl.create(:project, :visibility => 'hidden')) - @build_list3 = FactoryGirl.create(:build_list_core, :project => FactoryGirl.create(:project, :owner => @user, :visibility => 'hidden')) - @build_list4 = FactoryGirl.create(:build_list_core, :project => FactoryGirl.create(:project, :visibility => 'hidden')) - @build_list4.project.relations.create :role => 'reader', :actor_id => @user.id, :actor_type => 'User' + + @build_list2 = FactoryGirl.create(:build_list_core) + @build_list2.project.update_column(:visibility, 'hidden') + + project = FactoryGirl.create(:project, :visibility => 'hidden', :owner => @user) + @build_list3 = FactoryGirl.create(:build_list_core, :project => project) + + @build_list4 = FactoryGirl.create(:build_list_core) + @build_list4.project.update_column(:visibility, 'hidden') + @build_list4.project.relations.create! :role => 'reader', :actor_id => @user.id, :actor_type => 'User' end it 'should be able to perform index action' do @@ -175,33 +193,36 @@ describe Projects::BuildListsController do context 'for group' do before(:each) do - @owner_group = FactoryGirl.create(:group) - @owner_user = FactoryGirl.create(:user) - @owner_group.actors.create :role => 'admin', :actor_id => @owner_user.id, :actor_type => 'User' + + @user = FactoryGirl.create(:user) + set_session_for(@user) + + @build_list = FactoryGirl.create(:build_list_by_group_project) + @project = @build_list.project + @owner_group = @build_list.project.owner + @owner_user = @owner_group.owner + @member_group = FactoryGirl.create(:group) @member_user = FactoryGirl.create(:user) @member_group.actors.create :role => 'reader', :actor_id => @member_user.id, :actor_type => 'User' - - @group = FactoryGirl.create(:group) - @user = FactoryGirl.create(:user) - @group.actors.create :role => 'reader', :actor_id => @user.id, :actor_type => 'User' - - @project = FactoryGirl.create(:project, :owner => @owner_group) @project.relations.create :role => 'reader', :actor_id => @member_group.id, :actor_type => 'Group' - @build_list = FactoryGirl.create(:build_list_core, :project => @project) - - set_session_for(@user) @show_params = {:owner_name => @project.owner.uname, :project_name => @project.name, :id => @build_list.id} end - + context 'for all build lists' do before(:each) do @build_list1 = FactoryGirl.create(:build_list_core) - @build_list2 = FactoryGirl.create(:build_list_core, :project => FactoryGirl.create(:project, :visibility => 'hidden')) - @build_list3 = FactoryGirl.create(:build_list_core, :project => FactoryGirl.create(:project, :owner => @group, :visibility => 'hidden')) - @build_list4 = FactoryGirl.create(:build_list_core, :project => FactoryGirl.create(:project, :visibility => 'hidden')) - @build_list4.project.relations.create :role => 'reader', :actor_id => @group.id, :actor_type => 'Group' + + @build_list2 = FactoryGirl.create(:build_list_core) + @build_list2.project.update_column(:visibility, 'hidden') + + project = FactoryGirl.create(:project, :visibility => 'hidden', :owner => @user) + @build_list3 = FactoryGirl.create(:build_list_core, :project => project) + + @build_list4 = FactoryGirl.create(:build_list_core) + @build_list4.project.update_column(:visibility, 'hidden') + @build_list4.project.relations.create! :role => 'reader', :actor_id => @user.id, :actor_type => 'User' end it 'should be able to perform index action' do @@ -271,13 +292,13 @@ describe Projects::BuildListsController do get :index assigns[:build_server_status].should == {} response.should be_success - end + end end end context 'filter' do - - before(:each) do + + before(:each) do set_session_for FactoryGirl.create(:admin) @build_list1 = FactoryGirl.create(:build_list_core) @@ -317,7 +338,7 @@ describe Projects::BuildListsController do context 'callbacks' do let(:build_list) { FactoryGirl.create(:build_list_core) } - let(:build_list_package) { FactoryGirl.create(:build_list_package, :build_list_id => build_list.id, :platform_id => build_list.project.repositories.first.platform_id, :project_id => build_list.project_id, :version => "4.7.5.3", :release => 1) } + let(:build_list_package) { FactoryGirl.create(:build_list_package, :build_list_id => build_list.id, :platform_id => build_list.save_to_platform_id, :project_id => build_list.project_id, :version => "4.7.5.3", :release => 1) } before(:each) do mock(controller).authenticate_build_service! {true} @@ -357,7 +378,6 @@ describe Projects::BuildListsController do before do @item = build_list.items.create(:name => build_list.project.name, :version => build_list.project_version, :level => 0) repo = build_list.save_to_platform.repositories.first - repo.projects << build_list.project @project2 = FactoryGirl.create(:project) repo.projects << @project2 end diff --git a/spec/controllers/projects/comments_controller_for_commit_spec.rb b/spec/controllers/projects/comments_controller_for_commit_spec.rb index 7555d5a79..d3a364218 100644 --- a/spec/controllers/projects/comments_controller_for_commit_spec.rb +++ b/spec/controllers/projects/comments_controller_for_commit_spec.rb @@ -19,7 +19,7 @@ end shared_examples_for 'user with update own comment rights for commits' do it 'should be able to perform update action' do put :update, {:id => @own_comment.id}.merge(@update_params) - response.should redirect_to(commit_path(@project, @commit.id)) + response.status.should == 200 end it 'should update subscribe body' do @@ -31,7 +31,7 @@ end shared_examples_for 'user with update stranger comment rights for commits' do it 'should be able to perform update action' do put :update, {:id => @stranger_comment.id}.merge(@update_params) - response.should redirect_to(commit_path(@project, @commit.id)) + response.status.should == 200 end it 'should update comment title' do diff --git a/spec/controllers/projects/comments_controller_spec.rb b/spec/controllers/projects/comments_controller_spec.rb index 3fb36a7cd..97a55982f 100644 --- a/spec/controllers/projects/comments_controller_spec.rb +++ b/spec/controllers/projects/comments_controller_spec.rb @@ -11,7 +11,7 @@ shared_context "comments controller" do @user = FactoryGirl.create(:user) @own_comment = FactoryGirl.create(:comment, :commentable => @issue, :user => @user, :project_id => @project.id) - + set_session_for(@user) @address = {:owner_name => @project.owner.uname, :project_name => @project.name, :issue_id => @issue.serial_id} @@ -35,7 +35,7 @@ end shared_examples_for 'user with update own comment rights' do it 'should be able to perform update action' do put :update, {:id => @own_comment.id}.merge(@update_params) - response.should redirect_to([@project, @issue]) + response.status.should == 200 end it 'should update comment body' do @@ -47,7 +47,7 @@ end shared_examples_for 'user with update stranger comment rights' do it 'should be able to perform update action' do put :update, {:id => @comment.id}.merge(@update_params) - response.should redirect_to([@project, @issue]) + response.status.should == 200 end it 'should update comment body' do diff --git a/spec/controllers/projects/git/git_trees_controller_spec.rb b/spec/controllers/projects/git/git_trees_controller_spec.rb index 61c4d68af..5bf420e11 100644 --- a/spec/controllers/projects/git/git_trees_controller_spec.rb +++ b/spec/controllers/projects/git/git_trees_controller_spec.rb @@ -12,19 +12,21 @@ describe Projects::Git::TreesController do @project = FactoryGirl.create(:project) @another_user = FactoryGirl.create(:user) - @params = {:owner_name => @project.owner.uname, :project_name => @project.name, :treeish => 'master'} + @params = { :owner_name => @project.owner.uname, + :project_name => @project.name, + :treeish => "#{@project.owner.uname}-#{@project.name}-master"} end context 'for guest' do it 'should be able to perform archive action with anonymous acccess', :anonymous_access => true do fill_project - get :archive, @params.merge(:format => 'tar') + get :archive, @params.merge(:format => 'tar.gz') response.should be_success end it 'should not be able to perform archive action without anonymous acccess', :anonymous_access => false do fill_project - get :archive, @params.merge(:format => 'tar') + get :archive, @params.merge(:format => 'tar.gz') response.code.should == '401' end end @@ -33,14 +35,14 @@ describe Projects::Git::TreesController do it 'should not be able to archive empty project' do @user = FactoryGirl.create(:user) set_session_for(@user) - expect { get :archive, @params.merge(:format => 'tar') }.to raise_error(ActionController::RoutingError) + expect { get :archive, @params.merge(:format => 'tar.gz') }.to raise_error(ActionController::RoutingError) end it 'should not be able to injection code with format' do @user = FactoryGirl.create(:user) set_session_for(@user) fill_project - expect { get :archive, @params.merge(:format => "tar master > /dev/null; echo 'I am hacker!';\#") }.to raise_error(ActionController::RoutingError) + expect { get :archive, @params.merge(:format => "tar.gz master > /dev/null; echo 'I am hacker!';\#") }.to raise_error(ActionController::RoutingError) end it 'should not be able to injection code with treeish' do @@ -54,7 +56,7 @@ describe Projects::Git::TreesController do @user = FactoryGirl.create(:user) set_session_for(@user) fill_project - get :archive, @params.merge(:format => 'tar') + get :archive, @params.merge(:format => 'tar.gz') response.should be_success end end diff --git a/spec/controllers/projects/pull_requests_controller_spec.rb b/spec/controllers/projects/pull_requests_controller_spec.rb new file mode 100644 index 000000000..f18061209 --- /dev/null +++ b/spec/controllers/projects/pull_requests_controller_spec.rb @@ -0,0 +1,287 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +shared_context "pull request controller" do + after { FileUtils.rm_rf File.join(Rails.root, "tmp", Rails.env, "pull_requests") } + before do + FileUtils.rm_rf(APP_CONFIG['root_path']) + stub_symlink_methods + + @project = FactoryGirl.create(:project) + %x(cp -Rf #{Rails.root}/spec/tests.git/* #{@project.path}) + + @pull = @project.pull_requests.new :issue_attributes => {:title => 'test', :body => 'testing'} + @pull.issue.user, @pull.issue.project = @project.owner, @pull.to_project + @pull.to_ref = 'master' + @pull.from_project, @pull.from_ref = @project, 'non_conflicts' + @pull.save + + @create_params = { + :pull_request => {:issue_attributes => {:title => 'create', :body => 'creating'}, + :to_ref => 'non_conflicts', + :from_ref => 'master'}, + :to_project => @project.name_with_owner, + :owner_name => @project.owner.uname, + :project_name => @project.name } + @update_params = @create_params.merge( + :pull_request_action => 'close', + :id => @pull.serial_id) + @wrong_update_params = @create_params.merge( + :pull_request => {:issue_attributes => {:title => 'update', :body => 'updating', :id => @pull.issue.id}}, + :id => @pull.serial_id) + + @user = FactoryGirl.create(:user) + set_session_for(@user) + end +end + +shared_examples_for 'pull request user with project guest rights' do + it 'should be able to perform index action' do + get :index, :owner_name => @project.owner.uname, :project_name => @project.name + response.should render_template(:index) + end + + it 'should be able to perform show action when pull request has been created' do + @pull.check + get :show, :owner_name => @project.owner.uname, :project_name => @project.name, :id => @pull.serial_id + response.should render_template(:show) + end +end + +shared_examples_for 'pull request user with project reader rights' do + it 'should be able to perform index action on hidden project' do + @project.update_attributes(:visibility => 'hidden') + get :index, :owner_name => @project.owner.uname, :project_name => @project.name + response.should render_template(:index) + end + + it 'should be able to perform create action' do + post :create, @create_params + response.should redirect_to(project_pull_request_path(@project, @project.pull_requests.last)) + end + + it 'should create pull request object into db' do + lambda{ post :create, @create_params }.should change{ PullRequest.joins(:issue). + where(:issues => {:title => 'create', :body => 'creating'}).count }.by(1) + end + + it "should not create same pull" do + post :create, @create_params.merge({:pull_request => {:issue_attributes => {:title => 'same', :body => 'creating'}, :from_ref => 'non_conflicts', :to_ref => 'master'}, :to_project_id => @project.id}) + PullRequest.joins(:issue).where(:issues => {:title => 'same', :body => 'creating'}).count.should == 0 + end + + it "should not create already up-to-date pull" do + post :create, @create_params.merge({:pull_request => {:issue_attributes => {:title => 'already', :body => 'creating'}, :to_ref => 'master', :from_ref => 'master'}, :to_project_id => @project.id}) + PullRequest.joins(:issue).where(:issues => {:title => 'already', :body => 'creating'}).count.should == 0 + end + + it "should create pull request to the same project" do + @parent = FactoryGirl.create(:project) + @project.update_attributes({:parent_id => @parent}, :without_protection => true) + + lambda{ post :create, @create_params }.should change{ PullRequest.joins(:issue). + where(:issues => {:user_id => @user}, :to_project_id => @project, :from_project_id => @project).count }.by(1) + end + + it "should create pull request to the parent project" do + @parent = FactoryGirl.create(:project) + %x(cp -Rf #{Rails.root}/spec/tests.git/* #{@parent.path}) + @project.update_attributes({:parent_id => @parent}, :without_protection => true) + + lambda{ post :create, @create_params.merge({:to_project => @parent.name_with_owner}) }.should change{ PullRequest.joins(:issue). + where(:issues => {:user_id => @user}, :to_project_id => @parent, :from_project_id => @project).count }.by(1) + end +end + +shared_examples_for 'user with pull request update rights' do + it 'should be able to perform update action' do + put :update, @update_params + response.should redirect_to(project_pull_request_path(@pull.to_project, @pull)) + end + + it 'should be able to perform merge action' do + put :merge, @update_params + response.should redirect_to(project_pull_request_path(@pull.to_project, @pull)) + end + + let(:pull) { @project.pull_requests.find(@pull) } + it 'should update pull request status' do + put :update, @update_params + pull.status.should =='closed' + end + + it 'should not update pull request title' do + put :update, @wrong_update_params + pull.issue.title.should =='test' + end + + it 'should not update pull request body' do + put :update, @wrong_update_params + pull.issue.body.should =='testing' + end + + it 'should not update pull request title direct' do + put :update, @wrong_update_params + pull.issue.title.should_not =='update' + end + + it 'should not update pull request body direct' do + put :update, @wrong_update_params + pull.issue.body.should_not =='updating' + end +end + +shared_examples_for 'user without pull request update rights' do + it 'should not be able to perform update action' do + put :update, @update_params + response.should redirect_to(controller.current_user ? forbidden_path : new_user_session_path) + end + + it 'should not be able to perform merge action' do + put :merge, @update_params + response.should redirect_to(controller.current_user ? forbidden_path : new_user_session_path) + end + + let(:pull) { @project.pull_requests.find(@pull) } + it 'should not update pull request status' do + put :update, @update_params + pull.status.should_not =='closed' + end + it 'should not update pull request title' do + put :update, @wrong_update_params + pull.issue.title.should_not =='update' + end + + it 'should not update pull request body' do + put :update, @wrong_update_params + pull.issue.body.should_not =='updating' + end +end + +shared_examples_for 'pull request when project with issues turned off' do + before { @project.update_attributes(:has_issues => false) } + it 'should be able to perform index action' do + get :index, :project_id => @project.id + response.should render_template(:index) + end + + it 'should be able to perform show action when pull request has been created' do + @pull.check + get :show, :owner_name => @project.owner.uname, :project_name => @project.name, :id => @pull.serial_id + response.should render_template(:show) + end +end + +describe Projects::PullRequestsController do + include_context "pull request controller" + + context 'for global admin user' do + before do + @user.role = "admin" + @user.save + end + + it_should_behave_like 'pull request user with project guest rights' + it_should_behave_like 'pull request user with project reader rights' + it_should_behave_like 'user with pull request update rights' + it_should_behave_like 'pull request when project with issues turned off' + end + + context 'for project admin user' do + before do + @project.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'admin') + end + + it_should_behave_like 'pull request user with project guest rights' + it_should_behave_like 'pull request user with project reader rights' + it_should_behave_like 'user with pull request update rights' + it_should_behave_like 'pull request when project with issues turned off' + end + + context 'for project owner user' do + before do + @user = @project.owner + set_session_for(@user) + end + + it_should_behave_like 'pull request user with project guest rights' + it_should_behave_like 'pull request user with project reader rights' + it_should_behave_like 'user with pull request update rights' + it_should_behave_like 'pull request when project with issues turned off' + end + + context 'for project reader user' do + before do + @project.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'reader') + end + + it_should_behave_like 'pull request user with project guest rights' + it_should_behave_like 'pull request user with project reader rights' + it_should_behave_like 'user without pull request update rights' + it_should_behave_like 'pull request when project with issues turned off' + end + + context 'for project writer user' do + before do + @project.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'writer') + end + + it_should_behave_like 'pull request user with project guest rights' + it_should_behave_like 'pull request user with project reader rights' + it_should_behave_like 'user without pull request update rights' + it_should_behave_like 'pull request when project with issues turned off' + end + +=begin + context 'for pull request assign user' do + before do + set_session_for(@pull request_user) + end + + it_should_behave_like 'user without pull request update rights' + it_should_behave_like 'pull request when project with issues turned off' + end +=end + + context 'for guest' do + let(:guest) { User.new } + before do + set_session_for(guest) + end + + if APP_CONFIG['anonymous_access'] + + it_should_behave_like 'pull request user with project guest rights' + it_should_behave_like 'pull request when project with issues turned off' + + else + it 'should not be able to perform index action' do + get :index, :owner_name => @project.owner.uname, :project_name => @project.name + response.should redirect_to(new_user_session_path) + end + + it 'should not be able to perform show action' do + @pull.check + get :show, :owner_name => @project.owner.uname, :project_name => @project.name, :id => @pull.serial_id + response.should redirect_to(new_user_session_path) + end + + it 'should not be able to perform index action on hidden project' do + @project.update_attributes(:visibility => 'hidden') + get :index, :owner_name => @project.owner.uname, :project_name => @project.name + response.should redirect_to(new_user_session_path) + end + end + + it 'should not be able to perform create action' do + post :create, @create_params + response.should redirect_to(new_user_session_path) + end + + it 'should not create pull request object into db' do + lambda{ post :create, @create_params }.should_not change{ PullRequest.count } + end + + it_should_behave_like 'user without pull request update rights' + end +end diff --git a/spec/controllers/users/profile_controller_spec.rb b/spec/controllers/users/profile_controller_spec.rb index 76617cf8d..ba77ba243 100644 --- a/spec/controllers/users/profile_controller_spec.rb +++ b/spec/controllers/users/profile_controller_spec.rb @@ -15,7 +15,11 @@ describe Users::ProfileController do end context 'for guest' do - it 'should not be able to view profile' do + it 'should be able to view profile', :anonymous_access => true do + get :show, :uname => @simple_user.uname + response.code.should eq('200') + end + it 'should not be able to perform show action', :anonymous_access => false do get :show, :uname => @simple_user.uname response.should redirect_to(new_user_session_path) end diff --git a/spec/factories/advisories.rb b/spec/factories/advisories.rb new file mode 100644 index 000000000..11094106a --- /dev/null +++ b/spec/factories/advisories.rb @@ -0,0 +1,7 @@ +# -*- encoding : utf-8 -*- +FactoryGirl.define do + factory :advisory do + description { FactoryGirl.generate(:string) } + update_type 'security' + end +end diff --git a/spec/factories/build_lists.rb b/spec/factories/build_lists.rb index 5f2b0d574..498e106f7 100644 --- a/spec/factories/build_lists.rb +++ b/spec/factories/build_lists.rb @@ -2,22 +2,27 @@ FactoryGirl.define do factory :build_list do association :user - association :project + #association :project association :save_to_platform, :factory => :platform_with_repos + project { |bl| FactoryGirl.create(:project, :repositories => [bl.save_to_platform.repositories.first]) } association :arch build_for_platform {|bl| bl.save_to_platform} save_to_repository {|bl| bl.save_to_platform.repositories.first} - project_version "1.0" build_requires true update_type 'security' include_repos {|bl| bl.save_to_platform.repositories.map(&:id)} - commit_hash '1234567890abcdef1234567890abcdef12345678' + project_version 'latest_master' + after(:build) {|bl| test_git_commit bl.project } end factory :build_list_core, :parent => :build_list do bs_id { FactoryGirl.generate(:integer) } end + factory :build_list_by_group_project, :parent => :build_list_core do + project { |bl| FactoryGirl.create(:group_project, :repositories => [bl.save_to_platform.repositories.first]) } + end + factory :build_list_package, :class => BuildList::Package do association :build_list association :project diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index f82a8e20d..2c7a40af4 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -5,4 +5,8 @@ FactoryGirl.define do name { FactoryGirl.generate(:unixname) } association :owner, :factory => :user end + + factory :group_project, :parent => :project do + association :owner, :factory => :group + end end diff --git a/spec/factories/pull_request.rb b/spec/factories/pull_request.rb new file mode 100644 index 000000000..f016b8a6a --- /dev/null +++ b/spec/factories/pull_request.rb @@ -0,0 +1,10 @@ +# -*- encoding : utf-8 -*- +FactoryGirl.define do + factory :pull_request do + title { FactoryGirl.generate(:string) } + body { FactoryGirl.generate(:string) } + association :project, :factory => :project + association :user, :factory => :user + association :assignee, :factory => :user + end +end diff --git a/spec/models/build_list_observer_spec.rb b/spec/models/build_list_observer_spec.rb index 8cd2ba032..78ea54d44 100644 --- a/spec/models/build_list_observer_spec.rb +++ b/spec/models/build_list_observer_spec.rb @@ -1,94 +1,5 @@ require 'spec_helper' describe BuildListObserver do - - context "notify users" do - let!(:user) { FactoryGirl.create(:user) } - let!(:build_list) { FactoryGirl.create(:build_list, :user => user, :auto_publish => false) } - - before(:all) { ActionMailer::Base.deliveries = [] } - before { build_list.update_attribute(:status, BuildServer::BUILD_STARTED) } - after { ActionMailer::Base.deliveries = [] } - - shared_examples_for 'build list notifications by email' do - it "gets notification by email when status - Build complete" do - build_list.update_attribute(:status, BuildServer::SUCCESS) - should have(1).item - end - - it "gets notification by email when status - Build published" do - build_list.update_attribute(:status, BuildList::BUILD_PUBLISHED) - should have(1).item - end - - it "gets notification by email when auto_publish and status - Build published" do - build_list.update_attributes(:auto_publish => true, :status => BuildList::BUILD_PUBLISHED) - should have(1).item - end - - it "doesn't get notification by email when auto_publish and status - Build complete" do - build_list.update_attributes(:auto_publish => true, :status => BuildServer::SUCCESS) - should have(:no).items - end - - it "doesn't get notification by email when mass build" do - build_list.update_attributes(:mass_build_id => 1, :status => BuildList::BUILD_PUBLISHED) - should have(:no).items - end - - it "doesn't get notification by email when notification by email has been disabled" do - notifier.update_attribute(:can_notify, false) - build_list.update_attribute(:status, BuildServer::SUCCESS) - should have(:no).items - end - end - - subject { ActionMailer::Base.deliveries } - - context "user created build task" do - let!(:notifier) { user.notifier } - before do - notifier.update_attribute(:new_associated_build, false) - build_list.project.owner.notifier.update_attribute(:can_notify, false) - end - - it_should_behave_like 'build list notifications by email' - - it "doesn't get notification by email when 'build list' notifications has been disabled" do - notifier.update_attribute(:new_build, false) - build_list.update_attribute(:status, BuildServer::SUCCESS) - should have(:no).items - end - - it "doesn't get notification by email when 'build list' notifications - enabled, email notifications - disabled" do - notifier.update_attributes(:can_notify => false, :new_build => true) - build_list.update_attribute(:status, BuildServer::SUCCESS) - should have(:no).items - end - end - - context "build task has been created and associated user" do - let!(:notifier) { build_list.project.owner.notifier } - before do - notifier.update_attribute(:new_build, false) - user.notifier.update_attribute(:can_notify, false) - end - - it_should_behave_like 'build list notifications by email' - - it "doesn't get notification by email when 'associated build list' notifications has been disabled" do - notifier.update_attribute(:new_associated_build, false) - build_list.update_attribute(:status, BuildServer::SUCCESS) - should have(:no).items - end - - it "doesn't get notification by email when 'associated build list' notifications - enabled, email notifications - disabled" do - notifier.update_attributes(:can_notify => false, :new_associated_build => true) - build_list.update_attribute(:status, BuildServer::SUCCESS) - should have(:no).items - end - end - - end # notify users - + pending "add some examples to (or delete) #{__FILE__}" end diff --git a/spec/models/build_list_spec.rb b/spec/models/build_list_spec.rb new file mode 100644 index 000000000..421e39726 --- /dev/null +++ b/spec/models/build_list_spec.rb @@ -0,0 +1,146 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +describe BuildList do + + context "#notify_users" do + before { stub_symlink_methods } + let!(:user) { FactoryGirl.create(:user) } + let!(:build_list) { FactoryGirl.create(:build_list_core, + :user => user, + :auto_publish => false) } + let!(:build_list_package) { FactoryGirl.create(:build_list_package, + :build_list => build_list, + :project => build_list.project) } + + + before(:all) { ActionMailer::Base.deliveries = [] } + before do + test_git_commit(build_list.project) + build_list.update_attributes(:commit_hash => build_list.project.repo.commits('master').last.id, + :status => BuildServer::BUILD_STARTED,) + end + after { ActionMailer::Base.deliveries = [] } + + shared_examples_for 'build list notifications by email' do + it "gets notification by email when status - Build complete" do + build_list.build_success + should have(1).item + end + + it "gets notification by email when status - Build error" do + build_list.build_error + should have(1).item + end + + it "gets notification by email when auto_publish and status - Build error" do + build_list.update_attributes(:auto_publish => true) + build_list.build_error + should have(1).item + end + + it "gets notification by email when status - Failed publish" do + build_list.update_attributes(:status => BuildList::BUILD_PUBLISH) + build_list.fail_publish + should have(1).item + end + + it "gets notification by email when auto_publish and status - Failed publish" do + build_list.update_attributes(:auto_publish => true, :status => BuildList::BUILD_PUBLISH) + build_list.fail_publish + should have(1).item + end + + it "gets notification by email when status - Build published" do + build_list.update_attributes(:status => BuildList::BUILD_PUBLISH) + build_list.published + should have(1).item + end + + it "gets notification by email when auto_publish and status - Build published" do + build_list.update_attributes(:auto_publish => true, :status => BuildList::BUILD_PUBLISH) + build_list.published + should have(1).item + end + + it "doesn't get notification by email when auto_publish and status - Build complete" do + build_list.update_attributes(:auto_publish => true) + build_list.build_success + should have(:no).items + end + + it "doesn't get notification by email when mass build" do + build_list.update_attributes(:mass_build_id => 1, :status => BuildList::BUILD_PUBLISH) + build_list.published + should have(:no).items + end + + it "doesn't get notification by email when notification by email has been disabled" do + notifier.update_attributes(:can_notify => false) + build_list.build_success + should have(:no).items + end + end + + subject { ActionMailer::Base.deliveries } + + context "user created build task" do + let!(:notifier) { user.notifier } + before do + notifier.update_attributes(:new_associated_build => false) + build_list.project.owner.notifier.update_attributes(:can_notify => false) + end + + it_should_behave_like 'build list notifications by email' + + it "doesn't get notification by email when 'build list' notifications has been disabled" do + notifier.update_attributes(:new_build => false) + build_list.build_success + should have(:no).items + end + + it "doesn't get notification by email when 'build list' notifications - enabled, email notifications - disabled" do + notifier.update_attributes(:can_notify => false, :new_build => true) + build_list.build_success + should have(:no).items + end + end + + context "build task has been created and associated user" do + let!(:notifier) { build_list.project.owner.notifier } + before do + notifier.update_attributes(:new_build => false) + user.notifier.update_attributes(:can_notify => false) + end + + it_should_behave_like 'build list notifications by email' + + it "doesn't get notification by email when 'associated build list' notifications has been disabled" do + notifier.update_attributes(:new_associated_build => false) + build_list.build_success + should have(:no).items + end + + it "doesn't get notification by email when 'associated build list' notifications - enabled, email notifications - disabled" do + notifier.update_attributes(:can_notify => false, :new_associated_build => true) + build_list.build_success + should have(:no).items + end + end + + it "doesn't get 2 notification by email when user associated to project and created task" do + bl = FactoryGirl.create(:build_list_core, + :user => user, + :auto_publish => true, + :project => FactoryGirl.create(:project, :owner => user)) + FactoryGirl.create(:build_list_package, :build_list => bl, :project => bl.project) + test_git_commit(bl.project) + bl.update_attributes(:commit_hash => bl.project.repo.commits('master').last.id, + :status => BuildList::BUILD_PUBLISH) + bl.published + should have(1).item + end + + end # notify_users + +end diff --git a/spec/models/cancan_spec.rb b/spec/models/cancan_spec.rb index 5f0ba821a..cda03535f 100644 --- a/spec/models/cancan_spec.rb +++ b/spec/models/cancan_spec.rb @@ -290,26 +290,6 @@ describe CanCan do @ability.should be_able_to(:read, @repository) end end - end - - context 'build list relations' do - before(:each) do - @project = FactoryGirl.create(:project) - @project.relations.create!(:actor_id => @user.id, :actor_type => 'User', :role => 'writer') - @build_list = FactoryGirl.create(:build_list, :project => @project) - end - - it 'should be able to publish build list with SUCCESS status' do - @build_list.status = BuildServer::SUCCESS - @ability.should be_able_to(:publish, @build_list) - end - - it 'should not be able to publish build list with another status' do - @build_list.status = BuildServer::BUILD_ERROR - @ability.should_not be_able_to(:publish, @build_list) - end - end - end - - + end # 'repository relations' + end # 'Site user' end diff --git a/spec/models/comment_for_commit_spec.rb b/spec/models/comment_for_commit_spec.rb index 4b6ce9875..c27479215 100644 --- a/spec/models/comment_for_commit_spec.rb +++ b/spec/models/comment_for_commit_spec.rb @@ -148,10 +148,10 @@ describe Comment do @user = FactoryGirl.create(:user) @stranger = FactoryGirl.create(:user) set_comments_data_for_commit - + @project.owner = @user @project.save - + ActionMailer::Base.deliveries = [] end @@ -301,7 +301,7 @@ describe Comment do context 'for committer' do it 'should send an e-mail' do - @simple.update_column :email, 'code@tpope.net' + @simple.update_column :email, 'test@test.test' comment = create_comment(@user) ActionMailer::Base.deliveries.count.should == 1 ActionMailer::Base.deliveries.last.to.include?(@simple.email).should == true @@ -309,21 +309,21 @@ describe Comment do it 'should send a one e-mail when subscribed to commit' do Subscribe.subscribe_to_commit @subscribe_params.merge(:user_id => @simple.id) - @simple.update_column :email, 'code@tpope.net' + @simple.update_column :email, 'test@test.test' comment = create_comment(@user) ActionMailer::Base.deliveries.count.should == 1 ActionMailer::Base.deliveries.last.to.include?(@simple.email).should == true end it 'should not send an e-mail for own comment' do - @simple.update_column :email, 'code@tpope.net' + @simple.update_column :email, 'test@test.test' comment = create_comment(@simple) ActionMailer::Base.deliveries.count.should == 0 end it 'should not send an e-mail if global notify off' do @project.owner.notifier.update_column :can_notify, false - @simple.update_column :email, 'code@tpope.net' + @simple.update_column :email, 'test@test.test' @simple.notifier.update_column :can_notify, false comment = create_comment(@user) ActionMailer::Base.deliveries.count.should == 0 @@ -332,7 +332,7 @@ describe Comment do it 'should not send an e-mail if notify for my commits off' do Comment.destroy_all @simple.notifier.update_column :new_comment_commit_owner, false - @simple.update_column :email, 'code@tpope.net' + @simple.update_column :email, 'test@test.test' comment = create_comment(@user) ActionMailer::Base.deliveries.count.should == 0 end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index e1bede7bf..c843b2ab6 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -10,7 +10,7 @@ describe Group do end context 'for guest' do - [:read, :update, :destroy, :manage_members, :autocomplete_group_uname].each do |action| + [:read, :update, :destroy, :manage_members].each do |action| it "should not be able to #{action} group" do @ability.should_not be_able_to(action, @group) end @@ -23,7 +23,7 @@ describe Group do @ability = Ability.new(@admin) end - [:read, :update, :destroy, :manage_members, :autocomplete_group_uname].each do |action| + [:read, :update, :destroy, :manage_members].each do |action| it "should be able to #{action} group" do @ability.should be_able_to(action, @group) end @@ -38,7 +38,7 @@ describe Group do @ability = Ability.new(@user) end - [:read, :update, :manage_members, :autocomplete_group_uname].each do |action| + [:read, :update, :manage_members].each do |action| it "should be able to #{action} group" do @ability.should be_able_to(action, @group) end @@ -70,7 +70,7 @@ describe Group do @ability = Ability.new(@user) end - [:read, :update, :destroy, :manage_members, :autocomplete_group_uname].each do |action| + [:read, :update, :destroy, :manage_members].each do |action| it "should be able to #{action} group" do @ability.should be_able_to(action, @group) end @@ -84,7 +84,7 @@ describe Group do @ability = Ability.new(@user) end - [:read, :autocomplete_group_uname].each do |action| + [:read].each do |action| it "should be able to #{action} group" do @ability.should be_able_to(action, @group) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ab13f6ae3..c2892dec4 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -9,17 +9,35 @@ describe Project do @child_child_project = @child_project.fork(FactoryGirl.create(:user)) end - context 'for destroy root' do - before do - @root_project.destroy + context 'for destroy' do + let!(:root_project) { FactoryGirl.create(:project) } + let!(:child_project) { root_project.fork(FactoryGirl.create(:user)) } + let!(:child_child_project) { child_project.fork(FactoryGirl.create(:user)) } + + context 'root project' do + before { root_project.destroy } + + it "should not be delete child" do + Project.where(:id => child_project).count.should == 1 + end + + it "should not be delete child of the child" do + Project.where(:id => child_child_project).count.should == 1 + end end - it "should not be delete child" do - Project.where(:id => @child_project).count.should == 1 - end + pending 'when will be available :orphan_strategy => :adopt' do + context 'middle node' do + before{ child_project.destroy } - it "should not be delete child of the child" do - Project.where(:id => @child_child_project).count.should == 1 + it "should set root project as a parent for orphan child" do + Project.find(child_child_project).ancestry == root_project + end + + it "should not be delete child of the child" do + Project.where(:id => child_child_project).count.should == 1 + end + end end end @@ -48,19 +66,17 @@ describe Project do end end - # uncommit when will be available :orphan_strategy => :adopt + context 'truncates project name before validation' do + let!(:project) { FactoryGirl.build(:project, :name => ' test_name ') } - #context 'for destroy middle node' do - # before(:each) do - # @child_project.destroy - # end + it 'ensures that validation passed' do + project.valid?.should be_true + end + + it 'ensures that name has been truncated' do + project.valid? + project.name.should == 'test_name' + end + end - # it "should set root project as a parent for orphan child" do - # Project.find(@child_child_project).ancestry == @root_project - # end - - # it "should not be delete child of the child" do - # Project.where(:id => @child_child_project).count.should == 1 - # end - #end end diff --git a/spec/models/pull_request_spec.rb b/spec/models/pull_request_spec.rb new file mode 100644 index 000000000..617f365d5 --- /dev/null +++ b/spec/models/pull_request_spec.rb @@ -0,0 +1,115 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +def set_data_for_pull + @ability = Ability.new(@user) + + @project = FactoryGirl.create(:project, :owner => @user) + %x(cp -Rf #{Rails.root}/spec/tests.git/* #{@project.path}) + + @clone_path = File.join(APP_CONFIG['root_path'], 'repo_clone', @project.id.to_s) + FileUtils.mkdir_p(@clone_path) + + @other_project = FactoryGirl.create(:project, :owner => @user) + %x(cp -Rf #{Rails.root}/spec/tests.git/* #{@other_project.path}) + + any_instance_of(Project, :versions => ['v1.0', 'v2.0']) +end + +describe PullRequest do + + context 'for owner user' do + before do + stub_symlink_methods + @user = FactoryGirl.create(:user) + set_data_for_pull + @pull = @project.pull_requests.new(:issue_attributes => {:title => 'test', :body => 'testing'}) + @pull.issue.user, @pull.issue.project = @user, @pull.to_project + @pull.to_ref = 'master' + @pull.from_project, @pull.from_ref = @project, 'non_conflicts' + @pull.save + + @other_pull = @project.pull_requests.new(:issue_attributes => {:title => 'test_other', :body => 'testing_other'}) + @other_pull.issue.user, @other_pull.issue.project = @user, @other_pull.to_project + @other_pull.to_ref = 'master' + @other_pull.from_project, @other_pull.from_ref = @other_project, 'non_conflicts' + @other_pull.save + end + + it 'master should merge with non_conflicts branch' do + @pull.check + @pull.status.should == 'ready' + end + + it 'master should not merge with conflicts branch' do + @pull.from_ref = 'conflicts' + @pull.check + @pull.status.should == 'blocked' + end + + it 'should already merged when already up-to-date branches' do + @pull.from_ref = 'master' + @pull.check + @pull.status.should == 'merged' + end + + context 'for other head project' do + it 'master should merge with non_conflicts branch' do + @other_pull.check + @other_pull.status.should == 'ready' + end + + it 'master should not merge with conflicts branch' do + @other_pull.from_ref = 'conflicts' + @other_pull.check + @other_pull.status.should == 'blocked' + end + + it 'should already merged when already up-to-date branches' do + @other_pull.from_ref = 'master' + @other_pull.check + @other_pull.status.should == 'merged' + end + end + + it "should not create same pull" do + @same_pull = @project.pull_requests.new(:issue_attributes => {:title => 'same', :body => 'testing'}) + @same_pull.issue.user, @same_pull.issue.project = @user, @same_pull.to_project + @same_pull.to_ref = 'master' + @same_pull.from_project, @same_pull.from_ref = @project, 'non_conflicts' + @same_pull.save + @project.pull_requests.joins(:issue).where(:issues => {:title => @same_pull.title}).count.should == 0 + end + + it "should not create pull with wrong base ref" do + @wrong_pull = @project.pull_requests.new(:issue_attributes => {:title => 'wrong base', :body => 'testing'}) + @wrong_pull.issue.user, @wrong_pull.issue.project = @user, @wrong_pull.to_project + @wrong_pull.to_ref = 'wrong' + @wrong_pull.from_project, @wrong_pull.from_ref = @project, 'non_conflicts' + @wrong_pull.save + @project.pull_requests.joins(:issue).where(:issues => {:title => @wrong_pull.title}).count.should == 0 + end + + it "should not create pull with wrong head ref" do + @wrong_pull = @project.pull_requests.new(:issue_attributes => {:title => 'wrong head', :body => 'testing'}) + @wrong_pull.issue.user, @wrong_pull.issue.project = @user, @wrong_pull.to_project + @wrong_pull.to_ref = 'master' + @wrong_pull.from_project, @wrong_pull.from_ref = @project, 'wrong' + @wrong_pull.save + @project.pull_requests.joins(:issue).where(:issues => {:title => @wrong_pull.title}).count.should == 0 + end + end + + before do + FileUtils.rm_rf(APP_CONFIG['root_path']) + end + + it { should belong_to(:issue) } + it { should belong_to(:to_project) } + it { should belong_to(:from_project) } + + after do + FileUtils.rm_rf(APP_CONFIG['root_path']) + FileUtils.rm_rf File.join(Rails.root, "tmp", Rails.env, "pull_requests") + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e7ce00299..e168084b3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -27,7 +27,7 @@ RSpec.configure do |config| config.use_transactional_fixtures = true config.filter_run_excluding :anonymous_access => !(APP_CONFIG['anonymous_access']) - + end def set_session_for(user=nil) @@ -36,6 +36,11 @@ def set_session_for(user=nil) sign_in current_user end +def http_login(user=nil) + # FIXME: password constant is a bad choice... + request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user.email,'123456') +end + def stub_symlink_methods any_instance_of(Platform, :symlink_directory => true) any_instance_of(Platform, :remove_symlink_directory => true) diff --git a/spec/tests.git/HEAD b/spec/tests.git/HEAD index df9ee5473..cb089cd89 100644 --- a/spec/tests.git/HEAD +++ b/spec/tests.git/HEAD @@ -1 +1 @@ -bdc8b580b5b583aeb43efb19aac2ab8ce5566dff +ref: refs/heads/master diff --git a/spec/tests.git/config b/spec/tests.git/config index d38f8c6fe..07d359d07 100644 --- a/spec/tests.git/config +++ b/spec/tests.git/config @@ -1,11 +1,4 @@ [core] repositoryformatversion = 0 filemode = true - bare = false - logallrefupdates = true -[remote "origin"] - fetch = +refs/heads/*:refs/remotes/origin/* - url = git://github.com/tpope/vim-ragtag.git -[branch "master"] - remote = origin - merge = refs/heads/master + bare = true diff --git a/spec/tests.git/index b/spec/tests.git/index deleted file mode 100644 index 29cca27d4..000000000 Binary files a/spec/tests.git/index and /dev/null differ diff --git a/spec/tests.git/info/refs b/spec/tests.git/info/refs new file mode 100644 index 000000000..0e0a7c627 --- /dev/null +++ b/spec/tests.git/info/refs @@ -0,0 +1,5 @@ +5dc2326fab1022c9c35b27d09c62a73216fdff5e refs/heads/conficts +45d6bc11f4de8546ccbef16fdd3acf48fe0b411c refs/heads/conflicts +2babca2e12fe8e5f0ea95e8547a9ce20c322cb79 refs/heads/master +5c2312c4054bae9c1419dbec8633eda29f7c0731 refs/heads/non_conflicts +00a2f9225dcc5807b9eb46f1bcfc56ee939e595f refs/heads/not_conflicts diff --git a/spec/tests.git/logs/HEAD b/spec/tests.git/logs/HEAD deleted file mode 100644 index f47abecdc..000000000 --- a/spec/tests.git/logs/HEAD +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 bdc8b580b5b583aeb43efb19aac2ab8ce5566dff Alexander 1325695134 +0600 clone: from git://github.com/tpope/vim-ragtag.git -bdc8b580b5b583aeb43efb19aac2ab8ce5566dff bdc8b580b5b583aeb43efb19aac2ab8ce5566dff Alexander 1325695134 +0600 checkout: moving from master to bdc8b580b5b583aeb43efb19aac2ab8ce5566dff diff --git a/spec/tests.git/logs/refs/heads/master b/spec/tests.git/logs/refs/heads/master deleted file mode 100644 index 9219de536..000000000 --- a/spec/tests.git/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 bdc8b580b5b583aeb43efb19aac2ab8ce5566dff Alexander 1325695134 +0600 clone: from git://github.com/tpope/vim-ragtag.git diff --git a/spec/tests.git/objects/3b/efc54229f8e25165be593e67f33fb9a8495374 b/spec/tests.git/objects/3b/efc54229f8e25165be593e67f33fb9a8495374 new file mode 100644 index 000000000..e76437b56 --- /dev/null +++ b/spec/tests.git/objects/3b/efc54229f8e25165be593e67f33fb9a8495374 @@ -0,0 +1,2 @@ +xM +0a9L22I&E[im=g _]y@-\ZE+3C$+:M,(')=|!lD h3^C9\Ftpŀhп3-0~ \>y \ No newline at end of file diff --git a/spec/tests.git/objects/info/packs b/spec/tests.git/objects/info/packs new file mode 100644 index 000000000..870d85fea --- /dev/null +++ b/spec/tests.git/objects/info/packs @@ -0,0 +1,2 @@ +P pack-09ad35f874bea18d5f133a59210e4f80a1e81c17.pack + diff --git a/spec/tests.git/objects/pack/pack-09ad35f874bea18d5f133a59210e4f80a1e81c17.idx b/spec/tests.git/objects/pack/pack-09ad35f874bea18d5f133a59210e4f80a1e81c17.idx new file mode 100644 index 000000000..6cf3769c5 Binary files /dev/null and b/spec/tests.git/objects/pack/pack-09ad35f874bea18d5f133a59210e4f80a1e81c17.idx differ diff --git a/spec/tests.git/objects/pack/pack-09ad35f874bea18d5f133a59210e4f80a1e81c17.pack b/spec/tests.git/objects/pack/pack-09ad35f874bea18d5f133a59210e4f80a1e81c17.pack new file mode 100644 index 000000000..974873a7b Binary files /dev/null and b/spec/tests.git/objects/pack/pack-09ad35f874bea18d5f133a59210e4f80a1e81c17.pack differ diff --git a/spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.idx b/spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.idx deleted file mode 100644 index 46bd6036d..000000000 Binary files a/spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.idx and /dev/null differ diff --git a/spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.pack b/spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.pack deleted file mode 100644 index 593982972..000000000 Binary files a/spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.pack and /dev/null differ diff --git a/spec/tests.git/packed-refs b/spec/tests.git/packed-refs index b48a6248c..130291d58 100644 --- a/spec/tests.git/packed-refs +++ b/spec/tests.git/packed-refs @@ -1,5 +1,3 @@ -# pack-refs with: peeled -30aefeac002db3ec08ff278bd76290645469611e refs/tags/v2.0 -^644c62ad7bc7d9a4a5f19f5e8c41ef910782178b -235e4467107467feacc50553bbeda15e9bf99f57 refs/tags/v1.11 -bdc8b580b5b583aeb43efb19aac2ab8ce5566dff refs/remotes/origin/master +45d6bc11f4de8546ccbef16fdd3acf48fe0b411c refs/heads/conflicts +2babca2e12fe8e5f0ea95e8547a9ce20c322cb79 refs/heads/master +5c2312c4054bae9c1419dbec8633eda29f7c0731 refs/heads/non_conflicts diff --git a/spec/tests.git/refs/heads/master b/spec/tests.git/refs/heads/master deleted file mode 100644 index df9ee5473..000000000 --- a/spec/tests.git/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -bdc8b580b5b583aeb43efb19aac2ab8ce5566dff diff --git a/spec/tests.git/refs/remotes/origin/HEAD b/spec/tests.git/refs/remotes/origin/HEAD deleted file mode 100644 index 6efe28fff..000000000 --- a/spec/tests.git/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/remotes/origin/master diff --git a/vendor/assets/javascripts/bootstrap-tab.js b/vendor/assets/javascripts/bootstrap-tab.js new file mode 100644 index 000000000..a26da9b6c --- /dev/null +++ b/vendor/assets/javascripts/bootstrap-tab.js @@ -0,0 +1,130 @@ +/* ======================================================== + * bootstrap-tab.js v2.0.2 + * http://twitter.github.com/bootstrap/javascript.html#tabs + * ======================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================== */ + + +!function( $ ){ + + "use strict" + + /* TAB CLASS DEFINITION + * ==================== */ + + var Tab = function ( element ) { + this.element = $(element) + } + + Tab.prototype = { + + constructor: Tab + + , show: function () { + var $this = this.element + , $ul = $this.closest('ul:not(.dropdown-menu)') + , selector = $this.attr('data-target') + , previous + , $target + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + if ( $this.parent('li').hasClass('active') ) return + + previous = $ul.find('.active a').last()[0] + + $this.trigger({ + type: 'show' + , relatedTarget: previous + }) + + $target = $(selector) + + this.activate($this.parent('li'), $ul) + this.activate($target, $target.parent(), function () { + $this.trigger({ + type: 'shown' + , relatedTarget: previous + }) + }) + } + + , activate: function ( element, container, callback) { + var $active = container.find('> .active') + , transition = callback + && $.support.transition + && $active.hasClass('fade') + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + + element.addClass('active') + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if ( element.parent('.dropdown-menu') ) { + element.closest('li.dropdown').addClass('active') + } + + callback && callback() + } + + transition ? + $active.one($.support.transition.end, next) : + next() + + $active.removeClass('in') + } + } + + + /* TAB PLUGIN DEFINITION + * ===================== */ + + $.fn.tab = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('tab') + if (!data) $this.data('tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.tab.Constructor = Tab + + + /* TAB DATA-API + * ============ */ + + $(function () { + $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { + e.preventDefault() + $(this).tab('show') + }) + }) + +}( window.jQuery ); diff --git a/vendor/assets/javascripts/vendor.js b/vendor/assets/javascripts/vendor.js index a4d1ebcfb..08fc17f77 100644 --- a/vendor/assets/javascripts/vendor.js +++ b/vendor/assets/javascripts/vendor.js @@ -10,9 +10,11 @@ //= require bootstrap-modal //= require bootstrap-button //= require bootstrap-dropdown +//= require bootstrap-tab // require bootstrap-tooltip // require bootstrap-popover //= require bootstrap-alert +//= require bootstrap-tab //= require chosen.jquery // require html5shiv // require_tree . diff --git a/vendor/assets/stylesheets/bootstrap.css b/vendor/assets/stylesheets/bootstrap.css index f43391083..f5b1a58bc 100644 --- a/vendor/assets/stylesheets/bootstrap.css +++ b/vendor/assets/stylesheets/bootstrap.css @@ -419,4 +419,400 @@ -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -} \ No newline at end of file +} +.nav { + margin-left: 0; + margin-bottom: 20px; + list-style: none; +} +.nav > li > a { + display: block; +} +.nav > li > a:hover { + text-decoration: none; + background-color: #eeeeee; +} +.nav > .pull-right { + float: right; +} +.nav-header { + display: block; + padding: 3px 15px; + font-size: 11px; + font-weight: bold; + line-height: 20px; + color: #999999; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-transform: uppercase; +} +.nav li + .nav-header { + margin-top: 9px; +} +.nav-list { + padding-left: 15px; + padding-right: 15px; + margin-bottom: 0; +} +.nav-list > li > a, +.nav-list .nav-header { + margin-left: -15px; + margin-right: -15px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); +} +.nav-list > li > a { + padding: 3px 15px; +} +.nav-list > .active > a, +.nav-list > .active > a:hover { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); + background-color: #0088cc; +} +.nav-list [class^="icon-"] { + margin-right: 2px; +} +.nav-list .divider { + *width: 100%; + height: 1px; + margin: 9px 1px; + *margin: -5px 0 5px; + overflow: hidden; + background-color: #e5e5e5; + border-bottom: 1px solid #ffffff; +} +.nav-tabs, +.nav-pills { + *zoom: 1; +} +.nav-tabs:before, +.nav-pills:before, +.nav-tabs:after, +.nav-pills:after { + display: table; + content: ""; + line-height: 0; +} +.nav-tabs:after, +.nav-pills:after { + clear: both; +} +.nav-tabs > li, +.nav-pills > li { + float: left; +} +.nav-tabs > li > a, +.nav-pills > li > a { + padding-right: 12px; + padding-left: 12px; + margin-right: 2px; + line-height: 14px; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + margin-bottom: -1px; +} +.nav-tabs > li > a { + padding-top: 8px; + padding-bottom: 8px; + line-height: 20px; + border: 1px solid transparent; + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #dddddd; +} +.nav-tabs > .active > a, +.nav-tabs > .active > a:hover { + color: #555555; + background-color: #ffffff; + border: 1px solid #ddd; + border-bottom-color: transparent; + cursor: default; +} +.nav-pills > li > a { + padding-top: 8px; + padding-bottom: 8px; + margin-top: 2px; + margin-bottom: 2px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} +.nav-pills > .active > a, +.nav-pills > .active > a:hover { + color: #ffffff; + background-color: #0088cc; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li > a { + margin-right: 0; +} +.nav-tabs.nav-stacked { + border-bottom: 0; +} +.nav-tabs.nav-stacked > li > a { + border: 1px solid #ddd; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.nav-tabs.nav-stacked > li:first-child > a { + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + border-top-right-radius: 4px; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; +} +.nav-tabs.nav-stacked > li:last-child > a { + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; + border-bottom-right-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; +} +.nav-tabs.nav-stacked > li > a:hover { + border-color: #ddd; + z-index: 2; +} +.nav-pills.nav-stacked > li > a { + margin-bottom: 3px; +} +.nav-pills.nav-stacked > li:last-child > a { + margin-bottom: 1px; +} +.nav-tabs .dropdown-menu { + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; +} +.nav-pills .dropdown-menu { + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} +.nav .dropdown-toggle .caret { + border-top-color: #0088cc; + border-bottom-color: #0088cc; + margin-top: 6px; +} +.nav .dropdown-toggle:hover .caret { + border-top-color: #005580; + border-bottom-color: #005580; +} +/* move down carets for tabs */ +.nav-tabs .dropdown-toggle .caret { + margin-top: 8px; +} +.nav .active .dropdown-toggle .caret { + border-top-color: #fff; + border-bottom-color: #fff; +} +.nav-tabs .active .dropdown-toggle .caret { + border-top-color: #555555; + border-bottom-color: #555555; +} +.nav > .dropdown.active > a:hover { + cursor: pointer; +} +.nav-tabs .open .dropdown-toggle, +.nav-pills .open .dropdown-toggle, +.nav > li.dropdown.open.active > a:hover { + color: #ffffff; + background-color: #999999; + border-color: #999999; +} +.nav li.dropdown.open .caret, +.nav li.dropdown.open.active .caret, +.nav li.dropdown.open a:hover .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; + opacity: 1; + filter: alpha(opacity=100); +} +.tabs-stacked .open > a:hover { + border-color: #999999; +} +.tabbable { + *zoom: 1; +} +.tabbable:before, +.tabbable:after { + display: table; + content: ""; + line-height: 0; +} +.tabbable:after { + clear: both; +} +.tab-content { + overflow: auto; +} +.tabs-below > .nav-tabs, +.tabs-right > .nav-tabs, +.tabs-left > .nav-tabs { + border-bottom: 0; +} +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} +.tab-content > .active, +.pill-content > .active { + display: block; +} +.tabs-below > .nav-tabs { + border-top: 1px solid #ddd; +} +.tabs-below > .nav-tabs > li { + margin-top: -1px; + margin-bottom: 0; +} +.tabs-below > .nav-tabs > li > a { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} +.tabs-below > .nav-tabs > li > a:hover { + border-bottom-color: transparent; + border-top-color: #ddd; +} +.tabs-below > .nav-tabs > .active > a, +.tabs-below > .nav-tabs > .active > a:hover { + border-color: transparent #ddd #ddd #ddd; +} +.tabs-left > .nav-tabs > li, +.tabs-right > .nav-tabs > li { + float: none; +} +.tabs-left > .nav-tabs > li > a, +.tabs-right > .nav-tabs > li > a { + min-width: 74px; + margin-right: 0; + margin-bottom: 3px; +} +.tabs-left > .nav-tabs { + float: left; + margin-right: 19px; + border-right: 1px solid #ddd; +} +.tabs-left > .nav-tabs > li > a { + margin-right: -1px; + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} +.tabs-left > .nav-tabs > li > a:hover { + border-color: #eeeeee #dddddd #eeeeee #eeeeee; +} +.tabs-left > .nav-tabs .active > a, +.tabs-left > .nav-tabs .active > a:hover { + border-color: #ddd transparent #ddd #ddd; + *border-right-color: #ffffff; +} +.tabs-right > .nav-tabs { + float: right; + margin-left: 19px; + border-left: 1px solid #ddd; +} +.tabs-right > .nav-tabs > li > a { + margin-left: -1px; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} +.tabs-right > .nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #eeeeee #dddddd; +} +.tabs-right > .nav-tabs .active > a, +.tabs-right > .nav-tabs .active > a:hover { + border-color: #ddd #ddd #ddd transparent; + *border-left-color: #ffffff; +} +.nav > .disabled > a { + color: #999999; +} +.nav > .disabled > a:hover { + text-decoration: none; + background-color: transparent; + cursor: default; +} + + +.label-bootstrap, +.badge { + font-size: 10.998px; + font-weight: bold; + line-height: 14px; + color: #ffffff; + vertical-align: baseline; + white-space: nowrap; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #999999; +} +.label-bootstrap { + padding: 1px 4px 2px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.badge { + padding: 1px 9px 2px; + -webkit-border-radius: 9px; + -moz-border-radius: 9px; + border-radius: 9px; +} +a.label-bootstrap:hover, +a.badge:hover { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.label-important, +.badge-important { + background-color: #b94a48; +} +.label-important[href], +.badge-important[href] { + background-color: #953b39; +} +.label-warning, +.badge-warning { + background-color: #f89406; +} +.label-warning[href], +.badge-warning[href] { + background-color: #c67605; +} +.label-success, +.badge-success { + background-color: #468847; +} +.label-success[href], +.badge-success[href] { + background-color: #356635; +} +.label-info, +.badge-info { + background-color: #3a87ad; +} +.label-info[href], +.badge-info[href] { + background-color: #2d6987; +} +.label-inverse, +.badge-inverse { + background-color: #333333; +} +.label-inverse[href], +.badge-inverse[href] { + background-color: #1a1a1a; +} +