diff --git a/.rails_footnotes b/.rails_footnotes deleted file mode 100644 index 1019a70..0000000 --- a/.rails_footnotes +++ /dev/null @@ -1,3 +0,0 @@ -#this code temporarily disables notes for all controllers -# Footnotes::Filter.notes = [] - diff --git a/CHANGELOG b/CHANGELOG index 80b68b0..7322efb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,32 @@ +v 2.9.0 + - fixed inline notes bugs + - refactored rspecs + - refactored gitolite backend + - added factory_girl + - restyled projects list on dashboard + - ssh keys validation to prevent gitolite crash + - send notifications if changed premission in project + - scss refactoring. gitlab_bootstrap/ dir + - fix git push http body bigger than 112k problem + - list of labels page under issues tab + - API for milestones + - restyled buttons + +v 2.8.1 + - ability to disable gravatars + - improved MR diff logic + - ssh key help page + v 2.8.0 - Gitlab Flavored Markdown - Bulk issues update - Issues API - Cucumber coverage increased + - Post-receive files fixed + - UI improved + - Application cleanup + - more cucumber + - capybara-webkit + headless v 2.7.0 - Issue Labels diff --git a/Gemfile b/Gemfile index e8b0b24..b0724fa 100644 --- a/Gemfile +++ b/Gemfile @@ -54,7 +54,7 @@ gem "unicorn" gem "acts-as-taggable-on", "2.3.1" # Decorators -gem "drapper" +gem "draper" # Background jobs gem "resque", "~> 1.20.0" @@ -92,7 +92,6 @@ end group :development do gem "letter_opener" - gem "rails-footnotes" gem "annotate", :git => "https://github.com/ctran/annotate_models.git" gem 'rack-mini-profiler' end @@ -108,15 +107,18 @@ group :development, :test do gem "awesome_print" gem "database_cleaner" gem "launchy" + gem 'factory_girl_rails' end group :test do gem 'cucumber-rails', :require => false - gem 'minitest', ">= 2.10" - gem "turn", :require => false gem "simplecov", :require => false gem "shoulda-matchers" gem 'email_spec' gem 'resque_spec' gem "webmock" end + +group :production do + gem "gitlab_meta", '2.9' +end diff --git a/Gemfile.lock b/Gemfile.lock index b23bc47..f226c93 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -99,7 +99,6 @@ GEM acts-as-taggable-on (2.3.1) rails (~> 3.0) addressable (2.2.8) - ansi (1.4.2) arel (3.0.2) autotest (4.4.6) ZenTest (>= 4.4.1) @@ -156,7 +155,9 @@ GEM railties (~> 3.1) warden (~> 1.2.1) diff-lcs (1.1.3) - drapper (0.8.4) + draper (0.17.0) + actionpack (~> 3.2) + activesupport (~> 3.2) email_spec (1.2.1) mail (~> 2.2) rspec (~> 2.0) @@ -165,6 +166,11 @@ GEM eventmachine (0.12.10) execjs (1.4.0) multi_json (~> 1.0) + factory_girl (4.0.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.0.0) + factory_girl (~> 4.0.0) + railties (>= 3.0.0) ffaker (1.14.0) ffi (1.0.11) foreman (0.47.0) @@ -172,6 +178,7 @@ GEM gherkin (2.11.0) json (>= 1.4.6) git (1.2.5) + gitlab_meta (2.9) grape (0.2.1) hashie (~> 1.2) multi_json @@ -218,7 +225,6 @@ GEM treetop (~> 1.4.8) method_source (0.7.1) mime-types (1.19) - minitest (3.1.0) modernizr (2.5.3) sprockets (~> 2.0) multi_json (1.3.6) @@ -258,8 +264,6 @@ GEM activesupport (= 3.2.8) bundler (~> 1.0) railties (= 3.2.8) - rails-footnotes (3.7.8) - rails (>= 3.0.0) railties (3.2.8) actionpack (= 3.2.8) activesupport (= 3.2.8) @@ -349,8 +353,6 @@ GEM treetop (1.4.10) polyglot polyglot (>= 0.3.1) - turn (0.9.5) - ansi tzinfo (0.3.33) uglifier (1.0.3) execjs (>= 0.3.0) @@ -389,11 +391,13 @@ DEPENDENCIES cucumber-rails database_cleaner devise (~> 2.1.0) - drapper + draper email_spec + factory_girl_rails ffaker foreman git + gitlab_meta (= 2.9) gitolite! grack! grape (~> 0.2.1) @@ -407,7 +411,6 @@ DEPENDENCIES launchy letter_opener linguist (~> 1.0.0)! - minitest (>= 2.10) modernizr (= 2.5.3) mysql2 omniauth-ldap! @@ -415,7 +418,6 @@ DEPENDENCIES pygments.rb! rack-mini-profiler rails (= 3.2.8) - rails-footnotes raphael-rails (= 1.5.2) redcarpet (~> 2.1.1) resque (~> 1.20.0) @@ -432,7 +434,6 @@ DEPENDENCIES stamp therubyracer thin - turn uglifier (= 1.0.3) unicorn webmock diff --git a/README.md b/README.md index 26ed209..122cd98 100644 --- a/README.md +++ b/README.md @@ -39,5 +39,6 @@ Email ## Contribute +[Development Tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md) Want to help - send a pull request. We'll accept good pull requests. diff --git a/VERSION b/VERSION index 3f80e49..a564e65 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.8.0pre +2.9.0pre diff --git a/app/assets/images/file_dir.png b/app/assets/images/file_dir.png index 97b0539..ea277bb 100644 Binary files a/app/assets/images/file_dir.png and b/app/assets/images/file_dir.png differ diff --git a/app/assets/images/merge.png b/app/assets/images/merge.png new file mode 100644 index 0000000..4a6bb2e Binary files /dev/null and b/app/assets/images/merge.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index d9cbd5d..f69fd6f 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -72,7 +72,7 @@ $(document).ready(function(){ * Note markdown preview * */ - $('#preview-link').on('click', function(e) { + $(document).on('click', '#preview-link', function(e) { $('#preview-note').text('Loading...'); var previewLinkText = ($(this).text() == 'Preview' ? 'Edit' : 'Preview'); @@ -128,3 +128,23 @@ function showDiff(link) { function ajaxGet(url) { $.ajax({type: "GET", url: url, dataType: "script"}); } + +/** + * Disable button if text field is empty + */ +function disableButtonIfEmtpyField(field_selector, button_selector) { + field = $(field_selector); + if(field.val() == "") { + field.closest("form").find(button_selector).attr("disabled", "disabled").addClass("disabled"); + } + + field.on('keyup', function(){ + var field = $(this); + var closest_submit = field.closest("form").find(button_selector); + if(field.val() == "") { + closest_submit.attr("disabled", "disabled").addClass("disabled"); + } else { + closest_submit.removeAttr("disabled").removeClass("disabled"); + } + }) +} diff --git a/app/assets/javascripts/issues.js b/app/assets/javascripts/issues.js index bc05696..aae818d 100644 --- a/app/assets/javascripts/issues.js +++ b/app/assets/javascripts/issues.js @@ -5,6 +5,7 @@ function switchToNewIssue(form){ $('select#issue_milestone_id').chosen(); $("#new_issue_dialog").show("fade", { direction: "right" }, 150); $('.top-tabs .add_new').hide(); + disableButtonIfEmtpyField("#issue_title", ".save-btn"); }); } @@ -15,6 +16,7 @@ function switchToEditIssue(form){ $('select#issue_milestone_id').chosen(); $("#edit_issue_dialog").show("fade", { direction: "right" }, 150); $('.add_new').hide(); + disableButtonIfEmtpyField("#issue_title", ".save-btn"); }); } diff --git a/app/assets/javascripts/note.js b/app/assets/javascripts/note.js index d9ae45d..9cd3e36 100644 --- a/app/assets/javascripts/note.js +++ b/app/assets/javascripts/note.js @@ -1,161 +1,160 @@ var NoteList = { -notes_path: null, -target_params: null, -target_id: 0, -target_type: null, -first_id: 0, -last_id: 0, -disable:false, - -init: - function(tid, tt, path) { - this.notes_path = path + ".js"; - this.target_id = tid; - this.target_type = tt; - this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id; - - // get notes - this.getContent(); - - // get new notes every n seconds - this.initRefresh(); - - $('.delete-note').live('ajax:success', function() { - $(this).closest('li').fadeOut(); }); - - $("#new_note").live("ajax:before", function(){ - $(".submit_note").attr("disabled", "disabled"); - }) - - $("#new_note").live("ajax:complete", function(){ - $(".submit_note").removeAttr("disabled"); - }) - - $("#note_note").live("focus", function(){ - $(this).css("height", "80px"); - $('.note_advanced_opts').show(); - }); - - $("#note_attachment").change(function(e){ + notes_path: null, + target_params: null, + target_id: 0, + target_type: null, + first_id: 0, + last_id: 0, + disable:false, + + init: + function(tid, tt, path) { + this.notes_path = path + ".js"; + this.target_id = tid; + this.target_type = tt; + this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id; + + // get notes + this.getContent(); + + // get new notes every n seconds + this.initRefresh(); + + $('.delete-note').live('ajax:success', function() { + $(this).closest('li').fadeOut(); }); + + $(".note-form-holder").live("ajax:before", function(){ + $(".submit_note").attr("disabled", "disabled"); + }) + + $(".note-form-holder").live("ajax:complete", function(){ + $(".submit_note").removeAttr("disabled"); + }) + + disableButtonIfEmtpyField(".note-text", ".submit_note"); + + $(".note-text").live("focus", function(){ + $(this).css("height", "80px"); + $('.note_advanced_opts').show(); + }); + + $("#note_attachment").change(function(e){ var val = $('.input-file').val(); var filename = val.replace(/^.*[\\\/]/, ''); $(".file_name").text(filename); - }); + }); - }, + }, -/** - * Load new notes to fresh list called 'new_notes_list': - * - Replace 'new_notes_list' with new list every n seconds - * - Append new notes to this list after submit - */ + /** + * Load new notes to fresh list called 'new_notes_list': + * - Replace 'new_notes_list' with new list every n seconds + * - Append new notes to this list after submit + */ -initRefresh: - function() { - // init timer - var intNew = setInterval("NoteList.getNew()", 10000); - }, + initRefresh: + function() { + // init timer + var intNew = setInterval("NoteList.getNew()", 10000); + }, -replace: - function(html) { - $("#new_notes_list").html(html); - }, + replace: + function(html) { + $("#new_notes_list").html(html); + }, -prepend: - function(id, html) { - if(id != this.last_id) { - $("#new_notes_list").prepend(html); - } - }, + prepend: + function(id, html) { + if(id != this.last_id) { + $("#new_notes_list").prepend(html); + } + }, -getNew: - function() { - // refersh notes list - $.ajax({ - type: "GET", + getNew: + function() { + // refersh notes list + $.ajax({ + type: "GET", url: this.notes_path, data: "last_id=" + this.last_id + this.target_params, dataType: "script"}); - }, + }, -refresh: - function() { - // refersh notes list - $.ajax({ - type: "GET", + refresh: + function() { + // refersh notes list + $.ajax({ + type: "GET", url: this.notes_path, data: "first_id=" + this.first_id + "&last_id=" + this.last_id + this.target_params, dataType: "script"}); - }, + }, -/** - * Init load of notes: - * 1. Get content with ajax call - * 2. Set content of notes list with loaded one - */ + /** + * Init load of notes: + * 1. Get content with ajax call + * 2. Set content of notes list with loaded one + */ -getContent: - function() { - $.ajax({ - type: "GET", + getContent: + function() { + $.ajax({ + type: "GET", url: this.notes_path, data: "?" + this.target_params, complete: function(){ $('.status').removeClass("loading")}, beforeSend: function() { $('.status').addClass("loading") }, dataType: "script"}); - }, + }, -setContent: - function(fid, lid, html) { + setContent: + function(fid, lid, html) { this.last_id = lid; this.first_id = fid; $("#notes-list").html(html); // Init infinite scrolling this.initLoadMore(); - }, - - -/** - * Paging for old notes when scroll to bottom: - * 1. Init scroll events with 'initLoadMore' - * 2. Load onlder notes with 'getOld' method - * 3. append old notes to bottom of list with 'append' - * - */ - - -getOld: - function() { - $('.loading').show(); - $.ajax({ - type: "GET", - url: this.notes_path, - data: "first_id=" + this.first_id + this.target_params, - complete: function(){ $('.status').removeClass("loading")}, - beforeSend: function() { $('.status').addClass("loading") }, - dataType: "script"}); - }, - -append: - function(id, html) { - if(this.first_id == id) { - this.disable = true; - } else { - this.first_id = id; - $("#notes-list").append(html); - } - }, - + }, + + + /** + * Paging for old notes when scroll to bottom: + * 1. Init scroll events with 'initLoadMore' + * 2. Load onlder notes with 'getOld' method + * 3. append old notes to bottom of list with 'append' + * + */ + getOld: + function() { + $('.loading').show(); + $.ajax({ + type: "GET", + url: this.notes_path, + data: "first_id=" + this.first_id + this.target_params, + complete: function(){ $('.status').removeClass("loading")}, + beforeSend: function() { $('.status').addClass("loading") }, + dataType: "script"}); + }, + + append: + function(id, html) { + if(this.first_id == id) { + this.disable = true; + } else { + this.first_id = id; + $("#notes-list").append(html); + } + }, -initLoadMore: - function() { - $(document).endlessScroll({ - bottomPixels: 400, + initLoadMore: + function() { + $(document).endlessScroll({ + bottomPixels: 400, fireDelay: 1000, fireOnce:true, ceaseFire: function() { @@ -164,6 +163,20 @@ initLoadMore: callback: function(i) { NoteList.getOld(); } - }); - } + }); + } +}; + +var PerLineNotes = { + init: + function() { + $(".line_note_link, .line_note_reply_link").live("click", function(e) { + var form = $(".per_line_form"); + $(this).closest("tr").after(form); + form.find("#note_line_code").val($(this).attr("line_code")); + form.show(); + return false; + }); + disableButtonIfEmtpyField(".line-note-text", ".submit_inline_note"); + } } diff --git a/app/assets/javascripts/projects.js b/app/assets/javascripts/projects.js index 8427269..be1b75b 100644 --- a/app/assets/javascripts/projects.js +++ b/app/assets/javascripts/projects.js @@ -7,8 +7,10 @@ function Projects() { $('.new_project, .edit_project').live('ajax:before', function() { $('.project_new_holder, .project_edit_holder').hide(); - $('.ajax_loader').show(); + $('.save-project-loader').show(); }); $('form #project_default_branch').chosen(); + + disableButtonIfEmtpyField("#project_name", ".project-submit") } diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 6103d05..aa27a28 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -1,11 +1,9 @@ -.diff_file_header a, -.file_stats a { - color:$style_color; -} - - /** LAYOUT **/ +body { + margin-bottom:20px; +} + .container { padding-top:0; z-index:5; @@ -40,30 +38,6 @@ color: $link_color; } -.widget { - @include shade; - padding:20px; - margin-bottom:20px; - border: 1px solid #DDD; - border-radius: 5px; - background:#fafafa; - - .link_holder { - background:#eee; - position:relative; - left:-20px; - top:20px; - padding:10px 20px; - width:100%; - border-top:1px solid #ccc; - - a { - font-size:14px; - color:#666; - } - } -} - .help li { color:#111 } .back_link { @@ -88,16 +62,6 @@ padding-left:20px; } -.number { - border-radius: 4px; - text-shadow: none; - background: rgba(0,0,0,.12); - text-align: center; - padding: 2px 4px; - line-height:18px; - margin-left:2px; -} - table a code { position: relative; top: -2px; @@ -129,26 +93,18 @@ table a code { border-bottom:1px solid #ccc; h4 { - color:#444; - font-size:22px; + color:#666; + font-size:18px; + line-height:38px; padding-top:5px; margin:2px; + font-weight:normal; } } .git_url_wrapper { margin-right:50px } -.file_stats { - span { - img { - width:14px; - float:left; - margin-right:6px; - padding:2px 0; - } - } -} .handle:hover { cursor:move; @@ -172,10 +128,6 @@ span.update-author { display:block; } /** END UPDATE ITEM **/ -.ajax-tab-loading { - padding:40px; - display:none; -} .dashboard-loader { float:left; margin:10px; @@ -186,15 +138,110 @@ span.update-author { font-weight:bold; } -a.project-update.titled { - position:relative; - padding-left:35% !important; - .title-block { - padding:10px; - width:35%; - position:absolute; - left:0; - top:0; +.neib { + margin-right:10px; +} + +.label { + background-color: #474D57; + + &.label-issue { + background-color: #eee; + border: 1px solid #ccc; + padding:4px 6px; + color:#444; + text-shadow:0 0 1px #fff; + + &.grouped { + float: left; + margin-right: 6px; + padding: 6px; + } + } +} + +.event_label { + @extend .label; + background-color: #999; + + &.pushed { + background-color: #4A97BD; + } + + &.opened { + background-color: #469847; + } + + &.closed { + background-color: #B94A48; + } + + &.merged { + background-color: #2A2; + } +} + +form { + @extend .form-horizontal; + + .actions { + @extend .form-actions; + } + + .clearfix { + @extend .control-group; + } + + .input { + @extend .controls; + } + + label { + @extend .control-label; + } + .xlarge { + @extend .input-xlarge; + } + .xxlarge { + @extend .input-xxlarge; + } +} + + +.field_with_errors { + display:inline; +} + +ul.breadcrumb { + background:white; + border:none; + li { + display: inline; + text-shadow: 0 1px 0 white + } + + a { + color:#474D57; + font-weight:bold; + font-size:14px; + } + + .arrow { + background: url("images.png") no-repeat -85px -77px; + width: 19px; + height: 16px; + float: left; + position: relative; + left: -10px; + padding:0; + margin:0; + } +} + +input[type=text] { + &.large_text { + padding:6px; + font-size:16px; } } @@ -270,40 +317,6 @@ p.time { } -/** - * Dashboard page - * - */ -.dashboard_category { - margin-bottom:30px; - h3 a { - color:#474D57; - &:hover { - text-decoration:underline; - } - } - - .dashboard_block { - .dash_project_item { - margin-bottom:10px; - border:none; - padding:0px 5px; - .project_link { - color:#888; - &:hover { - color:#111; - .ico.project { - background-position:-209px -21px; - } - } - } - h4 { - color:#666; - } - } - } -} - .styled_image { border:2px solid #ddd; } @@ -393,39 +406,6 @@ p.time { } } -.btn { - &.very_small { - font-size:11px; - padding:2px 6px; - margin:2px; - } - - &.grouped { - margin-right:7px; - float:left; - } - - &.padded { - margin-right:3px; - padding:4px 10px 4px; - } -} - - -.prettyprint { - background-color: #fefbf3; - padding: 9px; - border: 1px solid rgba(0,0,0,.2); - -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1); - -moz-box-shadow: 0 1px 2px rgba(0,0,0,.1); - box-shadow: 0 1px 2px rgba(0,0,0,.1); -} - -.hint { - font-style: italic; - color: #999; -} - .upvotes { font-size: 14px; font-weight: bold; @@ -549,14 +529,6 @@ li.note { } -/** - * Milestones list - * - */ - -.milestone { - @extend .wll; -} /** * Admin area @@ -603,11 +575,10 @@ li.note { * */ .event_lp { - @extend .alert-info; + @extend .ui-box; + color:#777; margin-bottom:20px; padding:8px; - border-style: solid; - border-width: 1px; @include border-radius(4px); min-height:22px; @@ -621,88 +592,19 @@ li.note { cursor:pointer; } -/** - * Issues, MRs legend - * - */ - -.list_legend { - float:left; - margin-right:20px; - .icon { - width:12px; - height:12px; - float:left; - margin-right:5px; - margin-top: 2px; - @include border-radius(4px); - &.today{ - background: #ADA; - border:1px solid #8B8; - } - &.closed { - background: #DDD; - border:1px solid #BBB; - } - &.yours { - background: #AAD; - border:1px solid #88B; - } - &.merged { - background: #DAD; - border:1px solid #B8B; - } - } - .text { - padding-bottom: 10px; - float:left; - } -} - .merge_request, .issue { - .list_legend { - margin-right: 5px; - margin-top: 14px; - .icon { - width:8px; - height:8px; - float:left; - margin-right:5px; - @include border-radius(4px); - border:1px solid #ddd; - } - } - &.today{ background: #EFE; border-color:#CEC; - .icon { - background: #ADA; - border:1px solid #8B8; - } } &.closed { background: #F5f5f5; border-color:#E5E5E5; - .icon { - background: #DDD; - border:1px solid #BBB; - } - } - &.yours { - .icon { - background: #AAD; - border:1px solid #88B; - } } &.merged { background: #F5f5f5; border-color:#E5E5E5; - .icon { - background: #DAD; - border:1px solid #B8B; - } } } @@ -735,3 +637,11 @@ li.note { font-size: 12px; } } + +.error_message { + @extend .cred; + border-bottom: 1px solid #D21; + padding-bottom:20px; + text-align:center; + margin-bottom:10px; +} diff --git a/app/assets/stylesheets/gitlab_bootstrap.scss b/app/assets/stylesheets/gitlab_bootstrap.scss deleted file mode 100644 index 550046d..0000000 --- a/app/assets/stylesheets/gitlab_bootstrap.scss +++ /dev/null @@ -1,818 +0,0 @@ -body { - margin-bottom:20px; -} -a { - outline: none; - color: $link_color; - &:hover { - text-decoration:none; - color: $blue_link; - } - - &.btn { - color: $style_color; - } - - &.dark { - color: $style_color; - } - - &.lined { - text-decoration:underline; - &:hover { text-decoration:underline; } - } - - &.gray { - color:gray; - } - - &.supp_diff_link { - text-align:center; - padding:20px 0; - background:#f1f1f1; - width:100%; - float:left; - } - - &.neib { - margin-right:15px; - } -} - -.neib { - margin-right:10px; -} - -.alert-message { - @extend .alert; - - &.success { - @extend .alert-success; - } - - &.error { - @extend .alert-error; - } -} - -.alert { - &.alert-well { - background:#ddd; - border:1px solid #ccc; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #ddd), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#ddd 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#ddd 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#ddd 6.6%, #dfdfdf); - color:#111; - } -} - -h3, h4, h5, h6 { - line-height: 36px; -} - -h5 { - font-size:14px; -} - - -table { - width:100%; - th { - padding-top: 9px; - font-weight: bold; - vertical-align: middle; - } - th, td { - padding: 10px 10px 9px; - line-height: 18px; - text-align: left; - } - - &.bordered-table { - border: 1px solid #DDD; - border-collapse: separate; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - } - - &.zebra-striped { - @extend .table-striped; - } -} - -.btn { - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f1f1f1), color-stop(25%, #f1f1f1), to(#e6e6e6)); - background-image: -webkit-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: -moz-linear-gradient(top, #f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: -ms-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: -o-linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - background-image: linear-gradient(#f1f1f1, #f1f1f1 25%, #e6e6e6); - - &:hover { - } - - &.btn-primary { - background:$link_color; - border-color: #2A79A3; - &:hover { - background:$blue_link; - } - } - &.primary { - @extend .btn-primary; - } - - &.success { - color: #fff; - text-shadow: 0 0 1px #111; - background: #5bb75b;; - font-weight: bold; - - &:hover { - background-color: #51a351; - color: #fff; - } - } - - &.danger, - &.btn-danger { - color:#fff; - background: #DA4E49; - border-color: #BD362F; - - &:hover { - color:#fff; - background: #EE4E49; - } - } - - &.danger { - @extend .btn-danger; - } - - &.small { - @extend .btn-small; - } - - &.active { - border-color:#aaa; - background-color:#ccc; - } -} - -a:focus { - outline: none; -} - -.nav-pills a:hover { - background-color:#888; -} - -.nav-pills .active a { - background-color: $style_color; -} - -.label { - background-color: #474D57; - &.label-important { - background-color: #B94A48; - } - - &.label-issue { - background-color: #eee; - border: 1px solid #ccc; - padding:4px 6px; - color:#444; - text-shadow:0 0 1px #fff; - - &.grouped { - float: left; - margin-right: 6px; - padding: 6px; - } - } -} - -.nav-tabs > li > a, .nav-pills > li > a { - color:$style_color; -} - -.nav-tabs > .active > a { - font-weight:bold; -} - -/** COLORS **/ -.cgray { color:gray; } -.cred { color:#D12F19; } -.cgreen { color:#44aa22; } -.cblack { color:#111; } -.cdark { color:#444 } -.cwhite { color:#fff !important } -.bgred { background: #F2DEDE !important} - -/** COMMON STYLES **/ -.left { - float:left; -} -.right { - float:right !important; -} -.width-50p{ - width:50%; -} -.width-49p{ - width:49%; -} -.width-30p{ - width:30%; -} -.width-65p{ - width:65%; -} -.width-100p{ - width:100%; -} -.append-bottom-10 { - margin-bottom:10px; -} -.append-bottom-20 { - margin-bottom:20px; -} -.prepend-top-10 { - margin-top:10px; -} - -.prepend-top-20 { - margin-top:20px; -} - -.padded { - padding:20px; -} - -.ipadded { - padding:20px !important; -} -.lborder { - border-left:1px solid #eee; -} - -.borders { - border: 1px solid #ccc; - @include shade; -} -.no-borders { - border:none; -} -table.no-borders { - border:none; - tr, td { border:none } -} -.no-padding { - padding:0 !important; -} -.underlined { - border-bottom: 1px solid $border_color; -} -.vlink { - color: $link_color !important; -} - -.pretty_label { - @include round-borders-all(4px); - padding:2px 4px; - background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.076, #fefefe), to(#F6F7F8)); - background-image: -webkit-linear-gradient(#fefefe 7.6%, #F6F7F8); - background-image: -moz-linear-gradient(#fefefe 7.6%, #F6F7F8); - background-image: -o-linear-gradient(#fefefe 7.6%, #F6F7F8); - color: #777; - border: 1px solid #DEDFE1; - - &.branch { - border:none; - font-size:13px; - background: #474D57; - color:#fff; - font-weight:bold; - font-family: monospace; - } -} - -.event_label { - @extend .label; - background-color: #999; - - &.pushed { - background-color: #3A87AD; - } - - &.opened { - background-color: #468847; - } - - &.closed { - background-color: #B94A48; - } - - &.merged { - background-color: #2A2; - } -} - -img.avatar { - float:left; - margin-right:15px; - width:40px; - border:2px solid #ddd; - - &.s16 { - width:16px; - } - &.s24 { - width:24px; - } - &.s32 { - width:32px; - } -} - -img.lil_av { - padding-left: 4px; - padding-right:3px; -} - -form { - @extend .form-horizontal; - - .actions { - @extend .form-actions; - } - - .clearfix { - @extend .control-group; - } - - .input { - @extend .controls; - } - - label { - @extend .control-label; - } - .xlarge { - @extend .input-xlarge; - } - .xxlarge { - @extend .input-xxlarge; - } -} - -/** - * List li block element #1 - * - */ -.wll { - background-color: #FFF; - padding: 10px 5px; - min-height: 20px; - border-bottom: 1px solid #eee; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - &.smoke { - background-color:#f5f5f5; - } - &:hover { - background:$hover; - } - &:last-child { border:none } - p { padding-top:5px; margin:0; color:$style_color;} - .author { color: #999; } - p { - color:#222; - margin-bottom: 0; - img { - position:relative; - top:3px; - } - } -} - - -/** - * Block element #2 - * - */ -.entry { - position: relative; - padding: 7px 15px; - margin-bottom: 18px; - color: #404040; - filter:none; - - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - - background:#F1F1F1; - border: 1px solid #ccc; - - - p { - color:$style_color; - margin-bottom: 0; - img { - position:relative; - top:3px; - } - } -} - - -/** - * Big UI Block for show page content - * - */ -.ui-box { - background:#F9F9F9; - margin-bottom: 25px; - @include round-borders-all(4px); - border-color: #CCC; - @include solid_shade; - - ul { - margin:0; - } - - h5, .title { - padding: 0 10px; - @include round-borders-top(4px); - border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - - &.small { - line-height: 28px; - font-size: 14px; - line-height:28px; - text-shadow: 0 1px 1px white; - } - - form { - padding:9px 0; - margin:0px; - } - - .nav-pills { - li { - padding:3px 0; - &.active a { background-color:$style_color; } - a { - border-radius:7px; - } - } - } - } - - .bottom { - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - @include round-borders-bottom(4px); - border-bottom:none; - border-top: 1px solid #bbb; - } - - &.padded { - h5, .title { - margin: -20px; - margin-bottom: 0; - padding: 5px 20px; - } - .middle_title { - background:#f5f5f5; - margin:20px -20px; - padding: 0 20px; - border-top:1px solid #eee; - border-bottom:1px solid #eee; - font-size:14px; - color:#777; - } - } - .row_title { - font-weight:bold; - color:#444; - &:hover { - color:#444; - text-decoration:underline; - } - } - - li, .wll { - padding:10px; - &:first-child { - @include round-borders-top(4px); - border-top:none; - } - - &:last-child { - @include round-borders-bottom(4px); - border:none; - } - } - -} - -table.admin-table { - @extend .table-bordered; - @extend .zebra-striped; - @include solid_shade; - th { - border-color: #CCC; - border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - } -} - -.field_with_errors { - display:inline; -} - -ul.breadcrumb { - background:white; - border:none; - li { - display: inline; - text-shadow: 0 1px 0 white - } - - a { - color:#474D57; - font-weight:bold; - font-size:14px; - } - - .arrow { - background: url("images.png") no-repeat -85px -77px; - width: 19px; - height: 16px; - float: left; - position: relative; - left: -10px; - padding:0; - margin:0; - } -} - -.nothing_here_message { - text-align:center; - padding:20px; - color:#777; -} - -/** - * UI box element - * contains top, middle, bottom blocks - * - */ -.main_box { - @extend .borders; - @extend .prepend-top-20; - @extend .append-bottom-20; - border-width:1px; - @include solid_shade; - - - img { max-width: 100%; } - - pre { - code { - background: none !important; - } - } - - .top_box_content, - .middle_box_content, - .bottom_box_content { - padding:15px; - - pre { - background: none !important; - margin:0; - border:none; - padding:0; - } - } - - .middle_box_content { - border-radius:0; - border:none; - font-size:12px; - background-color:#f5f5f5; - border:none; - border-top:1px solid #eee; - } - - .bottom_box_content { - border-top:1px solid #eee; - } -} - -input[type=text] { - &.large_text { - padding:6px; - font-size:16px; - } -} - -p { - &.slead { - color:#456; - font-size:16px; - margin-bottom: 12px; - font-weight: 200; - line-height: 24px; - } -} - -h3.page_title { - color:#456; - font-size:20px; - font-weight: normal; - line-height: 28px; -} - -/** - * File content holder - * - */ -.file_holder { - border:1px solid #CCC; - margin-bottom:1em; - @include solid_shade; - - .file_title { - border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); - margin: 0; - font-weight: normal; - font-weight: bold; - text-align: left; - color: #666; - padding: 9px 10px; - height:18px; - - .options { - float:right; - margin-top: -5px; - } - - .file_name { - color:$style_color; - font-size:14px; - text-shadow: 0 1px 1px #fff; - small { - color:#999; - font-size:13px; - } - } - } - .file_content { - background:#fff; - font-size: 11px; - - &.wiki { - font-size: 13px; - code { - padding:0 4px; - } - padding:20px; - h1, h2 { - line-height: 46px; - } - h3, h4 { - line-height: 40px; - } - } - - &.image_file { - background:#eee; - text-align:center; - img { - padding:100px; - max-width:300px; - } - } - - &.blob_file { - - } - - /** - * Blame file - */ - &.blame { - tr { - border-bottom: 1px solid #eee; - } - td { - padding:5px; - } - .author, - .blame_commit { - background:#f5f5f5; - vertical-align:top; - } - .lines { - pre { - padding:0; - margin:0; - background:none; - border:none; - } - } - } - - &.logs { - background:#eee; - max-height: 700px; - overflow-y: auto; - - ol { - margin-left:40px; - padding: 10px 0; - border-left: 1px solid #CCC; - margin-bottom:0; - background: white; - li { - color:#888; - p { - margin:0; - color:#333; - line-height:24px; - padding-left: 10px; - } - - &:hover { - background:$hover; - } - } - } - } - - /** - * Code file - */ - &.code { - padding:0; - td.code { - width: 100%; - .highlight { - margin-left: 55px; - overflow:auto; - overflow-y:hidden; - } - } - .highlight pre { - white-space: pre; - word-wrap:normal; - } - - table.highlighttable { - border: none; - } - body.project-page table.highlighttable td { border: none } - table.highlighttable tr:hover { background:none;} - - table.highlighttable pre{ - line-height:16px !important; - font-size:12px !important; - } - - table.highlighttable .linenodiv pre { - text-align: right; - padding-right: 4px; - color:#666; - } - } - } -} diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss new file mode 100644 index 0000000..894cb30 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss @@ -0,0 +1,145 @@ +/** + * =================================== + * Contain 3 main UI block elements: + * .main_box - for show pages + * .ui-box - for simple block & widgets + * =================================== + */ + +/** + * UI box element + * contains top, middle, bottom blocks + * + */ +.main_box { + @extend .borders; + @extend .prepend-top-20; + @extend .append-bottom-20; + border-width:1px; + @include solid_shade; + + + img { max-width: 100%; } + + pre { + code { + background: none !important; + } + } + + .top_box_content, + .middle_box_content, + .bottom_box_content { + padding:15px; + + pre { + background: none !important; + margin:0; + border:none; + padding:0; + } + } + + .middle_box_content { + border-radius:0; + border:none; + font-size:12px; + background-color:#f5f5f5; + border:none; + border-top:1px solid #eee; + } + + .bottom_box_content { + border-top:1px solid #eee; + } +} + +/** + * Big UI Block for show page content + * + */ +.ui-box { + background:#F9F9F9; + margin-bottom: 25px; + @include round-borders-all(4px); + border-color: #CCC; + @include solid_shade; + + ul { + margin:0; + } + + h5, .title { + padding: 0 10px; + @include round-borders-top(4px); + @include bg-gray-gradient; + border-bottom: 1px solid #bbb; + + &.small { + line-height: 28px; + font-size: 14px; + line-height:28px; + text-shadow: 0 1px 1px white; + } + + form { + padding:9px 0; + margin:0px; + } + + .nav-pills { + li { + padding:3px 0; + &.active a { background-color:$style_color; } + a { + border-radius:7px; + } + } + } + } + + .bottom { + @include bg-gray-gradient; + @include round-borders-bottom(4px); + border-bottom:none; + border-top: 1px solid #bbb; + } + + &.padded { + h5, .title { + margin: -20px; + margin-bottom: 0; + padding: 5px 20px; + } + .middle_title { + background:#f5f5f5; + margin:20px -20px; + padding: 0 20px; + border-top:1px solid #eee; + border-bottom:1px solid #eee; + font-size:14px; + color:#777; + } + } + .row_title { + font-weight:bold; + color:#444; + &:hover { + color:#444; + text-decoration:underline; + } + } + + li, .wll { + padding:10px; + &:first-child { + @include round-borders-top(4px); + border-top:none; + } + + &:last-child { + @include round-borders-bottom(4px); + border:none; + } + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/buttons.scss b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss new file mode 100644 index 0000000..c838f3b --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/buttons.scss @@ -0,0 +1,105 @@ +.btn { + background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.076, #f7f7f7), to(#d5d5d5)); + background-image: -webkit-linear-gradient(#f7f7f7 7.6%, #d5d5d5); + background-image: -moz-linear-gradient(#f7f7f7 7.6%, #d5d5d5); + background-image: -o-linear-gradient(#f7f7f7 7.6%, #d5d5d5); + border-color:#aaa; + &:hover { + @include bg-gray-gradient; + border-color:#bbb; + color:#333; + } + + &.primary { + background:#2a79A3; + border-color: #2A79A3; + background-image: -webkit-linear-gradient(#47A7b7 7.6%, #2585b5); + background-image: -moz-linear-gradient(#47A7b7 7.6%, #2585b5); + background-image: -o-linear-gradient(#47A7b7 7.6%, #2585b5); + color:#fff; + text-shadow: 0 1px 1px #268; + &:hover { + background:$blue_link; + color:#fff; + } + + &.disabled { + color:#fff; + background:#29B; + } + } + + &.success { + border-color: #4A4; + background-image: -webkit-linear-gradient(#82D482 7.6%, #22B442); + background-image: -moz-linear-gradient(#82D482 7.6%, #22B442); + background-image: -o-linear-gradient(#82D482 7.6%, #22B442); + color: #fff; + text-shadow: 0 1px 1px #141; + + &:hover { + background: #6C6; + color: #fff; + } + + &.disabled { + color:#fff; + background:#2b2; + } + } + + &.save-btn { + @extend .wide; + @extend .primary; + } + + &.cancel-btn { + float:right; + } + + &.wide { + padding-left:30px; + padding-right:30px; + } + + &.danger, + &.btn-danger { + color:#fff; + background: #DA4E49; + border-color: #BD362F; + + &:hover { + color:#fff; + background: #EE4E49; + } + } + + &.danger { + @extend .btn-danger; + } + + &.small { + @extend .btn-small; + } + + &.active { + border-color:#aaa; + background-color:#ccc; + } + + &.very_small { + font-size:11px; + padding:2px 6px; + margin:2px; + } + + &.grouped { + margin-right:7px; + float:left; + } + + &.padded { + margin-right:3px; + padding:4px 10px 4px; + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss new file mode 100644 index 0000000..cd7145c --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -0,0 +1,52 @@ +/** COLORS **/ +.cgray { color:gray } +.cred { color:#D12F19 } +.cgreen { color:#4a2 } +.cblack { color:#111 } +.cdark { color:#444 } +.cwhite { color:#fff!important } +.bgred { background:#F2DEDE!important } + +/** COMMON CLASSES **/ +.left { float:left } +.right { float:right!important } +.width-50p { width:50% } +.width-49p { width:49% } +.width-30p { width:30% } +.width-65p { width:65% } +.width-100p { width:100% } +.append-bottom-10 { margin-bottom:10px } +.append-bottom-20 { margin-bottom:20px } +.prepend-top-10 { margin-top:10px } +.prepend-top-20 { margin-top:20px } +.padded { padding:20px } +.ipadded { padding:20px!important } +.lborder { border-left:1px solid #eee } +.no-padding { padding:0 !important; } +.underlined { border-bottom: 1px solid #CCC; } +.no-borders { border:none; } +.vlink { color: $link_color !important; } +.borders { border: 1px solid #ccc; @include shade; } +.hint { font-style: italic; color: #999; } + +/** PILLS & TABS**/ +.nav-pills a:hover { background-color:#888; } +.nav-pills .active a { background-color: $style_color; } +.nav-tabs > li > a, .nav-pills > li > a { color:$style_color; } +.nav-tabs > .active > a { font-weight:bold; } + +/** ALERT MESSAGES **/ +.alert-message { @extend .alert; } +.alert-messag.success { @extend .alert-success; } +.alert-message.error { @extend .alert-error; } + +/** AVATARS **/ +img.avatar { float:left; margin-right:15px; width:40px; border:1px solid #ddd; padding:1px; } +img.avatar.s16 { width:16px; height:16px; } +img.avatar.s24 { width:24px; height:24px; } +img.avatar.s32 { width:32px; height:32px; } +img.lil_av { padding-left: 4px; padding-right:3px; } + +/** HELPERS **/ +.nothing_here_message { text-align:center; padding:20px; color:#777; } +p.slead { color:#456; font-size:16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; } diff --git a/app/assets/stylesheets/gitlab_bootstrap/files.scss b/app/assets/stylesheets/gitlab_bootstrap/files.scss new file mode 100644 index 0000000..4ea5ed0 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/files.scss @@ -0,0 +1,156 @@ +/** + * File content holder + * + */ +.file_holder { + border:1px solid #CCC; + margin-bottom:1em; + @include solid_shade; + + .file_title { + border-bottom: 1px solid #bbb; + @include bg-gray-gradient; + margin: 0; + font-weight: normal; + font-weight: bold; + text-align: left; + color: #666; + padding: 9px 10px; + height:18px; + + .options { + float:right; + margin-top: -5px; + } + + .file_name { + color:$style_color; + font-size:14px; + text-shadow: 0 1px 1px #fff; + small { + color:#999; + font-size:13px; + } + } + } + .file_content { + background:#fff; + font-size: 11px; + + &.wiki { + font-size: 13px; + code { + padding:0 4px; + } + padding:20px; + h1, h2 { + line-height: 46px; + } + h3, h4 { + line-height: 40px; + } + } + + &.image_file { + background:#eee; + text-align:center; + img { + padding:100px; + max-width:300px; + } + } + + &.blob_file { + + } + + /** + * Blame file + */ + &.blame { + tr { + border-bottom: 1px solid #eee; + } + td { + padding:5px; + } + .author, + .blame_commit { + background:#f5f5f5; + vertical-align:top; + } + .lines { + pre { + padding:0; + margin:0; + background:none; + border:none; + } + } + } + + &.logs { + background:#eee; + max-height: 700px; + overflow-y: auto; + + ol { + margin-left:40px; + padding: 10px 0; + border-left: 1px solid #CCC; + margin-bottom:0; + background: white; + li { + color:#888; + p { + margin:0; + color:#333; + line-height:24px; + padding-left: 10px; + } + + &:hover { + background:$hover; + } + } + } + } + + /** + * Code file + */ + &.code { + padding:0; + td.code { + width: 100%; + .highlight { + margin-left: 55px; + overflow:auto; + overflow-y:hidden; + } + } + .highlight pre { + white-space: pre; + word-wrap:normal; + } + + table.highlighttable { + border: none; + } + body.project-page table.highlighttable td { border: none } + table.highlighttable tr:hover { background:none;} + + table.highlighttable pre{ + line-height:16px !important; + font-size:12px !important; + } + + table.highlighttable .linenodiv pre { + text-align: right; + padding-right: 4px; + color:#666; + } + } + } +} + diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/gitlab_bootstrap/lists.scss new file mode 100644 index 0000000..402ba04 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/lists.scss @@ -0,0 +1,30 @@ +/** LISTS **/ + +ul { + /** + * List li block element #1 + * + */ + .wll { + background-color: #FFF; + padding: 10px 5px; + min-height: 20px; + border-bottom: 1px solid #eee; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + + &.smoke { background-color:#f5f5f5; } + &:hover { background:$hover; } + &:last-child { border:none } + .author { color: #999; } + + p { + padding-top:5px; + margin:0; + color:#222; + img { + position:relative; + top:3px; + } + } + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/tables.scss b/app/assets/stylesheets/gitlab_bootstrap/tables.scss new file mode 100644 index 0000000..f78b1de --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/tables.scss @@ -0,0 +1,41 @@ +table { + width:100%; + th { + padding-top: 9px; + font-weight: bold; + vertical-align: middle; + } + th, td { + padding: 10px 10px 9px; + line-height: 18px; + text-align: left; + } + + &.bordered-table { + border: 1px solid #DDD; + border-collapse: separate; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } + + &.zebra-striped { + @extend .table-striped; + } +} + +table.admin-table { + @extend .table-bordered; + @extend .zebra-striped; + @include solid_shade; + th { + border-color: #CCC; + border-bottom: 1px solid #bbb; + @include bg-gray-gradient; + } +} + +table.no-borders { + border:none; + tr, td { border:none } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/typography.scss b/app/assets/stylesheets/gitlab_bootstrap/typography.scss new file mode 100644 index 0000000..97e8549 --- /dev/null +++ b/app/assets/stylesheets/gitlab_bootstrap/typography.scss @@ -0,0 +1,71 @@ +/** + * Headers + * + */ +h3, h4, h5, h6 { line-height: 36px; } +h5 { font-size:14px; } +h3.page_title { + color:#456; + font-size:20px; + font-weight: normal; + line-height: 28px; +} + +/** CODE **/ +pre { + font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; + + &.dark { + background: #333; + color:#f5f5f5; + } +} + +/** + * Links + * + */ +a { + outline: none; + color: $link_color; + &:hover { + text-decoration:none; + color: $blue_link; + } + + &.btn { + color: $style_color; + &:hover { + color: $style_color; + } + } + + &.dark { + color: $style_color; + } + + &.lined { + text-decoration:underline; + &:hover { text-decoration:underline; } + } + + &.gray { + color:gray; + } + + &.supp_diff_link { + text-align:center; + padding:20px 0; + background:#f1f1f1; + width:100%; + float:left; + } + + &.neib { + margin-right:15px; + } +} + +a:focus { + outline: none; +} diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index ad8be0b..be27d75 100644 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -2,29 +2,13 @@ @import "bootstrap-responsive"; /** GITLAB colors **/ -$text_color:#222; -$lite_text_color: #666; -$link_color:#2A79A3; -$active_link_color:#2FA0BB; -$active_bg_color:#79C3E0; -$active_bd_color: #2FA0BB; -$border_color:#CCC; -$lite_border_color:#EEE; -$min_app_width:980px; -$max_app_width:980px; -$app_padding:20px; -$bg_color: #FFF; -$styled_border_color: #2FA0BB; -$color: "#4BB8D2"; +$link_color:#3A89A3; $blue_link: #2fa0bb; - - -/** Style colors **/ -$style_color: #474D57; -$hover: #FDF5D9; +$style_color: #474d57; +$hover: #fdf5d9; /** GITLAB Fonts **/ -@font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); } +@font-face { font-family: Korolev; src: url('korolev-medium-compressed.otf'); } /** MIXINS **/ @mixin shade { @@ -72,7 +56,20 @@ $hover: #FDF5D9; border-radius: $radius; } +@mixin bg-gray-gradient { + background:#eee; + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); +} +@mixin bg-dark-gray-gradient { + background:#eee; + background-image: -webkit-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -moz-linear-gradient(#e9e9e9, #d7d7d7); + background-image: -o-linear-gradient(#e9e9e9, #d7d7d7); +} /** * Header of application. @@ -113,7 +110,13 @@ $hover: #FDF5D9; * Overrides some styles of twitter bootstrap. * Also give some common classes for gitlab app */ -@import "gitlab_bootstrap.scss"; +@import "gitlab_bootstrap/common.scss"; +@import "gitlab_bootstrap/typography.scss"; +@import "gitlab_bootstrap/buttons.scss"; +@import "gitlab_bootstrap/blocks.scss"; +@import "gitlab_bootstrap/files.scss"; +@import "gitlab_bootstrap/tables.scss"; +@import "gitlab_bootstrap/lists.scss"; /** diff --git a/app/assets/stylesheets/projects.css.scss b/app/assets/stylesheets/projects.css.scss deleted file mode 100644 index 4bdf5de..0000000 --- a/app/assets/stylesheets/projects.css.scss +++ /dev/null @@ -1,385 +0,0 @@ -.git_url_wrapper { margin-right:50px } - -.sidebar aside a{ - display: block; - position: relative; - padding: 15px 10px; - margin: 10px 0 0 0; - - font-size:13px; - font-weight:bold; - color:#333; - - &.current { - color: white; - background: $active_bg_color; - border: 1px solid $active_bd_color; - border-radius:5px; - - -webkit-border-top-right-radius: 0; - -webkit-border-bottom-right-radius: 0; - -moz-border-radius-topright: 0px; - -moz-border-radius-bottomright: 0px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - margin-right: -1px; - } -} - -body table .commit a{color: #{$blue_link}} -body table th, body table td{ border-bottom: 1px solid #DEE2E3;} -body .fixed{position: fixed; } - -/** File stat **/ -.file_stats { - span { - img { - width:14px; - float:left; - margin-right: 6px; - padding:2px 0; - } - } -} - -.round-borders { - @include round-borders-all(4px); - padding: 4px 0px; -} - -table.round-borders { - float:left; - text-align: left; -} - - - -/** PROJECTS **/ -input.ssh_project_url { - padding:5px; - margin:0px; - float:right; - width:400px; - text-align:center; -} - -#projects-list .project { - height:50px; -} - -#tree-slider .tree-item, -#projects-list .project, -#snippets-table .snippet, -#issues-table .issue{ - cursor:pointer; -} - -.clear { - clear: both; -} - - - -#user_projects_limit{ - width: 60px; -} - -.handle:hover{ - cursor: move; -} - -.project-refs-form { - span { - background: none !important; - position:static !important; - width:auto !important; - height: auto !important; - } -} - -.project-refs-select { - width:200px; -} - -.filter .left { margin-right:15px; } - -body table .commit { - a.tree-commit-link { - color:#444; - &:hover { - text-decoration:underline; - } - } -} - -/** NEW PROJECT **/ -.new-project-hodler { - .icon span { background-position: -31px -70px; } - td { border-bottom: 1px solid #DEE2E3; } -} - -/** Feed entry **/ -.commit, -.snippet, -.message { - .title { - color:#666; - a { color:#666 !important; } - p { margin-top:0px; } - } - .author { color: #999 } -} - -/** JQuery UI **/ -.ui-autocomplete { @include round-borders-all(5px); } -.ui-menu-item { cursor: pointer } -.ui-selectmenu{ - @include round-borders-all(4px); - margin-right:10px; - font-size:1.5em; - height:auto; - font-weight:bold; - .ui-selectmenu-status { - padding:3px 10px; - } -} - -#holder { - background:#FAFAFA; - border: 1px solid #EEE; - cursor: move; - height: 70%; - overflow: hidden; -} - -/* Project Dashboard Page */ -html, body { height: 100%; } - -.news-feed h2{float: left;} -.news-feed .project-updates {margin-bottom: 20px; display: block; width: 100%;} -.news-feed .project-updates .data{ padding: 0} -.news-feed .project-updates a.project-update {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;} -.news-feed .project-updates a.project-update:last-child{border-bottom: 0} -.news-feed .project-updates a.project-update img{float: left; margin-right: 10px;} -.news-feed .project-updates a.project-update span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;} -.news-feed .project-updates a.project-update span.update-title{margin-bottom: 10px} -.news-feed .project-updates a.project-update span.update-author{color: #999; font-weight: normal; font-style: italic;} -.news-feed .project-updates a.project-update span.update-author strong{font-weight: bold; font-style: normal;} -/* eo Dashboard Page */ - - -/** Update entry **/ -.update-data { padding: 0 } -.update-data { width:100%; } -.update-data.ui-box .data { padding:0; } -a.update-item {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;} -a.update-item:last-child{border-bottom: 0} -a.update-item img{float: left; margin-right: 10px;} -a.update-item span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;} -a.update-item span.update-title{margin-bottom: 10px} -a.update-item span.update-author{color: #999; font-weight: normal; font-style: italic;} -a.update-item span.update-author strong{font-weight: bold; font-style: normal;} - - -body .team_member_new .span-6, .team_member_edit .span-6{ padding:10px 0; } - -body.projects-page input.text.git-url.project_list_url { width:165px; } - - -body table.no-borders th { - background:none; - border-bottom:1px solid #CCC; - color:#333; -} - -body table.no-borders tr, -body table.no-borders td{ - border:none; -} - -.ajax-tab-loading { - padding:40px; - display:none; -} - -#tree-content-holder { float:left; width:100%; } - -#tree-readme-holder { - float:left; - width:100%; - - .readme { - @include round-borders-all(4px); - padding: 4px 15px; - background:#F7F7F7; - } -} - - - -/* Commit Page */ -.entity-info {float: right;} -.entity-button{ - background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.192, #fff), to(#f4f4f4)); - background-image: -webkit-linear-gradient(#fff 19.2%, #f4f4f4); - background-image: -moz-linear-gradient(#fff 19.2%, #f4f4f4); - background-image: -o-linear-gradient(#fff 19.2%, #f4f4f4); - box-shadow: 0 -1px 0 white inset; - display: block; - border: 1px solid #eee; - border-radius: 5px; - margin-bottom: 2px; - position: relative; - padding: 4px 10px; - font-size: 11px; - padding-right: 20px; -} - -.entity-button i{ - background: url('images.png') no-repeat -138px -27px; - width: 6px; - height: 9px; - float: right; - position: absolute; - top: 6px; - right: 5px; -} -.box-arrow{float: right; background: #E3E5EA; padding: 10px; border-radius: 5px; margin-top: 2px; text-shadow: none; color: #999; margin: 1.5em 0;} - -h4.dash-tabs { - margin: 0; - border-bottom: 1px solid #ccc; - padding: 10px 10px; - font-size: 11px; - padding-left:20px; - font-weight: bold; text-transform: uppercase; - background: #F7F7F7; - margin-bottom:20px; - height:13px; - -} - -.dash-button { - border-right: 1px solid #ddd; - background:none; - padding: 10px 15px; - float:left; - position:relative; - top:-10px; - left:0px; - height:13px; - - &:first-child { - border-left: 1px solid #ddd; - } - &.active { - background: #eaeaea; - } -} - - -.dashboard-loader { - float:right; - margin-right:30px; - display:none; -} - - -.merge-tabs { - margin: 0; - border: 1px solid #ccc; - padding: 5px; - font-size: 12px; - background: #F7F7F7; - margin-bottom:20px; - height:26px; - - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - - .tab { - font-weight: bold; - border-right: 1px solid #ddd; - background:none; - padding: 10px; - min-width:60px; - float:left; - position:relative; - top:-5px; - left:-5px; - height:16px; - padding-left:34px; - - span { - width: 20px; - height: 20px; - display: inline-block; - position: absolute; - left: 8px; - top: 8px; - } - - &.active { - background: #eaeaea; - } - } -} -.merge-tabs.repository .tab span{ background: url("images.png") no-repeat -38px -77px; } -.activities-tab span { background: url("images.png") no-repeat -161px -1px; } -.stat-tab span, -.team-tab span, -.snippets-tab span { background: url("images.png") no-repeat -38px -77px; } -.files-tab span { background: url("images.png") no-repeat -112px -23px; } - -.merge-notes-tab span { background: url("images.png") no-repeat -161px -1px; } -.merge-commits-tab span { background: url("images.png") no-repeat -86px 1px; } -.merge-diffs-tab span { background: url("images.png") no-repeat -118px 1px; } -.merge-tabs .dashboard-loader { padding:8px; } - -.user-mention { - color: #2FA0BB; - font-weight: bold; -} - -.author { - color: #999; -} - - - - -.dark_scheme_box { - padding:20px 0; - - label { - float:left; - box-shadow: 0 0px 5px rgba(0,0,0,.3); - - img { - } - } -} - -a.project-update.titled { - position: relative; - padding-left: 235px !important; - - .title-block { - padding: 10px; - width: 205px; - position: absolute; - left: 0; - top: 0; - } -} - -.add_new { - float: right; - background: #A6B807; - color: white; - padding: 4px 10px; - @include round-borders-all(4px); - font-size:11px; - margin: 10px 0; -} diff --git a/app/assets/stylesheets/ref_select.scss b/app/assets/stylesheets/ref_select.scss index 6f6a1bc..5b52e11 100644 --- a/app/assets/stylesheets/ref_select.scss +++ b/app/assets/stylesheets/ref_select.scss @@ -33,9 +33,7 @@ } .chzn-single { - background:#ddd; - //border:none; - //box-shadow:none; + @include bg-gray-gradient; div { background:transparent; diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index e2db701..75e38ae 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -206,4 +206,24 @@ min-width:65px; font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; } + + .commit-author-name { + color: #777; + } +} + +.diff_file_header a, +.file_stats a { + color:$style_color; +} + +.file_stats { + span { + img { + width:14px; + float:left; + margin-right:6px; + padding:2px 0; + } + } } diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/sections/graph.scss index 33d91de..2aa4463 100644 --- a/app/assets/stylesheets/sections/graph.scss +++ b/app/assets/stylesheets/sections/graph.scss @@ -6,11 +6,7 @@ h4 { padding:0 10px; border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); + @include bg-gray-gradient; } .graph { diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index 1b61ec3..230a7ae 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -65,6 +65,11 @@ input.check_all_issues { } } +@media (min-width: 800px) { .issues_filters select { width:160px; } } +@media (min-width: 1000px) { .issues_filters select { width:200px; } } +@media (min-width: 1200px) { .issues_filters select { width:220px; } } + + #issues-table-holder { .issues_filters { form { @@ -99,3 +104,11 @@ input.check_all_issues { #update_status { width:100px; } + +/** + * Milestones list + * + */ +.milestone { + @extend .wll; +} diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index ec84a64..7317191 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -11,23 +11,6 @@ background:#f1f1f1; } - .commit { - margin:0; - padding:0; - padding: 5px; - margin-bottom: 5px; - - .committed_ago { - display:none; - } - .browse_code_link_holder { - display:none; - } - list-style:none; - &:hover { - background:none; - } - } } /** @@ -55,6 +38,7 @@ background: #CEB; .accept_merge_request { + font-size:13px; float:left; } .remove_branch_holder { @@ -99,3 +83,42 @@ li.merge_request { @extend .padded; @extend .append-bottom-10; } + +.label_branch { + @include round-borders-all(4px); + padding:2px 4px; + border:none; + font-size:13px; + background: #474D57; + color:#fff; + font-weight:bold; + font-family: monospace; +} + +.mr_source_commit, +.mr_target_commit { + .commit { + margin:0; + padding:0; + padding: 5px; + margin-bottom: 5px; + .avatar { position:relative } + .row_title { + color:#444; + } + .commit-author-name, + .dash, + .committed_ago, + .browse_code_link_holder { + display:none; + } + list-style:none; + &:hover { + background:none; + } + } +} + +.mr_direction_tip { + margin-top:40px +} diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index 4a77364..097e819 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -6,13 +6,9 @@ ul.main_menu { border-radius: 4px; margin: auto; margin:30px 0; - background:#eee; - border:1px solid #bbb; + border:1px solid #AAA; height:37px; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); + @include bg-gray-gradient; position:relative; overflow:hidden; @include shade; @@ -89,7 +85,7 @@ ul.main_menu { line-height:36px; color: $style_color; text-shadow:0 1px 1px white; - + padding:0 10px; } } /* diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 30587ef..6a965fa 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -2,7 +2,7 @@ * Notes * */ -#notes-list, +#notes-list, #new_notes_list { display:block; list-style:none; @@ -10,7 +10,7 @@ padding:0px; } -#new_notes_list li:last-child{ +#new_notes_list li:last-child{ border-bottom:1px solid #aaa; } @@ -30,16 +30,24 @@ } #new_note { - #note_note { - height:25px; + .note-text { + height:40px; } .attach_holder { display:none; } } -.note { - padding: 8px 0; +.preview_note { + margin: 2px; + border: 1px solid #ddd; + padding: 10px; + min-height: 60px; + background:#f5f5f5; +} + +.note { + padding: 8px 0; border-bottom: 1px solid #eee; overflow: hidden; display: block; @@ -49,16 +57,16 @@ .note-author { color: $style_color;} .note-title { margin-left:45px; padding-top: 5px;} - .avatar { + .avatar { margin-top:3px; } - .delete-note { - display:none; + .delete-note { + display:none; float:right; } - &:hover { + &:hover { .delete-note { display:block; } } } @@ -72,18 +80,18 @@ p.notify_controls span{ font-weight: 700; } -tr.line_notes_row { +tr.line_notes_row { border-bottom:1px solid #DDD; border-left: 7px solid #2A79A3; - &.reply { + &.reply { background:#eee; border-left: 7px solid #2A79A3; border-top:1px solid #ddd; - td { + td { padding:7px 10px; } - a.line_note_reply_link { + a.line_note_reply_link { @include round-borders-all(4px); padding: 3px 10px; margin-left:5px; @@ -92,9 +100,9 @@ tr.line_notes_row { border-color: #2A79A3; } } - ul { + ul { margin:0; - li { + li { padding:0; border:none; } @@ -103,26 +111,26 @@ tr.line_notes_row { .line_notes_row, .per_line_form { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } -.per_line_form { +.per_line_form { background:#f5f5f5; border-top:1px solid #eee; form { margin: 0; } - td { + td { border-bottom:1px solid #ddd; } - .note_actions { + .note_actions { margin:0; padding-top: 10px; - .buttons { + .buttons { float:left; width:300px; } - .options { - .labels { + .options { + .labels { float:left; padding-left:10px; - label { + label { padding: 6px 0; margin: 0; width:120px; @@ -132,7 +140,7 @@ tr.line_notes_row { } } -td .line_note_link { +td .line_note_link { position:absolute; margin-left:-70px; margin-top:-10px; @@ -144,14 +152,14 @@ td .line_note_link { opacity: 0.0; filter: alpha(opacity=0); - &:hover { + &:hover { opacity: 1.0; filter: alpha(opacity=100); } } .diff_file_content tr.line_holder:hover > td { background: $hover !important; } -.diff_file_content tr.line_holder:hover > td .line_note_link { +.diff_file_content tr.line_holder:hover > td .line_note_link { opacity: 1.0; filter: alpha(opacity=100); } @@ -169,8 +177,8 @@ td .line_note_link { margin: 0; } - .note_advanced_opts { - h6 { + .note_advanced_opts { + h6 { line-height: 32px; padding-right: 15px; } @@ -183,7 +191,7 @@ td .line_note_link { overflow:hidden; margin:0 0 5px !important; - .input_file { + .input_file { .file_upload { position: absolute; right:14px; @@ -196,7 +204,7 @@ td .line_note_link { height:28px; overflow:hidden; } - .input-file { + .input-file { width: 260px; height: 41px; float: right; @@ -204,3 +212,8 @@ td .line_note_link { } } } + +.note-text { + border: 1px solid #aaa; + box-shadow:none; +} diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 8c79e45..721b569 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -1,17 +1,44 @@ -.projects { +.projects { @extend .row; .activities { } - .side { + .side { @extend .span4; @extend .right; - .projects_box { - h5 { + .projects_box { + h5 { color:$style_color; font-size:16px; text-shadow: 0 1px 1px #fff; + padding: 2px 10px; + } + ul { + li { + padding:0; + a { + display:block; + .project_name { + color:#4fa2bd; + font-size:14px; + line-height:18px; + } + .arrow { + float:right; + padding:10px; + margin:0; + } + .last_activity { + padding-top:5px; + display:block; + span, strong { + font-size:12px; + color:#666; + } + } + } + } } @extend .leftbar; @extend .ui-box; @@ -19,21 +46,47 @@ } } -.new_project, -.edit_project { - .project_name_holder { +.new_project, +.edit_project { + .project_name_holder { input, - label { + label { font-size:16px; line-height:20px; padding:8px; } - label { + label { color:#888; } - .btn { - padding:6px; + .btn { + padding:6px 10px; margin-left:10px; + margin-bottom:8px; } } + .adv_settings { + h6 { margin-left:40px; } + } +} + +.project_clone_panel { + @include border-radius(4px); + @include bg-gray-gradient; + padding: 4px 7px; + border: 1px solid #CCC; + margin-bottom:5px; + input[type=text] { + border: 1px solid #BBB; + } +} + +.save-project-loader { + img { + margin-top:50px; + margin-bottom:50px; + } + h3 { + @extend .page_title; + } + } diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/sections/tree.scss index 2663fc9..891f5e2 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/sections/tree.scss @@ -72,11 +72,7 @@ th { border-color: #CCC; border-bottom: 1px solid #bbb; - background:#eee; - background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); - background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); - background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); + @include bg-gray-gradient; } } diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index 2808ad3..c630f38 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -20,6 +20,10 @@ .fbtn { .btn { + i { + position: relative; + top: 1px; + } margin-left:8px; background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #595D63), to(#31363E)); background-image: -webkit-linear-gradient(#595D63 6.6%, #31363E); @@ -32,6 +36,10 @@ background-image: -moz-linear-gradient(#595D63 6.6%, #202227); background-image: -o-linear-gradient(#595D63 6.6%, #202227); background-position:0 0; + color:#fff; + i { + @extend .icon-white; + } } border: 1px solid #31363E; diff --git a/app/contexts/merge_requests_load.rb b/app/contexts/merge_requests_load.rb index 6778db3..e2f68e3 100644 --- a/app/contexts/merge_requests_load.rb +++ b/app/contexts/merge_requests_load.rb @@ -1,13 +1,13 @@ class MergeRequestsLoad < BaseContext def execute - type = params[:f].to_i + type = params[:f] merge_requests = project.merge_requests merge_requests = case type - when 1 then merge_requests - when 2 then merge_requests.closed - when 3 then merge_requests.opened.assigned(current_user) + when 'all' then merge_requests + when 'closed' then merge_requests.closed + when 'assigned-to-me' then merge_requests.opened.assigned(current_user) else merge_requests.opened end.page(params[:page]).per(20) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7c1941e..9aab250 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,6 +14,10 @@ class ApplicationController < ActionController::Base render "errors/gitolite", layout: "error" end + rescue_from Gitlab::Gitolite::InvalidKey do |exception| + render "errors/invalid_ssh_key", layout: "error" + end + rescue_from Encoding::CompatibilityError do |exception| render "errors/encoding", layout: "error", status: 404 end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 889a7d9..a47b384 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -60,7 +60,13 @@ class IssuesController < ApplicationController @issue.save respond_to do |format| - format.html { redirect_to project_issue_path(@project, @issue) } + format.html do + if @issue.valid? + redirect_to project_issue_path(@project, @issue) + else + render :new + end + end format.js end end @@ -162,10 +168,10 @@ class IssuesController < ApplicationController def issues_filter { - all: "1", - closed: "2", - to_me: "3", - open: "0" + all: "all", + closed: "closed", + to_me: "assigned-to-me", + open: "open" } end end diff --git a/app/controllers/labels_controller.rb b/app/controllers/labels_controller.rb new file mode 100644 index 0000000..e703f82 --- /dev/null +++ b/app/controllers/labels_controller.rb @@ -0,0 +1,25 @@ +class LabelsController < ApplicationController + before_filter :authenticate_user! + before_filter :project + before_filter :module_enabled + + layout "project" + + # Authorize + before_filter :add_project_abilities + + # Allow read any issue + before_filter :authorize_read_issue! + + respond_to :js, :html + + def index + @labels = @project.issues.tag_counts_on(:labels).order('count DESC') + end + + protected + + def module_enabled + return render_404 unless @project.issues_enabled + end +end diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb index 4938a29..187bb40 100644 --- a/app/controllers/merge_requests_controller.rb +++ b/app/controllers/merge_requests_controller.rb @@ -103,10 +103,12 @@ class MergeRequestsController < ApplicationController def branch_from @commit = project.commit(params[:ref]) + @commit = CommitDecorator.decorate(@commit) end def branch_to @commit = project.commit(params[:ref]) + @commit = CommitDecorator.decorate(@commit) end protected diff --git a/app/controllers/milestones_controller.rb b/app/controllers/milestones_controller.rb index 7acb378..10f089f 100644 --- a/app/controllers/milestones_controller.rb +++ b/app/controllers/milestones_controller.rb @@ -17,8 +17,8 @@ class MilestonesController < ApplicationController respond_to :html def index - @milestones = case params[:f].to_i - when 1; @project.milestones + @milestones = case params[:f] + when 'all'; @project.milestones else @project.milestones.active end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index d19931e..d472936 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -12,8 +12,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def ldap # We only find ourselves here if the authentication to LDAP was successful. - info = request.env["omniauth.auth"]["info"] - @user = User.find_for_ldap_auth(info) + @user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user) if @user.persisted? @user.remember_me = true end diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb index 0cc58c3..0846f09 100644 --- a/app/controllers/team_members_controller.rb +++ b/app/controllers/team_members_controller.rb @@ -9,6 +9,7 @@ class TeamMembersController < ApplicationController def show @team_member = project.users_projects.find(params[:id]) + @events = @team_member.user.recent_events.where(:project_id => @project.id).limit(7) end def new diff --git a/app/decorators/application_decorator.rb b/app/decorators/application_decorator.rb index 7bc8864..3023699 100644 --- a/app/decorators/application_decorator.rb +++ b/app/decorators/application_decorator.rb @@ -1,4 +1,4 @@ -class ApplicationDecorator < Drapper::Base +class ApplicationDecorator < Draper::Base # Lazy Helpers # PRO: Call Rails helpers without the h. proxy # ex: number_to_currency(model.price) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 51569b0..3dafb75 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -2,10 +2,13 @@ require 'digest/md5' module ApplicationHelper def gravatar_icon(user_email = '', size = 40) - return unless user_email - gravatar_host = request.ssl? ? "https://secure.gravatar.com" : "http://www.gravatar.com" - user_email.strip! - "#{gravatar_host}/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=identicon" + if Gitlab.config.disable_gravatar? || user_email.blank? + 'no_avatar.png' + else + gravatar_prefix = request.ssl? ? "https://secure" : "http://www" + user_email.strip! + "#{gravatar_prefix}.gravatar.com/avatar/#{Digest::MD5.hexdigest(user_email.downcase)}?s=#{size}&d=identicon" + end end def request_protocol @@ -75,16 +78,16 @@ module ApplicationHelper end def show_last_push_widget?(event) - event && + event && event.last_push_to_non_root? && !event.rm_ref? && - event.project && + event.project && event.project.merge_requests_enabled end def tab_class(tab_key) active = case tab_key - + # Project Area when :wall; wall_tab? when :wiki; controller.controller_name == "wikis" @@ -123,4 +126,13 @@ module ApplicationHelper def hexdigest(string) Digest::SHA1.hexdigest string end + + def project_last_activity project + activity = project.last_activity + if activity && activity.created_at + time_ago_in_words(activity.created_at) + " ago" + else + "Never" + end + end end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 24bc3e8..9da695b 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -1,9 +1,18 @@ module GitlabMarkdownHelper + # Replaces references (i.e. @abc, #123, !456, ...) in the text with links to + # the appropriate items in Gitlab. + # + # text - the source text + # html_options - extra options for the reference links as given to link_to + # + # note: reference links will only be generated if @project is set + # + # see Gitlab::Markdown for details on the supported syntax def gfm(text, html_options = {}) return text if text.nil? return text if @project.nil? - # Extract pre blocks + # Extract pre blocks so they are not altered # from http://github.github.com/github-flavored-markdown/ extractions = {} text.gsub!(%r{
.*?|
.*?
}m) do |match|
@@ -22,10 +31,18 @@ module GitlabMarkdownHelper
extractions[$1]
end
- text.html_safe
+ sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class )
end
- # circumvents nesting links, which will behave bad in browsers
+ # Use this in places where you would normally use link_to(gfm(...), ...).
+ #
+ # It solves a problem occurring with nested links (i.e.
+ # "outer text gfm ref more outer text"). This will not be
+ # interpreted as intended. Browsers will parse something like
+ # "outer text gfm ref more outer text" (notice the last part is
+ # not linked any more). link_to_gfm corrects that. It wraps all parts to
+ # explicitly produce the correct linking behavior (i.e.
+ # "outer text gfm ref more outer text").
def link_to_gfm(body, url, html_options = {})
gfm_body = gfm(body, html_options)
@@ -37,17 +54,24 @@ module GitlabMarkdownHelper
end
def markdown(text)
- @__renderer ||= Redcarpet::Markdown.new(Redcarpet::Render::GitlabHTML.new(self, filter_html: true, with_toc_data: true), {
- no_intra_emphasis: true,
- tables: true,
- fenced_code_blocks: true,
- autolink: true,
- strikethrough: true,
- lax_html_blocks: true,
- space_after_headers: true,
- superscript: true
- })
-
- @__renderer.render(text).html_safe
+ unless @markdown
+ gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self,
+ # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
+ filter_html: true,
+ with_toc_data: true,
+ hard_wrap: true)
+ @markdown ||= Redcarpet::Markdown.new(gitlab_renderer,
+ # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
+ no_intra_emphasis: true,
+ tables: true,
+ fenced_code_blocks: true,
+ autolink: true,
+ strikethrough: true,
+ lax_html_blocks: true,
+ space_after_headers: true,
+ superscript: true)
+ end
+
+ @markdown.render(text).html_safe
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 9563fdb..91136fe 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -12,74 +12,117 @@ class Notify < ActionMailer::Base
def new_user_email(user_id, password)
@user = User.find(user_id)
@password = password
- mail(to: @user.email, subject: "gitlab | Account was created for you")
+ mail(to: @user.email, subject: subject("Account was created for you"))
end
def new_issue_email(issue_id)
@issue = Issue.find(issue_id)
@project = @issue.project
- mail(to: @issue.assignee_email, subject: "gitlab | new issue ##{@issue.id} | #{@issue.title} | #{@project.name}")
+ mail(to: @issue.assignee_email, subject: subject("new issue ##{@issue.id}", @issue.title))
end
def note_wall_email(recipient_id, note_id)
- recipient = User.find(recipient_id)
@note = Note.find(note_id)
@project = @note.project
- mail(to: recipient.email, subject: "gitlab | #{@project.name}")
+ mail(to: recipient(recipient_id), subject: subject)
end
def note_commit_email(recipient_id, note_id)
- recipient = User.find(recipient_id)
@note = Note.find(note_id)
@commit = @note.target
@commit = CommitDecorator.decorate(@commit)
@project = @note.project
- mail(to: recipient.email, subject: "gitlab | note for commit #{@commit.short_id} | #{@commit.title} | #{@project.name}")
+ mail(to: recipient(recipient_id), subject: subject("note for commit #{@commit.short_id}", @commit.title))
end
def note_merge_request_email(recipient_id, note_id)
- recipient = User.find(recipient_id)
@note = Note.find(note_id)
@merge_request = @note.noteable
@project = @note.project
- mail(to: recipient.email, subject: "gitlab | note for merge request !#{@merge_request.id} | #{@project.name}")
+ mail(to: recipient(recipient_id), subject: subject("note for merge request !#{@merge_request.id}"))
end
def note_issue_email(recipient_id, note_id)
- recipient = User.find(recipient_id)
@note = Note.find(note_id)
@issue = @note.noteable
@project = @note.project
- mail(to: recipient.email, subject: "gitlab | note for issue ##{@issue.id} | #{@project.name}")
+ mail(to: recipient(recipient_id), subject: subject("note for issue ##{@issue.id}"))
end
def note_wiki_email(recipient_id, note_id)
- recipient = User.find(recipient_id)
@note = Note.find(note_id)
@wiki = @note.noteable
@project = @note.project
- mail(to: recipient.email, subject: "gitlab | note for wiki | #{@project.name}")
+ mail(to: recipient(recipient_id), subject: subject("note for wiki"))
end
def new_merge_request_email(merge_request_id)
@merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
- mail(to: @merge_request.assignee_email, subject: "gitlab | new merge request !#{@merge_request.id} | #{@merge_request.title} | #{@project.name}")
+ mail(to: @merge_request.assignee_email, subject: subject("new merge request !#{@merge_request.id}", @merge_request.title))
end
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
- recipient = User.find(recipient_id)
@merge_request = MergeRequest.find(merge_request_id)
@previous_assignee ||= User.find(previous_assignee_id)
@project = @merge_request.project
- mail(to: recipient.email, subject: "gitlab | changed merge request !#{@merge_request.id} | #{@merge_request.title} | #{@project.name}")
+ mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title))
end
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
- recipient = User.find(recipient_id)
@issue = Issue.find(issue_id)
@previous_assignee ||= User.find(previous_assignee_id)
@project = @issue.project
- mail(to: recipient.email, subject: "gitlab | changed issue ##{@issue.id} | #{@issue.title} | #{@project.name}")
+ mail(to: recipient(recipient_id), subject: subject("changed issue ##{@issue.id}", @issue.title))
+ end
+
+ def project_access_granted_email(user_project_id)
+ @users_project = UsersProject.find user_project_id
+ @project = @users_project.project
+ mail(to: @users_project.user.email,
+ subject: subject("access to project was granted"))
+ end
+
+ def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
+ @issue = Issue.find issue_id
+ @issue_status = status
+ @updated_by = User.find updated_by_user_id
+ mail(to: recipient(recipient_id),
+ subject: subject("changed issue ##{@issue.id}", @issue.title))
+ end
+
+ private
+
+ # Look up a User by their ID and return their email address
+ #
+ # recipient_id - User ID
+ #
+ # Returns a String containing the User's email address.
+ def recipient(recipient_id)
+ if recipient = User.find(recipient_id)
+ recipient.email
+ end
+ end
+
+ # Formats arguments into a String suitable for use as an email subject
+ #
+ # extra - Extra Strings to be inserted into the subject
+ #
+ # Examples
+ #
+ # >> subject('Lorem ipsum')
+ # => "gitlab | Lorem ipsum"
+ #
+ # # Automatically inserts Project name when @project is set
+ # >> @project = Project.last
+ # => #```
and you won't need to indent manually to trigger a code block.
+
+ %pre= %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```}
+ %p becomes
+ = markdown %Q{```ruby\nrequire 'redcarpet'\nmarkdown = Redcarpet.new("Hello World!")\nputs markdown.to_html\n```}
+
+ %h4 Special Gitlab references
+
+ %p
+ GFM recognizes special references.
+ You can easily reference e.g. a team member, an issue or a commit within a project.
+ GFM will turn that reference into a link so you can navigate between them easily.
+
+ %p GFM will recognize the following references:
+ %ul
+ %li
+ %code @foo
+ for team members
+ %li
+ %code #123
+ for issues
+ %li
+ %code !123
+ for merge request
+ %li
+ %code $123
+ for snippets
+ %li
+ %code 1234567
+ for commits
+
+ -# this example will only be shown if the user has a project with at least one issue
+ - if @project = current_user.projects.first
+ - if issue = @project.issues.first
+ %p For example in your #{link_to @project.name, project_path(@project)} project something like
+ %pre= "This is related to ##{issue.id}. @#{current_user.name} is working on solving it."
+ %p becomes
+ = markdown "This is related to ##{issue.id}. @#{current_user.name} is working on solving it."
+
+
+
+ .span4.right
+ .alert.alert-info
+ %p
+ If you're not already familiar with Markdown, you should spend 15 minutes and go over the excellent
+ %strong= link_to "Markdown Syntax Guide", "http://daringfireball.net/projects/markdown/syntax"
+ at Daring Fireball.
diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml
index 7511d15..f9287fa 100644
--- a/app/views/help/permissions.html.haml
+++ b/app/views/help/permissions.html.haml
@@ -1,6 +1,6 @@
-%h3 Permissions
+%h3.page_title Permissions
.back_link
- = link_to help_path do
+ = link_to help_path do
← to index
%hr
diff --git a/app/views/help/ssh.html.haml b/app/views/help/ssh.html.haml
new file mode 100644
index 0000000..6a58120
--- /dev/null
+++ b/app/views/help/ssh.html.haml
@@ -0,0 +1,25 @@
+%h3.page_title SSH Keys
+.back_link
+ = link_to help_path do
+ ← to index
+%hr
+
+%p.slead
+ SSH key allows you to establish a secure connection between your computer and Gitlab
+
+%p.slead
+ To generate a new SSH key just open your terminal and use code below.
+
+%pre.dark
+ ssh-keygen -t rsa -C "#{current_user.email}"
+
+ \# Creates a new ssh key using the provided email
+ \# Generating public/private rsa key pair...
+
+%p.slead
+ Next just use code below to dump your public key and add to GITLAB SSH Keys
+
+%pre.dark
+ cat ~/.ssh/id_rsa.pub
+
+ \# ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6eNtGpNGwstc....
diff --git a/app/views/help/system_hooks.html.haml b/app/views/help/system_hooks.html.haml
index 2088208..9fc8cba 100644
--- a/app/views/help/system_hooks.html.haml
+++ b/app/views/help/system_hooks.html.haml
@@ -1,10 +1,10 @@
%h3 System hooks
.back_link
- = link_to :back do
+ = link_to :back do
← back
%hr
-%p.slead
+%p.slead
Your Gitlab instance can perform HTTP POST request on next event: create_project, delete_project, create_user, delete_user, change_team_member.
%br
System Hooks can be used for logging or change information in LDAP server.
diff --git a/app/views/help/web_hooks.html.haml b/app/views/help/web_hooks.html.haml
index 3acea62..263eadf 100644
--- a/app/views/help/web_hooks.html.haml
+++ b/app/views/help/web_hooks.html.haml
@@ -1,11 +1,11 @@
-%h3 Web hooks
+%h3.page_title Web hooks
.back_link
- = link_to help_path do
+ = link_to help_path do
← to index
%hr
-%p.slead
- Every Gitlab project can trigger a web server whenever the repo is pushed to.
+%p.slead
+ Every Gitlab project can trigger a web server whenever the repo is pushed to.
%br
Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
%br
diff --git a/app/views/help/workflow.html.haml b/app/views/help/workflow.html.haml
index 7db8133..a3fe3b0 100644
--- a/app/views/help/workflow.html.haml
+++ b/app/views/help/workflow.html.haml
@@ -1,7 +1,6 @@
-- bash_lexer = Pygments::Lexer[:bash]
-%h3 Workflow
+%h3.page_title Workflow
.back_link
- = link_to help_path do
+ = link_to help_path do
← to index
%hr
@@ -9,25 +8,25 @@
%li
%p Clone project
.bash
- %pre
+ %pre.dark
git clone git@example.com:project-name.git
%li
%p Create branch with your feature
.bash
- %pre
+ %pre.dark
git checkout -b $feature_name
%li
%p Write code. Commit changes
.bash
- %pre
+ %pre.dark
git commit -am "My feature is ready"
%li
%p Push your branch to gitlabhq
.bash
- %pre
+ %pre.dark
git push origin $feature_name
%li
diff --git a/app/views/hooks/_data_ex.html.erb b/app/views/hooks/_data_ex.html.erb
index 8d3de3f..e43714e 100644
--- a/app/views/hooks/_data_ex.html.erb
+++ b/app/views/hooks/_data_ex.html.erb
@@ -37,7 +37,7 @@
}
}
],
- total_commits_count => 3
+ total_commits_count => 4
}
eos
%>
diff --git a/app/views/hooks/index.html.haml b/app/views/hooks/index.html.haml
index 4e15dc5..3d2a381 100644
--- a/app/views/hooks/index.html.haml
+++ b/app/views/hooks/index.html.haml
@@ -8,7 +8,7 @@
Read more about web hooks
%strong #{link_to "here", help_web_hooks_path, class: "vlink"}
-= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project) do |f|
+= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-inline' } do |f|
-if @hook.errors.any?
.alert-message.block-message.error
- @hook.errors.full_messages.each do |msg|
diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml
index 1b67eab..db7920b 100644
--- a/app/views/issues/_form.html.haml
+++ b/app/views/issues/_form.html.haml
@@ -38,19 +38,20 @@
= f.label :description, "Details"
.input
= f.text_area :description, maxlength: 2000, class: "xxlarge", rows: 14
- %p.hint Markdown is enabled.
+ %p.hint Issues are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
.actions
- if @issue.new_record?
- = f.submit 'Submit new issue', class: "primary btn"
+ = f.submit 'Submit new issue', class: "btn save-btn"
-else
- = f.submit 'Save changes', class: "primary btn"
+ = f.submit 'Save changes', class: "save-btn btn"
+ - cancel_class = 'btn cancel-btn'
- if request.xhr?
- = link_to "Cancel", "#back", onclick: "backToIssues();", class: "btn"
+ = link_to "Cancel", "#back", onclick: "backToIssues();", class: cancel_class
- else
- if @issue.new_record?
- = link_to "Cancel", project_issues_path(@project), class: "btn"
+ = link_to "Cancel", project_issues_path(@project), class: cancel_class
- else
- = link_to "Cancel", project_issue_path(@project, @issue), class: "btn"
+ = link_to "Cancel", project_issue_path(@project, @issue), class: cancel_class
diff --git a/app/views/issues/_head.html.haml b/app/views/issues/_head.html.haml
index 1f6e7d7..8ebe3e0 100644
--- a/app/views/issues/_head.html.haml
+++ b/app/views/issues/_head.html.haml
@@ -5,6 +5,9 @@
%li{class: "#{'active' if current_page?(project_milestones_path(@project))}"}
= link_to project_milestones_path(@project), class: "tab" do
Milestones
+ %li{class: "#{'active' if current_page?(project_labels_path(@project))}"}
+ = link_to project_labels_path(@project), class: "tab" do
+ Labels
%li.right
%span.rss-icon
= link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml
index a6836fd..010b885 100644
--- a/app/views/issues/index.html.haml
+++ b/app/views/issues/index.html.haml
@@ -6,7 +6,7 @@
.right
.span5
- if can? current_user, :write_issue, @project
- = link_to new_project_issue_path(@project), class: "right btn small", title: "New Issue", remote: true do
+ = link_to new_project_issue_path(@project), class: "right btn", title: "New Issue", remote: true do
%i.icon-plus
New Issue
= form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do
diff --git a/app/views/keys/_form.html.haml b/app/views/keys/_form.html.haml
index ee2eafd..2670080 100644
--- a/app/views/keys/_form.html.haml
+++ b/app/views/keys/_form.html.haml
@@ -11,8 +11,14 @@
.input= f.text_field :title
.clearfix
= f.label :key
- .input= f.text_area :key, class: [:xxlarge, :thin_area]
+ .input
+ = f.text_area :key, class: [:xxlarge, :thin_area]
+ %p.hint
+ Paste your public key here. Read more about how generate it
+ = link_to "here", help_ssh_path
+
+
.actions
- = f.submit 'Save', class: "primary btn"
- = link_to "Cancel", keys_path, class: "btn"
+ = f.submit 'Save', class: "btn save-btn"
+ = link_to "Cancel", keys_path, class: "btn cancel-btn"
diff --git a/app/views/keys/index.html.haml b/app/views/keys/index.html.haml
index 04e9e4c..9b5663e 100644
--- a/app/views/keys/index.html.haml
+++ b/app/views/keys/index.html.haml
@@ -1,6 +1,6 @@
%h3.page_title
SSH Keys
- = link_to "Add new", new_key_path, class: "btn small right"
+ = link_to "Add new", new_key_path, class: "btn right"
%hr
%p.slead
diff --git a/app/views/keys/new.html.haml b/app/views/keys/new.html.haml
index 02e782b..fff3805 100644
--- a/app/views/keys/new.html.haml
+++ b/app/views/keys/new.html.haml
@@ -1,4 +1,4 @@
-%h3.page_title New key
+%h3.page_title Add an SSH Key
%hr
= render 'form'
diff --git a/app/views/labels/_label.html.haml b/app/views/labels/_label.html.haml
new file mode 100644
index 0000000..32158c2
--- /dev/null
+++ b/app/views/labels/_label.html.haml
@@ -0,0 +1,4 @@
+%li.wll
+ %strong= label.name
+ .right
+ %span= pluralize label.count, 'issue'
diff --git a/app/views/labels/index.html.haml b/app/views/labels/index.html.haml
new file mode 100644
index 0000000..4e41d37
--- /dev/null
+++ b/app/views/labels/index.html.haml
@@ -0,0 +1,14 @@
+= render "issues/head"
+
+%h3.page_title
+ Labels
+%br
+%div.ui-box
+ %ul.unstyled.labels-table
+ - @labels.each do |label|
+ = render 'label', label: label
+
+ - unless @labels.present?
+ %li
+ %h3.nothing_here_message Nothing to show here
+
diff --git a/app/views/merge_requests/_form.html.haml b/app/views/merge_requests/_form.html.haml
index b6c1239..b554c05 100644
--- a/app/views/merge_requests/_form.html.haml
+++ b/app/views/merge_requests/_form.html.haml
@@ -9,7 +9,7 @@
%br
.row
- .span6
+ .span5
.mr_branch_box
%h5 From (Head Branch)
.body
@@ -17,10 +17,11 @@
= f.label :source_branch, "From", class: "control-label"
.controls
= f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
- .bottom_commit
- .mr_source_commit
+ .mr_source_commit
- .span6
+ .span2
+ %center= image_tag "merge.png", class: 'mr_direction_tip'
+ .span5
.mr_branch_box
%h5 To (Base Branch)
.body
@@ -28,8 +29,7 @@
= f.label :target_branch, "To", class: "control-label"
.controls
= f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
- .bottom_commit
- .mr_target_commit
+ .mr_target_commit
%h4.cdark 2. Fill info
@@ -48,18 +48,19 @@
.control-group
.form-actions
- = f.submit 'Save', class: "btn-primary btn"
+ = f.submit 'Save', class: "btn save-btn"
- if @merge_request.new_record?
- = link_to project_merge_requests_path(@project), class: "btn" do
+ = link_to project_merge_requests_path(@project), class: "btn cancel-btn" do
Cancel
- else
- = link_to project_merge_request_path(@project, @merge_request), class: "btn" do
+ = link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do
Cancel
:javascript
$(function(){
+ disableButtonIfEmtpyField("#merge_request_title", ".save-btn");
$('select#merge_request_assignee_id').chosen();
$('select#merge_request_source_branch').chosen();
$('select#merge_request_target_branch').chosen();
diff --git a/app/views/merge_requests/index.html.haml b/app/views/merge_requests/index.html.haml
index 4ad6e5c..bbf35dc 100644
--- a/app/views/merge_requests/index.html.haml
+++ b/app/views/merge_requests/index.html.haml
@@ -1,7 +1,7 @@
%h3.page_title
Merge Requests
- if can? current_user, :write_issue, @project
- = link_to new_project_merge_request_path(@project), class: "right btn small", title: "New Merge Request" do
+ = link_to new_project_merge_request_path(@project), class: "right btn", title: "New Merge Request" do
New Merge Request
%br
@@ -10,17 +10,17 @@
.ui-box
.title
%ul.nav.nav-pills
- %li{class: ("active" if (params[:f] == "0" || !params[:f]))}
- = link_to project_merge_requests_path(@project, f: 0) do
+ %li{class: ("active" if (params[:f] == 'open' || !params[:f]))}
+ = link_to project_merge_requests_path(@project, f: 'open') do
Open
- %li{class: ("active" if params[:f] == "2")}
- = link_to project_merge_requests_path(@project, f: 2) do
+ %li{class: ("active" if params[:f] == "closed")}
+ = link_to project_merge_requests_path(@project, f: "closed") do
Closed
- %li{class: ("active" if params[:f] == "3")}
- = link_to project_merge_requests_path(@project, f: 3) do
+ %li{class: ("active" if params[:f] == 'assigned-to-me')}
+ = link_to project_merge_requests_path(@project, f: 'assigned-to-me') do
To Me
- %li{class: ("active" if params[:f] == "1")}
- = link_to project_merge_requests_path(@project, f: 1) do
+ %li{class: ("active" if params[:f] == 'all')}
+ = link_to project_merge_requests_path(@project, f: 'all') do
All
%ul.unstyled
diff --git a/app/views/merge_requests/show/_how_to_merge.html.haml b/app/views/merge_requests/show/_how_to_merge.html.haml
index c21f272..69881d4 100644
--- a/app/views/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/merge_requests/show/_how_to_merge.html.haml
@@ -3,13 +3,12 @@
%a.close{href: "#"} ×
%h3 How To Merge
.modal-body
- %pre
+ %pre.dark
= preserve do
- :erb
- git checkout <%= @merge_request.target_branch %>
- git fetch origin
- git merge origin/<%= @merge_request.source_branch %>
- git push origin <%= @merge_request.target_branch %>
+ git checkout #{@merge_request.target_branch}
+ git fetch origin
+ git merge origin/#{@merge_request.source_branch}
+ git push origin #{@merge_request.target_branch}
:javascript
diff --git a/app/views/merge_requests/show/_mr_title.html.haml b/app/views/merge_requests/show/_mr_title.html.haml
index 31fa077..3ae1050 100644
--- a/app/views/merge_requests/show/_mr_title.html.haml
+++ b/app/views/merge_requests/show/_mr_title.html.haml
@@ -1,9 +1,9 @@
%h3.page_title
= "Merge Request ##{@merge_request.id}:"
- %span.pretty_label.branch= @merge_request.source_branch
+ %span.label_branch= @merge_request.source_branch
→
- %span.pretty_label.branch= @merge_request.target_branch
+ %span.label_branch= @merge_request.target_branch
%span.right
- if @merge_request.merged?
diff --git a/app/views/milestones/_form.html.haml b/app/views/milestones/_form.html.haml
index 1cd08ac..41cbd6a 100644
--- a/app/views/milestones/_form.html.haml
+++ b/app/views/milestones/_form.html.haml
@@ -22,7 +22,7 @@
= f.label :description, "Description", class: "control-label"
.controls
= f.text_area :description, maxlength: 2000, class: "input-xlarge", rows: 10
- %p.hint Markdown is enabled.
+ %p.hint Milestones are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
.span6
.control-group
= f.label :due_date, "Due Date", class: "control-label"
@@ -32,20 +32,16 @@
.form-actions
- if @milestone.new_record?
- = f.submit 'Create milestone', class: "primary btn"
+ = f.submit 'Create milestone', class: "save-btn btn"
+ = link_to "Cancel", project_milestones_path(@project), class: "btn cancel-btn"
-else
- = f.submit 'Save changes', class: "primary btn"
+ = f.submit 'Save changes', class: "save-btn btn"
+ = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn cancel-btn"
- - if request.xhr?
- = link_to "Cancel", "#back", onclick: "backToIssues();", class: "btn"
- - else
- - if @milestone.new_record?
- = link_to "Cancel", project_milestones_path(@project), class: "btn"
- - else
- = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn"
:javascript
$(function() {
+ disableButtonIfEmtpyField("#milestone_title", ".save-btn");
$( ".datepicker" ).datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
diff --git a/app/views/milestones/index.html.haml b/app/views/milestones/index.html.haml
index ecb008d..c5333b0 100644
--- a/app/views/milestones/index.html.haml
+++ b/app/views/milestones/index.html.haml
@@ -8,11 +8,11 @@
%div.ui-box
.title
%ul.nav.nav-pills
- %li{class: ("active" if (params[:f] == "0" || !params[:f]))}
- = link_to project_milestones_path(@project, f: 0) do
+ %li{class: ("active" if (params[:f] == "active" || !params[:f]))}
+ = link_to project_milestones_path(@project, f: "active") do
Active
- %li{class: ("active" if params[:f] == "1")}
- = link_to project_milestones_path(@project, f: 1) do
+ %li{class: ("active" if params[:f] == "all")}
+ = link_to project_milestones_path(@project, f: "all") do
All
%ul.unstyled
diff --git a/app/views/notes/_create_common.js.haml b/app/views/notes/_create_common.js.haml
index 847ff38..e80eccb 100644
--- a/app/views/notes/_create_common.js.haml
+++ b/app/views/notes/_create_common.js.haml
@@ -1,9 +1,12 @@
- if note.valid?
:plain
- $("#new_note .errors").remove();
- $('#new_note textarea').val("");
+ $(".note-form-holder .error").remove();
+ $('.note-form-holder textarea').val("");
+ $('.note-form-holder #preview-link').text('Preview');
+ $('.note-form-holder #preview-note').hide();
+ $('.note-form-holder').show();
NoteList.prepend(#{note.id}, "#{escape_javascript(render partial: "notes/show", locals: {note: note})}");
- else
:plain
- $("#new_note").replaceWith("#{escape_javascript(render('form'))}");
+ $(".note-form-holder").replaceWith("#{escape_javascript(render('form'))}");
diff --git a/app/views/notes/_create_line.js.haml b/app/views/notes/_create_line.js.haml
index 13809be..662909f 100644
--- a/app/views/notes/_create_line.js.haml
+++ b/app/views/notes/_create_line.js.haml
@@ -1,7 +1,7 @@
- if note.valid?
:plain
$(".per_line_form").hide();
- $('#new_note textarea').val("");
+ $('.line-note-form-holder textarea').val("");
$("a.line_note_reply_link[line_code='#{note.line_code}']").closest("tr").remove();
var trEl = $(".#{note.line_code}").parent();
trEl.after("#{escape_javascript(render partial: "notes/per_line_show", locals: {note: note})}");
diff --git a/app/views/notes/_form.html.haml b/app/views/notes/_form.html.haml
index dac026b..7211a0a 100644
--- a/app/views/notes/_form.html.haml
+++ b/app/views/notes/_form.html.haml
@@ -1,38 +1,39 @@
-= form_for [@project, @note], remote: "true", multipart: true do |f|
- %h3.page_title Leave a comment
- -if @note.errors.any?
- .alert-message.block-message.error
- - @note.errors.full_messages.each do |msg|
- %div= msg
+.note-form-holder
+ = form_for [@project, @note], remote: "true", multipart: true do |f|
+ %h3.page_title Leave a comment
+ -if @note.errors.any?
+ .alert-message.block-message.error
+ - @note.errors.full_messages.each do |msg|
+ %div= msg
- = f.hidden_field :noteable_id
- = f.hidden_field :noteable_type
- = f.text_area :note, size: 255
- #preview-note.well.hide
- %p.hint
- = link_to "Gitlab Markdown", help_markdown_path, target: '_blank'
- is enabled.
- = link_to 'Preview', preview_project_notes_path(@project), id: 'preview-link'
+ = f.hidden_field :noteable_id
+ = f.hidden_field :noteable_type
+ = f.text_area :note, size: 255, class: 'note-text'
+ #preview-note.preview_note.hide
+ .hint
+ .right Comments are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
+ .clearfix
- .row.note_advanced_opts.hide
- .span2
- = f.submit 'Add Comment', class: "btn primary submit_note", id: "submit_note"
- .span4.notify_opts
- %h6.left Notify via email:
- = label_tag :notify do
- = check_box_tag :notify, 1, @note.noteable_type != "Commit"
- %span Project team
+ .row.note_advanced_opts.hide
+ .span3
+ = f.submit 'Add Comment', class: "btn success submit_note grouped", id: "submit_note"
+ = link_to 'Preview', preview_project_notes_path(@project), class: 'btn grouped', id: 'preview-link'
+ .span4.notify_opts
+ %h6.left Notify via email:
+ = label_tag :notify do
+ = check_box_tag :notify, 1, @note.noteable_type != "Commit"
+ %span Project team
- - if @note.notify_only_author?(current_user)
- = label_tag :notify_author do
- = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
- %span Commit author
- .span6.attachments
- %h6.left Attachment:
- %span.file_name File name...
-
- .input.input_file
- %a.file_upload.btn.small Upload File
- = f.file_field :attachment, class: "input-file"
- %span.hint Any file less than 10 MB
+ - if @note.notify_only_author?(current_user)
+ = label_tag :notify_author do
+ = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
+ %span Commit author
+ .span5.attachments
+ %h6.left Attachment:
+ %span.file_name File name...
+
+ .input.input_file
+ %a.file_upload.btn.small Upload File
+ = f.file_field :attachment, class: "input-file"
+ %span.hint Any file less than 10 MB
diff --git a/app/views/notes/_per_line_form.html.haml b/app/views/notes/_per_line_form.html.haml
index afb0b30..8e31b59 100644
--- a/app/views/notes/_per_line_form.html.haml
+++ b/app/views/notes/_per_line_form.html.haml
@@ -1,33 +1,34 @@
%table{style: "display:none;"}
%tr.per_line_form
%td{colspan: 3 }
- = form_for [@project, @note], remote: "true", multipart: true do |f|
- %h3.page_title Leave a note
- %div.span10
- -if @note.errors.any?
- .alert-message.block-message.error
- - @note.errors.full_messages.each do |msg|
- %div= msg
+ .line-note-form-holder
+ = form_for [@project, @note], remote: "true", multipart: true do |f|
+ %h3.page_title Leave a note
+ %div.span10
+ -if @note.errors.any?
+ .alert-message.block-message.error
+ - @note.errors.full_messages.each do |msg|
+ %div= msg
- = f.hidden_field :noteable_id
- = f.hidden_field :noteable_type
- = f.hidden_field :line_code
- = f.text_area :note, size: 255
- .note_actions
- .buttons
- = f.submit 'Add note', class: "btn primary submit_note", id: "submit_note"
- = link_to "Cancel", "#", class: "btn hide-button"
- .options
- %h6.left Notify via email:
- .labels
- = label_tag :notify do
- = check_box_tag :notify, 1, @note.noteable_type != "Commit"
- %span Project team
+ = f.hidden_field :noteable_id
+ = f.hidden_field :noteable_type
+ = f.hidden_field :line_code
+ = f.text_area :note, size: 255, class: 'line-note-text'
+ .note_actions
+ .buttons
+ = f.submit 'Add note', class: "btn save-btn submit_note submit_inline_note", id: "submit_note"
+ = link_to "Cancel", "#", class: "btn hide-button"
+ .options
+ %h6.left Notify via email:
+ .labels
+ = label_tag :notify do
+ = check_box_tag :notify, 1, @note.noteable_type != "Commit"
+ %span Project team
- - if @note.notify_only_author?(current_user)
- = label_tag :notify_author do
- = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
- %span Commit author
+ - if @note.notify_only_author?(current_user)
+ = label_tag :notify_author do
+ = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
+ %span Commit author
:javascript
$(function(){
diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml
new file mode 100644
index 0000000..59130f7
--- /dev/null
+++ b/app/views/notify/issue_status_changed_email.html.haml
@@ -0,0 +1,16 @@
+%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
+ %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
+ %tr
+ %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
+ %td{align: "left", style: "padding: 20px 0 0;"}
+ %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
+ = "Issue was #{@issue_status} by #{@updated_by.name}"
+ %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
+ %tr
+ %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
+ %td{align: "left", style: "padding: 20px 0 0;"}
+ %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
+ = "Issue ##{@issue.id}"
+ = link_to_gfm truncate(@issue.title, length: 45), project_issue_url(@issue.project, @issue), title: @issue.title
+ %br
+
diff --git a/app/views/notify/project_access_granted_email.html.haml b/app/views/notify/project_access_granted_email.html.haml
new file mode 100644
index 0000000..154c2aa
--- /dev/null
+++ b/app/views/notify/project_access_granted_email.html.haml
@@ -0,0 +1,14 @@
+%td.content{align: "left", style: "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", valign: "top", width: "600"}
+ %table{border: "0", cellpadding: "0", cellspacing: "0", style: "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", width: "600"}
+ %tr
+ %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
+ %td{align: "left", style: "padding: 20px 0 0;"}
+ %h2{style: "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
+ = "You got granted #{@users_project.project_access_human} access to project"
+ %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
+ %tr
+ %td{style: "font-size: 1px; line-height: 1px;", width: "21"}
+ %td{align: "left", style: "padding: 20px 0 0;"}
+ %h2{style: "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
+ = link_to_gfm truncate(@project.name, length: 45), project_url(@project), title: @project.name
+ %br
diff --git a/app/views/profile/password.html.haml b/app/views/profile/password.html.haml
index 257dacb..d0aee7a 100644
--- a/app/views/profile/password.html.haml
+++ b/app/views/profile/password.html.haml
@@ -16,4 +16,4 @@
= f.label :password_confirmation
.input= f.password_field :password_confirmation
.actions
- = f.submit 'Save', class: "btn primary"
+ = f.submit 'Save', class: "btn save-btn"
diff --git a/app/views/profile/show.html.haml b/app/views/profile/show.html.haml
index 95cce2b..22e840a 100644
--- a/app/views/profile/show.html.haml
+++ b/app/views/profile/show.html.haml
@@ -45,9 +45,10 @@
%span.help-block Tell us about yourself in fewer than 250 characters.
.span5.right
- %p.alert.alert-info
- %strong Tip:
- You can change your avatar at gravatar.com
+ -unless Gitlab.config.disable_gravatar?
+ %p.alert.alert-info
+ %strong Tip:
+ You can change your avatar at gravatar.com
%h4
Personal projects:
@@ -66,4 +67,4 @@
= link_to "Add Public Key", new_key_path, class: "btn small right"
.form-actions
- = f.submit 'Save', class: "btn-primary btn"
+ = f.submit 'Save', class: "btn save-btn"
diff --git a/app/views/projects/_clone_panel.html.haml b/app/views/projects/_clone_panel.html.haml
new file mode 100644
index 0000000..839a98a
--- /dev/null
+++ b/app/views/projects/_clone_panel.html.haml
@@ -0,0 +1,21 @@
+.project_clone_panel
+ .row
+ .span7
+ .form-horizontal
+ .input-prepend.project_clone_holder
+ = link_to "SSH", "#", class: "btn small active", :"data-clone" => @project.ssh_url_to_repo
+ = link_to "HTTP", "#", class: "btn small", :"data-clone" => @project.http_url_to_repo
+ = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5"
+ .span4.right
+ .right
+ - if can? current_user, :download_code, @project
+ = link_to archive_project_repository_path(@project), class: "btn small grouped" do
+ %i.icon-download-alt
+ Download
+ - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
+ = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn small grouped" do
+ Merge Request
+ - if @project.issues_enabled && can?(current_user, :write_issue, @project)
+ = link_to new_project_issue_path(@project), title: "New Issue", class: "btn small grouped" do
+ Issue
+
diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml
index ce66b2c..8bdeda1 100644
--- a/app/views/projects/_form.html.haml
+++ b/app/views/projects/_form.html.haml
@@ -10,9 +10,9 @@
.input
= f.text_field :name, placeholder: "Example Project", class: "xxlarge"
- %h5.page_title
- .alert.alert-info
- %h5 Advanced settings:
+ %hr
+ .adv_settings
+ %h6 Advanced settings:
.clearfix
= f.label :path do
Path
@@ -34,8 +34,9 @@
.input= f.select(:default_branch, @project.heads.map(&:name), {}, style: "width:210px;")
- unless @project.new_record?
- .alert.alert-info
- %h5 Features:
+ %hr
+ .adv_settings
+ %h6 Features:
.clearfix
= f.label :issues_enabled, "Issues"
@@ -56,7 +57,7 @@
%br
.actions
- = f.submit 'Save', class: "btn primary"
+ = f.submit 'Save', class: "btn save-btn"
= link_to 'Cancel', @project, class: "btn"
- unless @project.new_record?
.right
diff --git a/app/views/projects/_new_form.html.haml b/app/views/projects/_new_form.html.haml
index 5104df8..e6d5e93 100644
--- a/app/views/projects/_new_form.html.haml
+++ b/app/views/projects/_new_form.html.haml
@@ -7,11 +7,11 @@
Project name is
.input
= f.text_field :name, placeholder: "Example Project", class: "xxlarge"
- = f.submit 'Create project', class: "btn primary"
+ = f.submit 'Create project', class: "btn primary project-submit"
%hr
- .alert.alert-info
- %h5 Advanced settings:
+ %div.adv_settings
+ %h6 Advanced settings:
.clearfix
= f.label :path do
Git Clone
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index b8d0dad..d408c0a 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,47 +1,51 @@
-- if current_user.require_ssh_key?
- .alert-message.block-message.error
- %ul
- %li You have no ssh keys added to your profile.
- %li You wont be able to pull/push repository.
- %li Visit profile → keys and add public key of every machine you want to use for work with gitlabhq.
+= render 'shared/no_ssh'
+.project_clone_panel
+ .row
+ .span7
+ .form-horizontal
+ .input-prepend.project_clone_holder
+ = link_to "SSH", "#", class: "btn small active", :"data-clone" => @project.ssh_url_to_repo
+ = link_to "HTTP", "#", class: "btn small", :"data-clone" => @project.http_url_to_repo
+ = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5"
+%div.git-empty
+ %h4 Git global setup:
+ %pre.dark
+ = preserve do
+ git config --global user.name "#{current_user.name}"
+ git config --global user.email "#{current_user.email}"
-.alert-message.block-message.error
- %ul.unstyled.alert_holder
- %li You should push repository to proceed.
- %li After push you will be able to browse code, commits etc.
+ %h4.prepend-top-20 Create Repository
+ %pre.dark
+ = preserve do
+ mkdir #{@project.path}
+ cd #{@project.path}
+ git init
+ touch README
+ git add README
+ git commit -m 'first commit'
+ git remote add origin #{@project.url_to_repo}
+ git push -u origin master
-- bash_lexer = Pygments::Lexer[:bash]
-%div.git-empty
- %h3 Git global setup:
- - setup_str = ["git config --global user.name \"#{current_user.name}\"",
- "git config --global user.email \"#{current_user.email}\""].join("\n")
- = preserve do
- = raw bash_lexer.highlight(setup_str, lexer: 'bash', options: {encoding: 'utf-8'})
+ %h4.prepend-top-20 Existing Git Repo?
+ %pre.dark
+ = preserve do
+ cd existing_git_repo
+ git remote add origin #{@project.url_to_repo}
+ git push -u origin master
- %br
- %br
- %h3 Create Repository
- - repo_setup_str = ["mkdir #{@project.path}",
- "cd #{@project.path}",
- "git init",
- "touch README",
- "git add README",
- "git commit -m 'first commit'",
- "git remote add origin #{@project.url_to_repo}",
- "git push -u origin master"].join("\n")
+ - if can? current_user, :admin_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger right"
- = preserve do
- = raw bash_lexer.highlight(repo_setup_str)
- %br
- %br
- %h3 Existing Git Repo?
- - exist_repo_setup_str = ["cd existing_git_repo",
- "git remote add origin #{@project.url_to_repo}",
- "git push -u origin master"].join("\n")
- = preserve do
- = raw bash_lexer.highlight(exist_repo_setup_str)
- - if can? current_user, :admin_project, @project
- .alert-message.block-message.error.prepend-top-20
- = link_to 'Remove project', @project, confirm: 'Are you sure?', method: :delete, class: "btn danger"
+:javascript
+ $(function(){
+ var link_sel = ".project_clone_holder a";
+ $(link_sel).bind("click", function() {
+ $(link_sel).removeClass("active");
+ $(this).addClass("active");
+ $("#project_clone").val($(this).attr("data-clone"));
+ })
+ })
+
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 703e558..933cb67 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -3,10 +3,10 @@
New Project
%hr
= render 'new_form'
-%div.ajax_loader.hide
+%div.save-project-loader.hide
%center
- %div.padded= image_tag "ajax_loader.gif"
- %h3.prepend-top Creating project & repository. Please wait a few minutes
+ = image_tag "ajax_loader.gif"
+ %h3 Creating project & repository. Please wait a few minutes
:javascript
$(function(){ new Projects(); });
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index ebd2c8e..77a0ef1 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,35 +1,12 @@
= render "project_head"
-
-.entry
- .row
- .span7
- .form-horizontal
- .input-prepend.project_clone_holder
-
- %span.add-on git clone
- = link_to "SSH", "#", class: "btn small active", :"data-clone" => @project.ssh_url_to_repo
- = link_to "HTTP", "#", class: "btn small", :"data-clone" => @project.http_url_to_repo
- = text_field_tag :project_clone, @project.url_to_repo, class: "one_click_select span5"
- .span4.right
- .right
- - if can? current_user, :download_code, @project
- = link_to archive_project_repository_path(@project), class: "btn small grouped" do
- %i.icon-download-alt
- Download
- - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
- = link_to new_project_merge_request_path(@project), title: "New Merge Request", class: "btn small grouped" do
- Merge Request
- - if @project.issues_enabled && can?(current_user, :write_issue, @project)
- = link_to new_project_issue_path(@project), title: "New Issue", class: "btn small grouped" do
- Issue
-
+= render 'clone_panel'
= render "events/event_last_push", event: @last_push
.content_list= render @events
-:javascript
+:javascript
$(function(){
var link_sel = ".project_clone_holder a";
- $(link_sel).bind("click", function() {
+ $(link_sel).bind("click", function() {
$(link_sel).removeClass("active");
$(this).addClass("active");
$("#project_clone").val($(this).attr("data-clone"));
diff --git a/app/views/refs/_tree_item.html.haml b/app/views/refs/_tree_item.html.haml
index 2e6bbf6..d4c4ee8 100644
--- a/app/views/refs/_tree_item.html.haml
+++ b/app/views/refs/_tree_item.html.haml
@@ -2,7 +2,7 @@
%tr{ class: "tree-item #{tree_hex_class(content)}", url: tree_file_project_ref_path(@project, @ref, file) }
%td.tree-item-file-name
= tree_icon(content)
- = link_to truncate(content.name, length: 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), remote: :true
+ %strong= link_to truncate(content.name, length: 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), remote: :true
%td.tree_time_ago.cgray
- if index == 1
%span.log_loading
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index ed9da1f..d37ef67 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -1,10 +1,10 @@
-= form_tag search_path, method: :get do |f|
+= form_tag search_path, method: :get, class: 'form-inline' do |f|
.padded
= label_tag :search do
%strong Looking for
.input
= text_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge", id: "dashboard_search"
- = submit_tag 'Search', class: "btn btn-primary"
+ = submit_tag 'Search', class: "btn primary"
- if params[:search].present?
%br
%h3
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
new file mode 100644
index 0000000..b6ab666
--- /dev/null
+++ b/app/views/shared/_no_ssh.html.haml
@@ -0,0 +1,8 @@
+- if current_user.require_ssh_key?
+ %h6.error_message
+ %span
+ You wont be able to pull/push project code unless you
+ %strong
+ = link_to new_key_path, class: "vlink" do
+ add SSH key
+ to your profile
diff --git a/app/views/team_members/_show.html.haml b/app/views/team_members/_show.html.haml
index f47554c..2dc4fb6 100644
--- a/app/views/team_members/_show.html.haml
+++ b/app/views/team_members/_show.html.haml
@@ -9,7 +9,7 @@
%span.label Blocked
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
- = image_tag gravatar_icon(user.email, 40), class: "avatar"
+ = image_tag gravatar_icon(user.email, 40), class: "avatar s32"
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
%strong= truncate(user.name, lenght: 40)
%br
diff --git a/app/views/team_members/show.html.haml b/app/views/team_members/show.html.haml
index d7e09bc..6cb357c 100644
--- a/app/views/team_members/show.html.haml
+++ b/app/views/team_members/show.html.haml
@@ -51,7 +51,7 @@
= form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f|
= f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin
%hr
- = render user.recent_events.limit(5)
+ = render @events
:javascript
$(function(){
$('.repo-access-select, .project-access-select').live("change", function() {
diff --git a/app/views/wikis/_form.html.haml b/app/views/wikis/_form.html.haml
index 6b6411b..12b57e0 100644
--- a/app/views/wikis/_form.html.haml
+++ b/app/views/wikis/_form.html.haml
@@ -14,13 +14,14 @@
.middle_box_content
.input
%span.cgray
- Wiki content is parsed with #{link_to "Markdown", "http://en.wikipedia.org/wiki/Markdown"}.
- To add link to new page you can just type
+ Wiki content is parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.
+ To link to a (new) page you can just type
%code [Link Title](page-slug)
+ \.
.bottom_box_content
= f.label :content
.input= f.text_area :content, class: 'span8'
.actions
- = f.submit 'Save', class: "primary btn"
- = link_to "Cancel", project_wiki_path(@project, :index), class: "btn"
+ = f.submit 'Save', class: "save-btn btn"
+ = link_to "Cancel", project_wiki_path(@project, :index), class: "btn cancel-btn"
diff --git a/config/application.rb b/config/application.rb
index ecd88b1..ad41f19 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -23,7 +23,7 @@ module Gitlab
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Activate observers that should always be running.
- config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer
+ config.active_record.observers = :mailer_observer, :activity_observer, :project_observer, :key_observer, :issue_observer, :user_observer, :system_hook_observer, :users_project_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
diff --git a/config/environment.rb b/config/environment.rb
index c880a7a..3b186a9 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -3,5 +3,3 @@ require File.expand_path('../application', __FILE__)
# Initialize the rails application
Gitlab::Application.initialize!
-
-require File.join(Rails.root, "lib", "gitlab", "git_host")
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 1818f2c..d05cc1b 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -1,4 +1,4 @@
-# # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # #
# Gitlab application config file #
# # # # # # # # # # # # # # # # # #
@@ -19,27 +19,27 @@ email:
# Application specific settings
# Like default project limit for user etc
-app:
- default_projects_limit: 10
+app:
+ default_projects_limit: 10
# backup_path: "/vol/backups" # default: Rails.root + backups/
# backup_keep_time: 604800 # default: 0 (forever) (in seconds)
+ # disable_gravatar: true # default: false - Disable user avatars from Gravatar.com
-
-#
-# 2. Advanced settings:
+#
+# 2. Advanced settings:
# ==========================
# Git Hosting configuration
git_host:
admin_uri: git@localhost:gitolite-admin
base_path: /home/git/repositories/
+ # hooks_path: /var/lib/gitolite/.gitolite/hooks/ # only needed when gitolite is not installed according the manual
# host: localhost
git_user: git
upload_pack: true
receive_pack: true
# port: 22
-
# Git settings
# Use default values unless you understand it
git:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 5c5987a..27c5bc2 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -66,6 +66,10 @@ class Settings < Settingslogic
git_host['base_path'] || '/home/git/repositories/'
end
+ def git_hooks_path
+ git_host['hooks_path'] || '/home/git/share/gitolite/hooks/'
+ end
+
def git_upload_pack
if git_host['upload_pack'] != false
true
@@ -111,5 +115,9 @@ class Settings < Settingslogic
def backup_keep_time
app['backup_keep_time'] || 0
end
+
+ def disable_gravatar?
+ app['disable_gravatar'] || false
+ end
end
end
diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb
new file mode 100644
index 0000000..85f747a
--- /dev/null
+++ b/config/initializers/5_backend.rb
@@ -0,0 +1,5 @@
+# GIT over HTTP
+require Rails.root.join("lib", "gitlab", "backend", "grack_auth")
+
+# GITOLITE backend
+require Rails.root.join("lib", "gitlab", "backend", "gitolite")
diff --git a/config/initializers/grack_auth.rb b/config/initializers/grack_auth.rb
deleted file mode 100644
index 5995b87..0000000
--- a/config/initializers/grack_auth.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-module Grack
- class Auth < Rack::Auth::Basic
-
- def valid?
- # Authentication with username and password
- email, password = @auth.credentials
- user = User.find_by_email(email)
- return false unless user.try(:valid_password?, password)
-
- # Set GL_USER env variable
- ENV['GL_USER'] = email
- # Pass Gitolite update hook
- ENV['GL_BYPASS_UPDATE_HOOK'] = "true"
-
- # Need this patch because the rails mount
- @env['PATH_INFO'] = @env['REQUEST_PATH']
-
- # Find project by PATH_INFO from env
- if m = /^\/([\w-]+).git/.match(@env['PATH_INFO']).to_a
- return false unless project = Project.find_by_path(m.last)
- end
-
- # Git upload and receive
- if @env['REQUEST_METHOD'] == 'GET'
- true
- elsif @env['REQUEST_METHOD'] == 'POST'
- if @env['REQUEST_URI'].end_with?('git-upload-pack')
- return project.dev_access_for?(user)
- elsif @env['REQUEST_URI'].end_with?('git-receive-pack')
- if project.protected_branches.map(&:name).include?(current_ref)
- project.master_access_for?(user)
- else
- project.dev_access_for?(user)
- end
- else
- false
- end
- else
- false
- end
- end# valid?
-
- def current_ref
- if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
- input = Zlib::GzipReader.new(@request.body).string
- else
- input = @request.body.string
- end
-
- oldrev, newrev, ref = input.split(' ')
- /refs\/heads\/([\w-]+)/.match(ref).to_a.last
- end
- end# Auth
-end# Grack
diff --git a/config/initializers/rails_footnotes.rb b/config/initializers/rails_footnotes.rb
deleted file mode 100644
index afe6f3a..0000000
--- a/config/initializers/rails_footnotes.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-#if defined?(Footnotes) && Rails.env.development?
- #Footnotes.run! # first of all
-#end
diff --git a/config/routes.rb b/config/routes.rb
index 04e13bc..f895478 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -30,6 +30,7 @@ Gitlab::Application.routes.draw do
get 'help/web_hooks' => 'help#web_hooks'
get 'help/system_hooks' => 'help#system_hooks'
get 'help/markdown' => 'help#markdown'
+ get 'help/ssh' => 'help#ssh'
#
# Admin Area
@@ -196,7 +197,9 @@ Gitlab::Application.routes.draw do
end
resources :team_members
resources :milestones
+ resources :labels, :only => [:index]
resources :issues do
+
collection do
post :sort
post :bulk_update
diff --git a/db/fixtures/test/001_repo.rb b/db/fixtures/test/001_repo.rb
index ebf005a..67d4e7b 100644
--- a/db/fixtures/test/001_repo.rb
+++ b/db/fixtures/test/001_repo.rb
@@ -1,15 +1,23 @@
-# create tmp dir if not exist
-tmp_dir = File.join(Rails.root, "tmp")
-Dir.mkdir(tmp_dir) unless File.exists?(tmp_dir)
-
-# Create dir for test repo
-repo_dir = File.join(Rails.root, "tmp", "tests")
-Dir.mkdir(repo_dir) unless File.exists?(repo_dir)
-
-`cp spec/seed_project.tar.gz tmp/tests/`
-Dir.chdir(repo_dir)
-`tar -xf seed_project.tar.gz`
-3.times do |i|
-`cp -r gitlabhq/ gitlabhq_#{i}/`
-puts "Unpacked seed repo - tmp/tests/gitlabhq_#{i}"
+require 'fileutils'
+
+print "Unpacking seed repository..."
+
+SEED_REPO = 'seed_project.tar.gz'
+REPO_PATH = File.join(Rails.root, 'tmp', 'repositories')
+
+# Make whatever directories we need to make
+FileUtils.mkdir_p(REPO_PATH)
+
+# Copy the archive to the repo path
+FileUtils.cp(File.join(Rails.root, 'spec', SEED_REPO), REPO_PATH)
+
+# chdir to the repo path
+FileUtils.cd(REPO_PATH) do
+ # Extract the archive
+ `tar -xf #{SEED_REPO}`
+
+ # Remove the copy
+ FileUtils.rm(SEED_REPO)
end
+
+puts ' done.'
diff --git a/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb b/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb
new file mode 100644
index 0000000..d5e66ba
--- /dev/null
+++ b/db/migrate/20120729131232_add_extern_auth_provider_to_users.rb
@@ -0,0 +1,8 @@
+class AddExternAuthProviderToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :extern_uid, :string
+ add_column :users, :provider, :string
+
+ add_index :users, [:extern_uid, :provider], :unique => true
+ end
+end
diff --git a/db/pkey.example b/db/pkey.example
deleted file mode 100644
index ae04577..0000000
--- a/db/pkey.example
+++ /dev/null
@@ -1,3 +0,0 @@
-AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
-596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
-soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=
diff --git a/db/schema.rb b/db/schema.rb
index c4c54f5..46461e4 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20120712080407) do
+ActiveRecord::Schema.define(:version => 20120729131232) do
create_table "events", :force => true do |t|
t.string "target_type"
@@ -171,9 +171,12 @@ ActiveRecord::Schema.define(:version => 20120712080407) do
t.boolean "blocked", :default => false, :null => false
t.integer "failed_attempts", :default => 0
t.datetime "locked_at"
+ t.string "extern_uid"
+ t.string "provider"
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
+ add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
create_table "users_projects", :force => true do |t|
diff --git a/doc/api/README.md b/doc/api/README.md
index e011196..53b4983 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -27,4 +27,6 @@ The API uses JSON to serialize data. You don't need to specify `.json` at the en
+ [Users](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/users.md)
+ [Projects](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/projects.md)
++ [Snippets](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/snippets.md)
+ [Issues](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/issues.md)
++ [Milestones](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/milestones.md)
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
new file mode 100644
index 0000000..f68d8eb
--- /dev/null
+++ b/doc/api/milestones.md
@@ -0,0 +1,57 @@
+## List project milestones
+
+Get a list of project milestones.
+
+```
+GET /projects/:id/milestones
+```
+
+Parameters:
+
++ `id` (required) - The ID or code name of a project
+
+## Single milestone
+
+Get a single project milestone.
+
+```
+GET /projects/:id/milestones/:milestone_id
+```
+
+Parameters:
+
++ `id` (required) - The ID or code name of a project
++ `milestone_id` (required) - The ID of a project milestone
+
+## New milestone
+
+Create a new project milestone.
+
+```
+POST /projects/:id/milestones
+```
+
+Parameters:
+
++ `id` (required) - The ID or code name of a project
++ `milestone_id` (required) - The ID of a project milestone
++ `title` (required) - The title of an milestone
++ `description` (optional) - The description of the milestone
++ `due_date` (optional) - The due date of the milestone
+
+## Edit milestone
+
+Update an existing project milestone.
+
+```
+PUT /projects/:id/milestones/:milestone_id
+```
+
+Parameters:
+
++ `id` (required) - The ID or code name of a project
++ `milestone_id` (required) - The ID of a project milestone
++ `title` (optional) - The title of a milestone
++ `description` (optional) - The description of a milestone
++ `due_date` (optional) - The due date of the milestone
++ `closed` (optional) - The status of the milestone
diff --git a/doc/api/projects.md b/doc/api/projects.md
index ead3100..d680b5d 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -204,108 +204,6 @@ Parameters:
]
```
-# Project Snippets
-
-## List snippets
-
-Not implemented.
-
-## Single snippet
-
-Get a project snippet.
-
-```
-GET /projects/:id/snippets/:snippet_id
-```
-
-Parameters:
-
-+ `id` (required) - The ID or code name of a project
-+ `snippet_id` (required) - The ID of a project's snippet
-
-```json
-{
- "id": 1,
- "title": "test",
- "file_name": "add.rb",
- "author": {
- "id": 1,
- "email": "john@example.com",
- "name": "John Smith",
- "blocked": false,
- "created_at": "2012-05-23T08:00:58Z"
- },
- "expires_at": null,
- "updated_at": "2012-06-28T10:52:04Z",
- "created_at": "2012-06-28T10:52:04Z"
-}
-```
-
-## Snippet content
-
-Get a raw project snippet.
-
-```
-GET /projects/:id/snippets/:snippet_id/raw
-```
-
-Parameters:
-
-+ `id` (required) - The ID or code name of a project
-+ `snippet_id` (required) - The ID of a project's snippet
-
-## New snippet
-
-Create a new project snippet.
-
-```
-POST /projects/:id/snippets
-```
-
-Parameters:
-
-+ `id` (required) - The ID or code name of a project
-+ `title` (required) - The title of a snippet
-+ `file_name` (required) - The name of a snippet file
-+ `lifetime` (optional) - The expiration date of a snippet
-+ `code` (required) - The content of a snippet
-
-Will return created snippet with status `201 Created` on success, or `404 Not found` on fail.
-
-## Edit snippet
-
-Update an existing project snippet.
-
-```
-PUT /projects/:id/snippets/:snippet_id
-```
-
-Parameters:
-
-+ `id` (required) - The ID or code name of a project
-+ `snippet_id` (required) - The ID of a project's snippet
-+ `title` (optional) - The title of a snippet
-+ `file_name` (optional) - The name of a snippet file
-+ `lifetime` (optional) - The expiration date of a snippet
-+ `code` (optional) - The content of a snippet
-
-Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail.
-
-## Delete snippet
-
-Delete existing project snippet.
-
-```
-DELETE /projects/:id/snippets/:snippet_id
-```
-
-Parameters:
-
-+ `id` (required) - The ID or code name of a project
-+ `snippet_id` (required) - The ID of a project's snippet
-
-Status code `200` will be returned on success.
-
## Raw blob content
Get the raw file contents for a file.
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
new file mode 100644
index 0000000..0cd29ce
--- /dev/null
+++ b/doc/api/snippets.md
@@ -0,0 +1,100 @@
+## List snippets
+
+Not implemented.
+
+## Single snippet
+
+Get a project snippet.
+
+```
+GET /projects/:id/snippets/:snippet_id
+```
+
+Parameters:
+
++ `id` (required) - The ID or code name of a project
++ `snippet_id` (required) - The ID of a project's snippet
+
+```json
+{
+ "id": 1,
+ "title": "test",
+ "file_name": "add.rb",
+ "author": {
+ "id": 1,
+ "email": "john@example.com",
+ "name": "John Smith",
+ "blocked": false,
+ "created_at": "2012-05-23T08:00:58Z"
+ },
+ "expires_at": null,
+ "updated_at": "2012-06-28T10:52:04Z",
+ "created_at": "2012-06-28T10:52:04Z"
+}
+```
+
+## Snippet content
+
+Get a raw project snippet.
+
+```
+GET /projects/:id/snippets/:snippet_id/raw
+```
+
+Parameters:
+
++ `id` (required) - The ID or code name of a project
++ `snippet_id` (required) - The ID of a project's snippet
+
+## New snippet
+
+Create a new project snippet.
+
+```
+POST /projects/:id/snippets
+```
+
+Parameters:
+
++ `id` (required) - The ID or code name of a project
++ `title` (required) - The title of a snippet
++ `file_name` (required) - The name of a snippet file
++ `lifetime` (optional) - The expiration date of a snippet
++ `code` (required) - The content of a snippet
+
+Will return created snippet with status `201 Created` on success, or `404 Not found` on fail.
+
+## Edit snippet
+
+Update an existing project snippet.
+
+```
+PUT /projects/:id/snippets/:snippet_id
+```
+
+Parameters:
+
++ `id` (required) - The ID or code name of a project
++ `snippet_id` (required) - The ID of a project's snippet
++ `title` (optional) - The title of a snippet
++ `file_name` (optional) - The name of a snippet file
++ `lifetime` (optional) - The expiration date of a snippet
++ `code` (optional) - The content of a snippet
+
+Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail.
+
+## Delete snippet
+
+Delete existing project snippet.
+
+```
+DELETE /projects/:id/snippets/:snippet_id
+```
+
+Parameters:
+
++ `id` (required) - The ID or code name of a project
++ `snippet_id` (required) - The ID of a project's snippet
+
+Status code `200` will be returned on success.
+
diff --git a/doc/development.md b/doc/development.md
new file mode 100644
index 0000000..55be2bc
--- /dev/null
+++ b/doc/development.md
@@ -0,0 +1,45 @@
+## Development tips:
+
+### Start application in development mode
+
+#### 1. Via foreman
+
+ bundle exec foreman -p 3000
+
+#### 2. Via gitlab cli
+
+ ./gitlab start
+
+#### 3. Manually
+
+ bundle exec rails s
+ bundle exec rake environment resque:work QUEUE=* VVERBOSE=1
+
+
+### Run tests:
+
+#### 1. Packages
+
+ # ubuntu
+ sudo apt-get install libqt4-dev libqtwebkit-dev
+ sudo apt-get install xvfb
+
+ # Mac
+ brew install qt
+ brew install xvfb
+
+#### 2. DB & seeds
+
+ bundle exec rake db:setup RAILS_ENV=test
+ bundle exec rake db:seed_fu RAILS_ENV=test
+
+### 3. Run Tests
+
+ # All in one
+ bundle exec gitlab:test
+
+ # Rspec
+ bundle exec rake spec
+
+ # Cucumber
+ bundle exec rake cucumber
diff --git a/doc/installation.md b/doc/installation.md
index b6f1869..b3fe92b 100644
--- a/doc/installation.md
+++ b/doc/installation.md
@@ -119,7 +119,6 @@ Permissions:
sudo chmod -R g+rwX /home/git/repositories/
sudo chown -R git:git /home/git/repositories/
- sudo chown gitlab:gitlab /home/git/repositories/**/hooks/post-receive
#### CHECK: Logout & login again to apply git group to your user
@@ -178,6 +177,11 @@ Permissions:
sudo -u gitlab bundle exec rake gitlab:app:setup RAILS_ENV=production
+#### Setup gitlab hooks
+
+ sudo cp ./lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive
+ sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive
+
Checking status:
sudo -u gitlab bundle exec rake gitlab:app:status RAILS_ENV=production
@@ -196,6 +200,7 @@ Checking status:
Resolving deltas: 100% (174/174), done.
Can clone gitolite-admin?............YES
UMASK for .gitolite.rc is 0007? ............YES
+ /home/git/share/gitolite/hooks/common/post-receive exists? ............YES
If you got all YES - congrats! You can go to next step.
@@ -239,42 +244,15 @@ You can login via web using admin generated with setup:
sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb
sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D
-Edit /etc/nginx/nginx.conf. In the *http* section add the following section of code or replace it completely with https://raw.github.com/dosire/gitlabhq/master/aws/nginx.conf
-
- upstream gitlab {
- server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket;
- }
-
- server {
- listen YOUR_SERVER_IP:80; # e.g., listen 192.168.1.1:80;
- server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com;
- root /home/gitlab/gitlab/public;
-
- # individual nginx logs for this gitlab vhost
- access_log /var/log/nginx/gitlab_access.log;
- error_log /var/log/nginx/gitlab_error.log;
-
- location / {
- # serve static files from defined root folder;.
- # @gitlab is a named location for the upstream fallback, see below
- try_files $uri $uri/index.html $uri.html @gitlab;
- }
-
- # if a file, which is not found in the root folder is requested,
- # then the proxy pass the request to the upsteam (gitlab unicorn)
- location @gitlab {
- proxy_redirect off;
-
- # you need to change this to "https", if you set "ssl" directive to "on"
- proxy_set_header X-FORWARDED_PROTO http;
- proxy_set_header Host $http_host;
- proxy_set_header X-Real-IP $remote_addr;
+Add gitlab to nginx sites & change with your host specific settings
- proxy_pass http://gitlab;
- }
- }
+ sudo cp /home/gitlab/gitlab/lib/support/nginx-gitlab /etc/nginx/sites-available/gitlab
+ sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
-Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN** to the IP address and fully-qualified domain name of the host serving GitLab.
+ # Change **YOUR_SERVER_IP** and **YOUR_SERVER_FQDN**
+ # to the IP address and fully-qualified domain name
+ # of the host serving GitLab.
+ sudo vim /etc/nginx/sites-enabled/gitlab
Restart nginx:
@@ -282,60 +260,7 @@ Restart nginx:
Create init script in /etc/init.d/gitlab:
- #! /bin/bash
- ### BEGIN INIT INFO
- # Provides: gitlab
- # Required-Start: $local_fs $remote_fs $network $syslog redis-server
- # Required-Stop: $local_fs $remote_fs $network $syslog
- # Default-Start: 2 3 4 5
- # Default-Stop: 0 1 6
- # Short-Description: GitLab git repository management
- # Description: GitLab git repository management
- ### END INIT INFO
-
- DAEMON_OPTS="-c /home/gitlab/gitlab/config/unicorn.rb -E production -D"
- NAME=unicorn
- DESC="Gitlab service"
- PID=/home/gitlab/gitlab/tmp/pids/unicorn.pid
- RESQUE_PID=/home/gitlab/gitlab/tmp/pids/resque_worker.pid
-
- case "$1" in
- start)
- CD_TO_APP_DIR="cd /home/gitlab/gitlab"
- START_DAEMON_PROCESS="bundle exec unicorn_rails $DAEMON_OPTS"
- START_RESQUE_PROCESS="./resque.sh"
-
- echo -n "Starting $DESC: "
- if [ `whoami` = root ]; then
- sudo -u gitlab sh -l -c "$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS"
- else
- $CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS
- fi
- echo "$NAME."
- ;;
- stop)
- echo -n "Stopping $DESC: "
- kill -QUIT `cat $PID`
- kill -QUIT `cat $RESQUE_PID`
- echo "$NAME."
- ;;
- restart)
- echo -n "Restarting $DESC: "
- kill -USR2 `cat $PID`
- echo "$NAME."
- ;;
- reload)
- echo -n "Reloading $DESC configuration: "
- kill -HUP `cat $PID`
- echo "$NAME."
- ;;
- *)
- echo "Usage: $NAME {start|stop|restart|reload}" >&2
- exit 1
- ;;
- esac
-
- exit 0
+ cp /home/gitlab/gitlab/lib/support/init-gitlab /etc/init.d/gitlab
Adding permission:
diff --git a/features/profile/ssh_keys.feature b/features/profile/ssh_keys.feature
index c3a92f3..c81503e 100644
--- a/features/profile/ssh_keys.feature
+++ b/features/profile/ssh_keys.feature
@@ -3,8 +3,8 @@ Feature: SSH Keys
Given I signin as a user
And I have ssh keys:
| title |
- | Work |
- | Home |
+ | ssh-rsa Work |
+ | ssh-rsa Home |
And I visit profile keys page
Scenario: I should see SSH keys
diff --git a/features/projects/issues/labels.feature b/features/projects/issues/labels.feature
new file mode 100644
index 0000000..5a20bfd
--- /dev/null
+++ b/features/projects/issues/labels.feature
@@ -0,0 +1,13 @@
+Feature: Labels
+ Background:
+ Given I signin as a user
+ And I own project "Shop"
+ And project "Shop" have issues tags:
+ | name |
+ | bug |
+ | feature |
+ Given I visit project "Shop" labels page
+
+ Scenario: I should see active milestones
+ Then I should see label "bug"
+ And I should see label "feature"
diff --git a/features/projects/network.feature b/features/projects/network.feature
index 9655184..61c05eb 100644
--- a/features/projects/network.feature
+++ b/features/projects/network.feature
@@ -4,9 +4,7 @@ Feature: Project Network Graph
Background:
Given I signin as a user
And I own project "Shop"
- And I visit project "Shop" network page
+ And I visit project "Shop" network page
Scenario: I should see project network
Then page should have network graph
-
-
diff --git a/features/step_definitions/dashboard_steps.rb b/features/step_definitions/dashboard_steps.rb
index 90c3a69..a4edd22 100644
--- a/features/step_definitions/dashboard_steps.rb
+++ b/features/step_definitions/dashboard_steps.rb
@@ -91,36 +91,24 @@ Then /^I should see my merge requests$/ do
end
Given /^I have assigned issues$/ do
- project1 = Factory :project,
- :path => "project1",
- :code => "gitlabhq_1"
-
- project2 = Factory :project,
- :path => "project2",
- :code => "gitlabhq_2"
-
- project1.add_access(@user, :read, :write)
- project2.add_access(@user, :read, :write)
+ project = Factory :project
+ project.add_access(@user, :read, :write)
issue1 = Factory :issue,
:author => @user,
:assignee => @user,
- :project => project1
+ :project => project
issue2 = Factory :issue,
:author => @user,
:assignee => @user,
- :project => project2
+ :project => project
end
Given /^I have authored merge requests$/ do
- project1 = Factory :project,
- :path => "project1",
- :code => "gitlabhq_1"
+ project1 = Factory :project
- project2 = Factory :project,
- :path => "project2",
- :code => "gitlabhq_2"
+ project2 = Factory :project
project1.add_access(@user, :read, :write)
project2.add_access(@user, :read, :write)
diff --git a/features/step_definitions/profile/profile_keys_steps.rb b/features/step_definitions/profile/profile_keys_steps.rb
index 5ab7e04..25926c5 100644
--- a/features/step_definitions/profile/profile_keys_steps.rb
+++ b/features/step_definitions/profile/profile_keys_steps.rb
@@ -16,7 +16,7 @@ end
Given /^I submit new ssh key "(.*?)"$/ do |arg1|
fill_in "key_title", :with => arg1
- fill_in "key_key", :with => "publickey234="
+ fill_in "key_key", :with => "ssh-rsa publickey234="
click_button "Save"
end
diff --git a/features/step_definitions/project/project_issues_steps.rb b/features/step_definitions/project/project_issues_steps.rb
index 00a1721..27de03d 100644
--- a/features/step_definitions/project/project_issues_steps.rb
+++ b/features/step_definitions/project/project_issues_steps.rb
@@ -33,6 +33,25 @@ Given /^I visit issue page "(.*?)"$/ do |arg1|
end
Given /^I submit new issue "(.*?)"$/ do |arg1|
- fill_in "issue_title", :with => arg1
+ fill_in "issue_title", with: arg1
click_button "Submit new issue"
end
+
+Given /^project "(.*?)" have issues tags:$/ do |arg1, table|
+ project = Project.find_by_name(arg1)
+ table.hashes.each do |hash|
+ Factory :issue,
+ project: project,
+ label_list: [hash[:name]]
+ end
+end
+
+Given /^I visit project "(.*?)" labels page$/ do |arg1|
+ visit project_labels_path(Project.find_by_name(arg1))
+end
+
+Then /^I should see label "(.*?)"$/ do |arg1|
+ within ".labels-table" do
+ page.should have_content arg1
+ end
+end
diff --git a/features/step_definitions/project/projects_steps.rb b/features/step_definitions/project/projects_steps.rb
index c9af346..d981e1f 100644
--- a/features/step_definitions/project/projects_steps.rb
+++ b/features/step_definitions/project/projects_steps.rb
@@ -1,4 +1,4 @@
-include LoginMacros
+include LoginHelpers
Given /^I signin as a user$/ do
login_as :user
@@ -57,6 +57,11 @@ end
Given /^I visit project "(.*?)" network page$/ do |arg1|
project = Project.find_by_name(arg1)
+
+ # Stub out find_all to speed this up (10 commits vs. 650)
+ commits = Grit::Commit.find_all(project.repo, nil, {max_count: 10})
+ Grit::Commit.stub(:find_all).and_return(commits)
+
visit graph_project_path(project)
end
@@ -67,8 +72,8 @@ end
Given /^page should have network graph$/ do
page.should have_content "Project Network Graph"
within ".graph" do
- page.should have_content "stable"
- page.should have_content "notes_refacto..."
+ page.should have_content "master"
+ page.should have_content "scss_refactor..."
end
end
diff --git a/features/support/env.rb b/features/support/env.rb
index 496f23f..5357815 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -1,13 +1,16 @@
-require 'simplecov'
-SimpleCov.start 'rails'
+unless ENV['CI']
+ require 'simplecov'
+ SimpleCov.start 'rails'
+end
require 'cucumber/rails'
require 'webmock/cucumber'
+
WebMock.allow_net_connect!
-require Rails.root.join 'spec/monkeypatch'
-require Rails.root.join 'spec/factories'
-require Rails.root.join 'spec/support/login'
+require Rails.root.join 'spec/support/gitolite_stub'
+require Rails.root.join 'spec/support/stubbed_repository'
+require Rails.root.join 'spec/support/login_helpers'
require Rails.root.join 'spec/support/valid_commit'
Capybara.default_selector = :css
@@ -44,3 +47,13 @@ require 'headless'
headless = Headless.new
headless.start
+
+require 'cucumber/rspec/doubles'
+
+include GitoliteStub
+
+Before do
+ stub_gitolite!
+end
+
+World(FactoryGirl::Syntax::Methods)
diff --git a/gitlab b/gitlab
new file mode 100755
index 0000000..acafb3f
--- /dev/null
+++ b/gitlab
@@ -0,0 +1,75 @@
+#!/usr/bin/env ruby
+
+class GitlabCli
+ def initialize
+ @path = File.dirname(__FILE__)
+ @command = ARGV.shift
+ @mode = ARGV.shift
+ end
+
+ def execute
+ case @command
+ when 'start' then start
+ when 'stop' then stop
+ else
+ puts "-- Usage gitlab start production or gitlab stop development"
+ end
+ end
+
+ private
+
+ def start
+ case @mode
+ when 'production';
+ system(unicorn_start_cmd)
+ system(resque_start_cmd)
+ else
+ system(rails_start_cmd)
+ system(resque_dev_start_cmd)
+ end
+ end
+
+ def stop
+ case @mode
+ when 'production';
+ system(unicorn_stop_cmd)
+ else
+ system(rails_stop_cmd)
+ end
+ system(resque_stop_cmd)
+ end
+
+ def rails_start_cmd
+ "bundle exec rails s -d"
+ end
+
+ def rails_stop_cmd
+ pid = File.join(@path, "tmp/pids/server.pid")
+ "kill -QUIT `cat #{pid}`"
+ end
+
+ def unicorn_start_cmd
+ unicorn_conf = File.join(@path, "config/unicorn.rb")
+ "bundle exec unicorn_rails -c #{unicorn_conf} -E production -D"
+ end
+
+ def unicorn_stop_cmd
+ pid = File.join(@path, "/tmp/pids/unicorn.pid")
+ "kill -QUIT `cat #{pid}`"
+ end
+
+ def resque_dev_start_cmd
+ "./resque_dev.sh > /dev/null 2>&1"
+ end
+
+ def resque_start_cmd
+ "./resque.sh > /dev/null 2>&1"
+ end
+
+ def resque_stop_cmd
+ pid = File.join(@path, "tmp/pids/resque_worker.pid")
+ "kill -QUIT `cat #{pid}`"
+ end
+end
+
+GitlabCli.new.execute
diff --git a/lib/api.rb b/lib/api.rb
index 3ff3b38..be04701 100644
--- a/lib/api.rb
+++ b/lib/api.rb
@@ -16,5 +16,6 @@ module Gitlab
mount Users
mount Projects
mount Issues
+ mount Milestones
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 2abc20a..836c281 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -95,7 +95,7 @@ module Gitlab
end
end
- # Delete a project issue
+ # Delete a project issue (deprecated)
#
# Parameters:
# id (required) - The ID or code name of a project
@@ -103,8 +103,7 @@ module Gitlab
# Example Request:
# DELETE /projects/:id/issues/:issue_id
delete ":id/issues/:issue_id" do
- @issue = user_project.issues.find(params[:issue_id])
- @issue.destroy
+ error!({'message' => 'method not allowed'}, 405)
end
end
end
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
new file mode 100644
index 0000000..f537b8e
--- /dev/null
+++ b/lib/api/milestones.rb
@@ -0,0 +1,80 @@
+module Gitlab
+ # Milestones API
+ class Milestones < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ # Get a list of project milestones
+ #
+ # Parameters:
+ # id (required) - The ID or code name of a project
+ # Example Request:
+ # GET /projects/:id/milestones
+ get ":id/milestones" do
+ present user_project.milestones, with: Entities::Milestone
+ end
+
+ # Get a single project milestone
+ #
+ # Parameters:
+ # id (required) - The ID or code name of a project
+ # milestone_id (required) - The ID of a project milestone
+ # Example Request:
+ # GET /projects/:id/milestones/:milestone_id
+ get ":id/milestones/:milestone_id" do
+ @milestone = user_project.milestones.find(params[:milestone_id])
+ present @milestone, with: Entities::Milestone
+ end
+
+ # Create a new project milestone
+ #
+ # Parameters:
+ # id (required) - The ID or code name of the project
+ # title (required) - The title of the milestone
+ # description (optional) - The description of the milestone
+ # due_date (optional) - The due date of the milestone
+ # Example Request:
+ # POST /projects/:id/milestones
+ post ":id/milestones" do
+ @milestone = user_project.milestones.new(
+ title: params[:title],
+ description: params[:description],
+ due_date: params[:due_date]
+ )
+
+ if @milestone.save
+ present @milestone, with: Entities::Milestone
+ else
+ error!({'message' => '404 Not found'}, 404)
+ end
+ end
+
+ # Update an existing project milestone
+ #
+ # Parameters:
+ # id (required) - The ID or code name of a project
+ # milestone_id (required) - The ID of a project milestone
+ # title (optional) - The title of a milestone
+ # description (optional) - The description of a milestone
+ # due_date (optional) - The due date of a milestone
+ # closed (optional) - The status of the milestone
+ # Example Request:
+ # PUT /projects/:id/milestones/:milestone_id
+ put ":id/milestones/:milestone_id" do
+ @milestone = user_project.milestones.find(params[:milestone_id])
+ parameters = {
+ title: (params[:title] || @milestone.title),
+ description: (params[:description] || @milestone.description),
+ due_date: (params[:due_date] || @milestone.due_date),
+ closed: (params[:closed] || @milestone.closed)
+ }
+
+ if @milestone.update_attributes(parameters)
+ present @milestone, with: Entities::Milestone
+ else
+ error!({'message' => '404 Not found'}, 404)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/backend/gitolite.rb b/lib/gitlab/backend/gitolite.rb
new file mode 100644
index 0000000..b69f466
--- /dev/null
+++ b/lib/gitlab/backend/gitolite.rb
@@ -0,0 +1,202 @@
+require 'gitolite'
+require 'timeout'
+require 'fileutils'
+
+# TODO: refactor & cleanup
+module Gitlab
+ class Gitolite
+ class AccessDenied < StandardError; end
+ class InvalidKey < StandardError; end
+
+ def set_key key_id, key_content, projects
+ configure do |c|
+ c.update_keys(key_id, key_content)
+ c.update_projects(projects)
+ end
+ end
+
+ def remove_key key_id, projects
+ configure do |c|
+ c.delete_key(key_id)
+ c.update_projects(projects)
+ end
+ end
+
+ def update_repository project
+ configure do |c|
+ c.update_project(project.path, project)
+ end
+ end
+
+ alias_method :create_repository, :update_repository
+
+ def remove_repository project
+ configure do |c|
+ c.destroy_project(project)
+ end
+ end
+
+ def url_to_repo path
+ Gitlab.config.ssh_path + "#{path}.git"
+ end
+
+ def initialize
+ # create tmp dir
+ @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
+ end
+
+ def enable_automerge
+ configure do |git|
+ git.admin_all_repo
+ end
+ end
+
+ protected
+
+ def destroy_project(project)
+ FileUtils.rm_rf(project.path_to_repo)
+
+ ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
+ conf = ga_repo.config
+ conf.rm_repo(project.path)
+ ga_repo.save
+ end
+
+ #update or create
+ def update_keys(user, key)
+ File.open(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"), 'w') {|f| f.write(key.gsub(/\n/,'')) }
+ end
+
+ def delete_key(user)
+ File.unlink(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"))
+ `cd #{File.join(@local_dir,'gitolite')} ; git rm keydir/#{user}.pub`
+ end
+
+ # update or create
+ def update_project(repo_name, project)
+ ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
+ conf = ga_repo.config
+ repo = update_project_config(project, conf)
+ conf.add_repo(repo, true)
+
+ ga_repo.save
+ end
+
+ # Updates many projects and uses project.path as the repo path
+ # An order of magnitude faster than update_project
+ def update_projects(projects)
+ ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
+ conf = ga_repo.config
+
+ projects.each do |project|
+ repo = update_project_config(project, conf)
+ conf.add_repo(repo, true)
+ end
+
+ ga_repo.save
+ end
+
+ def update_project_config(project, conf)
+ repo_name = project.path
+
+ repo = if conf.has_repo?(repo_name)
+ conf.get_repo(repo_name)
+ else
+ ::Gitolite::Config::Repo.new(repo_name)
+ end
+
+ name_readers = project.repository_readers
+ name_writers = project.repository_writers
+ name_masters = project.repository_masters
+
+ pr_br = project.protected_branches.map(&:name).join("$ ")
+
+ repo.clean_permissions
+
+ # Deny access to protected branches for writers
+ unless name_writers.blank? || pr_br.blank?
+ repo.add_permission("-", pr_br.strip + "$ ", name_writers)
+ end
+
+ # Add read permissions
+ repo.add_permission("R", "", name_readers) unless name_readers.blank?
+
+ # Add write permissions
+ repo.add_permission("RW+", "", name_writers) unless name_writers.blank?
+ repo.add_permission("RW+", "", name_masters) unless name_masters.blank?
+
+ repo
+ end
+
+ def admin_all_repo
+ ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
+ conf = ga_repo.config
+ owner_name = ""
+
+ # Read gitolite-admin user
+ #
+ begin
+ repo = conf.get_repo("gitolite-admin")
+ owner_name = repo.permissions[0]["RW+"][""][0]
+ raise StandardError if owner_name.blank?
+ rescue => ex
+ puts "Can't determine gitolite-admin owner".red
+ raise StandardError
+ end
+
+ # @ALL repos premission for gitolite owner
+ repo_name = "@all"
+ repo = if conf.has_repo?(repo_name)
+ conf.get_repo(repo_name)
+ else
+ ::Gitolite::Config::Repo.new(repo_name)
+ end
+
+ repo.add_permission("RW+", "", owner_name)
+ conf.add_repo(repo, true)
+ ga_repo.save
+ end
+
+ private
+
+ def pull
+ # create tmp dir
+ @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
+ Dir.mkdir @local_dir
+
+ `git clone #{Gitlab.config.gitolite_admin_uri} #{@local_dir}/gitolite`
+ end
+
+ def push
+ Dir.chdir(File.join(@local_dir, "gitolite"))
+ `git add -A`
+ `git commit -am "Gitlab"`
+ `git push`
+ Dir.chdir(Rails.root)
+
+ FileUtils.rm_rf(@local_dir)
+ end
+
+ def configure
+ Timeout::timeout(30) do
+ File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f|
+ begin
+ f.flock(File::LOCK_EX)
+ pull
+ yield(self)
+ push
+ ensure
+ f.flock(File::LOCK_UN)
+ end
+ end
+ end
+ rescue Exception => ex
+ if ex.message =~ /is not a valid SSH key string/
+ raise Gitolite::InvalidKey.new("ssh key is not valid")
+ else
+ Gitlab::Logger.error(ex.message)
+ raise Gitolite::AccessDenied.new("gitolite timeout")
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
new file mode 100644
index 0000000..4f77c32
--- /dev/null
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -0,0 +1,54 @@
+module Grack
+ class Auth < Rack::Auth::Basic
+
+ def valid?
+ # Authentication with username and password
+ email, password = @auth.credentials
+ user = User.find_by_email(email)
+ return false unless user.try(:valid_password?, password)
+
+ # Set GL_USER env variable
+ ENV['GL_USER'] = email
+ # Pass Gitolite update hook
+ ENV['GL_BYPASS_UPDATE_HOOK'] = "true"
+
+ # Need this patch because the rails mount
+ @env['PATH_INFO'] = @env['REQUEST_PATH']
+
+ # Find project by PATH_INFO from env
+ if m = /^\/([\w-]+).git/.match(@env['PATH_INFO']).to_a
+ return false unless project = Project.find_by_path(m.last)
+ end
+
+ # Git upload and receive
+ if @env['REQUEST_METHOD'] == 'GET'
+ true
+ elsif @env['REQUEST_METHOD'] == 'POST'
+ if @env['REQUEST_URI'].end_with?('git-upload-pack')
+ return project.dev_access_for?(user)
+ elsif @env['REQUEST_URI'].end_with?('git-receive-pack')
+ if project.protected_branches.map(&:name).include?(current_ref)
+ project.master_access_for?(user)
+ else
+ project.dev_access_for?(user)
+ end
+ else
+ false
+ end
+ else
+ false
+ end
+ end# valid?
+
+ def current_ref
+ if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
+ input = Zlib::GzipReader.new(@request.body).read
+ else
+ input = @request.body.read
+ end
+ # Need to reset seek point
+ @request.body.rewind
+ /refs\/heads\/([\w-]+)/.match(input).to_a.first
+ end
+ end# Auth
+end# Grack
diff --git a/lib/gitlab/git_host.rb b/lib/gitlab/git_host.rb
deleted file mode 100644
index 76b2c7b..0000000
--- a/lib/gitlab/git_host.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require File.join(Rails.root, "lib", "gitlab", "gitolite")
-
-module Gitlab
- class GitHost
- def self.system
- Gitlab::Gitolite
- end
-
- def self.admin_uri
- Gitlab.config.git_host.admin_uri
- end
-
- def self.url_to_repo(path)
- Gitlab.config.ssh_path + "#{path}.git"
- end
- end
-end
diff --git a/lib/gitlab/gitolite.rb b/lib/gitlab/gitolite.rb
deleted file mode 100644
index e82f9e6..0000000
--- a/lib/gitlab/gitolite.rb
+++ /dev/null
@@ -1,157 +0,0 @@
-require 'gitolite'
-require 'timeout'
-require 'fileutils'
-
-module Gitlab
- class Gitolite
- class AccessDenied < StandardError; end
-
- def self.update_project(path, project)
- self.new.configure { |git| git.update_project(path, project) }
- end
-
- def self.destroy_project(project)
- self.new.configure { |git| git.destroy_project(project) }
- end
-
- def pull
- # create tmp dir
- @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
- Dir.mkdir @local_dir
-
- `git clone #{GitHost.admin_uri} #{@local_dir}/gitolite`
- end
-
- def push
- Dir.chdir(File.join(@local_dir, "gitolite"))
- `git add -A`
- `git commit -am "Gitlab"`
- `git push`
- Dir.chdir(Rails.root)
-
- FileUtils.rm_rf(@local_dir)
- end
-
- def configure
- Timeout::timeout(30) do
- File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f|
- begin
- f.flock(File::LOCK_EX)
- pull
- yield(self)
- push
- ensure
- f.flock(File::LOCK_UN)
- end
- end
- end
- rescue Exception => ex
- Gitlab::Logger.error(ex.message)
- raise Gitolite::AccessDenied.new("gitolite timeout")
- end
-
- def destroy_project(project)
- FileUtils.rm_rf(project.path_to_repo)
-
- ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
- conf = ga_repo.config
- conf.rm_repo(project.path)
- ga_repo.save
- end
-
- #update or create
- def update_keys(user, key)
- File.open(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"), 'w') {|f| f.write(key.gsub(/\n/,'')) }
- end
-
- def delete_key(user)
- File.unlink(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"))
- `cd #{File.join(@local_dir,'gitolite')} ; git rm keydir/#{user}.pub`
- end
-
- # update or create
- def update_project(repo_name, project)
- ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
- conf = ga_repo.config
- repo = update_project_config(project, conf)
- conf.add_repo(repo, true)
-
- ga_repo.save
- end
-
- # Updates many projects and uses project.path as the repo path
- # An order of magnitude faster than update_project
- def update_projects(projects)
- ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
- conf = ga_repo.config
-
- projects.each do |project|
- repo = update_project_config(project, conf)
- conf.add_repo(repo, true)
- end
-
- ga_repo.save
- end
-
- def update_project_config(project, conf)
- repo_name = project.path
-
- repo = if conf.has_repo?(repo_name)
- conf.get_repo(repo_name)
- else
- ::Gitolite::Config::Repo.new(repo_name)
- end
-
- name_readers = project.repository_readers
- name_writers = project.repository_writers
- name_masters = project.repository_masters
-
- pr_br = project.protected_branches.map(&:name).join(" ")
-
- repo.clean_permissions
-
- # Deny access to protected branches for writers
- unless name_writers.blank? || pr_br.blank?
- repo.add_permission("-", pr_br, name_writers)
- end
-
- # Add read permissions
- repo.add_permission("R", "", name_readers) unless name_readers.blank?
-
- # Add write permissions
- repo.add_permission("RW+", "", name_writers) unless name_writers.blank?
- repo.add_permission("RW+", "", name_masters) unless name_masters.blank?
-
- repo
- end
-
- def admin_all_repo
- ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
- conf = ga_repo.config
- owner_name = ""
-
- # Read gitolite-admin user
- #
- begin
- repo = conf.get_repo("gitolite-admin")
- owner_name = repo.permissions[0]["RW+"][""][0]
- raise StandardError if owner_name.blank?
- rescue => ex
- puts "Can't determine gitolite-admin owner".red
- raise StandardError
- end
-
- # @ALL repos premission for gitolite owner
- repo_name = "@all"
- repo = if conf.has_repo?(repo_name)
- conf.get_repo(repo_name)
- else
- ::Gitolite::Config::Repo.new(repo_name)
- end
-
- repo.add_permission("RW+", "", owner_name)
- conf.add_repo(repo, true)
- ga_repo.save
- end
- end
-end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index d3daed9..75fa835 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -1,5 +1,14 @@
module Gitlab
- # Custom parsing for Gitlab-flavored Markdown
+ # Custom parser for Gitlab-flavored Markdown
+ #
+ # It replaces references in the text with links to the appropriate items in Gitlab.
+ #
+ # Supported reference formats are:
+ # * @foo for team members
+ # * #123 for issues
+ # * !123 for merge requests
+ # * $123 for snippets
+ # * 123456 for commits
#
# Examples
#
@@ -67,25 +76,25 @@ module Gitlab
def reference_user(identifier)
if user = @project.users.where(name: identifier).first
member = @project.users_projects.where(user_id: user).first
- link_to("@#{user.name}", project_team_member_path(@project, member), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member
+ link_to("@#{identifier}", project_team_member_path(@project, member), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member
end
end
def reference_issue(identifier)
if issue = @project.issues.where(id: identifier).first
- link_to("##{issue.id}", project_issue_path(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}"))
+ link_to("##{identifier}", project_issue_path(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}"))
end
end
def reference_merge_request(identifier)
if merge_request = @project.merge_requests.where(id: identifier).first
- link_to("!#{merge_request.id}", project_merge_request_path(@project, merge_request), html_options.merge(title: "Merge Request: #{merge_request.title}", class: "gfm gfm-merge_request #{html_options[:class]}"))
+ link_to("!#{identifier}", project_merge_request_path(@project, merge_request), html_options.merge(title: "Merge Request: #{merge_request.title}", class: "gfm gfm-merge_request #{html_options[:class]}"))
end
end
def reference_snippet(identifier)
if snippet = @project.snippets.where(id: identifier).first
- link_to("$#{snippet.id}", project_snippet_path(@project, snippet), html_options.merge(title: "Snippet: #{snippet.title}", class: "gfm gfm-snippet #{html_options[:class]}"))
+ link_to("$#{identifier}", project_snippet_path(@project, snippet), html_options.merge(title: "Snippet: #{snippet.title}", class: "gfm gfm-snippet #{html_options[:class]}"))
end
end
diff --git a/lib/hooks/post-receive b/lib/hooks/post-receive
new file mode 100755
index 0000000..d38bd13
--- /dev/null
+++ b/lib/hooks/post-receive
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+# This file was placed here by Gitlab. It makes sure that your pushed commits
+# will be processed properly.
+
+while read oldrev newrev ref
+do
+ # For every branch or tag that was pushed, create a Resque job in redis.
+ pwd=`pwd`
+ reponame=`basename "$pwd" | cut -d. -f1`
+ env -i redis-cli rpush "resque:queue:post_receive" "{\"class\":\"PostReceive\",\"args\":[\"$reponame\",\"$oldrev\",\"$newrev\",\"$ref\",\"$GL_USER\"]}" > /dev/null 2>&1
+done
diff --git a/lib/post-receive-hook b/lib/post-receive-hook
deleted file mode 100755
index d38bd13..0000000
--- a/lib/post-receive-hook
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-
-# This file was placed here by Gitlab. It makes sure that your pushed commits
-# will be processed properly.
-
-while read oldrev newrev ref
-do
- # For every branch or tag that was pushed, create a Resque job in redis.
- pwd=`pwd`
- reponame=`basename "$pwd" | cut -d. -f1`
- env -i redis-cli rpush "resque:queue:post_receive" "{\"class\":\"PostReceive\",\"args\":[\"$reponame\",\"$oldrev\",\"$newrev\",\"$ref\",\"$GL_USER\"]}" > /dev/null 2>&1
-done
diff --git a/lib/support/init-gitlab b/lib/support/init-gitlab
new file mode 100644
index 0000000..f146e80
--- /dev/null
+++ b/lib/support/init-gitlab
@@ -0,0 +1,54 @@
+#! /bin/bash
+### BEGIN INIT INFO
+# Provides: gitlab
+# Required-Start: $local_fs $remote_fs $network $syslog redis-server
+# Required-Stop: $local_fs $remote_fs $network $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: GitLab git repository management
+# Description: GitLab git repository management
+### END INIT INFO
+
+DAEMON_OPTS="-c /home/gitlab/gitlab/config/unicorn.rb -E production -D"
+NAME=unicorn
+DESC="Gitlab service"
+PID=/home/gitlab/gitlab/tmp/pids/unicorn.pid
+RESQUE_PID=/home/gitlab/gitlab/tmp/pids/resque_worker.pid
+
+case "$1" in
+ start)
+ CD_TO_APP_DIR="cd /home/gitlab/gitlab"
+ START_DAEMON_PROCESS="bundle exec unicorn_rails $DAEMON_OPTS"
+ START_RESQUE_PROCESS="./resque.sh"
+
+ echo -n "Starting $DESC: "
+ if [ `whoami` = root ]; then
+ sudo -u gitlab sh -l -c "$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS"
+ else
+ $CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS && $START_RESQUE_PROCESS
+ fi
+ echo "$NAME."
+ ;;
+ stop)
+ echo -n "Stopping $DESC: "
+ kill -QUIT `cat $PID`
+ kill -QUIT `cat $RESQUE_PID`
+ echo "$NAME."
+ ;;
+ restart)
+ echo -n "Restarting $DESC: "
+ kill -USR2 `cat $PID`
+ echo "$NAME."
+ ;;
+ reload)
+ echo -n "Reloading $DESC configuration: "
+ kill -HUP `cat $PID`
+ echo "$NAME."
+ ;;
+ *)
+ echo "Usage: $NAME {start|stop|restart|reload}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/lib/support/nginx-gitlab b/lib/support/nginx-gitlab
new file mode 100644
index 0000000..fa15d20
--- /dev/null
+++ b/lib/support/nginx-gitlab
@@ -0,0 +1,33 @@
+upstream gitlab {
+ server unix:/home/gitlab/gitlab/tmp/sockets/gitlab.socket;
+}
+
+server {
+ listen YOUR_SERVER_IP:80; # e.g., listen 192.168.1.1:80;
+ server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com;
+ root /home/gitlab/gitlab/public;
+
+ # individual nginx logs for this gitlab vhost
+ access_log /var/log/nginx/gitlab_access.log;
+ error_log /var/log/nginx/gitlab_error.log;
+
+ location / {
+ # serve static files from defined root folder;.
+ # @gitlab is a named location for the upstream fallback, see below
+ try_files $uri $uri/index.html $uri.html @gitlab;
+ }
+
+ # if a file, which is not found in the root folder is requested,
+ # then the proxy pass the request to the upsteam (gitlab unicorn)
+ location @gitlab {
+ proxy_redirect off;
+
+ # you need to change this to "https", if you set "ssl" directive to "on"
+ proxy_set_header X-FORWARDED_PROTO http;
+ proxy_set_header Host $http_host;
+ proxy_set_header X-Real-IP $remote_addr;
+
+ proxy_pass http://gitlab;
+ }
+}
+
diff --git a/lib/tasks/bulk_add_permission.rake b/lib/tasks/bulk_add_permission.rake
new file mode 100644
index 0000000..5579782
--- /dev/null
+++ b/lib/tasks/bulk_add_permission.rake
@@ -0,0 +1,26 @@
+desc "Add all users to all projects, system administratos are added as masters"
+task :add_users_to_project_teams => :environment do |t, args|
+ users = User.find_all_by_admin(false, :select => 'id').map(&:id)
+ admins = User.find_all_by_admin(true, :select => 'id').map(&:id)
+
+ users.each do |user|
+ puts "#{user}"
+ end
+
+ Project.all.each do |project|
+ puts "Importing #{users.length} users into #{project.path}"
+ UsersProject.bulk_import(project, users, UsersProject::DEVELOPER)
+ puts "Importing #{admins.length} admins into #{project.path}"
+ UsersProject.bulk_import(project, admins, UsersProject::MASTER)
+ end
+end
+
+desc "Add user to as a developer to all projects"
+task :add_user_to_project_teams, [:email] => :environment do |t, args|
+ user_email = args.email
+ user = User.find_by_email(user_email)
+
+ project_ids = Project.all.map(&:id)
+
+ UsersProject.user_bulk_import(user,project_ids,UsersProject::DEVELOPER)
+end
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index d9053c2..04d240f 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -144,8 +144,7 @@ namespace :gitlab do
if Kernel.system("cd #{File.dirname(project.second)} > /dev/null 2>&1 && git clone --bare #{backup_path_repo}/#{project.first}.bundle #{project.first}.git > /dev/null 2>&1")
permission_commands = [
"sudo chmod -R g+rwX #{Gitlab.config.git_base_path}",
- "sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}",
- "sudo chown gitlab:gitlab /home/git/repositories/**/hooks/post-receive"
+ "sudo chown -R #{Gitlab.config.ssh_user}:#{Gitlab.config.ssh_user} #{Gitlab.config.git_base_path}"
]
permission_commands.each { |command| Kernel.system(command) }
puts "[DONE]".green
diff --git a/lib/tasks/gitlab/enable_automerge.rake b/lib/tasks/gitlab/enable_automerge.rake
index 07f8058..0a1a0fa 100644
--- a/lib/tasks/gitlab/enable_automerge.rake
+++ b/lib/tasks/gitlab/enable_automerge.rake
@@ -2,9 +2,7 @@ namespace :gitlab do
namespace :app do
desc "GITLAB | Enable auto merge"
task :enable_automerge => :environment do
- Gitlab::GitHost.system.new.configure do |git|
- git.admin_all_repo
- end
+ Gitlab::Gitolite.new.enable_automerge
Project.find_each do |project|
if project.repo_exists? && !project.satellite.exists?
diff --git a/lib/tasks/gitlab/gitolite_rebuild.rake b/lib/tasks/gitlab/gitolite_rebuild.rake
index 5ab1760..534aa31 100644
--- a/lib/tasks/gitlab/gitolite_rebuild.rake
+++ b/lib/tasks/gitlab/gitolite_rebuild.rake
@@ -16,7 +16,7 @@ namespace :gitlab do
task :update_keys => :environment do
puts "Starting Key"
Key.find_each(:batch_size => 100) do |key|
- key.update_repository
+ Gitlab::Gitolite.new.set_key(key.identifier, key.key, key.projects)
print '.'
end
puts "Done with keys"
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index d60e73e..49c8646 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -1,7 +1,11 @@
namespace :gitlab do
namespace :app do
desc "GITLAB | Setup production application"
- task :setup => ['db:setup', 'db:seed_fu', 'gitlab:app:enable_automerge']
+ task :setup => [
+ 'db:setup',
+ 'db:seed_fu',
+ 'gitlab:app:enable_automerge'
+ ]
end
end
diff --git a/lib/tasks/gitlab/status.rake b/lib/tasks/gitlab/status.rake
index bc4e86e..e5b5e12 100644
--- a/lib/tasks/gitlab/status.rake
+++ b/lib/tasks/gitlab/status.rake
@@ -56,6 +56,20 @@ namespace :gitlab do
return
end
+ gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common")
+ gitlab_hook_files = ['post-receive']
+ gitlab_hook_files.each do |file_name|
+ dest = File.join(gitolite_hooks_path, file_name)
+ print "#{dest} exists? ............"
+ if File.exists?(dest)
+ puts "YES".green
+ else
+ puts "NO".red
+ return
+ end
+ end
+
+
if Project.count > 0
puts "Validating projects repositories:".yellow
Project.find_each(:batch_size => 100) do |project|
@@ -67,13 +81,7 @@ namespace :gitlab do
next
end
-
- unless File.owned?(hook_file)
- puts "post-receive file is not owner by gitlab".red
- next
- end
-
- puts "post-reveice file ok".green
+ puts "post-receive file ok".green
end
end
diff --git a/lib/tasks/gitlab/update_hooks.rake b/lib/tasks/gitlab/update_hooks.rake
deleted file mode 100644
index 44e1617..0000000
--- a/lib/tasks/gitlab/update_hooks.rake
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace :gitlab do
- namespace :gitolite do
- desc "GITLAB | Rewrite hooks for repos"
- task :update_hooks => :environment do
- puts "Starting Projects"
- Project.find_each(:batch_size => 100) do |project|
- begin
- if project.commit
- project.write_hooks
- print ".".green
- end
- rescue Exception => e
- print e.message.red
- end
- end
- puts "\nDone with projects"
- end
- end
-end
diff --git a/lib/tasks/gitlab/write_hook.rake b/lib/tasks/gitlab/write_hook.rake
new file mode 100644
index 0000000..9ec9c83
--- /dev/null
+++ b/lib/tasks/gitlab/write_hook.rake
@@ -0,0 +1,23 @@
+namespace :gitlab do
+ namespace :gitolite do
+ desc "GITLAB | Write GITLAB hook for gitolite"
+ task :write_hooks => :environment do
+ gitolite_hooks_path = File.join(Gitlab.config.git_hooks_path, "common")
+ gitlab_hooks_path = Rails.root.join("lib", "hooks")
+
+ gitlab_hook_files = ['post-receive']
+
+ gitlab_hook_files.each do |file_name|
+ source = File.join(gitlab_hooks_path, file_name)
+ dest = File.join(gitolite_hooks_path, file_name)
+
+ puts "sudo -u root cp #{source} #{dest}".yellow
+ `sudo -u root cp #{source} #{dest}`
+
+ puts "sudo -u root chown git:git #{dest}".yellow
+ `sudo -u root chown git:git #{dest}`
+ end
+ end
+ end
+end
+
diff --git a/resque_dev.sh b/resque_dev.sh
index b09cfd9..0f1d6ed 100755
--- a/resque_dev.sh
+++ b/resque_dev.sh
@@ -1 +1,2 @@
-bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1
+mkdir -p tmp/pids
+bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1 PIDFILE=tmp/pids/resque_worker.pid RAILS_ENV=development BACKGROUND=yes
diff --git a/spec/api/issues_spec.rb b/spec/api/issues_spec.rb
deleted file mode 100644
index f6d8e37..0000000
--- a/spec/api/issues_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::API do
- let(:user) { Factory :user }
- let!(:project) { Factory :project, owner: user }
- let!(:issue) { Factory :issue, author: user, assignee: user, project: project }
- before { project.add_access(user, :read) }
-
- describe "GET /issues" do
- it "should return authentication error" do
- get "#{api_prefix}/issues"
- response.status.should == 401
- end
-
- describe "authenticated GET /issues" do
- it "should return an array of issues" do
- get "#{api_prefix}/issues?private_token=#{user.private_token}"
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['title'].should == issue.title
- end
- end
- end
-
- describe "GET /projects/:id/issues" do
- it "should return project issues" do
- get "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}"
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['title'].should == issue.title
- end
- end
-
- describe "GET /projects/:id/issues/:issue_id" do
- it "should return a project issue by id" do
- get "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}"
- response.status.should == 200
- json_response['title'].should == issue.title
- end
- end
-
- describe "POST /projects/:id/issues" do
- it "should create a new project issue" do
- post "#{api_prefix}/projects/#{project.code}/issues?private_token=#{user.private_token}",
- title: 'new issue', labels: 'label, label2'
- response.status.should == 201
- json_response['title'].should == 'new issue'
- json_response['description'].should be_nil
- json_response['labels'].should == ['label', 'label2']
- end
- end
-
- describe "PUT /projects/:id/issues/:issue_id" do
- it "should update a project issue" do
- put "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}",
- title: 'updated title', labels: 'label2', closed: 1
- response.status.should == 200
- json_response['title'].should == 'updated title'
- json_response['labels'].should == ['label2']
- json_response['closed'].should be_true
- end
- end
-
- describe "DELETE /projects/:id/issues/:issue_id" do
- it "should delete a project issue" do
- expect {
- delete "#{api_prefix}/projects/#{project.code}/issues/#{issue.id}?private_token=#{user.private_token}"
- }.to change { Issue.count }.by(-1)
- end
- end
-end
diff --git a/spec/api/projects_spec.rb b/spec/api/projects_spec.rb
deleted file mode 100644
index ff45619..0000000
--- a/spec/api/projects_spec.rb
+++ /dev/null
@@ -1,136 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::API do
- let(:user) { Factory :user }
- let!(:project) { Factory :project, owner: user }
- let!(:snippet) { Factory :snippet, author: user, project: project, title: 'example' }
- before { project.add_access(user, :read) }
-
- describe "GET /projects" do
- it "should return authentication error" do
- get "#{api_prefix}/projects"
- response.status.should == 401
- end
-
- describe "authenticated GET /projects" do
- it "should return an array of projects" do
- get "#{api_prefix}/projects?private_token=#{user.private_token}"
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['name'].should == project.name
- json_response.first['owner']['email'].should == user.email
- end
- end
- end
-
- describe "GET /projects/:id" do
- it "should return a project by id" do
- get "#{api_prefix}/projects/#{project.id}?private_token=#{user.private_token}"
- response.status.should == 200
- json_response['name'].should == project.name
- json_response['owner']['email'].should == user.email
- end
-
- it "should return a project by code name" do
- get "#{api_prefix}/projects/#{project.code}?private_token=#{user.private_token}"
- response.status.should == 200
- json_response['name'].should == project.name
- end
-
- it "should return a 404 error if not found" do
- get "#{api_prefix}/projects/42?private_token=#{user.private_token}"
- response.status.should == 404
- json_response['message'].should == '404 Not found'
- end
- end
-
- describe "GET /projects/:id/repository/branches" do
- it "should return an array of project branches" do
- get "#{api_prefix}/projects/#{project.code}/repository/branches?private_token=#{user.private_token}"
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name
- end
- end
-
- describe "GET /projects/:id/repository/branches/:branch" do
- it "should return the branch information for a single branch" do
- get "#{api_prefix}/projects/#{project.code}/repository/branches/new_design?private_token=#{user.private_token}"
- response.status.should == 200
-
- json_response['name'].should == 'new_design'
- json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
- end
- end
-
- describe "GET /projects/:id/repository/tags" do
- it "should return an array of project tags" do
- get "#{api_prefix}/projects/#{project.code}/repository/tags?private_token=#{user.private_token}"
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name
- end
- end
-
- describe "GET /projects/:id/snippets/:snippet_id" do
- it "should return a project snippet" do
- get "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}"
- response.status.should == 200
- json_response['title'].should == snippet.title
- end
- end
-
- describe "POST /projects/:id/snippets" do
- it "should create a new project snippet" do
- post "#{api_prefix}/projects/#{project.code}/snippets?private_token=#{user.private_token}",
- title: 'api test', file_name: 'sample.rb', code: 'test'
- response.status.should == 201
- json_response['title'].should == 'api test'
- end
- end
-
- describe "PUT /projects/:id/snippets" do
- it "should update an existing project snippet" do
- put "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}",
- code: 'updated code'
- response.status.should == 200
- json_response['title'].should == 'example'
- snippet.reload.content.should == 'updated code'
- end
- end
-
- describe "DELETE /projects/:id/snippets/:snippet_id" do
- it "should delete existing project snippet" do
- expect {
- delete "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}?private_token=#{user.private_token}"
- }.to change { Snippet.count }.by(-1)
- end
- end
-
- describe "GET /projects/:id/snippets/:snippet_id/raw" do
- it "should get a raw project snippet" do
- get "#{api_prefix}/projects/#{project.code}/snippets/#{snippet.id}/raw?private_token=#{user.private_token}"
- response.status.should == 200
- end
- end
-
- describe "GET /projects/:id/:sha/blob" do
- it "should get the raw file contents" do
- get "#{api_prefix}/projects/#{project.code}/repository/commits/master/blob?filepath=README.md&private_token=#{user.private_token}"
-
- response.status.should == 200
- end
-
- it "should return 404 for invalid branch_name" do
- get "#{api_prefix}/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md&private_token=#{user.private_token}"
-
- response.status.should == 404
- end
-
- it "should return 404 for invalid file" do
- get "#{api_prefix}/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid&private_token=#{user.private_token}"
-
- response.status.should == 404
- end
- end
-end
diff --git a/spec/api/users_spec.rb b/spec/api/users_spec.rb
deleted file mode 100644
index 32b9379..0000000
--- a/spec/api/users_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::API do
- let(:user) { Factory :user }
-
- describe "GET /users" do
- it "should return authentication error" do
- get "#{api_prefix}/users"
- response.status.should == 401
- end
-
- describe "authenticated GET /users" do
- it "should return an array of users" do
- get "#{api_prefix}/users?private_token=#{user.private_token}"
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['email'].should == user.email
- end
- end
- end
-
- describe "GET /users/:id" do
- it "should return a user by id" do
- get "#{api_prefix}/users/#{user.id}?private_token=#{user.private_token}"
- response.status.should == 200
- json_response['email'].should == user.email
- end
- end
-
- describe "GET /user" do
- it "should return current user" do
- get "#{api_prefix}/user?private_token=#{user.private_token}"
- response.status.should == 200
- json_response['email'].should == user.email
- end
- end
-end
diff --git a/spec/factories.rb b/spec/factories.rb
index ab2ca46..2e4acf3 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -1,92 +1,130 @@
-require File.join(Rails.root, 'spec', 'factory')
-
-Factory.add(:project, Project) do |obj|
- obj.name = Faker::Internet.user_name
- obj.path = 'gitlabhq'
- obj.owner = Factory(:user)
- obj.code = 'LGT'
+# Backwards compatibility with the old method
+def Factory(type, *args)
+ FactoryGirl.create(type, *args)
end
-Factory.add(:project_without_owner, Project) do |obj|
- obj.name = Faker::Internet.user_name
- obj.path = 'gitlabhq'
- obj.code = 'LGT'
-end
+module Factory
+ def self.create(type, *args)
+ FactoryGirl.create(type, *args)
+ end
-Factory.add(:public_project, Project) do |obj|
- obj.name = Faker::Internet.user_name
- obj.path = 'gitlabhq'
- obj.private_flag = false
- obj.owner = Factory(:user)
- obj.code = 'LGT'
+ def self.new(type, *args)
+ FactoryGirl.build(type, *args)
+ end
end
-Factory.add(:user, User) do |obj|
- obj.email = Faker::Internet.email
- obj.password = "123456"
- obj.name = Faker::Name.name
- obj.password_confirmation = "123456"
-end
+FactoryGirl.define do
+ sequence :sentence, aliases: [:title, :content] do
+ Faker::Lorem.sentence
+ end
-Factory.add(:admin, User) do |obj|
- obj.email = Faker::Internet.email
- obj.password = "123456"
- obj.name = Faker::Name.name
- obj.password_confirmation = "123456"
- obj.admin = true
-end
+ sequence :name, aliases: [:file_name] do
+ Faker::Name.name
+ end
-Factory.add(:issue, Issue) do |obj|
- obj.title = Faker::Lorem.sentence
- obj.author = Factory :user
- obj.assignee = Factory :user
-end
+ sequence(:url) { Faker::Internet.uri('http') }
-Factory.add(:merge_request, MergeRequest) do |obj|
- obj.title = Faker::Lorem.sentence
- obj.author = Factory :user
- obj.assignee = Factory :user
- obj.source_branch = "master"
- obj.target_branch = "stable"
- obj.closed = false
-end
+ factory :user, aliases: [:author, :assignee, :owner] do
+ email { Faker::Internet.email }
+ name
+ password "123456"
+ password_confirmation "123456"
-Factory.add(:snippet, Snippet) do |obj|
- obj.title = Faker::Lorem.sentence
- obj.file_name = Faker::Lorem.sentence
- obj.content = Faker::Lorem.sentences
-end
+ trait :admin do
+ admin true
+ end
-Factory.add(:note, Note) do |obj|
- obj.note = Faker::Lorem.sentence
-end
+ factory :admin, traits: [:admin]
+ end
-Factory.add(:key, Key) do |obj|
- obj.title = "Example key"
- obj.key = File.read(File.join(Rails.root, "db", "pkey.example"))
-end
+ factory :project do
+ sequence(:name) { |n| "project#{n}" }
+ path { name }
+ code { name }
+ owner
+ end
-Factory.add(:project_hook, ProjectHook) do |obj|
- obj.url = Faker::Internet.uri("http")
-end
+ factory :users_project do
+ user
+ project
+ end
-Factory.add(:system_hook, SystemHook) do |obj|
- obj.url = Faker::Internet.uri("http")
-end
+ factory :issue do
+ title
+ author
+ project
-Factory.add(:wiki, Wiki) do |obj|
- obj.title = Faker::Lorem.sentence
- obj.content = Faker::Lorem.sentence
- obj.user = Factory(:user)
- obj.project = Factory(:project)
-end
+ trait :closed do
+ closed true
+ end
-Factory.add(:event, Event) do |obj|
- obj.title = Faker::Lorem.sentence
- obj.project = Factory(:project)
-end
+ factory :closed_issue, traits: [:closed]
+ end
+
+ factory :merge_request do
+ title
+ author
+ project
+ source_branch "master"
+ target_branch "stable"
+ end
+
+ factory :note do
+ project
+ note "Note"
+ end
+
+ factory :event do
+ end
+
+ factory :key do
+ title
+ key do
+ """
+ ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
+ 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
+ soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=
+ """
+ end
+
+ factory :deploy_key do
+ project
+ end
+
+ factory :personal_key do
+ user
+ end
+ end
+
+ factory :milestone do
+ title
+ project
+ end
+
+ factory :system_hook do
+ url
+ end
+
+ factory :project_hook do
+ url
+ end
+
+ factory :wiki do
+ title
+ content
+ user
+ end
+
+ factory :snippet do
+ project
+ author
+ title
+ content
+ file_name
+ end
-Factory.add(:milestone, Milestone) do |obj|
- obj.title = Faker::Lorem.sentence
- obj.due_date = Date.today + 1.month
+ factory :protected_branch do
+ name
+ project
+ end
end
diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb
new file mode 100644
index 0000000..5760aad
--- /dev/null
+++ b/spec/factories_spec.rb
@@ -0,0 +1,91 @@
+require 'spec_helper'
+
+describe "Factories" do
+ describe 'User' do
+ it "builds a valid instance" do
+ build(:user).should be_valid
+ end
+
+ it "builds a valid admin instance" do
+ build(:admin).should be_valid
+ end
+ end
+
+ describe 'Project' do
+ it "builds a valid instance" do
+ build(:project).should be_valid
+ end
+ end
+
+ describe 'Issue' do
+ it "builds a valid instance" do
+ build(:issue).should be_valid
+ end
+
+ it "builds a valid closed instance" do
+ build(:closed_issue).should be_valid
+ end
+ end
+
+ describe 'MergeRequest' do
+ it "builds a valid instance" do
+ build(:merge_request).should be_valid
+ end
+ end
+
+ describe 'Note' do
+ it "builds a valid instance" do
+ build(:note).should be_valid
+ end
+ end
+
+ describe 'Event' do
+ it "builds a valid instance" do
+ build(:event).should be_valid
+ end
+ end
+
+ describe 'Key' do
+ it "builds a valid instance" do
+ build(:key).should be_valid
+ end
+
+ it "builds a valid deploy key instance" do
+ build(:deploy_key).should be_valid
+ end
+
+ it "builds a valid personal key instance" do
+ build(:personal_key).should be_valid
+ end
+ end
+
+ describe 'Milestone' do
+ it "builds a valid instance" do
+ build(:milestone).should be_valid
+ end
+ end
+
+ describe 'SystemHook' do
+ it "builds a valid instance" do
+ build(:system_hook).should be_valid
+ end
+ end
+
+ describe 'ProjectHook' do
+ it "builds a valid instance" do
+ build(:project_hook).should be_valid
+ end
+ end
+
+ describe 'Wiki' do
+ it "builds a valid instance" do
+ build(:wiki).should be_valid
+ end
+ end
+
+ describe 'Snippet' do
+ it "builds a valid instance" do
+ build(:snippet).should be_valid
+ end
+ end
+end
diff --git a/spec/factory.rb b/spec/factory.rb
deleted file mode 100644
index 1758b4d..0000000
--- a/spec/factory.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-class Factory
- @factories = {}
-
- class << self
- def add(name, klass, &block)
- @factories[name] = [klass, block]
- end
-
- def create(name, opts = {})
- new(name, opts).tap(&:save!)
- end
-
- def new(name, opts = {})
- factory= @factories[name]
- factory[0].new.tap do |obj|
- factory[1].call(obj)
- end.tap do |obj|
- opts.each do |k, opt|
- obj.send("#{k}=", opt)
- end
- end
- end
- end
-end
-
-def Factory(name, opts={})
- Factory.create name, opts
-end
-
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
new file mode 100644
index 0000000..9a2df31
--- /dev/null
+++ b/spec/helpers/application_helper_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe ApplicationHelper do
+ describe "gravatar_icon" do
+ let(:user_email) { 'user@email.com' }
+
+ it "should return a generic avatar path when Gravatar is disabled" do
+ Gitlab.config.stub(:disable_gravatar?).and_return(true)
+ gravatar_icon(user_email).should == 'no_avatar.png'
+ end
+
+ it "should return a generic avatar path when email is blank" do
+ gravatar_icon('').should == 'no_avatar.png'
+ end
+
+ it "should use SSL when appropriate" do
+ stub!(:request).and_return(double(:ssl? => true))
+ gravatar_icon(user_email).should match('https://secure.gravatar.com')
+ end
+
+ it "should accept a custom size" do
+ stub!(:request).and_return(double(:ssl? => false))
+ gravatar_icon(user_email, 64).should match(/\?s=64/)
+ end
+ end
+end
diff --git a/spec/helpers/gitlab_flavored_markdown_spec.rb b/spec/helpers/gitlab_flavored_markdown_spec.rb
index e147cb3..28bd46e 100644
--- a/spec/helpers/gitlab_flavored_markdown_spec.rb
+++ b/spec/helpers/gitlab_flavored_markdown_spec.rb
@@ -2,7 +2,7 @@ require "spec_helper"
describe GitlabMarkdownHelper do
before do
- @project = Project.find_by_path("gitlabhq") || Factory(:project)
+ @project = Factory(:project)
@commit = @project.repo.commits.first.parents.first
@commit = CommitDecorator.decorate(Commit.new(@commit))
@other_project = Factory :project, path: "OtherPath", code: "OtherCode"
@@ -157,7 +157,7 @@ describe GitlabMarkdownHelper do
gfm("Let @#{user.name} fix the *mess* in #{@commit.id}").should == "Let #{link_to "@#{user.name}", project_team_member_path(@project, member), class: "gfm gfm-team_member "} fix the *mess* in #{link_to @commit.id, project_commit_path(@project, id: @commit.id), title: "Commit: #{@commit.author_name} - #{@commit.title}", class: "gfm gfm-commit "}"
end
- it "should not trip over other stuff", focus: true do
+ it "should not trip over other stuff" do
gfm("_Please_ *stop* 'helping' and all the other b*$#%' you do.").should == "_Please_ *stop* 'helping' and all the other b*$#%' you do."
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 27af1e3..cf50b42 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -24,7 +24,7 @@ describe Notify do
end
it 'has the correct subject' do
- should have_subject /Account was created for you/
+ should have_subject /^gitlab \| Account was created for you$/
end
it 'contains the new user\'s login name' do
@@ -60,7 +60,7 @@ describe Notify do
it_behaves_like 'an assignee email'
it 'has the correct subject' do
- should have_subject /new issue ##{issue.id}/
+ should have_subject /new issue ##{issue.id} \| #{issue.title} \| #{project.name}/
end
it 'contains a link to the new issue' do
@@ -76,7 +76,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it 'has the correct subject' do
- should have_subject /changed issue/
+ should have_subject /changed issue ##{issue.id} \| #{issue.title}/
end
it 'contains the name of the previous assignee' do
@@ -91,6 +91,29 @@ describe Notify do
should have_body_text /#{project_issue_path project, issue}/
end
end
+
+ describe 'status changed' do
+ let(:current_user) { Factory.create :user, email: "current@email.com" }
+ let(:status) { 'closed' }
+ subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) }
+
+ it 'has the correct subject' do
+ should have_subject /changed issue ##{issue.id} \| #{issue.title}/i
+ end
+
+ it 'contains the new status' do
+ should have_body_text /#{status}/i
+ end
+
+ it 'contains the user name' do
+ should have_body_text /#{current_user.name}/i
+ end
+
+ it 'contains a link to the issue' do
+ should have_body_text /#{project_issue_path project, issue}/
+ end
+ end
+
end
context 'for merge requests' do
@@ -145,6 +168,26 @@ describe Notify do
end
end
+ describe 'project access changed' do
+ let(:project) { Factory.create(:project,
+ path: "Fuu",
+ code: "Fuu") }
+ let(:user) { Factory.create :user }
+ let(:users_project) { Factory.create(:users_project,
+ project: project,
+ user: user) }
+ subject { Notify.project_access_granted_email(users_project.id) }
+ it 'has the correct subject' do
+ should have_subject /access to project was granted/
+ end
+ it 'contains name of project' do
+ should have_body_text /#{project.name}/
+ end
+ it 'contains new user role' do
+ should have_body_text /#{users_project.project_access_human}/
+ end
+ end
+
context 'items that are noteable, the email for a note' do
let(:note_author) { Factory.create(:user, name: 'author_name') }
let(:note) { Factory.create(:note, project: project, author: note_author) }
diff --git a/spec/models/activity_observer_spec.rb b/spec/models/activity_observer_spec.rb
deleted file mode 100644
index 0db4a99..0000000
--- a/spec/models/activity_observer_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'spec_helper'
-
-describe ActivityObserver do
- let(:project) { Factory :project }
-
- def self.it_should_be_valid_event
- it { @event.should_not be_nil }
- it { @event.project.should == project }
- end
-
- describe "Merge Request created" do
- before do
- MergeRequest.observers.enable :activity_observer do
- @merge_request = Factory :merge_request, project: project
- @event = Event.last
- end
- end
-
- it_should_be_valid_event
- it { @event.action.should == Event::Created }
- it { @event.target.should == @merge_request }
- end
-
- describe "Issue created" do
- before do
- Issue.observers.enable :activity_observer do
- @issue = Factory :issue, project: project
- @event = Event.last
- end
- end
-
- it_should_be_valid_event
- it { @event.action.should == Event::Created }
- it { @event.target.should == @issue }
- end
-
- #describe "Issue commented" do
- #before do
- #@issue = Factory :issue, project: project
- #@note = Factory :note, noteable: @issue, project: project
- #@event = Event.last
- #end
-
- #it_should_be_valid_event
- #it { @event.action.should == Event::Commented }
- #it { @event.target.should == @note }
- #end
-end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 188f099..aaffda3 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -1,24 +1,9 @@
-# == Schema Information
-#
-# Table name: events
-#
-# id :integer(4) not null, primary key
-# target_type :string(255)
-# target_id :integer(4)
-# title :string(255)
-# data :text
-# project_id :integer(4)
-# created_at :datetime not null
-# updated_at :datetime not null
-# action :integer(4)
-# author_id :integer(4)
-#
-
require 'spec_helper'
describe Event do
describe "Associations" do
it { should belong_to(:project) }
+ it { should belong_to(:target) }
end
describe "Respond to" do
@@ -29,16 +14,6 @@ describe Event do
it { should respond_to(:commits) }
end
- describe "Creation" do
- before do
- @event = Factory :event
- end
-
- it "should create a valid event" do
- @event.should be_valid
- end
- end
-
describe "Push event" do
before do
project = Factory :project
diff --git a/spec/models/issue_observer_spec.rb b/spec/models/issue_observer_spec.rb
deleted file mode 100644
index c6a405f..0000000
--- a/spec/models/issue_observer_spec.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-require 'spec_helper'
-
-describe IssueObserver do
- let(:some_user) { double(:user, id: 1) }
- let(:assignee) { double(:user, id: 2) }
- let(:issue) { double(:issue, id: 42, assignee: assignee) }
-
- before(:each) { subject.stub(:current_user).and_return(some_user) }
-
- subject { IssueObserver.instance }
-
- describe '#after_create' do
-
- it 'is called when an issue is created' do
- subject.should_receive(:after_create)
-
- Issue.observers.enable :issue_observer do
- Factory.create(:issue, project: Factory.create(:project))
- end
- end
-
- it 'sends an email to the assignee' do
- Notify.should_receive(:new_issue_email).with(issue.id).
- and_return(double(deliver: true))
-
- subject.after_create(issue)
- end
-
- it 'does not send an email to the assignee if assignee created the issue' do
- subject.stub(:current_user).and_return(assignee)
- Notify.should_not_receive(:new_issue_email)
-
- subject.after_create(issue)
- end
- end
-
- context '#after_update' do
- before(:each) do
- issue.stub(:is_being_reassigned?).and_return(false)
- issue.stub(:is_being_closed?).and_return(false)
- issue.stub(:is_being_reopened?).and_return(false)
- end
-
- it 'is called when an issue is changed' do
- changed = Factory.create(:issue, project: Factory.create(:project))
- subject.should_receive(:after_update)
-
- Issue.observers.enable :issue_observer do
- changed.description = 'I changed'
- changed.save
- end
- end
-
- context 'a reassigned email' do
- it 'is sent if the issue is being reassigned' do
- issue.should_receive(:is_being_reassigned?).and_return(true)
- subject.should_receive(:send_reassigned_email).with(issue)
-
- subject.after_update(issue)
- end
-
- it 'is not sent if the issue is not being reassigned' do
- issue.should_receive(:is_being_reassigned?).and_return(false)
- subject.should_not_receive(:send_reassigned_email)
-
- subject.after_update(issue)
- end
- end
-
- context 'a status "closed" note' do
- it 'is created if the issue is being closed' do
- issue.should_receive(:is_being_closed?).and_return(true)
- Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
-
- subject.after_update(issue)
- end
-
- it 'is not created if the issue is not being closed' do
- issue.should_receive(:is_being_closed?).and_return(false)
- Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
-
- subject.after_update(issue)
- end
- end
-
- context 'a status "reopened" note' do
- it 'is created if the issue is being reopened' do
- issue.should_receive(:is_being_reopened?).and_return(true)
- Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
-
- subject.after_update(issue)
- end
-
- it 'is not created if the issue is not being reopened' do
- issue.should_receive(:is_being_reopened?).and_return(false)
- Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened')
-
- subject.after_update(issue)
- end
- end
- end
-
- describe '#send_reassigned_email' do
- let(:previous_assignee) { double(:user, id: 3) }
-
- before(:each) do
- issue.stub(:assignee_id).and_return(assignee.id)
- issue.stub(:assignee_id_was).and_return(previous_assignee.id)
- end
-
- def it_sends_a_reassigned_email_to(recipient)
- Notify.should_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id).
- and_return(double(deliver: true))
- end
-
- def it_does_not_send_a_reassigned_email_to(recipient)
- Notify.should_not_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id)
- end
-
- it 'sends a reassigned email to the previous and current assignees' do
- it_sends_a_reassigned_email_to assignee.id
- it_sends_a_reassigned_email_to previous_assignee.id
-
- subject.send(:send_reassigned_email, issue)
- end
-
- context 'does not send an email to the user who made the reassignment' do
- it 'if the user is the assignee' do
- subject.stub(:current_user).and_return(assignee)
- it_sends_a_reassigned_email_to previous_assignee.id
- it_does_not_send_a_reassigned_email_to assignee.id
-
- subject.send(:send_reassigned_email, issue)
- end
- it 'if the user is the previous assignee' do
- subject.stub(:current_user).and_return(previous_assignee)
- it_sends_a_reassigned_email_to assignee.id
- it_does_not_send_a_reassigned_email_to previous_assignee.id
-
- subject.send(:send_reassigned_email, issue)
- end
- end
- end
-end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index e9cbd72..69829a4 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -2,28 +2,19 @@ require 'spec_helper'
describe Issue do
describe "Associations" do
- it { should belong_to(:project) }
- it { should belong_to(:author) }
- it { should belong_to(:assignee) }
it { should belong_to(:milestone) }
end
describe "Validation" do
- it { should validate_presence_of(:title) }
- it { should validate_presence_of(:author_id) }
- it { should validate_presence_of(:project_id) }
+ it { should ensure_length_of(:description).is_within(0..2000) }
end
- describe "Scope" do
- it { Issue.should respond_to :closed }
- it { Issue.should respond_to :opened }
+ describe 'modules' do
+ it { should include_module(IssueCommonality) }
+ it { should include_module(Upvote) }
end
- subject { Factory.create(:issue,
- author: Factory(:user),
- assignee: Factory(:user),
- project: Factory.create(:project)) }
- it { should be_valid }
+ subject { Factory.create(:issue) }
describe '#is_being_reassigned?' do
it 'returns true if the issue assignee has changed' do
@@ -41,11 +32,7 @@ describe Issue do
subject.is_being_closed?.should be_true
end
it 'returns false if the closed attribute has changed and is now false' do
- issue = Factory.create(:issue,
- closed: true,
- author: Factory(:user),
- assignee: Factory(:user),
- project: Factory.create(:project))
+ issue = Factory.create(:closed_issue)
issue.closed = false
issue.is_being_closed?.should be_false
end
@@ -57,11 +44,7 @@ describe Issue do
describe '#is_being_reopened?' do
it 'returns true if the closed attribute has changed and is now false' do
- issue = Factory.create(:issue,
- closed: true,
- author: Factory(:user),
- assignee: Factory(:user),
- project: Factory.create(:project))
+ issue = Factory.create(:closed_issue)
issue.closed = false
issue.is_being_reopened?.should be_true
end
@@ -73,64 +56,4 @@ describe Issue do
subject.is_being_reopened?.should be_false
end
end
-
- describe "plus 1" do
- let(:project) { Factory(:project) }
- subject {
- Factory.create(:issue,
- author: Factory(:user),
- assignee: Factory(:user),
- project: project)
- }
-
- it "with no notes has a 0/0 score" do
- subject.upvotes.should == 0
- end
-
- it "should recognize non-+1 notes" do
- subject.notes << Factory(:note, note: "No +1 here", project: Factory(:project, path: 'plusone', code: 'plusone'))
- subject.should have(1).note
- subject.notes.first.upvote?.should be_false
- subject.upvotes.should == 0
- end
-
- it "should recognize a single +1 note" do
- subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone'))
- subject.upvotes.should == 1
- end
-
- it "should recognize a multiple +1 notes" do
- subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone'))
- subject.notes << Factory(:note, note: "+1 I want this", project: Factory(:project, path: 'plustwo', code: 'plustwo'))
- subject.upvotes.should == 2
- end
- end
-
- describe ".search" do
- let!(:issue) { Factory.create(:issue, title: "Searchable issue",
- project: Factory.create(:project)) }
-
- it "matches by title" do
- Issue.search('able').all.should == [issue]
- end
- end
end
-# == Schema Information
-#
-# Table name: issues
-#
-# id :integer(4) not null, primary key
-# title :string(255)
-# assignee_id :integer(4)
-# author_id :integer(4)
-# project_id :integer(4)
-# created_at :datetime not null
-# updated_at :datetime not null
-# closed :boolean(1) default(FALSE), not null
-# position :integer(4) default(0)
-# critical :boolean(1) default(FALSE), not null
-# branch_name :string(255)
-# description :text
-# milestone_id :integer(4)
-#
-
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 0f9b317..85cd291 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -2,12 +2,15 @@ require 'spec_helper'
describe Key do
describe "Associations" do
- it { should belong_to(:user) or belong_to(:project) }
+ it { should belong_to(:user) }
+ it { should belong_to(:project) }
end
describe "Validation" do
it { should validate_presence_of(:title) }
it { should validate_presence_of(:key) }
+ it { should ensure_length_of(:title).is_within(0..255) }
+ it { should ensure_length_of(:key).is_within(0..5000) }
end
describe "Methods" do
@@ -17,20 +20,15 @@ describe Key do
context "validation of uniqueness" do
context "as a deploy key" do
- let(:project) { Factory.create(:project, path: 'alpha', code: 'alpha') }
- let(:another_project) { Factory.create(:project, path: 'beta', code: 'beta') }
-
- before do
- deploy_key = Factory.create(:key, project: project)
- end
+ let!(:deploy_key) { create(:deploy_key) }
it "does not accept the same key twice for a project" do
- key = Factory.new(:key, project: project)
+ key = build(:key, project: deploy_key.project)
key.should_not be_valid
end
it "does accept the same key for another project" do
- key = Factory.new(:key, project: another_project)
+ key = build(:key, project_id: 0)
key.should be_valid
end
end
@@ -39,27 +37,13 @@ describe Key do
let(:user) { Factory.create(:user) }
it "accepts the key once" do
- Factory.new(:key, user: user).should be_valid
+ build(:key, user: user).should be_valid
end
it "does not accepts the key twice" do
- Factory.create(:key, user: user)
- Factory.new(:key, user: user).should_not be_valid
+ create(:key, user: user)
+ build(:key, user: user).should_not be_valid
end
end
end
end
-# == Schema Information
-#
-# Table name: keys
-#
-# id :integer(4) not null, primary key
-# user_id :integer(4)
-# created_at :datetime not null
-# updated_at :datetime not null
-# key :text
-# title :string(255)
-# identifier :string(255)
-# project_id :integer(4)
-#
-
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c7ad08a..d1253b3 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1,88 +1,13 @@
require 'spec_helper'
describe MergeRequest do
- describe "Associations" do
- it { should belong_to(:project) }
- it { should belong_to(:author) }
- it { should belong_to(:assignee) }
- end
-
describe "Validation" do
it { should validate_presence_of(:target_branch) }
it { should validate_presence_of(:source_branch) }
- it { should validate_presence_of(:title) }
- it { should validate_presence_of(:author_id) }
- it { should validate_presence_of(:project_id) }
end
- describe "Scope" do
- it { MergeRequest.should respond_to :closed }
- it { MergeRequest.should respond_to :opened }
- end
-
- it { Factory.create(:merge_request,
- author: Factory(:user),
- assignee: Factory(:user),
- project: Factory.create(:project)).should be_valid }
-
- describe "plus 1" do
- let(:project) { Factory(:project) }
- subject {
- Factory.create(:merge_request,
- author: Factory(:user),
- assignee: Factory(:user),
- project: project)
- }
-
- it "with no notes has a 0/0 score" do
- subject.upvotes.should == 0
- end
-
- it "should recognize non-+1 notes" do
- subject.notes << Factory(:note, note: "No +1 here", project: Factory(:project, path: 'plusone', code: 'plusone'))
- subject.should have(1).note
- subject.notes.first.upvote?.should be_false
- subject.upvotes.should == 0
- end
-
- it "should recognize a single +1 note" do
- subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone'))
- subject.upvotes.should == 1
- end
-
- it "should recognize a multiple +1 notes" do
- subject.notes << Factory(:note, note: "+1 This is awesome", project: Factory(:project, path: 'plusone', code: 'plusone'))
- subject.notes << Factory(:note, note: "+1 I want this", project: Factory(:project, path: 'plustwo', code: 'plustwo'))
- subject.upvotes.should == 2
- end
- end
-
- describe ".search" do
- let!(:issue) { Factory.create(:issue, title: "Searchable issue",
- project: Factory.create(:project)) }
-
- it "matches by title" do
- Issue.search('able').all.should == [issue]
- end
+ describe 'modules' do
+ it { should include_module(IssueCommonality) }
+ it { should include_module(Upvote) }
end
end
-# == Schema Information
-#
-# Table name: merge_requests
-#
-# id :integer(4) not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# project_id :integer(4) not null
-# author_id :integer(4)
-# assignee_id :integer(4)
-# title :string(255)
-# closed :boolean(1) default(FALSE), not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# st_commits :text(2147483647
-# st_diffs :text(2147483647
-# merged :boolean(1) default(FALSE), not null
-# state :integer(4) default(1), not null
-#
-
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 880d3f3..fa15fc8 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: milestones
-#
-# id :integer(4) not null, primary key
-# title :string(255) not null
-# project_id :integer(4) not null
-# description :text
-# due_date :date
-# closed :boolean(1) default(FALSE), not null
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
require 'spec_helper'
describe Milestone do
@@ -25,30 +11,36 @@ describe Milestone do
it { should validate_presence_of(:project_id) }
end
- let(:project) { Factory :project }
- let(:milestone) { Factory :milestone, project: project }
- let(:issue) { Factory :issue, project: project }
-
- it { milestone.should be_valid }
+ let(:milestone) { Factory :milestone }
+ let(:issue) { Factory :issue }
- describe "Issues" do
- before do
+ describe "#percent_complete" do
+ it "should not count open issues" do
milestone.issues << issue
+ milestone.percent_complete.should == 0
end
- it { milestone.percent_complete.should == 0 }
+ it "should count closed issues" do
+ issue.update_attributes(closed: true)
+ milestone.issues << issue
+ milestone.percent_complete.should == 100
+ end
- it do
- issue.update_attributes closed: true
+ it "should recover from dividing by zero" do
+ milestone.issues.should_receive(:count).and_return(0)
milestone.percent_complete.should == 100
end
end
- describe :expires_at do
- before do
- milestone.update_attributes due_date: Date.today + 1.day
+ describe "#expires_at" do
+ it "should be nil when due_date is unset" do
+ milestone.update_attributes(due_date: nil)
+ milestone.expires_at.should be_nil
end
- it { milestone.expires_at.should_not be_nil }
+ it "should not be nil when due_date is set" do
+ milestone.update_attributes(due_date: Date.tomorrow)
+ milestone.expires_at.should be_present
+ end
end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index c97b23c..ffaf442 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -1,11 +1,10 @@
require 'spec_helper'
describe Note do
- let(:project) { Factory :project }
- let!(:commit) { project.commit }
-
describe "Associations" do
it { should belong_to(:project) }
+ it { should belong_to(:noteable) }
+ it { should belong_to(:author).class_name('User') }
end
describe "Validation" do
@@ -13,8 +12,6 @@ describe Note do
it { should validate_presence_of(:project) }
end
- it { Factory.create(:note,
- project: project).should be_valid }
describe "Scopes" do
it "should have a today named scope that returns ..." do
Note.today.where_values.should == ["created_at >= '#{Date.today}'"]
@@ -25,26 +22,27 @@ describe Note do
let(:project) { Factory(:project) }
it "recognizes a neutral note" do
- note = Factory(:note, project: project, note: "This is not a +1 note")
+ note = Factory(:note, note: "This is not a +1 note")
note.should_not be_upvote
end
it "recognizes a +1 note" do
- note = Factory(:note, project: project, note: "+1 for this")
+ note = Factory(:note, note: "+1 for this")
note.should be_upvote
end
it "recognizes a -1 note as no vote" do
- note = Factory(:note, project: project, note: "-1 for this")
+ note = Factory(:note, note: "-1 for this")
note.should_not be_upvote
end
end
- describe "Commit notes" do
+ let(:project) { create(:project) }
+ let(:commit) { project.commit }
+ describe "Commit notes" do
before do
@note = Factory :note,
- project: project,
noteable_id: commit.id,
noteable_type: "Commit"
end
@@ -58,7 +56,6 @@ describe Note do
describe "Pre-line commit notes" do
before do
@note = Factory :note,
- project: project,
noteable_id: commit.id,
noteable_type: "Commit",
line_code: "0_16_1"
@@ -91,8 +88,8 @@ describe Note do
describe :authorization do
before do
- @p1 = project
- @p2 = Factory :project, code: "alien", path: "gitlabhq_1"
+ @p1 = create(:project)
+ @p2 = Factory :project
@u1 = Factory :user
@u2 = Factory :user
@u3 = Factory :user
@@ -135,19 +132,3 @@ describe Note do
end
end
end
-# == Schema Information
-#
-# Table name: notes
-#
-# id :integer(4) not null, primary key
-# note :text
-# noteable_id :string(255)
-# noteable_type :string(255)
-# author_id :integer(4)
-# created_at :datetime not null
-# updated_at :datetime not null
-# project_id :integer(4)
-# attachment :string(255)
-# line_code :string(255)
-#
-
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 5bba4ff..5add7ff 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2,23 +2,52 @@ require 'spec_helper'
describe Project do
describe "Associations" do
+ it { should belong_to(:owner).class_name('User') }
it { should have_many(:users) }
- it { should have_many(:protected_branches).dependent(:destroy) }
it { should have_many(:events).dependent(:destroy) }
- it { should have_many(:wikis).dependent(:destroy) }
it { should have_many(:merge_requests).dependent(:destroy) }
- it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:issues).dependent(:destroy) }
+ it { should have_many(:milestones).dependent(:destroy) }
+ it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:notes).dependent(:destroy) }
it { should have_many(:snippets).dependent(:destroy) }
- it { should have_many(:hooks).dependent(:destroy) }
it { should have_many(:deploy_keys).dependent(:destroy) }
+ it { should have_many(:hooks).dependent(:destroy) }
+ it { should have_many(:wikis).dependent(:destroy) }
+ it { should have_many(:protected_branches).dependent(:destroy) }
end
describe "Validation" do
+ let!(:project) { create(:project) }
+
it { should validate_presence_of(:name) }
+ it { should validate_uniqueness_of(:name) }
+ it { should ensure_length_of(:name).is_within(0..255) }
+
it { should validate_presence_of(:path) }
+ it { should validate_uniqueness_of(:path) }
+ it { should ensure_length_of(:path).is_within(0..255) }
+ # TODO: Formats
+
+ it { should ensure_length_of(:description).is_within(0..2000) }
+
it { should validate_presence_of(:code) }
+ it { should validate_uniqueness_of(:code) }
+ it { should ensure_length_of(:code).is_within(1..255) }
+ # TODO: Formats
+
+ it { should validate_presence_of(:owner) }
+
+ it "should not allow new projects beyond user limits" do
+ project.stub(:owner).and_return(double(can_create_project?: false, projects_limit: 1))
+ project.should_not be_valid
+ project.errors[:base].first.should match(/Your own projects limit is 1/)
+ end
+
+ it "should not allow 'gitolite-admin' as repo name" do
+ should allow_value("blah").for(:path)
+ should_not allow_value("gitolite-admin").for(:path)
+ end
end
describe "Respond to" do
@@ -40,7 +69,6 @@ describe Project do
it { should respond_to(:commits_with_refs) }
it { should respond_to(:commits_since) }
it { should respond_to(:commits_between) }
- it { should respond_to(:write_hooks) }
it { should respond_to(:satellite) }
it { should respond_to(:update_repository) }
it { should respond_to(:destroy_repository) }
@@ -74,9 +102,11 @@ describe Project do
it { should respond_to(:trigger_post_receive) }
end
- it "should not allow 'gitolite-admin' as repo name" do
- should allow_value("blah").for(:path)
- should_not allow_value("gitolite-admin").for(:path)
+ describe 'modules' do
+ it { should include_module(Repository) }
+ it { should include_module(PushObserver) }
+ it { should include_module(Authority) }
+ it { should include_module(Team) }
end
it "should return valid url to repo" do
@@ -86,7 +116,7 @@ describe Project do
it "should return path to repo" do
project = Project.new(path: "somewhere")
- project.path_to_repo.should == File.join(Rails.root, "tmp", "tests", "somewhere")
+ project.path_to_repo.should == File.join(Rails.root, "tmp", "repositories", "somewhere")
end
it "returns the full web URL for this repo" do
@@ -111,7 +141,7 @@ describe Project do
let(:last_event) { double }
before do
- project.stub(:events).and_return( [ double, double, last_event ] )
+ project.stub_chain(:events, :order).and_return( [ double, double, last_event ] )
end
it { project.last_activity.should == last_event }
@@ -237,23 +267,3 @@ describe Project do
end
end
end
-# == Schema Information
-#
-# Table name: projects
-#
-# id :integer(4) not null, primary key
-# name :string(255)
-# path :string(255)
-# description :text
-# created_at :datetime not null
-# updated_at :datetime not null
-# private_flag :boolean(1) default(TRUE), not null
-# code :string(255)
-# owner_id :integer(4)
-# default_branch :string(255) default("master"), not null
-# issues_enabled :boolean(1) default(TRUE), not null
-# wall_enabled :boolean(1) default(TRUE), not null
-# merge_requests_enabled :boolean(1) default(TRUE), not null
-# wiki_enabled :boolean(1) default(TRUE), not null
-#
-
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index 1654e3b..9180bc3 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -1,19 +1,6 @@
-# == Schema Information
-#
-# Table name: protected_branches
-#
-# id :integer(4) not null, primary key
-# project_id :integer(4) not null
-# name :string(255) not null
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
require 'spec_helper'
describe ProtectedBranch do
- let(:project) { Factory(:project) }
-
describe 'Associations' do
it { should belong_to(:project) }
end
@@ -24,26 +11,26 @@ describe ProtectedBranch do
end
describe 'Callbacks' do
- subject { ProtectedBranch.new(project: project, name: 'branch_name') }
+ let(:branch) { build(:protected_branch) }
it 'call update_repository after save' do
- subject.should_receive(:update_repository)
- subject.save
+ branch.should_receive(:update_repository)
+ branch.save
end
it 'call update_repository after destroy' do
- subject.should_receive(:update_repository)
- subject.destroy
+ branch.save
+ branch.should_receive(:update_repository)
+ branch.destroy
end
end
describe '#commit' do
- subject { ProtectedBranch.new(project: project, name: 'cant_touch_this') }
+ let(:branch) { create(:protected_branch) }
it 'commits itself to its project' do
- project.should_receive(:commit).with('cant_touch_this')
-
- subject.commit
+ branch.project.should_receive(:commit).with(branch.name)
+ branch.commit
end
end
end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 9b4aaa1..ffb861c 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -3,29 +3,21 @@ require 'spec_helper'
describe Snippet do
describe "Associations" do
it { should belong_to(:project) }
- it { should belong_to(:author) }
+ it { should belong_to(:author).class_name('User') }
+ it { should have_many(:notes).dependent(:destroy) }
end
describe "Validation" do
- it { should validate_presence_of(:title) }
it { should validate_presence_of(:author_id) }
it { should validate_presence_of(:project_id) }
+
+ it { should validate_presence_of(:title) }
+ it { should ensure_length_of(:title).is_within(0..255) }
+
it { should validate_presence_of(:file_name) }
+ it { should ensure_length_of(:title).is_within(0..255) }
+
it { should validate_presence_of(:content) }
+ it { should ensure_length_of(:content).is_within(0..10_000) }
end
end
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer(4) not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer(4) not null
-# project_id :integer(4) not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# file_name :string(255)
-# expires_at :datetime
-#
-
diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb
index 56d76ed..fe2a583 100644
--- a/spec/models/system_hook_spec.rb
+++ b/spec/models/system_hook_spec.rb
@@ -10,13 +10,12 @@ describe SystemHook do
end
it "project_create hook" do
- user = Factory :user
with_resque do
- project = Factory :project_without_owner, owner: user
+ project = Factory :project
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once
end
-
+
it "project_destroy hook" do
project = Factory :project
with_resque do
@@ -31,7 +30,7 @@ describe SystemHook do
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_create/).once
end
-
+
it "user_destroy hook" do
user = Factory :user
with_resque do
@@ -39,7 +38,7 @@ describe SystemHook do
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_destroy/).once
end
-
+
it "project_create hook" do
user = Factory :user
project = Factory :project
@@ -48,7 +47,7 @@ describe SystemHook do
end
WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once
end
-
+
it "project_destroy hook" do
user = Factory :user
project = Factory :project
diff --git a/spec/models/user_observer_spec.rb b/spec/models/user_observer_spec.rb
deleted file mode 100644
index 23dac98..0000000
--- a/spec/models/user_observer_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'spec_helper'
-
-describe UserObserver do
- subject { UserObserver.instance }
-
- it 'calls #after_create when new users are created' do
- new_user = Factory.new(:user)
- subject.should_receive(:after_create).with(new_user)
-
- User.observers.enable :user_observer do
- new_user.save
- end
- end
-
- context 'when a new user is created' do
- let(:user) { double(:user, id: 42, password: 'P@ssword!') }
- let(:notification) { double :notification }
-
- it 'sends an email' do
- notification.should_receive(:deliver)
- Notify.should_receive(:new_user_email).with(user.id, user.password).and_return(notification)
-
- subject.after_create(user)
- end
- end
-end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 265dcef..ca34f07 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2,12 +2,26 @@ require 'spec_helper'
describe User do
describe "Associations" do
+ it { should have_many(:users_projects).dependent(:destroy) }
it { should have_many(:projects) }
- it { should have_many(:users_projects) }
- it { should have_many(:issues) }
- it { should have_many(:assigned_issues) }
- it { should have_many(:merge_requests) }
- it { should have_many(:assigned_merge_requests) }
+ it { should have_many(:my_own_projects).class_name('Project') }
+ it { should have_many(:keys).dependent(:destroy) }
+ it { should have_many(:events).class_name('Event').dependent(:destroy) }
+ it { should have_many(:recent_events).class_name('Event') }
+ it { should have_many(:issues).dependent(:destroy) }
+ it { should have_many(:notes).dependent(:destroy) }
+ it { should have_many(:assigned_issues).dependent(:destroy) }
+ it { should have_many(:merge_requests).dependent(:destroy) }
+ it { should have_many(:assigned_merge_requests).dependent(:destroy) }
+ end
+
+ describe 'validations' do
+ it { should validate_presence_of(:projects_limit) }
+ it { should validate_numericality_of(:projects_limit) }
+ it { should allow_value(0).for(:projects_limit) }
+ it { should_not allow_value(-1).for(:projects_limit) }
+
+ it { should ensure_length_of(:bio).is_within(0..255) }
end
describe "Respond to" do
@@ -49,49 +63,4 @@ describe User do
user = Factory(:user)
user.authentication_token.should_not == ""
end
-
- describe "dependent" do
- before do
- @user = Factory :user
- @note = Factory :note,
- author: @user,
- project: Factory(:project)
- end
-
- it "should destroy all notes with user" do
- Note.find_by_id(@note.id).should_not be_nil
- @user.destroy
- Note.find_by_id(@note.id).should be_nil
- end
- end
end
-# == Schema Information
-#
-# Table name: users
-#
-# id :integer(4) not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(128) default(""), not null
-# reset_password_token :string(255)
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer(4) default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
-# created_at :datetime not null
-# updated_at :datetime not null
-# name :string(255)
-# admin :boolean(1) default(FALSE), not null
-# projects_limit :integer(4) default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
-# dark_scheme :boolean(1) default(FALSE), not null
-# theme_id :integer(4) default(1), not null
-# bio :string(255)
-# blocked :boolean(1) default(FALSE), not null
-#
-
diff --git a/spec/models/users_project_spec.rb b/spec/models/users_project_spec.rb
index 87fbfbf..3197ba6 100644
--- a/spec/models/users_project_spec.rb
+++ b/spec/models/users_project_spec.rb
@@ -7,7 +7,11 @@ describe UsersProject do
end
describe "Validation" do
+ let!(:users_project) { create(:users_project) }
+
it { should validate_presence_of(:user_id) }
+ it { should validate_uniqueness_of(:user_id).scoped_to(:project_id) }
+
it { should validate_presence_of(:project_id) }
end
@@ -16,15 +20,3 @@ describe UsersProject do
it { should respond_to(:user_email) }
end
end
-# == Schema Information
-#
-# Table name: users_projects
-#
-# id :integer(4) not null, primary key
-# user_id :integer(4) not null
-# project_id :integer(4) not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# project_access :integer(4) default(0), not null
-#
-
diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb
index 8859476..3cba5b6 100644
--- a/spec/models/web_hook_spec.rb
+++ b/spec/models/web_hook_spec.rb
@@ -52,14 +52,3 @@ describe ProjectHook do
end
end
end
-# == Schema Information
-#
-# Table name: web_hooks
-#
-# id :integer(4) not null, primary key
-# url :string(255)
-# project_id :integer(4)
-# created_at :datetime not null
-# updated_at :datetime not null
-#
-
diff --git a/spec/models/wiki_spec.rb b/spec/models/wiki_spec.rb
index 892d0e8..de6ce42 100644
--- a/spec/models/wiki_spec.rb
+++ b/spec/models/wiki_spec.rb
@@ -4,27 +4,13 @@ describe Wiki do
describe "Associations" do
it { should belong_to(:project) }
it { should belong_to(:user) }
+ it { should have_many(:notes).dependent(:destroy) }
end
describe "Validation" do
it { should validate_presence_of(:title) }
+ it { should ensure_length_of(:title).is_within(1..250) }
it { should validate_presence_of(:content) }
it { should validate_presence_of(:user_id) }
end
-
- it { Factory(:wiki).should be_valid }
end
-# == Schema Information
-#
-# Table name: wikis
-#
-# id :integer(4) not null, primary key
-# title :string(255)
-# content :text
-# project_id :integer(4)
-# created_at :datetime not null
-# updated_at :datetime not null
-# slug :string(255)
-# user_id :integer(4)
-#
-
diff --git a/spec/monkeypatch.rb b/spec/monkeypatch.rb
deleted file mode 100644
index 855a31f..0000000
--- a/spec/monkeypatch.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# Stubbing Project <-> git host path
-# create project using Factory only
-class Project
- def update_repository
- true
- end
-
- def destroy_repository
- true
- end
-
- def path_to_repo
- File.join(Rails.root, "tmp", "tests", path)
- end
-
- def satellite
- @satellite ||= FakeSatellite.new
- end
-end
-
-class Key
- def update_repository
- true
- end
-
- def repository_delete_key
- true
- end
-end
-
-class UsersProject
- def update_repository
- true
- end
-end
-
-class FakeSatellite
- def exists?
- true
- end
-
- def create
- true
- end
-end
-
-class ProtectedBranch
- def update_repository
- true
- end
-end
diff --git a/spec/observers/activity_observer_spec.rb b/spec/observers/activity_observer_spec.rb
new file mode 100644
index 0000000..0db4a99
--- /dev/null
+++ b/spec/observers/activity_observer_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe ActivityObserver do
+ let(:project) { Factory :project }
+
+ def self.it_should_be_valid_event
+ it { @event.should_not be_nil }
+ it { @event.project.should == project }
+ end
+
+ describe "Merge Request created" do
+ before do
+ MergeRequest.observers.enable :activity_observer do
+ @merge_request = Factory :merge_request, project: project
+ @event = Event.last
+ end
+ end
+
+ it_should_be_valid_event
+ it { @event.action.should == Event::Created }
+ it { @event.target.should == @merge_request }
+ end
+
+ describe "Issue created" do
+ before do
+ Issue.observers.enable :activity_observer do
+ @issue = Factory :issue, project: project
+ @event = Event.last
+ end
+ end
+
+ it_should_be_valid_event
+ it { @event.action.should == Event::Created }
+ it { @event.target.should == @issue }
+ end
+
+ #describe "Issue commented" do
+ #before do
+ #@issue = Factory :issue, project: project
+ #@note = Factory :note, noteable: @issue, project: project
+ #@event = Event.last
+ #end
+
+ #it_should_be_valid_event
+ #it { @event.action.should == Event::Commented }
+ #it { @event.target.should == @note }
+ #end
+end
diff --git a/spec/observers/issue_observer_spec.rb b/spec/observers/issue_observer_spec.rb
new file mode 100644
index 0000000..b5943f2
--- /dev/null
+++ b/spec/observers/issue_observer_spec.rb
@@ -0,0 +1,199 @@
+require 'spec_helper'
+
+describe IssueObserver do
+ let(:some_user) { double(:user, id: 1) }
+ let(:assignee) { double(:user, id: 2) }
+ let(:author) { double(:user, id: 3) }
+ let(:issue) { double(:issue, id: 42, assignee: assignee, author: author) }
+
+ before(:each) { subject.stub(:current_user).and_return(some_user) }
+
+ subject { IssueObserver.instance }
+
+ describe '#after_create' do
+
+ it 'is called when an issue is created' do
+ subject.should_receive(:after_create)
+
+ Issue.observers.enable :issue_observer do
+ Factory.create(:issue, project: Factory.create(:project))
+ end
+ end
+
+ it 'sends an email to the assignee' do
+ Notify.should_receive(:new_issue_email).with(issue.id).
+ and_return(double(deliver: true))
+
+ subject.after_create(issue)
+ end
+
+ it 'does not send an email to the assignee if assignee created the issue' do
+ subject.stub(:current_user).and_return(assignee)
+ Notify.should_not_receive(:new_issue_email)
+
+ subject.after_create(issue)
+ end
+ end
+
+ context '#after_update' do
+ before(:each) do
+ issue.stub(:is_being_reassigned?).and_return(false)
+ issue.stub(:is_being_closed?).and_return(false)
+ issue.stub(:is_being_reopened?).and_return(false)
+ end
+
+ it 'is called when an issue is changed' do
+ changed = Factory.create(:issue, project: Factory.create(:project))
+ subject.should_receive(:after_update)
+
+ Issue.observers.enable :issue_observer do
+ changed.description = 'I changed'
+ changed.save
+ end
+ end
+
+ context 'a reassigned email' do
+ it 'is sent if the issue is being reassigned' do
+ issue.should_receive(:is_being_reassigned?).and_return(true)
+ subject.should_receive(:send_reassigned_email).with(issue)
+
+ subject.after_update(issue)
+ end
+
+ it 'is not sent if the issue is not being reassigned' do
+ issue.should_receive(:is_being_reassigned?).and_return(false)
+ subject.should_not_receive(:send_reassigned_email)
+
+ subject.after_update(issue)
+ end
+ end
+
+ context 'a status "closed"' do
+ it 'note is created if the issue is being closed' do
+ issue.should_receive(:is_being_closed?).and_return(true)
+ Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
+
+ subject.after_update(issue)
+ end
+
+ it 'note is not created if the issue is not being closed' do
+ issue.should_receive(:is_being_closed?).and_return(false)
+ Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
+
+ subject.after_update(issue)
+ end
+
+ it 'notification is delivered if the issue being closed' do
+ issue.stub(:is_being_closed?).and_return(true)
+ Notify.should_receive(:issue_status_changed_email).twice
+ Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
+
+ subject.after_update(issue)
+ end
+
+ it 'notification is not delivered if the issue not being closed' do
+ issue.stub(:is_being_closed?).and_return(false)
+ Notify.should_not_receive(:issue_status_changed_email)
+ Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
+
+ subject.after_update(issue)
+ end
+
+ it 'notification is delivered only to author if the issue being closed' do
+ issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)
+ issue_without_assignee.stub(:is_being_reassigned?).and_return(false)
+ issue_without_assignee.stub(:is_being_closed?).and_return(true)
+ issue_without_assignee.stub(:is_being_reopened?).and_return(false)
+ Notify.should_receive(:issue_status_changed_email).once
+ Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'closed')
+
+ subject.after_update(issue_without_assignee)
+ end
+ end
+
+ context 'a status "reopened"' do
+ it 'note is created if the issue is being reopened' do
+ issue.should_receive(:is_being_reopened?).and_return(true)
+ Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
+
+ subject.after_update(issue)
+ end
+
+ it 'note is not created if the issue is not being reopened' do
+ issue.should_receive(:is_being_reopened?).and_return(false)
+ Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened')
+
+ subject.after_update(issue)
+ end
+
+ it 'notification is delivered if the issue being reopened' do
+ issue.stub(:is_being_reopened?).and_return(true)
+ Notify.should_receive(:issue_status_changed_email).twice
+ Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
+
+ subject.after_update(issue)
+ end
+
+ it 'notification is not delivered if the issue not being reopened' do
+ issue.stub(:is_being_reopened?).and_return(false)
+ Notify.should_not_receive(:issue_status_changed_email)
+ Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened')
+
+ subject.after_update(issue)
+ end
+
+ it 'notification is delivered only to author if the issue being reopened' do
+ issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)
+ issue_without_assignee.stub(:is_being_reassigned?).and_return(false)
+ issue_without_assignee.stub(:is_being_closed?).and_return(false)
+ issue_without_assignee.stub(:is_being_reopened?).and_return(true)
+ Notify.should_receive(:issue_status_changed_email).once
+ Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'reopened')
+
+ subject.after_update(issue_without_assignee)
+ end
+ end
+ end
+
+ describe '#send_reassigned_email' do
+ let(:previous_assignee) { double(:user, id: 3) }
+
+ before(:each) do
+ issue.stub(:assignee_id).and_return(assignee.id)
+ issue.stub(:assignee_id_was).and_return(previous_assignee.id)
+ end
+
+ def it_sends_a_reassigned_email_to(recipient)
+ Notify.should_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id).
+ and_return(double(deliver: true))
+ end
+
+ def it_does_not_send_a_reassigned_email_to(recipient)
+ Notify.should_not_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id)
+ end
+
+ it 'sends a reassigned email to the previous and current assignees' do
+ it_sends_a_reassigned_email_to assignee.id
+ it_sends_a_reassigned_email_to previous_assignee.id
+
+ subject.send(:send_reassigned_email, issue)
+ end
+
+ context 'does not send an email to the user who made the reassignment' do
+ it 'if the user is the assignee' do
+ subject.stub(:current_user).and_return(assignee)
+ it_sends_a_reassigned_email_to previous_assignee.id
+ it_does_not_send_a_reassigned_email_to assignee.id
+
+ subject.send(:send_reassigned_email, issue)
+ end
+ it 'if the user is the previous assignee' do
+ subject.stub(:current_user).and_return(previous_assignee)
+ it_sends_a_reassigned_email_to assignee.id
+ it_does_not_send_a_reassigned_email_to previous_assignee.id
+
+ subject.send(:send_reassigned_email, issue)
+ end
+ end
+ end
+end
diff --git a/spec/observers/key_observer_spec.rb b/spec/observers/key_observer_spec.rb
new file mode 100644
index 0000000..7f2a76a
--- /dev/null
+++ b/spec/observers/key_observer_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe KeyObserver do
+ before do
+ @key = double('Key',
+ identifier: 'admin_654654',
+ key: '== a vaild ssh key',
+ projects: [],
+ is_deploy_key: false
+ )
+
+ @gitolite = double('Gitlab::Gitolite',
+ set_key: true,
+ remove_key: true
+ )
+
+ @observer = KeyObserver.instance
+ @observer.stub(:git_host => @gitolite)
+ end
+
+ context :after_save do
+ it do
+ @gitolite.should_receive(:set_key).with(@key.identifier, @key.key, @key.projects)
+ @observer.after_save(@key)
+ end
+ end
+
+ context :after_destroy do
+ it do
+ @gitolite.should_receive(:remove_key).with(@key.identifier, @key.projects)
+ @observer.after_destroy(@key)
+ end
+ end
+end
diff --git a/spec/observers/user_observer_spec.rb b/spec/observers/user_observer_spec.rb
new file mode 100644
index 0000000..23dac98
--- /dev/null
+++ b/spec/observers/user_observer_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe UserObserver do
+ subject { UserObserver.instance }
+
+ it 'calls #after_create when new users are created' do
+ new_user = Factory.new(:user)
+ subject.should_receive(:after_create).with(new_user)
+
+ User.observers.enable :user_observer do
+ new_user.save
+ end
+ end
+
+ context 'when a new user is created' do
+ let(:user) { double(:user, id: 42, password: 'P@ssword!') }
+ let(:notification) { double :notification }
+
+ it 'sends an email' do
+ notification.should_receive(:deliver)
+ Notify.should_receive(:new_user_email).with(user.id, user.password).and_return(notification)
+
+ subject.after_create(user)
+ end
+ end
+end
diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb
new file mode 100644
index 0000000..3e39204
--- /dev/null
+++ b/spec/observers/users_project_observer_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe UsersProjectObserver do
+ let(:user) { Factory.create :user }
+ let(:project) { Factory.create(:project,
+ code: "Fuu",
+ path: "Fuu" ) }
+ let(:users_project) { Factory.create(:users_project,
+ project: project,
+ user: user )}
+ subject { UsersProjectObserver.instance }
+
+ describe "#after_create" do
+ it "should called when UsersProject created" do
+ subject.should_receive(:after_create)
+ UsersProject.observers.enable :users_project_observer do
+ Factory.create(:users_project,
+ project: project,
+ user: user)
+ end
+ end
+ it "should send email to user" do
+ Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true))
+ subject.after_create(users_project)
+ end
+ end
+
+ describe "#after_update" do
+ it "should called when UsersProject updated" do
+ subject.should_receive(:after_update)
+ UsersProject.observers.enable :users_project_observer do
+ users_project.update_attribute(:project_access, 40)
+ end
+ end
+ it "should send email to user" do
+ Notify.should_receive(:project_access_granted_email).with(users_project.id).and_return(double(deliver: true))
+ subject.after_update(users_project)
+ end
+ end
+end
diff --git a/spec/requests/admin/admin_projects_spec.rb b/spec/requests/admin/admin_projects_spec.rb
index 0ce66f5..2edfb59 100644
--- a/spec/requests/admin/admin_projects_spec.rb
+++ b/spec/requests/admin/admin_projects_spec.rb
@@ -87,7 +87,7 @@ describe "Admin::Projects" do
visit new_admin_project_path
fill_in 'project_name', with: 'NewProject'
fill_in 'project_code', with: 'NPR'
- fill_in 'project_path', with: 'gitlabhq_1'
+ fill_in 'project_path', with: 'newproject'
expect { click_button "Create project" }.to change { Project.count }.by(1)
@project = Project.last
end
diff --git a/spec/requests/admin/security_spec.rb b/spec/requests/admin/security_spec.rb
index 0c36974..6306832 100644
--- a/spec/requests/admin/security_spec.rb
+++ b/spec/requests/admin/security_spec.rb
@@ -2,20 +2,26 @@ require 'spec_helper'
describe "Admin::Projects" do
describe "GET /admin/projects" do
- it { admin_projects_path.should be_allowed_for :admin }
- it { admin_projects_path.should be_denied_for :user }
- it { admin_projects_path.should be_denied_for :visitor }
+ subject { admin_projects_path }
+
+ it { should be_allowed_for :admin }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /admin/users" do
- it { admin_users_path.should be_allowed_for :admin }
- it { admin_users_path.should be_denied_for :user }
- it { admin_users_path.should be_denied_for :visitor }
+ subject { admin_users_path }
+
+ it { should be_allowed_for :admin }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /admin/hooks" do
- it { admin_hooks_path.should be_allowed_for :admin }
- it { admin_hooks_path.should be_denied_for :user }
- it { admin_hooks_path.should be_denied_for :visitor }
+ subject { admin_hooks_path }
+
+ it { should be_allowed_for :admin }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
new file mode 100644
index 0000000..293ea83
--- /dev/null
+++ b/spec/requests/api/issues_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Gitlab::API do
+ include ApiHelpers
+
+ let(:user) { Factory :user }
+ let!(:project) { Factory :project, owner: user }
+ let!(:issue) { Factory :issue, author: user, assignee: user, project: project }
+ before { project.add_access(user, :read) }
+
+ describe "GET /issues" do
+ it "should return authentication error" do
+ get api("/issues")
+ response.status.should == 401
+ end
+
+ describe "authenticated GET /issues" do
+ it "should return an array of issues" do
+ get api("/issues", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['title'].should == issue.title
+ end
+ end
+ end
+
+ describe "GET /projects/:id/issues" do
+ it "should return project issues" do
+ get api("/projects/#{project.code}/issues", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['title'].should == issue.title
+ end
+ end
+
+ describe "GET /projects/:id/issues/:issue_id" do
+ it "should return a project issue by id" do
+ get api("/projects/#{project.code}/issues/#{issue.id}", user)
+ response.status.should == 200
+ json_response['title'].should == issue.title
+ end
+ end
+
+ describe "POST /projects/:id/issues" do
+ it "should create a new project issue" do
+ post api("/projects/#{project.code}/issues", user),
+ title: 'new issue', labels: 'label, label2'
+ response.status.should == 201
+ json_response['title'].should == 'new issue'
+ json_response['description'].should be_nil
+ json_response['labels'].should == ['label', 'label2']
+ end
+ end
+
+ describe "PUT /projects/:id/issues/:issue_id" do
+ it "should update a project issue" do
+ put api("/projects/#{project.code}/issues/#{issue.id}", user),
+ title: 'updated title', labels: 'label2', closed: 1
+ response.status.should == 200
+ json_response['title'].should == 'updated title'
+ json_response['labels'].should == ['label2']
+ json_response['closed'].should be_true
+ end
+ end
+
+ describe "DELETE /projects/:id/issues/:issue_id" do
+ it "should delete a project issue" do
+ delete api("/projects/#{project.code}/issues/#{issue.id}", user)
+ response.status.should == 405
+ end
+ end
+end
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
new file mode 100644
index 0000000..cf5f65f
--- /dev/null
+++ b/spec/requests/api/milestones_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Gitlab::API do
+ include ApiHelpers
+
+ let(:user) { Factory :user }
+ let!(:project) { Factory :project, owner: user }
+ let!(:milestone) { Factory :milestone, project: project }
+
+ before { project.add_access(user, :read) }
+
+ describe "GET /projects/:id/milestones" do
+ it "should return project milestones" do
+ get api("/projects/#{project.code}/milestones", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['title'].should == milestone.title
+ end
+ end
+
+ describe "GET /projects/:id/milestones/:milestone_id" do
+ it "should return a project milestone by id" do
+ get api("/projects/#{project.code}/milestones/#{milestone.id}", user)
+ response.status.should == 200
+ json_response['title'].should == milestone.title
+ end
+ end
+
+ describe "POST /projects/:id/milestones" do
+ it "should create a new project milestone" do
+ post api("/projects/#{project.code}/milestones", user),
+ title: 'new milestone'
+ response.status.should == 201
+ json_response['title'].should == 'new milestone'
+ json_response['description'].should be_nil
+ end
+ end
+
+ describe "PUT /projects/:id/milestones/:milestone_id" do
+ it "should update a project milestone" do
+ put api("/projects/#{project.code}/milestones/#{milestone.id}", user),
+ title: 'updated title'
+ response.status.should == 200
+ json_response['title'].should == 'updated title'
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
new file mode 100644
index 0000000..0cbc12a
--- /dev/null
+++ b/spec/requests/api/projects_spec.rb
@@ -0,0 +1,135 @@
+require 'spec_helper'
+
+describe Gitlab::API do
+ include ApiHelpers
+
+ let(:user) { Factory :user }
+ let!(:project) { Factory :project, owner: user }
+ let!(:snippet) { Factory :snippet, author: user, project: project, title: 'example' }
+ before { project.add_access(user, :read) }
+
+ describe "GET /projects" do
+ it "should return authentication error" do
+ get api("/projects")
+ response.status.should == 401
+ end
+
+ describe "authenticated GET /projects" do
+ it "should return an array of projects" do
+ get api("/projects", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['name'].should == project.name
+ json_response.first['owner']['email'].should == user.email
+ end
+ end
+ end
+
+ describe "GET /projects/:id" do
+ it "should return a project by id" do
+ get api("/projects/#{project.id}", user)
+ response.status.should == 200
+ json_response['name'].should == project.name
+ json_response['owner']['email'].should == user.email
+ end
+
+ it "should return a project by code name" do
+ get api("/projects/#{project.code}", user)
+ response.status.should == 200
+ json_response['name'].should == project.name
+ end
+
+ it "should return a 404 error if not found" do
+ get api("/projects/42", user)
+ response.status.should == 404
+ json_response['message'].should == '404 Not found'
+ end
+ end
+
+ describe "GET /projects/:id/repository/branches" do
+ it "should return an array of project branches" do
+ get api("/projects/#{project.code}/repository/branches", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name
+ end
+ end
+
+ describe "GET /projects/:id/repository/branches/:branch" do
+ it "should return the branch information for a single branch" do
+ get api("/projects/#{project.code}/repository/branches/new_design", user)
+ response.status.should == 200
+
+ json_response['name'].should == 'new_design'
+ json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
+ end
+ end
+
+ describe "GET /projects/:id/repository/tags" do
+ it "should return an array of project tags" do
+ get api("/projects/#{project.code}/repository/tags", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name
+ end
+ end
+
+ describe "GET /projects/:id/snippets/:snippet_id" do
+ it "should return a project snippet" do
+ get api("/projects/#{project.code}/snippets/#{snippet.id}", user)
+ response.status.should == 200
+ json_response['title'].should == snippet.title
+ end
+ end
+
+ describe "POST /projects/:id/snippets" do
+ it "should create a new project snippet" do
+ post api("/projects/#{project.code}/snippets", user),
+ title: 'api test', file_name: 'sample.rb', code: 'test'
+ response.status.should == 201
+ json_response['title'].should == 'api test'
+ end
+ end
+
+ describe "PUT /projects/:id/snippets" do
+ it "should update an existing project snippet" do
+ put api("/projects/#{project.code}/snippets/#{snippet.id}", user),
+ code: 'updated code'
+ response.status.should == 200
+ json_response['title'].should == 'example'
+ snippet.reload.content.should == 'updated code'
+ end
+ end
+
+ describe "DELETE /projects/:id/snippets/:snippet_id" do
+ it "should delete existing project snippet" do
+ expect {
+ delete api("/projects/#{project.code}/snippets/#{snippet.id}", user)
+ }.to change { Snippet.count }.by(-1)
+ end
+ end
+
+ describe "GET /projects/:id/snippets/:snippet_id/raw" do
+ it "should get a raw project snippet" do
+ get api("/projects/#{project.code}/snippets/#{snippet.id}/raw", user)
+ response.status.should == 200
+ end
+ end
+
+ describe "GET /projects/:id/:sha/blob" do
+ it "should get the raw file contents" do
+ get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.md", user)
+ response.status.should == 200
+ end
+
+ it "should return 404 for invalid branch_name" do
+ get api("/projects/#{project.code}/repository/commits/invalid_branch_name/blob?filepath=README.md", user)
+ response.status.should == 404
+ end
+
+ it "should return 404 for invalid file" do
+ get api("/projects/#{project.code}/repository/commits/master/blob?filepath=README.invalid", user)
+ response.status.should == 404
+ end
+ end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
new file mode 100644
index 0000000..d791962
--- /dev/null
+++ b/spec/requests/api/users_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Gitlab::API do
+ include ApiHelpers
+
+ let(:user) { Factory :user }
+
+ describe "GET /users" do
+ it "should return authentication error" do
+ get api("/users")
+ response.status.should == 401
+ end
+
+ describe "authenticated GET /users" do
+ it "should return an array of users" do
+ get api("/users", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.first['email'].should == user.email
+ end
+ end
+ end
+
+ describe "GET /users/:id" do
+ it "should return a user by id" do
+ get api("/users/#{user.id}", user)
+ response.status.should == 200
+ json_response['email'].should == user.email
+ end
+ end
+
+ describe "GET /user" do
+ it "should return current user" do
+ get api("/user", user)
+ response.status.should == 200
+ json_response['email'].should == user.email
+ end
+ end
+end
diff --git a/spec/requests/atom/dashboard_issues_spec.rb b/spec/requests/atom/dashboard_issues_spec.rb
index 9b4ffc0..79a9b8e 100644
--- a/spec/requests/atom/dashboard_issues_spec.rb
+++ b/spec/requests/atom/dashboard_issues_spec.rb
@@ -6,13 +6,9 @@ describe "User Issues Dashboard" do
login_as :user
- @project1 = Factory :project,
- path: "project1",
- code: "TEST1"
+ @project1 = Factory :project
- @project2 = Factory :project,
- path: "project2",
- code: "TEST2"
+ @project2 = Factory :project
@project1.add_access(@user, :read, :write)
@project2.add_access(@user, :read, :write)
diff --git a/spec/requests/projects_deploy_keys_spec.rb b/spec/requests/projects_deploy_keys_spec.rb
index 0fea7b4..894aa6d 100644
--- a/spec/requests/projects_deploy_keys_spec.rb
+++ b/spec/requests/projects_deploy_keys_spec.rb
@@ -42,7 +42,7 @@ describe "Projects", "DeployKeys" do
describe "fill in" do
before do
fill_in "key_title", with: "laptop"
- fill_in "key_key", with: "publickey234="
+ fill_in "key_key", with: "ssh-rsa publickey234="
end
it { expect { click_button "Save" }.to change {Key.count}.by(1) }
@@ -55,12 +55,12 @@ describe "Projects", "DeployKeys" do
end
end
- describe "Show page" do
+ describe "Show page" do
before do
@key = Factory :key, project: project
- visit project_deploy_key_path(project, @key)
+ visit project_deploy_key_path(project, @key)
end
-
+
it { page.should have_content @key.title }
it { page.should have_content @key.key[0..10] }
end
diff --git a/spec/requests/security/profile_access_spec.rb b/spec/requests/security/profile_access_spec.rb
index b8ed27f..9f6fe6a 100644
--- a/spec/requests/security/profile_access_spec.rb
+++ b/spec/requests/security/profile_access_spec.rb
@@ -11,24 +11,30 @@ describe "Users Security" do
end
describe "GET /keys" do
- it { keys_path.should be_allowed_for @u1 }
- it { keys_path.should be_allowed_for :admin }
- it { keys_path.should be_allowed_for :user }
- it { keys_path.should be_denied_for :visitor }
+ subject { keys_path }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /profile" do
- it { profile_path.should be_allowed_for @u1 }
- it { profile_path.should be_allowed_for :admin }
- it { profile_path.should be_allowed_for :user }
- it { profile_path.should be_denied_for :visitor }
+ subject { profile_path }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /profile/password" do
- it { profile_password_path.should be_allowed_for @u1 }
- it { profile_password_path.should be_allowed_for :admin }
- it { profile_password_path.should be_allowed_for :user }
- it { profile_password_path.should be_denied_for :visitor }
+ subject { profile_password_path }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for :admin }
+ it { should be_allowed_for :user }
+ it { should be_denied_for :visitor }
end
end
end
diff --git a/spec/requests/security/project_access_spec.rb b/spec/requests/security/project_access_spec.rb
index d503cf8..0cdf43b 100644
--- a/spec/requests/security/project_access_spec.rb
+++ b/spec/requests/security/project_access_spec.rb
@@ -26,64 +26,76 @@ describe "Application access" do
end
describe "GET /project_code" do
- it { project_path(@project).should be_allowed_for @u1 }
- it { project_path(@project).should be_allowed_for @u3 }
- it { project_path(@project).should be_denied_for :admin }
- it { project_path(@project).should be_denied_for @u2 }
- it { project_path(@project).should be_denied_for :user }
- it { project_path(@project).should be_denied_for :visitor }
+ subject { project_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/master/tree" do
- it { tree_project_ref_path(@project, @project.root_ref).should be_allowed_for @u1 }
- it { tree_project_ref_path(@project, @project.root_ref).should be_allowed_for @u3 }
- it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :admin }
- it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for @u2 }
- it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :user }
- it { tree_project_ref_path(@project, @project.root_ref).should be_denied_for :visitor }
+ subject { tree_project_ref_path(@project, @project.root_ref) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/commits" do
- it { project_commits_path(@project).should be_allowed_for @u1 }
- it { project_commits_path(@project).should be_allowed_for @u3 }
- it { project_commits_path(@project).should be_denied_for :admin }
- it { project_commits_path(@project).should be_denied_for @u2 }
- it { project_commits_path(@project).should be_denied_for :user }
- it { project_commits_path(@project).should be_denied_for :visitor }
+ subject { project_commits_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/commit" do
- it { project_commit_path(@project, @project.commit.id).should be_allowed_for @u1 }
- it { project_commit_path(@project, @project.commit.id).should be_allowed_for @u3 }
- it { project_commit_path(@project, @project.commit.id).should be_denied_for :admin }
- it { project_commit_path(@project, @project.commit.id).should be_denied_for @u2 }
- it { project_commit_path(@project, @project.commit.id).should be_denied_for :user }
- it { project_commit_path(@project, @project.commit.id).should be_denied_for :visitor }
+ subject { project_commit_path(@project, @project.commit.id) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/team" do
- it { team_project_path(@project).should be_allowed_for @u1 }
- it { team_project_path(@project).should be_allowed_for @u3 }
- it { team_project_path(@project).should be_denied_for :admin }
- it { team_project_path(@project).should be_denied_for @u2 }
- it { team_project_path(@project).should be_denied_for :user }
- it { team_project_path(@project).should be_denied_for :visitor }
+ subject { team_project_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/wall" do
- it { wall_project_path(@project).should be_allowed_for @u1 }
- it { wall_project_path(@project).should be_allowed_for @u3 }
- it { wall_project_path(@project).should be_denied_for :admin }
- it { wall_project_path(@project).should be_denied_for @u2 }
- it { wall_project_path(@project).should be_denied_for :user }
- it { wall_project_path(@project).should be_denied_for :visitor }
+ subject { wall_project_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/blob" do
before do
- @commit = @project.commit
- @path = @commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name
- @blob_path = blob_project_ref_path(@project, @commit.id, path: @path)
+ commit = @project.commit
+ path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name
+ @blob_path = blob_project_ref_path(@project, commit.id, path: path)
end
it { @blob_path.should be_allowed_for @u1 }
@@ -95,93 +107,113 @@ describe "Application access" do
end
describe "GET /project_code/edit" do
- it { edit_project_path(@project).should be_allowed_for @u1 }
- it { edit_project_path(@project).should be_denied_for @u3 }
- it { edit_project_path(@project).should be_denied_for :admin }
- it { edit_project_path(@project).should be_denied_for @u2 }
- it { edit_project_path(@project).should be_denied_for :user }
- it { edit_project_path(@project).should be_denied_for :visitor }
+ subject { edit_project_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_denied_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/deploy_keys" do
- it { project_deploy_keys_path(@project).should be_allowed_for @u1 }
- it { project_deploy_keys_path(@project).should be_denied_for @u3 }
- it { project_deploy_keys_path(@project).should be_denied_for :admin }
- it { project_deploy_keys_path(@project).should be_denied_for @u2 }
- it { project_deploy_keys_path(@project).should be_denied_for :user }
- it { project_deploy_keys_path(@project).should be_denied_for :visitor }
+ subject { project_deploy_keys_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_denied_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/issues" do
- it { project_issues_path(@project).should be_allowed_for @u1 }
- it { project_issues_path(@project).should be_allowed_for @u3 }
- it { project_issues_path(@project).should be_denied_for :admin }
- it { project_issues_path(@project).should be_denied_for @u2 }
- it { project_issues_path(@project).should be_denied_for :user }
- it { project_issues_path(@project).should be_denied_for :visitor }
+ subject { project_issues_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/snippets" do
- it { project_snippets_path(@project).should be_allowed_for @u1 }
- it { project_snippets_path(@project).should be_allowed_for @u3 }
- it { project_snippets_path(@project).should be_denied_for :admin }
- it { project_snippets_path(@project).should be_denied_for @u2 }
- it { project_snippets_path(@project).should be_denied_for :user }
- it { project_snippets_path(@project).should be_denied_for :visitor }
+ subject { project_snippets_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/merge_requests" do
- it { project_merge_requests_path(@project).should be_allowed_for @u1 }
- it { project_merge_requests_path(@project).should be_allowed_for @u3 }
- it { project_merge_requests_path(@project).should be_denied_for :admin }
- it { project_merge_requests_path(@project).should be_denied_for @u2 }
- it { project_merge_requests_path(@project).should be_denied_for :user }
- it { project_merge_requests_path(@project).should be_denied_for :visitor }
+ subject { project_merge_requests_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/repository" do
- it { project_repository_path(@project).should be_allowed_for @u1 }
- it { project_repository_path(@project).should be_allowed_for @u3 }
- it { project_repository_path(@project).should be_denied_for :admin }
- it { project_repository_path(@project).should be_denied_for @u2 }
- it { project_repository_path(@project).should be_denied_for :user }
- it { project_repository_path(@project).should be_denied_for :visitor }
+ subject { project_repository_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/repository/branches" do
- it { branches_project_repository_path(@project).should be_allowed_for @u1 }
- it { branches_project_repository_path(@project).should be_allowed_for @u3 }
- it { branches_project_repository_path(@project).should be_denied_for :admin }
- it { branches_project_repository_path(@project).should be_denied_for @u2 }
- it { branches_project_repository_path(@project).should be_denied_for :user }
- it { branches_project_repository_path(@project).should be_denied_for :visitor }
+ subject { branches_project_repository_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/repository/tags" do
- it { tags_project_repository_path(@project).should be_allowed_for @u1 }
- it { tags_project_repository_path(@project).should be_allowed_for @u3 }
- it { tags_project_repository_path(@project).should be_denied_for :admin }
- it { tags_project_repository_path(@project).should be_denied_for @u2 }
- it { tags_project_repository_path(@project).should be_denied_for :user }
- it { tags_project_repository_path(@project).should be_denied_for :visitor }
+ subject { tags_project_repository_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/hooks" do
- it { project_hooks_path(@project).should be_allowed_for @u1 }
- it { project_hooks_path(@project).should be_allowed_for @u3 }
- it { project_hooks_path(@project).should be_denied_for :admin }
- it { project_hooks_path(@project).should be_denied_for @u2 }
- it { project_hooks_path(@project).should be_denied_for :user }
- it { project_hooks_path(@project).should be_denied_for :visitor }
+ subject { project_hooks_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
describe "GET /project_code/files" do
- it { files_project_path(@project).should be_allowed_for @u1 }
- it { files_project_path(@project).should be_allowed_for @u3 }
- it { files_project_path(@project).should be_denied_for :admin }
- it { files_project_path(@project).should be_denied_for @u2 }
- it { files_project_path(@project).should be_denied_for :user }
- it { files_project_path(@project).should be_denied_for :visitor }
+ subject { files_project_path(@project) }
+
+ it { should be_allowed_for @u1 }
+ it { should be_allowed_for @u3 }
+ it { should be_denied_for :admin }
+ it { should be_denied_for @u2 }
+ it { should be_denied_for :user }
+ it { should be_denied_for :visitor }
end
end
end
diff --git a/spec/roles/issue_commonality_spec.rb b/spec/roles/issue_commonality_spec.rb
new file mode 100644
index 0000000..77b98b4
--- /dev/null
+++ b/spec/roles/issue_commonality_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Issue, "IssueCommonality" do
+ let(:issue) { create(:issue) }
+
+ describe "Associations" do
+ it { should belong_to(:project) }
+ it { should belong_to(:author) }
+ it { should belong_to(:assignee) }
+ it { should have_many(:notes).dependent(:destroy) }
+ end
+
+ describe "Validation" do
+ it { should validate_presence_of(:project_id) }
+ it { should validate_presence_of(:author_id) }
+ it { should validate_presence_of(:title) }
+ it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) }
+ end
+
+ describe "Scope" do
+ it { described_class.should respond_to(:opened) }
+ it { described_class.should respond_to(:closed) }
+ it { described_class.should respond_to(:assigned) }
+ end
+
+ it "has an :author_id_of_changes accessor" do
+ issue.should respond_to(:author_id_of_changes)
+ issue.should respond_to(:author_id_of_changes=)
+ end
+
+ describe ".search" do
+ let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
+
+ it "matches by title" do
+ described_class.search('able').all.should == [searchable_issue]
+ end
+ end
+
+ describe "#today?" do
+ it "returns true when created today" do
+ # Avoid timezone differences and just return exactly what we want
+ Date.stub(:today).and_return(issue.created_at.to_date)
+ issue.today?.should be_true
+ end
+
+ it "returns false when not created today" do
+ Date.stub(:today).and_return(Date.yesterday)
+ issue.today?.should be_false
+ end
+ end
+
+ describe "#new?" do
+ it "returns true when created today and record hasn't been updated" do
+ issue.stub(:today?).and_return(true)
+ issue.new?.should be_true
+ end
+
+ it "returns false when not created today" do
+ issue.stub(:today?).and_return(false)
+ issue.new?.should be_false
+ end
+
+ it "returns false when record has been updated" do
+ issue.stub(:today?).and_return(true)
+ issue.touch
+ issue.new?.should be_false
+ end
+ end
+end
diff --git a/spec/roles/upvote_spec.rb b/spec/roles/upvote_spec.rb
new file mode 100644
index 0000000..24288ad
--- /dev/null
+++ b/spec/roles/upvote_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Issue, "Upvote" do
+ let(:issue) { create(:issue) }
+
+ it "with no notes has a 0/0 score" do
+ issue.upvotes.should == 0
+ end
+
+ it "should recognize non-+1 notes" do
+ issue.notes << create(:note, note: "No +1 here")
+ issue.should have(1).note
+ issue.notes.first.upvote?.should be_false
+ issue.upvotes.should == 0
+ end
+
+ it "should recognize a single +1 note" do
+ issue.notes << create(:note, note: "+1 This is awesome")
+ issue.upvotes.should == 1
+ end
+
+ it "should recognize multiple +1 notes" do
+ issue.notes << create(:note, note: "+1 This is awesome")
+ issue.notes << create(:note, note: "+1 I want this")
+ issue.upvotes.should == 2
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 5c0bb61..d381b3f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,5 +1,7 @@
-require 'simplecov'
-SimpleCov.start 'rails'
+unless ENV['CI']
+ require 'simplecov'
+ SimpleCov.start 'rails'
+end
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
@@ -7,10 +9,7 @@ require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/rails'
require 'capybara/rspec'
-require 'capybara/dsl'
require 'webmock/rspec'
-require 'factories'
-require 'monkeypatch'
require 'email_spec'
require 'headless'
@@ -21,10 +20,14 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
# Use capybara-webkit
Capybara.javascript_driver = :webkit
+WebMock.disable_net_connect!(allow_localhost: true)
+
RSpec.configure do |config|
config.mock_with :rspec
- config.include LoginMacros
+ config.include LoginHelpers, type: :request
+ config.include GitoliteStub
+ config.include FactoryGirl::Syntax::Methods
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
@@ -36,35 +39,11 @@ RSpec.configure do |config|
headless.start
end
- config.before :each, type: :integration do
- DeviseSessionMock.disable
- end
-
config.before do
- if example.metadata[:js]
- DatabaseCleaner.strategy = :truncation
- Capybara::Selenium::Driver::DEFAULT_OPTIONS[:resynchronize] = true
- else
- DatabaseCleaner.strategy = :transaction
- end
-
- DatabaseCleaner.start
-
- WebMock.disable_net_connect!(allow_localhost: true)
+ stub_gitolite!
# !!! Observers disabled by default in tests
- #
- # Use next code to enable observers
- # before(:each) { ActiveRecord::Base.observers.enable(:all) }
- #
- ActiveRecord::Base.observers.disable :all
- end
-
- config.after do
- DatabaseCleaner.clean
+ ActiveRecord::Base.observers.disable(:all)
+ # ActiveRecord::Base.observers.enable(:all)
end
-
- config.include RSpec::Rails::RequestExampleGroup, type: :request, example_group: {
- file_path: /spec\/api/
- }
end
diff --git a/spec/support/api.rb b/spec/support/api.rb
deleted file mode 100644
index d363d8b..0000000
--- a/spec/support/api.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-def api_prefix
- "/api/#{Gitlab::API::VERSION}"
-end
-
-def json_response
- JSON.parse(response.body)
-end
diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb
new file mode 100644
index 0000000..7d90119
--- /dev/null
+++ b/spec/support/api_helpers.rb
@@ -0,0 +1,34 @@
+module ApiHelpers
+ # Public: Prepend a request path with the path to the API
+ #
+ # path - Path to append
+ # user - User object - If provided, automatically appends private_token query
+ # string for authenticated requests
+ #
+ # Examples
+ #
+ # >> api('/issues')
+ # => "/api/v2/issues"
+ #
+ # >> api('/issues', User.last)
+ # => "/api/v2/issues?private_token=..."
+ #
+ # >> api('/issues?foo=bar', User.last)
+ # => "/api/v2/issues?foo=bar&private_token=..."
+ #
+ # Returns the relative path to the requested API resource
+ def api(path, user = nil)
+ "/api/#{Gitlab::API::VERSION}#{path}" +
+
+ # Normalize query string
+ (path.index('?') ? '' : '?') +
+
+ # Append private_token if given a User object
+ (user.respond_to?(:private_token) ?
+ "&private_token=#{user.private_token}" : "")
+ end
+
+ def json_response
+ JSON.parse(response.body)
+ end
+end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
new file mode 100644
index 0000000..f1e072a
--- /dev/null
+++ b/spec/support/db_cleaner.rb
@@ -0,0 +1,18 @@
+require 'database_cleaner'
+
+RSpec.configure do |config|
+ config.before do
+ if example.metadata[:js]
+ DatabaseCleaner.strategy = :truncation
+ Capybara::Selenium::Driver::DEFAULT_OPTIONS[:resynchronize] = true
+ else
+ DatabaseCleaner.strategy = :transaction
+ end
+
+ DatabaseCleaner.start
+ end
+
+ config.after do
+ DatabaseCleaner.clean
+ end
+end
diff --git a/spec/support/gitolite_stub.rb b/spec/support/gitolite_stub.rb
new file mode 100644
index 0000000..2a907f9
--- /dev/null
+++ b/spec/support/gitolite_stub.rb
@@ -0,0 +1,35 @@
+module GitoliteStub
+ def stub_gitolite!
+ stub_gitlab_gitolite
+ stub_gitolite_admin
+ end
+
+ def stub_gitolite_admin
+ gitolite_repo = mock(
+ clean_permissions: true,
+ add_permission: true
+ )
+
+ gitolite_config = mock(
+ add_repo: true,
+ get_repo: gitolite_repo,
+ has_repo?: true
+ )
+
+ gitolite_admin = double(
+ 'Gitolite::GitoliteAdmin',
+ config: gitolite_config,
+ save: true,
+ )
+
+ Gitolite::GitoliteAdmin.stub(new: gitolite_admin)
+
+ end
+
+ def stub_gitlab_gitolite
+ gitlab_gitolite = Gitlab::Gitolite.new
+ Gitlab::Gitolite.stub(new: gitlab_gitolite)
+ gitlab_gitolite.stub(configure: ->() { yield(self) })
+ gitlab_gitolite.stub(update_keys: true)
+ end
+end
diff --git a/spec/support/js_patch.rb b/spec/support/js_patch.rb
deleted file mode 100644
index 0d4ab26..0000000
--- a/spec/support/js_patch.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-module JsPatch
- def confirm_js_popup
- page.evaluate_script("window.alert = function(msg) { return true; }")
- page.evaluate_script("window.confirm = function(msg) { return true; }")
- end
-end
diff --git a/spec/support/login.rb b/spec/support/login.rb
deleted file mode 100644
index 78a907b..0000000
--- a/spec/support/login.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module LoginMacros
- def login_as role
- @user = User.create(email: "user#{User.count}@mail.com",
- name: "John Smith",
- password: "123456",
- password_confirmation: "123456",
- skype: 'user_skype')
-
- if role == :admin
- @user.admin = true
- @user.save!
- end
-
- visit new_user_session_path
- fill_in "user_email", with: @user.email
- fill_in "user_password", with: "123456"
- click_button "Sign in"
- end
-
- def login_with(user)
- visit new_user_session_path
- fill_in "user_email", with: user.email
- fill_in "user_password", with: "123456"
- click_button "Sign in"
- end
-
- def logout
- click_link "Logout" rescue nil
- end
-end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
new file mode 100644
index 0000000..769034e
--- /dev/null
+++ b/spec/support/login_helpers.rb
@@ -0,0 +1,23 @@
+module LoginHelpers
+ # Internal: Create and log in as a user of the specified role
+ #
+ # role - User role (e.g., :admin, :user)
+ def login_as(role)
+ @user = Factory(role)
+ login_with(@user)
+ end
+
+ # Internal: Login as the specified user
+ #
+ # user - User instance to login with
+ def login_with(user)
+ visit new_user_session_path
+ fill_in "user_email", with: user.email
+ fill_in "user_password", with: "123456"
+ click_button "Sign in"
+ end
+
+ def logout
+ click_link "Logout" rescue nil
+ end
+end
diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb
index e067216..cb1dcba 100644
--- a/spec/support/matchers.rb
+++ b/spec/support/matchers.rb
@@ -28,6 +28,16 @@ RSpec::Matchers.define :be_404_for do |user|
end
end
+RSpec::Matchers.define :include_module do |expected|
+ match do
+ described_class.included_modules.include?(expected)
+ end
+
+ failure_message_for_should do
+ "expected #{described_class} to include the #{expected} module"
+ end
+end
+
module UrlAccess
def url_allowed?(user, url)
emulate_user(user)
@@ -57,3 +67,17 @@ module UrlAccess
login_with(user) if user
end
end
+
+# Extend shoulda-matchers
+module Shoulda::Matchers::ActiveModel
+ class EnsureLengthOfMatcher
+ # Shortcut for is_at_least and is_at_most
+ def is_within(range)
+ if range.exclude_end?
+ is_at_least(range.first) && is_at_most(range.last - 1)
+ else
+ is_at_least(range.first) && is_at_most(range.last)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples.rb b/spec/support/shared_examples.rb
deleted file mode 100644
index 9fd207d..0000000
--- a/spec/support/shared_examples.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-shared_examples_for :project_side_pane do
- subject { page }
- it { should have_content((@project || project).name) }
- it { should have_content("Commits") }
- it { should have_content("Files") }
-end
-
-shared_examples_for :tree_view do
- subject { page }
-
- it "should have Tree View of project" do
- should have_content("app")
- should have_content("History")
- should have_content("Gemfile")
- end
-end
diff --git a/spec/support/stubbed_repository.rb b/spec/support/stubbed_repository.rb
new file mode 100644
index 0000000..90491e4
--- /dev/null
+++ b/spec/support/stubbed_repository.rb
@@ -0,0 +1,31 @@
+# Stubs out all Git repository access done by models so that specs can run
+# against fake repositories without Grit complaining that they don't exist.
+module StubbedRepository
+ def path_to_repo
+ if new_record? || path == 'newproject'
+ # There are a couple Project specs and features that expect the Project's
+ # path to be in the returned path, so let's patronize them.
+ File.join(Rails.root, 'tmp', 'repositories', path)
+ else
+ # For everything else, just give it the path to one of our real seeded
+ # repos.
+ File.join(Rails.root, 'tmp', 'repositories', 'gitlabhq')
+ end
+ end
+
+ def satellite
+ FakeSatellite.new
+ end
+
+ class FakeSatellite
+ def exists?
+ true
+ end
+
+ def create
+ true
+ end
+ end
+end
+
+Project.send(:include, StubbedRepository)
--
libgit2 0.21.2