diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..9041530d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +## Contribute to GitLab + +If you want to contribute to GitLab, follow this process: + +1. Fork the project +2. Create a feature branch +3. Code +4. Create a pull request + +We only accept pull requests if: + +* Your code has proper tests and all tests pass +* Your code can be merged w/o problems +* It wont broke existing functionality +* Its a quality code +* We like it :) + +## [You may need a developer VM](https://github.com/gitlabhq/developer-vm) + +## Running tests + +To run the specs for GitLab, you need to run seeds for test db. + + cd gitlabhq + rake db:seed_fu RAILS_ENV=test + +Then you can run the test suite with rake: + + rake gitlab:test + diff --git a/Gemfile b/Gemfile index 20302954..b17d9cf3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,13 @@ source "http://rubygems.org" +def darwin_only(require_as) + RUBY_PLATFORM.include?('darwin') && require_as +end + +def linux_only(require_as) + RUBY_PLATFORM.include?('linux') && require_as +end + gem "rails", "3.2.8" # Supported DBs @@ -8,6 +16,10 @@ gem "mysql2" # Auth gem "devise", "~> 2.1.0" +gem 'omniauth' +gem 'omniauth-google-oauth2' +gem 'omniauth-twitter' +gem 'omniauth-github' # GITLAB patched libs gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837" @@ -98,21 +110,28 @@ group :development do end group :development, :test do + gem 'spinach-rails' gem "rspec-rails" gem "capybara" gem "capybara-webkit" gem "headless" - gem "autotest" - gem "autotest-rails" gem "pry" gem "awesome_print" gem "database_cleaner" gem "launchy" gem 'factory_girl_rails' + + # Guard + gem 'guard-rspec' + gem 'guard-spinach' + + # Notification + gem 'rb-fsevent', :require => darwin_only('rb-fsevent') + gem 'growl', :require => darwin_only('growl') + gem 'rb-inotify', :require => linux_only('rb-inotify') end group :test do - gem 'cucumber-rails', :require => false gem "simplecov", :require => false gem "shoulda-matchers" gem 'email_spec' diff --git a/Gemfile.lock b/Gemfile.lock index 671e8e6c..100e88d0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,7 +68,6 @@ GIT GEM remote: http://rubygems.org/ specs: - ZenTest (4.8.1) actionmailer (3.2.8) actionpack (= 3.2.8) mail (~> 2.4.4) @@ -100,10 +99,6 @@ GEM rails (~> 3.0) addressable (2.2.8) arel (3.0.2) - autotest (4.4.6) - ZenTest (>= 4.4.1) - autotest-rails (4.1.2) - ZenTest (~> 4.5) awesome_print (1.0.2) bcrypt-ruby (3.0.1) blankslate (2.1.2.4) @@ -137,16 +132,8 @@ GEM execjs coffee-script-source (1.3.3) colored (1.2) + colorize (0.5.8) crack (0.3.1) - cucumber (1.2.1) - builder (>= 2.1.2) - diff-lcs (>= 1.1.3) - gherkin (~> 2.11.0) - json (>= 1.4.6) - cucumber-rails (1.3.0) - capybara (>= 1.1.2) - cucumber (>= 1.1.8) - nokogiri (>= 1.5.0) daemons (1.1.8) database_cleaner (0.8.0) devise (2.1.2) @@ -171,12 +158,13 @@ GEM factory_girl_rails (4.0.0) factory_girl (~> 4.0.0) railties (>= 3.0.0) + faraday (0.8.4) + multipart-post (~> 1.1) ffaker (1.14.0) ffi (1.0.11) foreman (0.47.0) thor (>= 0.13.6) - gherkin (2.11.0) - json (>= 1.4.6) + gherkin-ruby (0.2.1) git (1.2.5) github-markup (0.7.4) gitlab_meta (2.9) @@ -186,6 +174,15 @@ GEM multi_xml rack rack-mount + growl (1.0.3) + guard (1.3.2) + listen (>= 0.4.2) + thor (>= 0.14.6) + guard-rspec (1.2.1) + guard (>= 1.1) + guard-spinach (0.0.2) + guard (>= 1.1) + spinach haml (3.1.6) haml-rails (0.3.4) actionpack (~> 3.0) @@ -199,6 +196,7 @@ GEM httparty (0.8.3) multi_json (~> 1.0) multi_xml + httpauth (0.1) i18n (0.6.1) journey (1.0.4) jquery-rails (2.0.2) @@ -208,6 +206,8 @@ GEM jquery-rails railties (>= 3.1.0) json (1.7.5) + jwt (0.1.5) + multi_json (>= 1.0) kaminari (0.14.0) actionpack (>= 3.0.0) activesupport (>= 3.0.0) @@ -219,6 +219,7 @@ GEM libv8 (3.3.10.4) libwebsocket (0.1.3) addressable + listen (0.5.0) mail (2.4.4) i18n (>= 0.4.0) mime-types (~> 1.16) @@ -229,12 +230,35 @@ GEM sprockets (~> 2.0) multi_json (1.3.6) multi_xml (0.5.1) + multipart-post (1.1.5) mysql2 (0.3.11) net-ldap (0.2.2) nokogiri (1.5.3) + oauth (0.4.7) + oauth2 (0.8.0) + faraday (~> 0.8) + httpauth (~> 0.1) + jwt (~> 0.1.4) + multi_json (~> 1.0) + rack (~> 1.2) omniauth (1.1.0) hashie (~> 1.2) rack + omniauth-github (1.0.3) + omniauth (~> 1.0) + omniauth-oauth2 (~> 1.1) + omniauth-google-oauth2 (0.1.13) + omniauth (~> 1.0) + omniauth-oauth2 + omniauth-oauth (1.0.1) + oauth + omniauth (~> 1.0) + omniauth-oauth2 (1.1.0) + oauth2 (~> 0.8.0) + omniauth (~> 1.0) + omniauth-twitter (0.0.13) + multi_json (~> 1.3) + omniauth-oauth (~> 1.0) orm_adapter (0.3.0) polyglot (0.3.3) posix-spawn (0.3.6) @@ -274,6 +298,9 @@ GEM raindrops (0.9.0) rake (0.9.2.2) raphael-rails (1.5.2) + rb-fsevent (0.9.1) + rb-inotify (0.8.8) + ffi (>= 0.5.0) rdoc (3.12) json (~> 1.4) redcarpet (2.1.1) @@ -336,6 +363,13 @@ GEM tilt (~> 1.3, >= 1.3.3) six (0.2.0) slop (2.4.4) + spinach (0.5.2) + colorize + gherkin-ruby (~> 0.2.0) + spinach-rails (0.1.8) + capybara (~> 1) + railties (>= 3) + spinach (>= 0.4) sprockets (2.1.3) hike (~> 1.2) rack (~> 1.0) @@ -378,8 +412,6 @@ PLATFORMS DEPENDENCIES acts-as-taggable-on (= 2.3.1) annotate! - autotest - autotest-rails awesome_print bootstrap-sass (= 2.0.4) capybara @@ -389,7 +421,6 @@ DEPENDENCIES chosen-rails coffee-rails (= 3.2.2) colored - cucumber-rails database_cleaner devise (~> 2.1.0) draper @@ -404,6 +435,9 @@ DEPENDENCIES grack! grape (~> 0.2.1) grit! + growl + guard-rspec + guard-spinach haml-rails headless httparty @@ -415,12 +449,18 @@ DEPENDENCIES linguist (~> 1.0.0)! modernizr (= 2.5.3) mysql2 + omniauth + omniauth-github + omniauth-google-oauth2 omniauth-ldap! + omniauth-twitter pry pygments.rb! rack-mini-profiler rails (= 3.2.8) raphael-rails (= 1.5.2) + rb-fsevent + rb-inotify redcarpet (~> 2.1.1) resque (~> 1.20.0) resque_mailer @@ -432,6 +472,7 @@ DEPENDENCIES shoulda-matchers simplecov six + spinach-rails sqlite3 stamp test_after_commit diff --git a/Guardfile b/Guardfile new file mode 100644 index 00000000..50a10af9 --- /dev/null +++ b/Guardfile @@ -0,0 +1,26 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } + + # Rails example + watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } + watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } + watch(%r{^spec/support/(.+)\.rb$}) { "spec" } + watch('config/routes.rb') { "spec/routing" } + watch('app/controllers/application_controller.rb') { "spec/controllers" } + + # Capybara request specs + watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" } +end + +guard 'spinach' do + watch(%r|^features/(.*)\.feature|) + watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m| + "features/#{m[1]}#{m[2]}.feature" + end +end diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index 269a7a76..76454c29 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -6,3 +6,7 @@ $ -> elems.val('').attr 'disabled', true else elems.removeAttr 'disabled' + + $('.log-tabs a').click (e) -> + e.preventDefault() + $(this).tab('show') diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 4c487ec1..f9fdb0f7 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -11,7 +11,7 @@ //= require jquery.endless-scroll //= require jquery.highlight //= require jquery.waitforimages -//= require bootstrap-modal +//= require bootstrap //= require modernizr //= require chosen-jquery //= require raphael diff --git a/app/assets/javascripts/main.js.coffee b/app/assets/javascripts/main.js.coffee index a01b3932..86b19162 100644 --- a/app/assets/javascripts/main.js.coffee +++ b/app/assets/javascripts/main.js.coffee @@ -24,6 +24,9 @@ $ -> # Click a .one_click_select field, select the contents $(".one_click_select").live 'click', -> $(this).select() + # Initialize chosen selects + $('select.chosen').chosen() + # Disable form buttons while a form is submitting $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> buttons = $('[type="submit"]', this) diff --git a/app/assets/javascripts/note.js b/app/assets/javascripts/note.js deleted file mode 100644 index 79ab086b..00000000 --- a/app/assets/javascripts/note.js +++ /dev/null @@ -1,182 +0,0 @@ -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(); }); - - $(".note-form-holder").live("ajax:before", function(){ - $(".submit_note").disable() - }) - - $(".note-form-holder").live("ajax:complete", function(){ - $(".submit_note").enable() - }) - - disableButtonIfEmptyField(".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 - */ - - initRefresh: - function() { - // init timer - var intNew = setInterval("NoteList.getNew()", 10000); - }, - - replace: - function(html) { - $("#new_notes_list").html(html); - }, - - prepend: - function(id, html) { - if(id != this.last_id) { - $("#new_notes_list").prepend(html); - } - }, - - 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", - 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 - */ - - - 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) { - 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); - } - }, - - initLoadMore: - function() { - $(document).endlessScroll({ - bottomPixels: 400, - fireDelay: 1000, - fireOnce:true, - ceaseFire: function() { - return NoteList.disable; - }, - 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; - }); - disableButtonIfEmptyField(".line-note-text", ".submit_inline_note"); - } -} diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js new file mode 100644 index 00000000..e1ad1d2f --- /dev/null +++ b/app/assets/javascripts/notes.js @@ -0,0 +1,293 @@ +var NoteList = { + + notes_path: null, + target_params: null, + target_id: 0, + target_type: null, + top_id: 0, + bottom_id: 0, + loading_more_disabled: false, + reversed: false, + + init: + function(tid, tt, path) { + this.notes_path = path + ".js"; + this.target_id = tid; + this.target_type = tt; + this.reversed = $("#notes-list").hasClass("reversed"); + this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id; + + // get initial set of notes + this.getContent(); + + $("#notes-list, #new-notes-list").on("ajax:success", ".delete-note", function() { + $(this).closest('li').fadeOut(function() { + $(this).remove(); + NoteList.updateVotes(); + }); + }); + + $(".note-form-holder").on("ajax:before", function(){ + $(".submit_note").disable(); + }) + + $(".note-form-holder").on("ajax:complete", function(){ + $(".submit_note").enable(); + }) + + disableButtonIfEmptyField(".note-text", ".submit_note"); + + $("#note_attachment").change(function(e){ + var val = $('.input-file').val(); + var filename = val.replace(/^.*[\\\/]/, ''); + $(".file_name").text(filename); + }); + + if(this.reversed) { + var textarea = $(".note-text"); + $('.note_advanced_opts').hide(); + textarea.css("height", "40px"); + textarea.on("focus", function(){ + $(this).css("height", "80px"); + $('.note_advanced_opts').show(); + }); + } + }, + + + /** + * Handle loading the initial set of notes. + * And set up loading more notes when scrolling to the bottom of the page. + */ + + + /** + * Gets an inital set of notes. + */ + getContent: + function() { + $.ajax({ + type: "GET", + url: this.notes_path, + data: "?" + this.target_params, + complete: function(){ $('.notes-status').removeClass("loading")}, + beforeSend: function() { $('.notes-status').addClass("loading") }, + dataType: "script"}); + }, + + /** + * Called in response to getContent(). + * Replaces the content of #notes-list with the given html. + */ + setContent: + function(first_id, last_id, html) { + this.top_id = first_id; + this.bottom_id = last_id; + $("#notes-list").html(html); + + // init infinite scrolling + this.initLoadMore(); + + // init getting new notes + if (this.reversed) { + this.initRefreshNew(); + } + }, + + + /** + * Handle loading more notes when scrolling to the bottom of the page. + * The id of the last note in the list is in this.bottom_id. + * + * Set up refreshing only new notes after all notes have been loaded. + */ + + + /** + * Initializes loading more notes when scrolling to the bottom of the page. + */ + initLoadMore: + function() { + $(document).endlessScroll({ + bottomPixels: 400, + fireDelay: 1000, + fireOnce:true, + ceaseFire: function() { + return NoteList.loading_more_disabled; + }, + callback: function(i) { + NoteList.getMore(); + } + }); + }, + + /** + * Gets an additional set of notes. + */ + getMore: + function() { + // only load more notes if there are no "new" notes + $('.loading').show(); + $.ajax({ + type: "GET", + url: this.notes_path, + data: "loading_more=1&" + (this.reversed ? "before_id" : "after_id") + "=" + this.bottom_id + this.target_params, + complete: function(){ $('.notes-status').removeClass("loading")}, + beforeSend: function() { $('.notes-status').addClass("loading") }, + dataType: "script"}); + }, + + /** + * Called in response to getMore(). + * Append notes to #notes-list. + */ + appendMoreNotes: + function(id, html) { + if(id != this.bottom_id) { + this.bottom_id = id; + $("#notes-list").append(html); + } + }, + + /** + * Called in response to getMore(). + * Disables loading more notes when scrolling to the bottom of the page. + * Initalizes refreshing new notes. + */ + finishedLoadingMore: + function() { + this.loading_more_disabled = true; + + // from now on only get new notes + if (!this.reversed) { + this.initRefreshNew(); + } + // make sure we are up to date + this.updateVotes(); + }, + + + /** + * Handle refreshing and adding of new notes. + * + * New notes are all notes that are created after the site has been loaded. + * The "old" notes are in #notes-list the "new" ones will be in #new-notes-list. + * The id of the last "old" note is in this.bottom_id. + */ + + + /** + * Initializes getting new notes every n seconds. + */ + initRefreshNew: + function() { + setInterval("NoteList.getNew()", 10000); + }, + + /** + * Gets the new set of notes. + */ + getNew: + function() { + $.ajax({ + type: "GET", + url: this.notes_path, + data: "loading_new=1&after_id=" + (this.reversed ? this.top_id : this.bottom_id) + this.target_params, + dataType: "script"}); + }, + + /** + * Called in response to getNew(). + * Replaces the content of #new-notes-list with the given html. + */ + replaceNewNotes: + function(html) { + $("#new-notes-list").html(html); + this.updateVotes(); + }, + + /** + * Adds a single note to #new-notes-list. + */ + appendNewNote: + function(id, html) { + if (this.reversed) { + $("#new-notes-list").prepend(html); + } else { + $("#new-notes-list").append(html); + } + this.updateVotes(); + }, + + /** + * Recalculates the votes and updates them (if they are displayed at all). + * + * Assumes all relevant notes are displayed (i.e. there are no more notes to + * load via getMore()). + * Might produce inaccurate results when not all notes have been loaded and a + * recalculation is triggered (e.g. when deleting a note). + */ + updateVotes: + function() { + var votes = $("#votes .votes"); + var notes = $("#notes-list, #new-notes-list").find(".note.vote"); + + // only update if there is a vote display + if (votes.size()) { + var upvotes = notes.filter(".upvote").size(); + var downvotes = notes.filter(".downvote").size(); + var votesCount = upvotes + downvotes; + var upvotesPercent = votesCount ? (100.0 / votesCount * upvotes) : 0; + var downvotesPercent = votesCount ? (100.0 - upvotesPercent) : 0; + + // change vote bar lengths + votes.find(".bar-success").css("width", upvotesPercent+"%"); + votes.find(".bar-danger").css("width", downvotesPercent+"%"); + // replace vote numbers + votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes)); + votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes)); + } + } +}; + +var PerLineNotes = { + init: + function() { + /** + * Called when clicking on the "add note" or "reply" button for a diff line. + * + * Shows the note form below the line. + * Sets some hidden fields in the form. + */ + $(".diff_file_content").on("click", ".line_note_link, .line_note_reply_link", function(e) { + var form = $(".per_line_form"); + $(this).closest("tr").after(form); + form.find("#note_line_code").val($(this).data("lineCode")); + form.show(); + return false; + }); + + disableButtonIfEmptyField(".line-note-text", ".submit_inline_note"); + + /** + * Called in response to successfully deleting a note on a diff line. + * + * Removes the actual note from view. + * Removes the reply button if the last note for that line has been removed. + */ + $(".diff_file_content").on("ajax:success", ".delete-note", function() { + var trNote = $(this).closest("tr"); + trNote.fadeOut(function() { + $(this).remove(); + }); + + // check if this is the last note for this line + // elements must really be removed for this to work reliably + var trLine = trNote.prev(); + var trRpl = trNote.next(); + if (trLine.hasClass("line_holder") && trRpl.hasClass("reply")) { + trRpl.fadeOut(function() { $(this).remove(); }); + } + }); + } +} diff --git a/app/assets/javascripts/projects.js.coffee b/app/assets/javascripts/projects.js.coffee index 14738e14..008fa8e9 100644 --- a/app/assets/javascripts/projects.js.coffee +++ b/app/assets/javascripts/projects.js.coffee @@ -10,11 +10,15 @@ window.Projects = -> $('form #project_default_branch').chosen() disableButtonIfEmptyField '#project_name', '.project-submit' -# Git clone panel switcher $ -> + # Git clone panel switcher scope = $ '.project_clone_holder' if scope.length > 0 $('a, button', scope).click -> $('a, button', scope).removeClass 'active' $(@).addClass 'active' $('#project_clone', scope).val $(@).data 'clone' + + # Ref switcher + $('.project-refs-select').on 'change', -> + $(@).parents('form').submit() diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 012aad03..c5b37916 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -145,6 +145,19 @@ span.update-author { .label { background-color: #474D57; + &.label-tag { + background: none; + border: none; + padding:4px 6px; + color:#444; + text-shadow:0 0 1px #fff; + + &.grouped { + float: left; + margin-right: 6px; + padding: 6px; + } + } &.label-issue { background-color: #eee; border: 1px solid #ccc; @@ -158,6 +171,18 @@ span.update-author { padding: 6px; } } + + &.label-success { + background-color: #8D8; + color: #333; + text-shadow: 0 1px 1px white; + } + + &.label-error { + background-color: #D88; + color: #333; + text-shadow: 0 1px 1px white; + } } .event_label { @@ -181,11 +206,12 @@ span.update-author { } &.joined { - background-color: #1cb9ff; + background-color: #1ca9dd; } &.left { - background-color: #ff5057; + background-color: #888; + float:none; } } @@ -414,13 +440,48 @@ p.time { } } -.upvotes { - font-size: 14px; - font-weight: bold; - color: #468847; - text-align: right; - padding: 4px; - margin: 2px; +.votes { + font-size: 13px; + line-height: 15px; + .progress { + height: 4px; + margin: 0; + .bar { + float: left; + height: 100%; + } + .bar-success { + background-color: #468847; + @include bg-gradient(#62C462, #51A351); + } + .bar-danger { + background-color: #B94A48; + @include bg-gradient(#EE5F5B, #BD362F); + } + } + .upvotes { + display: inline-block; + color: #468847; + } + .downvotes { + display: inline-block; + color: #B94A48; + } +} +.votes-block { + margin: 14px 6px 6px 0; + .downvotes { + float: right; + } +} +.votes-inline { + display: inline-block; + margin: 0 8px; + .progress { + display: inline-block; + padding: 0 0 2px; + width: 45px; + } } /* Fix for readme code (stopped it from being yellow) */ @@ -624,7 +685,7 @@ li.note { margin-right:40px; .prev { - @extend .borders; + @extend .thumbnail; height:120px; width:175px; margin-bottom:10px; @@ -653,3 +714,31 @@ li.note { text-align:center; margin-bottom:10px; } + +.oauth_select_holder { + padding:20px; + img { + padding:5px; + margin-right:10px; + } + .active { + img { + border:1px solid #ccc; + background:$hover; + @include border-radius(5px); + } + } +} + +.btn-build-token { + float: left; + padding: 6px 20px; + margin-right: 12px; +} + +.gitlab-promo { + a { + color:#aaa; + margin-right: 30px; + } +} diff --git a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss index 70f7889f..ae66bd20 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/blocks.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/blocks.scss @@ -65,6 +65,10 @@ border-color: #CCC; @include solid_shade; + &.white { + background:#fff; + } + ul { margin:0; } @@ -142,4 +146,8 @@ border:none; } } + + .ui-box-body { + padding:10px; + } } diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss index cd7145c9..b9459ee6 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/common.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -33,7 +33,29 @@ .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; } +.nav.nav-tabs { + li { + > a { + padding:8px 20px; + margin-right: 7px; + border-color: #EEE; + color:#888; + border-bottom: 1px solid #ddd; + .badge { + background-color: #eee; + color:#888; + text-shadow:0 1px 1px #fff; + } + } + &.active { + > a { + border-color: #CCC; + border-bottom: 1px solid #fff; + color:#333; + } + } + } +} /** ALERT MESSAGES **/ .alert-message { @extend .alert; } @@ -50,3 +72,13 @@ 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; } + +/** FORMS **/ +input[type='search'].search-text-input { + background-image: url("icon-search.png"); + background-repeat: no-repeat; + background-position: 10px; + padding-left:25px; + @include border-radius(4px); + border:1px solid #ccc; +} diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index 80446a4c..75001d3a 100644 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -135,7 +135,6 @@ $hover: #fdf5d9; */ @import "common.scss"; - /** * Styles related to specific part of app */ @@ -161,6 +160,11 @@ $hover: #fdf5d9; */ @import "sections/notes.scss"; +/** + * This file represent profile styles + */ +@import "sections/profile.scss"; + /** * Devise styles */ diff --git a/app/assets/stylesheets/ref_select.scss b/app/assets/stylesheets/ref_select.scss index 5b52e11b..ed6760f1 100644 --- a/app/assets/stylesheets/ref_select.scss +++ b/app/assets/stylesheets/ref_select.scss @@ -12,35 +12,45 @@ width:120px; } -.project-refs-form .chzn-container { +.project-refs-form .chzn-container { position: relative; top: 0; left: 0; margin-right: 10px; - .chzn-drop { + .chzn-drop { margin:7px 0; - border: 1px solid #CCC; - min-width: 300px; + min-width: 400px; + border: 2px solid $blue_link; + @include border-radius(4px); - .chzn-results { + .chzn-results { max-height:300px; + + .group-result { + color: $blue_link; + } + .active-result { + &.highlighted { + background: $blue_link; + } + } } .chzn-search input { - min-width:200px; + min-width:365px; } } - .chzn-single { + .chzn-single { @include bg-gray-gradient; - div { + div { background:transparent; border-left:none; } - span { + span { font-weight: normal; } } diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss index 230a7aea..10def510 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/sections/issues.scss @@ -1,55 +1,55 @@ -.issue_form_box { +.issue_form_box { @extend .main_box; - .issue_title { + .issue_title { @extend .top_box_content; - .clearfix { - margin-bottom:0px; - input { + .clearfix { + margin-bottom:0px; + input { @extend .span8; } } } - .issue_middle_block { + .issue_middle_block { @extend .middle_box_content; height:30px; - .issue_assignee { + .issue_assignee { @extend .span6; float:left; } - .issue_milestone { + .issue_milestone { @extend .span4; float:left; } } - .issue_description { + .issue_description { @extend .bottom_box_content; } } -.issues_table { - .issue { +.issues_table { + .issue { padding:7px 10px; - .issue_check { + .issue_check { float:left; padding: 8px 0; padding-right: 8px; min-width: 15px; } - p { + p { padding-top:0; padding-bottom:2px; } - img.avatar { + img.avatar { width:32px; margin-top:4px; } } } -input.check_all_issues { +input.check_all_issues { float:left; padding: 0; margin:0; @@ -59,8 +59,8 @@ input.check_all_issues { height: 22px; } -.issues_content { - .title { +.issues_content { + .title { height: 40px; } } @@ -70,30 +70,30 @@ input.check_all_issues { @media (min-width: 1200px) { .issues_filters select { width:220px; } } -#issues-table-holder { - .issues_filters { - form { +#issues-table-holder { + .issues_filters { + form { padding:0; margin:0; margin-top:7px } - } + } - .issues_bulk_update { + .issues_bulk_update { margin: 0; - form { + form { padding:0; margin:0; margin-top:7px } - .update_selected_issues { + .update_selected_issues { position:relative; top:-2px; margin-left:4px; float:left; } - - .update_issues_text { + + .update_issues_text { padding:3px; line-height: 18px; float:left; @@ -101,10 +101,11 @@ input.check_all_issues { } } -#update_status { +#update_status { width:100px; } + /** * Milestones list * diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index 73171915..c932f0fc 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -1,13 +1,13 @@ -/** +/** * MR form * */ -.mr_branch_box { +.mr_branch_box { @extend .ui-box; margin-bottom:20px; - .body { + .body { background:#f1f1f1; } @@ -17,19 +17,19 @@ * MR -> show: Automerge widget * */ -.automerge_widget { - &.can_be_merged { +.automerge_widget { + &.can_be_merged { background: #DFF0D8; } - form { + form { margin-bottom:0; - .clearfix { + .clearfix { margin-bottom:0; } } - .accept_group { + .accept_group { float:left; border: 1px solid #ADA; padding: 2px; @@ -37,29 +37,29 @@ border-radius: 5px; background: #CEB; - .accept_merge_request { + .accept_merge_request { font-size:13px; float:left; } - .remove_branch_holder { + .remove_branch_holder { margin-left:20px; margin-right:10px; float:left; } - label { + label { color:#444; } } - .how_to_merge_link { + .how_to_merge_link { @extend .primary; } } -.mr_nav_tabs { - li { - a { +.mr_nav_tabs { + li { + a { font-weight:bold; padding:8px 20px; text-align:center; @@ -67,19 +67,19 @@ } } -li.merge_request { +li.merge_request { padding:7px 10px; - img.avatar { + img.avatar { width: 32px; margin-top: 4px; } - p { + p { padding: 0px; padding-bottom: 2px; } } -.merge_in_progress { +.merge_in_progress { @extend .padded; @extend .append-bottom-10; } @@ -88,22 +88,21 @@ li.merge_request { @include round-borders-all(4px); padding:2px 4px; border:none; - font-size:13px; + font-size:14px; background: #474D57; color:#fff; - font-weight:bold; - font-family: monospace; + font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; } -.mr_source_commit, -.mr_target_commit { - .commit { +.mr_source_commit, +.mr_target_commit { + .commit { margin:0; padding:0; padding: 5px; margin-bottom: 5px; .avatar { position:relative } - .row_title { + .row_title { color:#444; } .commit-author-name, @@ -113,12 +112,12 @@ li.merge_request { display:none; } list-style:none; - &:hover { + &:hover { background:none; } } } -.mr_direction_tip { +.mr_direction_tip { margin-top:40px } diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index 6f6a0e8e..2d902918 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -55,7 +55,6 @@ ul.main_menu { &.current { background-color:#D5D5D5; - border-bottom: 1px solid #AAA; border-right: 1px solid #BBB; border-left: 1px solid #BBB; border-radius: 0 0 1px 1px; diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 6a965fa4..267a9b43 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -3,17 +3,13 @@ * */ #notes-list, -#new_notes_list { +#new-notes-list { display:block; list-style:none; margin:0px; padding:0px; } -#new_notes_list li:last-child{ - border-bottom:1px solid #aaa; -} - .issue_notes, .wiki_notes { .note_content { @@ -30,9 +26,6 @@ } #new_note { - .note-text { - height:40px; - } .attach_holder { display:none; } @@ -48,7 +41,6 @@ .note { padding: 8px 0; - border-bottom: 1px solid #eee; overflow: hidden; display: block; img {float: left; margin-right: 10px;} @@ -70,6 +62,23 @@ .delete-note { display:block; } } } +#notes-list:not(.reversed) .note, +#new-notes-list:not(.reversed) .note { + border-bottom: 1px solid #eee; +} +#notes-list.reversed .note, +#new-notes-list.reversed .note { + border-top: 1px solid #eee; +} + +/* mark vote notes */ +.voting_notes .note { + padding: 8px 0; +} + +.notes-status { + margin: 18px; +} p.notify_controls input{ @@ -213,7 +222,7 @@ td .line_note_link { } } -.note-text { +.note-text { border: 1px solid #aaa; box-shadow:none; } diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss new file mode 100644 index 00000000..206da3a9 --- /dev/null +++ b/app/assets/stylesheets/sections/profile.scss @@ -0,0 +1,8 @@ +.profile_history { + .event_feed { + min-height:20px; + .avatar { + width:20px; + } + } +} diff --git a/app/contexts/notes/load_context.rb b/app/contexts/notes/load_context.rb index c89a7d19..f92a7801 100644 --- a/app/contexts/notes/load_context.rb +++ b/app/contexts/notes/load_context.rb @@ -3,30 +3,31 @@ module Notes def execute target_type = params[:target_type] target_id = params[:target_id] - first_id = params[:first_id] - last_id = params[:last_id] + after_id = params[:after_id] + before_id = params[:before_id] @notes = case target_type - when "commit" - then project.commit_notes(project.commit(target_id)).fresh.limit(20) - when "snippet" - then project.snippets.find(target_id).notes - when "wall" - then project.common_notes.order("created_at DESC").fresh.limit(50) + when "commit" + project.commit_notes(project.commit(target_id)).fresh.limit(20) when "issue" - then project.issues.find(target_id).notes.inc_author.order("created_at DESC").limit(20) + project.issues.find(target_id).notes.inc_author.fresh.limit(20) when "merge_request" - then project.merge_requests.find(target_id).notes.inc_author.order("created_at DESC").limit(20) + project.merge_requests.find(target_id).notes.inc_author.fresh.limit(20) + when "snippet" + project.snippets.find(target_id).notes.fresh + when "wall" + # this is the only case, where the order is DESC + project.common_notes.order("created_at DESC, id DESC").limit(50) when "wiki" - then project.wikis.reverse.map {|w| w.notes.fresh }.flatten[0..20] + project.wiki_notes.limit(20) end - @notes = if last_id - @notes.where("id > ?", last_id) - elsif first_id - @notes.where("id < ?", first_id) - else + @notes = if after_id + @notes.where("id > ?", after_id) + elsif before_id + @notes.where("id < ?", before_id) + else @notes end end diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index ad80f4d5..5152f6fa 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -1,8 +1,4 @@ -class Admin::DashboardController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! - +class Admin::DashboardController < AdminController def index @workers = Resque.workers @pending_jobs = Resque.size(:post_receive) diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index 7f832fd5..91a1d633 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -1,8 +1,4 @@ -class Admin::HooksController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! - +class Admin::HooksController < AdminController def index @hooks = SystemHook.all @hook = SystemHook.new @@ -15,7 +11,7 @@ class Admin::HooksController < ApplicationController redirect_to admin_hooks_path, notice: 'Hook was successfully created.' else @hooks = SystemHook.all - render :index + render :index end end diff --git a/app/controllers/admin/logs_controller.rb b/app/controllers/admin/logs_controller.rb index c130b4b8..28c321a9 100644 --- a/app/controllers/admin/logs_controller.rb +++ b/app/controllers/admin/logs_controller.rb @@ -1,6 +1,2 @@ -class Admin::LogsController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! +class Admin::LogsController < AdminController end - diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 80d11f03..24406525 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -1,7 +1,4 @@ -class Admin::ProjectsController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! +class Admin::ProjectsController < AdminController before_filter :admin_project, only: [:edit, :show, :update, :destroy, :team_update] def index @@ -43,7 +40,7 @@ class Admin::ProjectsController < ApplicationController def update owner_id = params[:project].delete(:owner_id) - if owner_id + if owner_id @admin_project.owner = User.find(owner_id) end @@ -60,7 +57,7 @@ class Admin::ProjectsController < ApplicationController redirect_to admin_projects_url, notice: 'Project was successfully deleted.' end - private + private def admin_project @admin_project = Project.find_by_code(params[:id]) diff --git a/app/controllers/admin/resque_controller.rb b/app/controllers/admin/resque_controller.rb index dc575cc2..9d8e7e30 100644 --- a/app/controllers/admin/resque_controller.rb +++ b/app/controllers/admin/resque_controller.rb @@ -1,5 +1,4 @@ -class Admin::ResqueController < ApplicationController - layout 'admin' +class Admin::ResqueController < AdminController def show end -end \ No newline at end of file +end diff --git a/app/controllers/admin/team_members_controller.rb b/app/controllers/admin/team_members_controller.rb index 57803b01..07320805 100644 --- a/app/controllers/admin/team_members_controller.rb +++ b/app/controllers/admin/team_members_controller.rb @@ -1,8 +1,4 @@ -class Admin::TeamMembersController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! - +class Admin::TeamMembersController < AdminController def edit @admin_team_member = UsersProject.find(params[:id]) end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 1e8f420b..e2d61864 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,8 +1,4 @@ -class Admin::UsersController < ApplicationController - layout "admin" - before_filter :authenticate_user! - before_filter :authenticate_admin! - +class Admin::UsersController < AdminController def index @admin_users = User.scoped @admin_users = @admin_users.filter(params[:filter]) @@ -24,7 +20,7 @@ class Admin::UsersController < ApplicationController @admin_user = User.find(params[:id]) UsersProject.user_bulk_import( - @admin_user, + @admin_user, params[:project_ids], params[:project_access] ) @@ -41,22 +37,22 @@ class Admin::UsersController < ApplicationController @admin_user = User.find(params[:id]) end - def block + def block @admin_user = User.find(params[:id]) if @admin_user.block redirect_to :back, alert: "Successfully blocked" - else + else redirect_to :back, alert: "Error occured. User was not blocked" end end - def unblock + def unblock @admin_user = User.find(params[:id]) if @admin_user.update_attribute(:blocked, false) redirect_to :back, alert: "Successfully unblocked" - else + else redirect_to :back, alert: "Error occured. User was not unblocked" end end diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb new file mode 100644 index 00000000..bce9f692 --- /dev/null +++ b/app/controllers/admin_controller.rb @@ -0,0 +1,11 @@ +# Provides a base class for Admin controllers to subclass +# +# Automatically sets the layout and ensures an administrator is logged in +class AdminController < ApplicationController + layout 'admin' + before_filter :authenticate_admin! + + def authenticate_admin! + return render_404 unless current_user.is_admin? + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a0040298..5ac5c639 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -84,10 +84,6 @@ class ApplicationController < ActionController::Base abilities << Ability end - def authenticate_admin! - return render_404 unless current_user.is_admin? - end - def authorize_project!(action) return access_denied! unless can?(current_user, action, project) end diff --git a/app/controllers/commits_controller.rb b/app/controllers/commits_controller.rb index 4ab40c30..6d3f1aea 100644 --- a/app/controllers/commits_controller.rb +++ b/app/controllers/commits_controller.rb @@ -64,7 +64,7 @@ class CommitsController < ApplicationController @commit.to_patch, type: "text/plain", disposition: 'attachment', - filename: "#{@commit.id.patch}" + filename: "#{@commit.id}.patch" ) end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 3d305238..1d78a6d9 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -17,7 +17,7 @@ class IssuesController < ApplicationController before_filter :authorize_write_issue!, only: [:new, :create] # Allow modify issue - before_filter :authorize_modify_issue!, only: [:close, :edit, :update] + before_filter :authorize_modify_issue!, only: [:edit, :update] # Allow destroy issue before_filter :authorize_admin_issue!, only: [:destroy] @@ -87,8 +87,6 @@ class IssuesController < ApplicationController end def destroy - return access_denied! unless can?(current_user, :admin_issue, @issue) - @issue.destroy respond_to do |format| diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index d472936b..2fb783b2 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,4 +1,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController + Gitlab.config.omniauth_providers.each do |provider| + define_method provider['name'] do + handle_omniauth + end + end # Extend the standard message generation to accept our custom exception def failure_message @@ -9,7 +14,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController error ||= env["omniauth.error.type"].to_s error.to_s.humanize if error end - + def ldap # We only find ourselves here if the authentication to LDAP was successful. @user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user) @@ -19,4 +24,27 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController sign_in_and_redirect @user end + private + + def handle_omniauth + oauth = request.env['omniauth.auth'] + provider, uid = oauth['provider'], oauth['uid'] + + if current_user + # Change a logged-in user's authentication method: + current_user.extern_uid = uid + current_user.provider = provider + current_user.save + redirect_to profile_path + else + @user = User.find_or_new_for_omniauth(oauth) + + if @user + sign_in_and_redirect @user + else + flash[:notice] = "There's no such user!" + redirect_to new_user_session_path + end + end + end end diff --git a/app/controllers/profile_controller.rb b/app/controllers/profile_controller.rb index a95a3310..7ddbfe11 100644 --- a/app/controllers/profile_controller.rb +++ b/app/controllers/profile_controller.rb @@ -16,9 +16,6 @@ class ProfileController < ApplicationController def token end - def password - end - def password_update params[:user].reject!{ |k, v| k != "password" && k != "password_confirmation"} @@ -32,10 +29,14 @@ class ProfileController < ApplicationController def reset_private_token current_user.reset_authentication_token! - redirect_to profile_token_path + redirect_to profile_account_path end - private + def history + @events = current_user.recent_events.page(params[:page]).per(20) + end + + private def user @user = current_user diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb index 606cb972..a50dcd3e 100644 --- a/app/controllers/team_members_controller.rb +++ b/app/controllers/team_members_controller.rb @@ -5,7 +5,10 @@ class TeamMembersController < ApplicationController # Authorize before_filter :add_project_abilities before_filter :authorize_read_project! - before_filter :authorize_admin_project!, except: [:show] + before_filter :authorize_admin_project!, except: [:index, :show] + + def index + end def show @team_member = project.users_projects.find(params[:id]) @@ -22,7 +25,7 @@ class TeamMembersController < ApplicationController params[:project_access] ) - redirect_to team_project_path(@project) + redirect_to project_team_index_path(@project) end def update @@ -32,7 +35,7 @@ class TeamMembersController < ApplicationController unless @team_member.valid? flash[:alert] = "User should have at least one role" end - redirect_to team_project_path(@project) + redirect_to project_team_index_path(@project) end def destroy @@ -40,7 +43,7 @@ class TeamMembersController < ApplicationController @team_member.destroy respond_to do |format| - format.html { redirect_to team_project_path(@project) } + format.html { redirect_to project_team_index_path(@project) } format.js { render nothing: true } end end diff --git a/app/decorators/commit_decorator.rb b/app/decorators/commit_decorator.rb index f813ed25..c85f7400 100644 --- a/app/decorators/commit_decorator.rb +++ b/app/decorators/commit_decorator.rb @@ -16,7 +16,7 @@ class CommitDecorator < ApplicationDecorator # In case this first line is longer than 80 characters, it is cut off # after 70 characters and ellipses (`&hellp;`) are appended. def title - return no_commit_message unless safe_message + return no_commit_message if safe_message.blank? title_end = safe_message.index(/\n/) if (!title_end && safe_message.length > 80) || (title_end && title_end > 80) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3dafb753..0938dc23 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -62,7 +62,7 @@ module ApplicationHelper { label: "#{@project.name} / Wall", url: wall_project_path(@project) }, { label: "#{@project.name} / Tree", url: tree_project_ref_path(@project, @project.root_ref) }, { label: "#{@project.name} / Commits", url: project_commits_path(@project) }, - { label: "#{@project.name} / Team", url: team_project_path(@project) } + { label: "#{@project.name} / Team", url: project_team_index_path(@project) } ] end @@ -104,7 +104,8 @@ module ApplicationHelper # Profile Area when :profile; current_page?(controller: "profile", action: :show) - when :password; current_page?(controller: "profile", action: :password) + when :history; current_page?(controller: "profile", action: :history) + when :account; current_page?(controller: "profile", action: :account) when :token; current_page?(controller: "profile", action: :token) when :design; current_page?(controller: "profile", action: :design) when :ssh_keys; controller.controller_name == "keys" @@ -135,4 +136,10 @@ module ApplicationHelper "Never" end end + + def authbutton(provider, size = 64) + file_name = "#{provider.to_s.split('_').first}_#{size}.png" + image_tag("authbuttons/#{file_name}", + alt: "Sign in with #{provider.to_s.titleize}") + end end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index e97e46f5..111982e9 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -11,7 +11,9 @@ module GitlabMarkdownHelper # 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) + return "" if body.blank? + + gfm_body = gfm(escape_once(body), html_options) gfm_body.gsub!(%r{.*?}m) do |match| "#{match}#{link_to("", url, html_options)[0..-5]}" # "".length +1 diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb new file mode 100644 index 00000000..65389e38 --- /dev/null +++ b/app/helpers/notes_helper.rb @@ -0,0 +1,17 @@ +module NotesHelper + def loading_more_notes? + params[:loading_more].present? + end + + def loading_new_notes? + params[:loading_new].present? + end + + def note_vote_class(note) + if note.upvote? + "vote upvote" + elsif note.downvote? + "vote downvote" + end + end +end diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb new file mode 100644 index 00000000..80d67009 --- /dev/null +++ b/app/helpers/profile_helper.rb @@ -0,0 +1,7 @@ +module ProfileHelper + def oauth_active_class provider + if current_user.provider == provider.to_s + 'active' + end + end +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 34dbb06c..c7dc54ee 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -2,5 +2,9 @@ module ProjectsHelper def grouper_project_members(project) @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access) end + + def remove_from_team_message(project, member) + "You are going to remove #{member.user_name} from #{project.name}. Are you sure?" + end end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 1740864b..b5d7ccb7 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -8,7 +8,7 @@ module TabHelper end def project_tab_class - [:show, :files, :team, :edit, :update].each do |action| + [:show, :files, :edit, :update].each do |action| return "current" if current_page?(controller: "projects", action: action, id: @project) end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index c51ee84a..a5d5c742 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -18,7 +18,8 @@ module TreeHelper end def tree_full_path(content) - if params[:path] + content.name.force_encoding('utf-8') + if params[:path] File.join(params[:path], content.name) else content.name diff --git a/app/models/event.rb b/app/models/event.rb index 308ffd63..b11b21bd 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -35,13 +35,21 @@ class Event < ActiveRecord::Base end # Next events currently enabled for system - # - push + # - push # - new issue # - merge request def allowed? push? || issue? || merge_request? || membership_changed? end + def project_name + if project + project.name + else + "(deleted)" + end + end + def push? action == self.class::Pushed && valid_push? end @@ -58,31 +66,31 @@ class Event < ActiveRecord::Base action == self.class::Reopened end - def issue? + def issue? target_type == "Issue" end - def merge_request? + def merge_request? target_type == "MergeRequest" end - def new_issue? - target_type == "Issue" && + def new_issue? + target_type == "Issue" && action == Created end - def new_merge_request? - target_type == "MergeRequest" && + def new_merge_request? + target_type == "MergeRequest" && action == Created end - def changed_merge_request? - target_type == "MergeRequest" && + def changed_merge_request? + target_type == "MergeRequest" && [Closed, Reopened].include?(action) end - def changed_issue? - target_type == "Issue" && + def changed_issue? + target_type == "Issue" && [Closed, Reopened].include?(action) end @@ -98,7 +106,7 @@ class Event < ActiveRecord::Base joined? || left? end - def issue + def issue target if target_type == "Issue" end @@ -106,7 +114,7 @@ class Event < ActiveRecord::Base target if target_type == "MergeRequest" end - def author + def author @author ||= User.find(author_id) end @@ -119,7 +127,7 @@ class Event < ActiveRecord::Base 'joined' elsif left? 'left' - else + else "opened" end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 6409eeba..96a54907 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -1,6 +1,6 @@ class Issue < ActiveRecord::Base include IssueCommonality - include Upvote + include Votes acts_as_taggable_on :labels diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 2e457f72..184ac5fc 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -2,7 +2,7 @@ require File.join(Rails.root, "app/models/commit") class MergeRequest < ActiveRecord::Base include IssueCommonality - include Upvote + include Votes BROKEN_DIFF = "--broken-diff" diff --git a/app/models/note.rb b/app/models/note.rb index d8494edd..34edb94e 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -36,7 +36,7 @@ class Note < ActiveRecord::Base scope :today, where("created_at >= :date", date: Date.today) scope :last_week, where("created_at >= :date", date: (Date.today - 7.days)) scope :since, lambda { |day| where("created_at >= :date", date: (day)) } - scope :fresh, order("created_at DESC") + scope :fresh, order("created_at ASC, id ASC") scope :inc_author_project, includes(:project, :author) scope :inc_author, includes(:author) @@ -105,6 +105,12 @@ class Note < ActiveRecord::Base def upvote? note.start_with?('+1') || note.start_with?(':+1:') end + + # Returns true if this is a downvote note, + # otherwise false is returned + def downvote? + note.start_with?('-1') || note.start_with?(':-1:') + end end # == Schema Information # diff --git a/app/models/project.rb b/app/models/project.rb index 4de836c7..56d5d791 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -171,6 +171,10 @@ class Project < ActiveRecord::Base end end + def wiki_notes + Note.where(noteable_id: wikis.map(&:id), noteable_type: 'Wiki', project_id: self.id) + end + def project_id self.id end diff --git a/app/models/tree.rb b/app/models/tree.rb index bc95d335..d65e50ab 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -16,7 +16,7 @@ class Tree def initialize(raw_tree, project, ref = nil, path = nil) @project, @ref, @path = project, ref, path, @tree = if path - raw_tree / path + raw_tree / path.dup.force_encoding('ascii-8bit') else raw_tree end diff --git a/app/models/user.rb b/app/models/user.rb index ad6af6a6..47876722 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -86,33 +86,20 @@ class User < ActiveRecord::Base where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') end - def self.find_for_ldap_auth(auth, signed_in_resource=nil) - uid = auth.info.uid - provider = auth.provider - name = auth.info.name.force_encoding("utf-8") - email = auth.info.email.downcase unless auth.info.email.nil? - raise OmniAuth::Error, "LDAP accounts must provide an uid and email address" if uid.nil? or email.nil? + def self.create_from_omniauth(auth, ldap = false) + gitlab_auth.create_from_omniauth(auth, ldap) + end - if @user = User.find_by_extern_uid_and_provider(uid, provider) - @user - # workaround for backward compatibility - elsif @user = User.find_by_email(email) - logger.info "Updating legacy LDAP user #{email} with extern_uid => #{uid}" - @user.update_attributes(:extern_uid => uid, :provider => provider) - @user - else - logger.info "Creating user from LDAP login {uid => #{uid}, name => #{name}, email => #{email}}" - password = Devise.friendly_token[0, 8].downcase - @user = User.create( - :extern_uid => uid, - :provider => provider, - :name => name, - :email => email, - :password => password, - :password_confirmation => password, - :projects_limit => Gitlab.config.default_projects_limit - ) - end + def self.find_or_new_for_omniauth(auth) + gitlab_auth.find_or_new_for_omniauth(auth) + end + + def self.find_for_ldap_auth(auth, signed_in_resource = nil) + gitlab_auth.find_for_ldap_auth(auth, signed_in_resource) + end + + def self.gitlab_auth + Gitlab::Auth.new end def self.search query @@ -148,4 +135,3 @@ end # bio :string(255) # blocked :boolean(1) default(FALSE), not null # - diff --git a/app/models/wiki.rb b/app/models/wiki.rb index 3c4952cd..ebb1ff49 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -28,7 +28,6 @@ class Wiki < ActiveRecord::Base end new_wiki end - end end # == Schema Information diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb index 135959ab..03a61709 100644 --- a/app/observers/project_observer.rb +++ b/app/observers/project_observer.rb @@ -4,6 +4,18 @@ class ProjectObserver < ActiveRecord::Observer end def after_destroy(project) + log_info("Project \"#{project.name}\" was removed") + project.destroy_repository end + + def after_create project + log_info("#{project.owner.name} created a new project \"#{project.name}\"") + end + + protected + + def log_info message + Gitlab::AppLogger.info message + end end diff --git a/app/observers/user_observer.rb b/app/observers/user_observer.rb index d12bcc99..654621f7 100644 --- a/app/observers/user_observer.rb +++ b/app/observers/user_observer.rb @@ -1,5 +1,17 @@ class UserObserver < ActiveRecord::Observer def after_create(user) + log_info("User \"#{user.name}\" (#{user.email}) was created") + Notify.new_user_email(user.id, user.password).deliver end + + def after_destroy user + log_info("User \"#{user.name}\" (#{user.email}) was removed") + end + + protected + + def log_info message + Gitlab::AppLogger.info message + end end diff --git a/app/observers/users_project_observer.rb b/app/observers/users_project_observer.rb index 1df33237..0c9c2b26 100644 --- a/app/observers/users_project_observer.rb +++ b/app/observers/users_project_observer.rb @@ -14,8 +14,8 @@ class UsersProjectObserver < ActiveRecord::Observer def after_destroy(users_project) Event.create( - project_id: users_project.project.id, - action: Event::Left, + project_id: users_project.project.id, + action: Event::Left, author_id: users_project.user.id ) end diff --git a/app/roles/upvote.rb b/app/roles/upvote.rb deleted file mode 100644 index 7efa6f20..00000000 --- a/app/roles/upvote.rb +++ /dev/null @@ -1,6 +0,0 @@ -module Upvote - # Return the number of +1 comments (upvotes) - def upvotes - notes.select(&:upvote?).size - end -end diff --git a/app/roles/votes.rb b/app/roles/votes.rb new file mode 100644 index 00000000..043a6feb --- /dev/null +++ b/app/roles/votes.rb @@ -0,0 +1,32 @@ +module Votes + # Return the number of +1 comments (upvotes) + def upvotes + notes.select(&:upvote?).size + end + + def upvotes_in_percent + if votes_count.zero? + 0 + else + 100.0 / votes_count * upvotes + end + end + + # Return the number of -1 comments (downvotes) + def downvotes + notes.select(&:downvote?).size + end + + def downvotes_in_percent + if votes_count.zero? + 0 + else + 100.0 - upvotes_in_percent + end + end + + # Return the total number of votes + def votes_count + upvotes + downvotes + end +end diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index 800d3bb2..0efe6db7 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,9 +1,26 @@ -.file_holder#README - .file_title - %i.icon-file - githost.log - .file_content.logs - %ol - - Gitlab::Logger.read_latest.each do |line| - %li - %p= line +%ul.nav.nav-tabs.log-tabs + %li.active + = link_to "githost.log", "#githost", 'data-toggle' => 'tab' + %li + = link_to "application.log", "#application", 'data-toggle' => 'tab' +.tab-content + .tab-pane.active#githost + .file_holder#README + .file_title + %i.icon-file + githost.log + .file_content.logs + %ol + - Gitlab::GitLogger.read_latest.each do |line| + %li + %p= line + .tab-pane#application + .file_holder#README + .file_title + %i.icon-file + application.log + .file_content.logs + %ol + - Gitlab::AppLogger.read_latest.each do |line| + %li + %p= line diff --git a/app/views/admin/projects/_form.html.haml b/app/views/admin/projects/_form.html.haml index 87d212e5..4848e739 100644 --- a/app/views/admin/projects/_form.html.haml +++ b/app/views/admin/projects/_form.html.haml @@ -32,7 +32,7 @@ - unless project.new_record? .clearfix = f.label :owner_id - .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] } + .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'} - if project.repo_exists? .clearfix @@ -69,7 +69,6 @@ :javascript $(function(){ - $('#project_owner_id').chosen(); new Projects(); }) diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 65d888f5..63987410 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -71,25 +71,11 @@ %th Project Access: %tr - %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true - %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select" + %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5' + %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"} %tr %td= submit_tag 'Add', class: "btn primary" %td Read more about project permissions %strong= link_to "here", help_permissions_path, class: "vlink" - -:css - form select { - width:150px; - } - - #user_ids { - width:300px; - } - -:javascript - $('select#user_ids').chosen(); - $('select#repo_access').chosen(); - $('select#project_access').chosen(); diff --git a/app/views/admin/resque/show.html.haml b/app/views/admin/resque/show.html.haml index d889a5d0..8850e378 100644 --- a/app/views/admin/resque/show.html.haml +++ b/app/views/admin/resque/show.html.haml @@ -1,2 +1,4 @@ -%h3 Resque -%iframe{src: resque_url, width: 1168, height: 600, style: "border: none"} +%h3.page_title Resque +%br +.ui-box + %iframe{src: resque_url, width: '100%', height: 600, style: "border: none"} diff --git a/app/views/admin/team_members/_form.html.haml b/app/views/admin/team_members/_form.html.haml index 6a128de9..9cd94fdd 100644 --- a/app/views/admin/team_members/_form.html.haml +++ b/app/views/admin/team_members/_form.html.haml @@ -8,20 +8,9 @@ .clearfix %label Project Access: .input - = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select" + = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3" %br .actions = f.submit 'Save', class: "btn primary" = link_to 'Cancel', :back, class: "btn" - -:css - form select { - width:300px; - } - -:javascript - $('select#team_member_user_id').chosen(); - $('select#team_member_project_id').chosen(); - $('select#team_member_repo_access').chosen(); - $('select#team_member_project_access').chosen(); diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 4d2b9832..731916e9 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -68,8 +68,8 @@ %th Project Access: %tr - %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true - %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select" + %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' + %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3" %tr %td= submit_tag 'Add', class: "btn primary" @@ -97,17 +97,3 @@ %td= select_tag :tm_project_access, options_for_select(Project.access_options, tm.project_access), class: "medium project-access-select", disabled: :disabled %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger" - -:css - form select { - width:150px; - } - - #project_ids { - width:300px; - } - -:javascript - $('select#project_ids').chosen(); - $('select#repo_access').chosen(); - $('select#project_access').chosen(); diff --git a/app/views/commits/_commit_box.html.haml b/app/views/commits/_commit_box.html.haml index 506f4e09..572337de 100644 --- a/app/views/commits/_commit_box.html.haml +++ b/app/views/commits/_commit_box.html.haml @@ -11,10 +11,10 @@ = link_to tree_project_ref_path(@project, @commit.id), class: "browse-button primary grouped" do %strong Browse Code » %h3.commit-title.page_title - = gfm @commit.title + = gfm escape_once(@commit.title) - if @commit.description.present? %pre.commit-description - = gfm @commit.description + = gfm escape_once(@commit.description) .commit-info .row .span4 diff --git a/app/views/commits/_head.html.haml b/app/views/commits/_head.html.haml index a211329f..a8111a72 100644 --- a/app/views/commits/_head.html.haml +++ b/app/views/commits/_head.html.haml @@ -1,9 +1,5 @@ %ul.nav.nav-tabs - %li - = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do - = select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select" - = hidden_field_tag :destination, "commits" - + %li= render partial: 'shared/ref_switcher', locals: {destination: 'commits'} %li{class: "#{'active' if current_page?(project_commits_path(@project)) }"} = link_to project_commits_path(@project) do Commits @@ -20,14 +16,8 @@ Tags %span.badge= @project.repo.tag_count - - if current_page?(project_commits_path(@project)) && current_user.private_token %li.right %span.rss-icon = link_to project_commits_path(@project, :atom, { private_token: current_user.private_token, ref: @ref }), title: "Feed" do = image_tag "rss_ui.png", title: "feed" - -:javascript - $(function(){ - $('.project-refs-select').chosen(); - }); diff --git a/app/views/commits/_text_file.html.haml b/app/views/commits/_text_file.html.haml index 0f6210f2..9f5b5345 100644 --- a/app/views/commits/_text_file.html.haml +++ b/app/views/commits/_text_file.html.haml @@ -13,14 +13,11 @@ %td.old_line = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code - if @comments_allowed - = link_to "", "#", class: "line_note_link", "line_code" => line_code, title: "Add note for this line" + = render "notes/per_line_note_link", line_code: line_code %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line}  " - if @comments_allowed - - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at).reverse + - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at) - unless comments.empty? - - comments.each_with_index do |note, i| - = render "notes/reply_button", line_code: line_code if i.zero? - = render "notes/per_line_show", note: note - - @line_notes.reject!{ |n| n == note } + = render "notes/per_line_notes_with_reply", notes: comments diff --git a/app/views/commits/show.html.haml b/app/views/commits/show.html.haml index e01f8ea5..d12fff96 100644 --- a/app/views/commits/show.html.haml +++ b/app/views/commits/show.html.haml @@ -1,6 +1,6 @@ = render "commits/commit_box" = render "commits/diffs", diffs: @commit.diffs -= render "notes/notes", tid: @commit.id, tt: "commit" += render "notes/notes_with_form", tid: @commit.id, tt: "commit" = render "notes/per_line_form" diff --git a/app/views/dashboard/index.html.haml b/app/views/dashboard/index.html.haml index e13640fb..791c18e3 100644 --- a/app/views/dashboard/index.html.haml +++ b/app/views/dashboard/index.html.haml @@ -31,13 +31,19 @@ %span= project_last_activity(project) .bottom= paginate @projects, theme: "gitlab" - %hr %div %span.rss-icon = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do = image_tag "rss_ui.png", title: "feed" %strong News Feed + %hr + .gitlab-promo + = link_to "Homepage", "http://gitlabhq.com" + = link_to "Blog", "http://blog.gitlabhq.com" + = link_to "@gitlabhq", "https://twitter.com/gitlabhq" + + - else %h3.nothing_here_message There are no projects you have access to. %br diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 85010df7..4233aa61 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -15,7 +15,7 @@ $(function() { $('#new_user').toggle(); }); - = form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f| += form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f| = f.text_field :email, :class => "text top", :placeholder => "Email" = f.password_field :password, :class => "text bottom", :placeholder => "Password" - if devise_mapping.rememberable? diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 6e86186c..07ecf70b 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -15,7 +15,7 @@ .right = render :partial => "devise/shared/links" - if devise_mapping.omniauthable? + %hr/ - resource_class.omniauth_providers.each do |provider| - %hr/ - = link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider), :class => "btn primary" - %br/ + %span + = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider) diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml index 1e5c00cb..cb25d831 100644 --- a/app/views/events/_commit.html.haml +++ b/app/views/events/_commit.html.haml @@ -5,4 +5,4 @@ %strong.cdark= commit.author_name – = image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16 - = gfm truncate(commit.title, length: 50) rescue "--broken encoding" + = gfm escape_once(truncate(commit.title, length: 50)) rescue "--broken encoding" diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index 66e14936..aa1d28f2 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -2,7 +2,7 @@ .event_lp %div = image_tag gravatar_icon(event.author_email), class: "avatar" - %span Your pushed to + %span Your pushed to = event.ref_type = link_to project_commits_path(event.project, ref: event.ref_name) do %strong= truncate(event.ref_name, length: 28) diff --git a/app/views/events/_event_membership_changed.html.haml b/app/views/events/_event_membership_changed.html.haml index b079c138..464f24b3 100644 --- a/app/views/events/_event_membership_changed.html.haml +++ b/app/views/events/_event_membership_changed.html.haml @@ -2,7 +2,7 @@ %strong #{event.author_name} %span.event_label{class: event.action_name}= event.action_name project -%strong= link_to event.project.name, event.project +%strong= link_to event.project_name, event.project %span.cgray = time_ago_in_words(event.created_at) ago. diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml index 23de7e8e..813ecab2 100644 --- a/app/views/issues/_form.html.haml +++ b/app/views/issues/_form.html.haml @@ -18,12 +18,12 @@ = f.label :assignee_id do %i.icon-user Assign to - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }) + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'}) .issue_milestone = f.label :milestone_id do %i.icon-time Milestone - .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }) + .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'}) .issue_description .clearfix diff --git a/app/views/issues/_show.html.haml b/app/views/issues/_show.html.haml index 8500cd40..64401bdd 100644 --- a/app/views/issues/_show.html.haml +++ b/app/views/issues/_show.html.haml @@ -4,7 +4,7 @@ = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) .right - issue.labels.each do |label| - %span.label.label-issue.grouped + %span.label.label-tag.grouped %i.icon-tag = label.name - if issue.notes.any? @@ -34,5 +34,5 @@ - else   - - if issue.upvotes > 0 - %span.badge.badge-success= "+#{issue.upvotes}" + - if issue.votes_count > 0 + = render 'votes/votes_inline', votable: issue diff --git a/app/views/issues/edit.html.haml b/app/views/issues/edit.html.haml index 3c9877f8..b1bc3ba0 100644 --- a/app/views/issues/edit.html.haml +++ b/app/views/issues/edit.html.haml @@ -1,8 +1 @@ = render "form" - -:javascript - $(function(){ - $('select#issue_assignee_id').chosen(); - $('select#issue_milestone_id').chosen(); - }); - diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml index bc5c86e6..22c34baa 100644 --- a/app/views/issues/index.html.haml +++ b/app/views/issues/index.html.haml @@ -12,7 +12,7 @@ = form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do = hidden_field_tag :project_id, @project.id, { id: 'project_id' } = hidden_field_tag :status, params[:f] - = search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search span3 right neib' } + = search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search span3 right neib search-text-input' } .clearfix diff --git a/app/views/issues/new.html.haml b/app/views/issues/new.html.haml index 3c9877f8..b1bc3ba0 100644 --- a/app/views/issues/new.html.haml +++ b/app/views/issues/new.html.haml @@ -1,8 +1 @@ = render "form" - -:javascript - $(function(){ - $('select#issue_assignee_id').chosen(); - $('select#issue_milestone_id').chosen(); - }); - diff --git a/app/views/issues/show.html.haml b/app/views/issues/show.html.haml index dce8cf6a..da2aeac4 100644 --- a/app/views/issues/show.html.haml +++ b/app/views/issues/show.html.haml @@ -8,22 +8,22 @@ %span.right - if can?(current_user, :admin_project, @project) || @issue.author == current_user - if @issue.closed - = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn small" + = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn grouped success" - else - = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn small", title: "Close Issue" + = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn grouped danger", title: "Close Issue" - if can?(current_user, :admin_project, @project) || @issue.author == current_user - = link_to edit_project_issue_path(@project, @issue), class: "btn small" do + = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do %i.icon-edit Edit - %br - - if @issue.upvotes > 0 - .upvotes#upvotes= "+#{pluralize @issue.upvotes, 'upvote'}" +.right + .span3#votes= render 'votes/votes_block', votable: @issue .back_link = link_to project_issues_path(@project) do ← To issues list + .main_box .top_box_content %h4 @@ -31,7 +31,7 @@ .alert-message.error.status_info Closed - else .alert-message.success.status_info Open - = gfm @issue.title + = gfm escape_once(@issue.title) .middle_box_content %cite.cgray Created by @@ -61,4 +61,4 @@ = markdown @issue.description -.issue_notes#notes= render "notes/notes", tid: @issue.id, tt: "issue" +.issue_notes.voting_notes#notes= render "notes/notes_with_form", tid: @issue.id, tt: "issue" diff --git a/app/views/labels/_label.html.haml b/app/views/labels/_label.html.haml index 32158c20..8a465a9e 100644 --- a/app/views/labels/_label.html.haml +++ b/app/views/labels/_label.html.haml @@ -1,4 +1,9 @@ %li.wll - %strong= label.name + %strong + %i.icon-tag + = label.name .right - %span= pluralize label.count, 'issue' + = link_to project_issues_path(label_name: label.name) do + %strong + = pluralize(label.count, 'issue') + = "»" diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index b624415d..62c8db5b 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -9,20 +9,20 @@ %li.home{class: tab_class(:profile)} = link_to "Profile", profile_path - %li{class: tab_class(:password)} - = link_to "Password", profile_password_path + %li{class: tab_class(:account)} + = link_to "Account", profile_account_path %li{class: tab_class(:ssh_keys)} = link_to keys_path do SSH Keys %span.count= current_user.keys.count - %li{class: tab_class(:token)} - = link_to "Token", profile_token_path - %li{class: tab_class(:design)} = link_to "Design", profile_design_path + %li{class: tab_class(:history)} + = link_to "History", profile_history_path + .content = yield diff --git a/app/views/merge_requests/_form.html.haml b/app/views/merge_requests/_form.html.haml index d5271ed0..96692c0f 100644 --- a/app/views/merge_requests/_form.html.haml +++ b/app/views/merge_requests/_form.html.haml @@ -16,7 +16,7 @@ .padded = f.label :source_branch, "From", class: "control-label" .controls - = f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px") + = f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'}) .mr_source_commit .span2 @@ -28,7 +28,7 @@ .padded = f.label :target_branch, "To", class: "control-label" .controls - = f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px") + = f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'}) .mr_target_commit %h4.cdark 2. Fill info @@ -43,7 +43,7 @@ = f.label :assignee_id do %i.icon-user Assign to - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, style: "width:250px") + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'}) .control-group @@ -56,18 +56,12 @@ = link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do Cancel - - :javascript $(function(){ disableButtonIfEmptyField("#merge_request_title", ".save-btn"); - $('select#merge_request_assignee_id').chosen(); - $('select#merge_request_source_branch').chosen(); - $('select#merge_request_target_branch').chosen(); var source_branch = $("#merge_request_source_branch"); var target_branch = $("#merge_request_target_branch"); - $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: source_branch.val() }); $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: target_branch.val() }); @@ -79,4 +73,3 @@ $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() }); }); }); - diff --git a/app/views/merge_requests/_merge_request.html.haml b/app/views/merge_requests/_merge_request.html.haml index 74996090..9d94d670 100644 --- a/app/views/merge_requests/_merge_request.html.haml +++ b/app/views/merge_requests/_merge_request.html.haml @@ -23,5 +23,6 @@ authored by #{merge_request.author_name} = time_ago_in_words(merge_request.created_at) ago - - if merge_request.upvotes > 0 - %span.badge.badge-success= "+#{merge_request.upvotes}" + + - if merge_request.votes_count > 0 + = render 'votes/votes_inline', votable: merge_request diff --git a/app/views/merge_requests/_show.html.haml b/app/views/merge_requests/_show.html.haml index f1b3fa9f..f1d0c8aa 100644 --- a/app/views/merge_requests/_show.html.haml +++ b/app/views/merge_requests/_show.html.haml @@ -15,8 +15,8 @@ %i.icon-list-alt Diff -.merge_request_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } - = render("notes/notes", tid: @merge_request.id, tt: "merge_request") +.merge_request_notes.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } + = render("notes/notes_with_form", tid: @merge_request.id, tt: "merge_request") .merge-request-diffs = render "merge_requests/show/diffs" if @diffs .status diff --git a/app/views/merge_requests/diffs.html.haml b/app/views/merge_requests/diffs.html.haml index 176b19bc..a755491c 100644 --- a/app/views/merge_requests/diffs.html.haml +++ b/app/views/merge_requests/diffs.html.haml @@ -1,2 +1,6 @@ = render "show" +:javascript + $(function(){ + PerLineNotes.init(); + }); diff --git a/app/views/merge_requests/diffs.js.haml b/app/views/merge_requests/diffs.js.haml index b147e5be..98539985 100644 --- a/app/views/merge_requests/diffs.js.haml +++ b/app/views/merge_requests/diffs.js.haml @@ -1,4 +1,7 @@ :plain $(".merge-request-diffs").html("#{escape_javascript(render(partial: "merge_requests/show/diffs"))}"); + $(function(){ + PerLineNotes.init(); + }); diff --git a/app/views/merge_requests/show.js.haml b/app/views/merge_requests/show.js.haml index 7a27b166..f44ccbb5 100644 --- a/app/views/merge_requests/show.js.haml +++ b/app/views/merge_requests/show.js.haml @@ -1,2 +1,2 @@ :plain - $(".merge-request-notes").html("#{escape_javascript(render("notes/notes", tid: @merge_request.id, tt: "merge_request"))}"); + $(".merge-request-notes").html("#{escape_javascript(render notes/notes_with_form", tid: @merge_request.id, tt: "merge_request")}"); diff --git a/app/views/merge_requests/show/_mr_box.html.haml b/app/views/merge_requests/show/_mr_box.html.haml index 81ab83f3..89c3110b 100644 --- a/app/views/merge_requests/show/_mr_box.html.haml +++ b/app/views/merge_requests/show/_mr_box.html.haml @@ -5,7 +5,7 @@ .alert-message.error.status_info Closed - else .alert-message.success.status_info Open - = gfm @merge_request.title + = gfm escape_once(@merge_request.title) .middle_box_content %div diff --git a/app/views/merge_requests/show/_mr_title.html.haml b/app/views/merge_requests/show/_mr_title.html.haml index 3ae1050d..8708469c 100644 --- a/app/views/merge_requests/show/_mr_title.html.haml +++ b/app/views/merge_requests/show/_mr_title.html.haml @@ -23,10 +23,8 @@ %i.icon-edit Edit - %br - - if @merge_request.upvotes > 0 - .upvotes#upvotes= "+#{pluralize @merge_request.upvotes, 'upvote'}" - +.right + .span3#votes= render 'votes/votes_block', votable: @merge_request .back_link = link_to project_merge_requests_path(@project) do diff --git a/app/views/milestones/edit.html.haml b/app/views/milestones/edit.html.haml index af975a84..b1bc3ba0 100644 --- a/app/views/milestones/edit.html.haml +++ b/app/views/milestones/edit.html.haml @@ -1,7 +1 @@ = render "form" - -:javascript - $(function(){ - $('select#issue_assignee_id').chosen(); - }); - diff --git a/app/views/milestones/show.html.haml b/app/views/milestones/show.html.haml index 0d6cb2a0..ba71ead7 100644 --- a/app/views/milestones/show.html.haml +++ b/app/views/milestones/show.html.haml @@ -21,7 +21,7 @@ .alert-message.error.status_info Closed - else .alert-message.success.status_info Open - = gfm @milestone.title + = gfm escape_once(@milestone.title) %small.right= @milestone.expires_at .middle_box_content diff --git a/app/views/notes/_form.html.haml b/app/views/notes/_common_form.html.haml similarity index 97% rename from app/views/notes/_form.html.haml rename to app/views/notes/_common_form.html.haml index 6d559cfc..fc6e3c7e 100644 --- a/app/views/notes/_form.html.haml +++ b/app/views/notes/_common_form.html.haml @@ -14,7 +14,7 @@ .right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. .clearfix - .row.note_advanced_opts.hide + .row.note_advanced_opts .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' diff --git a/app/views/notes/_create_common.js.haml b/app/views/notes/_create_common_note.js.haml similarity index 72% rename from app/views/notes/_create_common.js.haml rename to app/views/notes/_create_common_note.js.haml index e80eccb1..bbebc247 100644 --- a/app/views/notes/_create_common.js.haml +++ b/app/views/notes/_create_common_note.js.haml @@ -5,8 +5,9 @@ $('.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})}"); + NoteList.appendNewNote(#{note.id}, "#{escape_javascript(render "notes/note", note: note)}"); + - else :plain - $(".note-form-holder").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 deleted file mode 100644 index 662909f7..00000000 --- a/app/views/notes/_create_line.js.haml +++ /dev/null @@ -1,8 +0,0 @@ -- if note.valid? - :plain - $(".per_line_form").hide(); - $('.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})}"); - trEl.after("#{escape_javascript(render partial: "notes/reply_button", locals: {line_code: note.line_code})}"); diff --git a/app/views/notes/_create_per_line_note.js.haml b/app/views/notes/_create_per_line_note.js.haml new file mode 100644 index 00000000..180960e3 --- /dev/null +++ b/app/views/notes/_create_per_line_note.js.haml @@ -0,0 +1,19 @@ +- if note.valid? + :plain + // hide and reset the form + $(".per_line_form").hide(); + $('.line-note-form-holder textarea').val(""); + + // find the reply button for this line + // (might not be there if this is the first note) + var trRpl = $("a.line_note_reply_link[data-line-code='#{note.line_code}']").closest("tr"); + if (trRpl.size() == 0) { + // find the commented line ... + var trEl = $(".#{note.line_code}").parent(); + // ... and insert the note and the reply button after it + trEl.after("#{escape_javascript(render "notes/per_line_reply_button", line_code: note.line_code)}"); + trEl.after("#{escape_javascript(render "notes/per_line_note", note: note)}"); + } else { + // instert new note before reply button + trRpl.before("#{escape_javascript(render "notes/per_line_note", note: note)}"); + } diff --git a/app/views/notes/_load.js.haml b/app/views/notes/_load.js.haml deleted file mode 100644 index c16a699a..00000000 --- a/app/views/notes/_load.js.haml +++ /dev/null @@ -1,17 +0,0 @@ -- unless @notes.blank? - - if params[:last_id] - :plain - NoteList.replace("#{escape_javascript(render(partial: 'notes/notes_list'))}"); - - - elsif params[:first_id] - :plain - NoteList.append(#{@notes.last.id}, "#{escape_javascript(render(partial: 'notes/notes_list'))}"); - - - else - :plain - NoteList.setContent(#{@notes.last.id}, #{@notes.first.id}, "#{escape_javascript(render(partial: 'notes/notes_list'))}"); - -- else - - if params[:first_id] - :plain - NoteList.append(#{params[:first_id]}, ""); diff --git a/app/views/notes/_show.html.haml b/app/views/notes/_note.html.haml similarity index 73% rename from app/views/notes/_show.html.haml rename to app/views/notes/_note.html.haml index 3412e4eb..5234e55d 100644 --- a/app/views/notes/_show.html.haml +++ b/app/views/notes/_note.html.haml @@ -1,4 +1,4 @@ -%li{id: dom_id(note), class: "note"} +%li{id: dom_id(note), class: "note #{note_vote_class(note)}"} = image_tag gravatar_icon(note.author.email), class: "avatar s32" %div.note-author %strong= note.author_name @@ -6,8 +6,16 @@ %cite.cgray = time_ago_in_words(note.updated_at) ago + - if note.upvote? + %span.label.label-success + %i.icon-thumbs-up + \+1 + - if note.downvote? + %span.label.label-error + %i.icon-thumbs-down + \-1 - if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project) - = link_to [@project, note], confirm: 'Are you sure?', method: :delete, remote: true, class: "cred delete-note btn very_small" do + = link_to [@project, note], confirm: 'Are you sure?', method: :delete, remote: true, class: "cred delete-note btn very_small" do %i.icon-trash Remove diff --git a/app/views/notes/_notes.html.haml b/app/views/notes/_notes.html.haml index e692e746..adb5dfcb 100644 --- a/app/views/notes/_notes.html.haml +++ b/app/views/notes/_notes.html.haml @@ -1,13 +1,4 @@ -- if can? current_user, :write_note, @project - = render "notes/form" -.clear -%hr -%ul#new_notes_list -%ul#notes-list -.status +- @notes.each do |note| + - next unless note.author + = render "note", note: note - -:javascript - $(function(){ - NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}"); - }); diff --git a/app/views/notes/_notes_list.html.haml b/app/views/notes/_notes_list.html.haml deleted file mode 100644 index 5673988d..00000000 --- a/app/views/notes/_notes_list.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -- @notes.each do |note| - - next unless note.author - = render partial: "notes/show", locals: {note: note} - diff --git a/app/views/notes/_notes_with_form.html.haml b/app/views/notes/_notes_with_form.html.haml new file mode 100644 index 00000000..53716c1d --- /dev/null +++ b/app/views/notes/_notes_with_form.html.haml @@ -0,0 +1,11 @@ +%ul#notes-list +%ul#new-notes-list +.notes-status + +- if can? current_user, :write_note, @project + = render "notes/common_form" + +:javascript + $(function(){ + NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}"); + }); diff --git a/app/views/notes/_per_line_note.html.haml b/app/views/notes/_per_line_note.html.haml new file mode 100644 index 00000000..28bcd6e0 --- /dev/null +++ b/app/views/notes/_per_line_note.html.haml @@ -0,0 +1,5 @@ +%tr.line_notes_row + %td{colspan: 3} + %ul + = render "notes/note", note: note + diff --git a/app/views/notes/_per_line_note_link.html.haml b/app/views/notes/_per_line_note_link.html.haml new file mode 100644 index 00000000..72b59a59 --- /dev/null +++ b/app/views/notes/_per_line_note_link.html.haml @@ -0,0 +1 @@ += link_to "", "#", class: "line_note_link", data: { line_code: line_code }, title: "Add note for this line" diff --git a/app/views/notes/_per_line_notes_with_reply.html.haml b/app/views/notes/_per_line_notes_with_reply.html.haml new file mode 100644 index 00000000..1bcfc41f --- /dev/null +++ b/app/views/notes/_per_line_notes_with_reply.html.haml @@ -0,0 +1,3 @@ +- notes.each do |note| + = render "notes/per_line_note", note: note += render "notes/per_line_reply_button", line_code: notes.first.line_code diff --git a/app/views/notes/_per_line_reply_button.html.haml b/app/views/notes/_per_line_reply_button.html.haml new file mode 100644 index 00000000..42c737c7 --- /dev/null +++ b/app/views/notes/_per_line_reply_button.html.haml @@ -0,0 +1,4 @@ +%tr.line_notes_row.reply + %td{colspan: 3} + %i.icon-comment + = link_to "Reply", "#", class: "line_note_reply_link", data: { line_code: line_code }, title: "Add note for this line" diff --git a/app/views/notes/_per_line_show.html.haml b/app/views/notes/_per_line_show.html.haml deleted file mode 100644 index cf1769c0..00000000 --- a/app/views/notes/_per_line_show.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%tr.line_notes_row - %td{colspan: 3} - %ul - = render partial: "notes/show", locals: {note: note} - diff --git a/app/views/notes/_reply_button.html.haml b/app/views/notes/_reply_button.html.haml deleted file mode 100644 index c981fb9f..00000000 --- a/app/views/notes/_reply_button.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%tr.line_notes_row.reply - %td{colspan: 3} - %i.icon-comment - = link_to "Reply", "#", class: "line_note_reply_link", "line_code" => line_code, title: "Add note for this line" diff --git a/app/views/notes/_reversed_notes_with_form.html.haml b/app/views/notes/_reversed_notes_with_form.html.haml new file mode 100644 index 00000000..24d59924 --- /dev/null +++ b/app/views/notes/_reversed_notes_with_form.html.haml @@ -0,0 +1,11 @@ +- if can? current_user, :write_note, @project + = render "notes/common_form" + +%ul.reversed#new-notes-list +%ul.reversed#notes-list +.notes-status + +:javascript + $(function(){ + NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}"); + }); diff --git a/app/views/notes/create.js.haml b/app/views/notes/create.js.haml index 8f631f38..03866591 100644 --- a/app/views/notes/create.js.haml +++ b/app/views/notes/create.js.haml @@ -1,7 +1,7 @@ - if @note.line_code - = render "create_line", note: @note + = render "create_per_line_note", note: @note - else - = render "create_common", note: @note + = render "create_common_note", note: @note -# Enable submit button :plain diff --git a/app/views/notes/index.js.haml b/app/views/notes/index.js.haml index ee31c0b8..3814dbd4 100644 --- a/app/views/notes/index.js.haml +++ b/app/views/notes/index.js.haml @@ -1 +1,17 @@ -= render "notes/load" +- unless @notes.blank? + - if loading_more_notes? + :plain + NoteList.appendMoreNotes(#{@notes.last.id}, "#{escape_javascript(render 'notes/notes')}"); + + - elsif loading_new_notes? + :plain + NoteList.replaceNewNotes("#{escape_javascript(render 'notes/notes')}"); + + - else + :plain + NoteList.setContent(#{@notes.first.id}, #{@notes.last.id}, "#{escape_javascript(render 'notes/notes')}"); + +- else + - if loading_more_notes? + :plain + NoteList.finishedLoadingMore(); diff --git a/app/views/profile/account.html.haml b/app/views/profile/account.html.haml new file mode 100644 index 00000000..6707a8ff --- /dev/null +++ b/app/views/profile/account.html.haml @@ -0,0 +1,57 @@ +- if Gitlab.config.omniauth_enabled? + %fieldset + %legend + %h3.page_title Social Accounts + .oauth_select_holder + %p.hint Tip: Click on icon to activate sigin with one of the following services + - User.omniauth_providers.each do |provider| + %span{class: oauth_active_class(provider) } + = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) + + +%fieldset + %legend + %h3.page_title + Private token + %span.cred.right + keep it in secret! + .padded + = form_for @user, url: profile_reset_private_token_path, method: :put do |f| + .data + %p.slead + Private token used to access application resources without authentication. + %br + It can be used for atom feed or API + %p.cgray + - if current_user.private_token + = text_field_tag "token", current_user.private_token, class: "xxlarge large_text" + = f.submit 'Reset', confirm: "Are you sure?", class: "btn primary btn-build-token" + - else + %span You don`t have one yet. Click generate to fix it. + = f.submit 'Generate', class: "btn success btn-build-token" + +%fieldset + %legend + %h3.page_title Password + = form_for @user, url: profile_password_path, method: :put do |f| + .padded + %p.slead After successful password update you will be redirected to login page where you should login with new password + -if @user.errors.any? + .alert-message.block-message.error + %ul + - @user.errors.full_messages.each do |msg| + %li= msg + + .clearfix + = f.label :password + .input= f.password_field :password + .clearfix + = f.label :password_confirmation + .input= f.password_field :password_confirmation + .actions + = f.submit 'Save', class: "btn save-btn" + + + + + diff --git a/app/views/profile/history.html.haml b/app/views/profile/history.html.haml new file mode 100644 index 00000000..aa7006c5 --- /dev/null +++ b/app/views/profile/history.html.haml @@ -0,0 +1,5 @@ +.profile_history + = render @events +%hr += paginate @events, theme: "gitlab" + diff --git a/app/views/profile/password.html.haml b/app/views/profile/password.html.haml deleted file mode 100644 index d0aee7ac..00000000 --- a/app/views/profile/password.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -%h3.page_title Password -%hr -= form_for @user, url: profile_password_path, method: :put do |f| - .data - %p.slead After successful password update you will be redirected to login page where you should login with new password - -if @user.errors.any? - .alert-message.block-message.error - %ul - - @user.errors.full_messages.each do |msg| - %li= msg - - .clearfix - = f.label :password - .input= f.password_field :password - .clearfix - = f.label :password_confirmation - .input= f.password_field :password_confirmation - .actions - = f.submit 'Save', class: "btn save-btn" diff --git a/app/views/profile/show.html.haml b/app/views/profile/show.html.haml index 22e840a0..7b625291 100644 --- a/app/views/profile/show.html.haml +++ b/app/views/profile/show.html.haml @@ -6,7 +6,6 @@ %small = @user.email - %hr = form_for @user, url: profile_update_path, method: :put, html: { class: "edit_user form-horizontal" } do |f| @@ -28,7 +27,23 @@ = f.text_field :email, class: "input-xlarge" %span.help-block We also use email for avatar detection. - %hr + .span5.right + %div.tips + %h6 Tips: + %ul + -unless Gitlab.config.disable_gravatar? + %li + %p.hint You can change your avatar at #{link_to "gravatar.com", "http://gravatar.com"} + + - if Gitlab.config.omniauth_enabled? && @user.provider? + %li + %p.hint + You can login through #{@user.provider.titleize}! + = link_to "click here to change", profile_account_path + + %hr + .row + .span7 .control-group = f.label :skype, class: "control-label" .controls= f.text_field :skype, class: "input-xlarge" @@ -44,27 +59,24 @@ = f.text_area :bio, rows: 6, class: "input-xlarge", maxlength: 250 %span.help-block Tell us about yourself in fewer than 250 characters. .span5.right + .ui-box.white + .ui-box-body + %h4 + Personal projects: + %small.right + %span= current_user.my_own_projects.count + of + %span= current_user.projects_limit + .progress + .bar{style: "width: #{current_user.projects_limit_percent}%;"} - -unless Gitlab.config.disable_gravatar? - %p.alert.alert-info - %strong Tip: - You can change your avatar at gravatar.com + .ui-box.white + .ui-box-body + %h4 + SSH public keys: + %strong.right= link_to current_user.keys.count, keys_path - %h4 - Personal projects: - %small.right - %span= current_user.my_own_projects.count - of - %span= current_user.projects_limit - .progress - .bar{style: "width: #{current_user.projects_limit_percent}%;"} - - %h4 - SSH public keys: - %small.right - %span= link_to current_user.keys.count, keys_path - - = link_to "Add Public Key", new_key_path, class: "btn small right" + = link_to "Add Public Key", new_key_path, class: "btn small" .form-actions = f.submit 'Save', class: "btn save-btn" diff --git a/app/views/profile/token.html.haml b/app/views/profile/token.html.haml deleted file mode 100644 index 6c870c36..00000000 --- a/app/views/profile/token.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -%h3.page_title - Private token - %span.cred.right - keep it in secret! -%hr -= form_for @user, url: profile_reset_private_token_path, method: :put do |f| - .data - %p.slead - Private token used to access application resources without authentication. - %br - It can be used for atom feed or API - %p.cgray - - if current_user.private_token - = text_field_tag "token", current_user.private_token, class: "xxlarge large_text" - - else - You don`t have one yet. Click generate to fix it. - .actions - - if current_user.private_token - = f.submit 'Reset', confirm: "Are you sure?", class: "btn" - - else - = f.submit 'Generate', class: "btn primary" - - diff --git a/app/views/projects/_project_head.html.haml b/app/views/projects/_project_head.html.haml index ba64ee7f..4f38bef8 100644 --- a/app/views/projects/_project_head.html.haml +++ b/app/views/projects/_project_head.html.haml @@ -3,8 +3,8 @@ = link_to project_path(@project), class: "activities-tab tab" do %i.icon-home Show - %li{ class: " #{'active' if (controller.controller_name == "team_members") || current_page?(team_project_path(@project)) }" } - = link_to team_project_path(@project), class: "team-tab tab" do + %li{ class: " #{'active' if (controller.controller_name == "team_members") || current_page?(project_team_index_path(@project)) }" } + = link_to project_team_index_path(@project), class: "team-tab tab" do %i.icon-user Team %li{ class: "#{'active' if current_page?(files_project_path(@project)) }" } diff --git a/app/views/projects/_refs.html.haml b/app/views/projects/_refs.html.haml deleted file mode 100644 index 804b8523..00000000 --- a/app/views/projects/_refs.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do - = select_tag "ref", grouped_options_refs, onchange: "this.form.submit();", class: "project-refs-select" - = hidden_field_tag :destination, destination - -:javascript - $(function(){ - $('.project-refs-select').chosen(); - }) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 987d43ec..fdd537da 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -3,10 +3,10 @@ %h3.page_title Edit Project %hr = render "projects/form" -%div.ajax_loader.hide +%div.save-project-loader.hide %center - %div.padded= image_tag "ajax_loader.gif" - %h3.prepend-top Saving project & repository. Please wait... + = image_tag "ajax_loader.gif" + %h3 Saving project. Please wait a few minutes :javascript $(function(){ new Projects(); }); diff --git a/app/views/projects/wall.html.haml b/app/views/projects/wall.html.haml index 97765d7a..591a8cd0 100644 --- a/app/views/projects/wall.html.haml +++ b/app/views/projects/wall.html.haml @@ -1,2 +1,2 @@ %div.wall_page - = render "notes/notes", tid: nil, tt: "wall" + = render "notes/reversed_notes_with_form", tid: nil, tt: "wall" diff --git a/app/views/protected_branches/index.html.haml b/app/views/protected_branches/index.html.haml index 33bb448a..43884de1 100644 --- a/app/views/protected_branches/index.html.haml +++ b/app/views/protected_branches/index.html.haml @@ -19,7 +19,7 @@ .entry.clearfix = f.label :name, "Branch" .span3 - = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , { include_blank: "-- Select branch" }, { class: "span3" }) + = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "chosen span3"})   = f.submit 'Protect', class: "primary btn" @@ -46,6 +46,3 @@ %td - if can? current_user, :admin_project, @project = link_to 'Unprotect', [@project, branch], confirm: 'Are you sure?', method: :delete, class: "danger btn small" - -:javascript - $('select#protected_branch_name').chosen(); diff --git a/app/views/refs/_head.html.haml b/app/views/refs/_head.html.haml index d51602de..3592f573 100644 --- a/app/views/refs/_head.html.haml +++ b/app/views/refs/_head.html.haml @@ -1,9 +1,6 @@ %ul.nav.nav-tabs %li - = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form", remote: true do - = select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select" - = hidden_field_tag :destination, "tree" - = hidden_field_tag :path, params[:path] + = render partial: 'shared/ref_switcher', locals: {destination: 'tree', path: params[:path]} %li{class: "#{'active' if (controller.controller_name == "refs") }"} = link_to tree_project_ref_path(@project, @ref) do Source diff --git a/app/views/refs/_tree.html.haml b/app/views/refs/_tree.html.haml index 297a3b5f..ec6dba4e 100644 --- a/app/views/refs/_tree.html.haml +++ b/app/views/refs/_tree.html.haml @@ -47,17 +47,15 @@ :javascript $(function(){ - $('.project-refs-select').chosen(); - history.pushState({ path: this.path }, '', "#{@history_path}"); - - }); - - // Load last commit log for each file in tree - $(window).load(function(){ - ajaxGet('#{@logs_path}'); }); +- unless tree.is_blob? + :javascript + // Load last commit log for each file in tree + $(window).load(function(){ + ajaxGet('#{@logs_path}'); + }); - if params[:path] && request.xhr? :javascript diff --git a/app/views/refs/_tree_file.html.haml b/app/views/refs/_tree_file.html.haml index 765f271a..f6566ccf 100644 --- a/app/views/refs/_tree_file.html.haml +++ b/app/views/refs/_tree_file.html.haml @@ -2,7 +2,7 @@ .file_title %i.icon-file %span.file_name - = name + = name.force_encoding('utf-8') %small #{file.mode} %span.options = link_to "raw", blob_project_ref_path(@project, @ref, path: params[:path]), class: "btn very_small", target: "_blank" diff --git a/app/views/refs/blame.html.haml b/app/views/refs/blame.html.haml index 34478d4b..eb66f597 100644 --- a/app/views/refs/blame.html.haml +++ b/app/views/refs/blame.html.haml @@ -38,8 +38,3 @@ = preserve do %pre = Gitlab::Encode.utf8 lines.join("\n") - -:javascript - $(function(){ - $('.project-refs-select').chosen(); - }); diff --git a/app/views/repositories/_branch.html.haml b/app/views/repositories/_branch.html.haml index cf8558ec..64a633be 100644 --- a/app/views/repositories/_branch.html.haml +++ b/app/views/repositories/_branch.html.haml @@ -11,7 +11,7 @@ %code= commit.short_id = image_tag gravatar_icon(commit.author_email), class: "", width: 16 - = gfm truncate(commit.title, length: 40) + = gfm escape_once(truncate(commit.title, length: 40)) %span.update-author.right = time_ago_in_words(commit.committed_date) ago diff --git a/app/views/repositories/_feed.html.haml b/app/views/repositories/_feed.html.haml index ac4eb483..0c13551d 100644 --- a/app/views/repositories/_feed.html.haml +++ b/app/views/repositories/_feed.html.haml @@ -13,7 +13,7 @@ = link_to project_commits_path(@project, commit.id) do %code= commit.short_id = image_tag gravatar_icon(commit.author_email), class: "", width: 16 - = gfm truncate(commit.title, length: 40) + = gfm escape_once(truncate(commit.title, length: 40)) %td %span.right.cgray = time_ago_in_words(commit.committed_date) diff --git a/app/views/repositories/tags.html.haml b/app/views/repositories/tags.html.haml index 0e870c80..a4114586 100644 --- a/app/views/repositories/tags.html.haml +++ b/app/views/repositories/tags.html.haml @@ -17,7 +17,7 @@ = link_to project_commit_path(@project, commit.id) do %code= commit.short_id = image_tag gravatar_icon(commit.author_email), class: "", width: 16 - = gfm truncate(commit.title, length: 40) + = gfm escape_once(truncate(commit.title, length: 40)) %td %span.update-author.right = time_ago_in_words(commit.committed_date) diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index b4a38b57..d85c24ec 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -3,8 +3,8 @@ = 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 primary" + = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search" + = submit_tag 'Search', class: "btn primary wide" - if params[:search].present? %br %h3 @@ -15,8 +15,9 @@ .row .span6 %table - %tr - %th Projects + %thead + %tr + %th Projects %tbody - @projects.each do |project| %tr @@ -32,8 +33,9 @@ %h4.nothing_here_message No Projects %br %table - %tr - %th Merge Requests + %thead + %tr + %th Merge Requests %tbody - @merge_requests.each do |merge_request| %tr @@ -50,8 +52,9 @@ %h4.nothing_here_message No Merge Requests .span6 %table - %tr - %th Issues + %thead + %tr + %th Issues %tbody - @issues.each do |issue| %tr diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml new file mode 100644 index 00000000..e0c89522 --- /dev/null +++ b/app/views/shared/_ref_switcher.html.haml @@ -0,0 +1,5 @@ += form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do + = select_tag "ref", grouped_options_refs, class: "project-refs-select chosen" + = hidden_field_tag :destination, destination + - if respond_to?(:path) + = hidden_field_tag :path, path diff --git a/app/views/snippets/_form.html.haml b/app/views/snippets/_form.html.haml index b8d8c098..e61e61a7 100644 --- a/app/views/snippets/_form.html.haml +++ b/app/views/snippets/_form.html.haml @@ -16,7 +16,7 @@ .input= f.text_field :file_name, placeholder: "example.rb" .clearfix = f.label "Lifetime" - .input= f.select :expires_at, lifetime_select_options, {}, style: "width:200px;" + .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'} .clearfix = f.label :content, "Code" .input= f.text_area :content, class: "span8" @@ -26,11 +26,3 @@ = link_to "Cancel", project_snippets_path(@project), class: " btn" - unless @snippet.new_record? .right= link_to 'Destroy', [@project, @snippet], confirm: 'Are you sure?', method: :delete, class: "btn right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" - - - -:javascript - $(function(){ - $('select#snippet_expires_at').chosen(); - }); - diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 0800b81d..4188a9f1 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -17,4 +17,4 @@ %div{class: current_user.dark_scheme ? "black" : ""} = raw @snippet.colorize(options: { linenos: 'True'}) -= render "notes/notes", tid: @snippet.id, tt: "snippet" += render "notes/notes_with_form", tid: @snippet.id, tt: "snippet" diff --git a/app/views/team_members/_form.html.haml b/app/views/team_members/_form.html.haml index 192f2735..92167138 100644 --- a/app/views/team_members/_form.html.haml +++ b/app/views/team_members/_form.html.haml @@ -10,21 +10,14 @@ %h6 1. Choose people you want in the team .clearfix - = f.label :user_ids, "Peolpe" - .input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).all, :id, :name), { class: "xxlarge", multiple: true }) - + = f.label :user_ids, "People" + .input= select_tag(:user_ids, options_from_collection_for_select(User.not_in_project(@project).all, :id, :name), {data: {placeholder: "Select users"}, class: "chosen xxlarge", multiple: true}) %h6 2. Set access level for them .clearfix = f.label :project_access, "Project Access" - .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select" - + .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen" .actions = f.submit 'Save', class: "btn save-btn" - = link_to "Cancel", team_project_path(@project), class: "btn cancel-btn" - - -:javascript - $('select#user_ids').chosen(); - $('select#project_access').chosen(); + = link_to "Cancel", project_team_index_path(@project), class: "btn cancel-btn" diff --git a/app/views/team_members/_show.html.haml b/app/views/team_members/_show.html.haml index d9a72494..f68f8eb4 100644 --- a/app/views/team_members/_show.html.haml +++ b/app/views/team_members/_show.html.haml @@ -1,20 +1,26 @@ - user = member.user - allow_admin = can? current_user, :admin_project, @project %tr{id: dom_id(member), class: "team_member_row user_#{user.id}"} - %td + %td.span6 = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do = 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 - %div.cgray= user.email + %br + %small.cgray= user.email - %td + %td.span5 .right + - if current_user == user + %span.btn.disabled This is you! - if @project.owner == user - %span.btn.disabled.success Project Owner - - if user.blocked + %span.btn.disabled.success Owner + - elsif user.blocked %span.btn.disabled.blocked Blocked + - elsif allow_admin + = link_to project_team_member_path(project_id: @project, id: member.id), confirm: remove_from_team_message(@project, member), method: :delete, class: "very_small btn danger" do + %i.icon-minus.icon-white + - if allow_admin = form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f| - = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select" + = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2" diff --git a/app/views/projects/_team.html.haml b/app/views/team_members/_team.html.haml similarity index 100% rename from app/views/projects/_team.html.haml rename to app/views/team_members/_team.html.haml diff --git a/app/views/projects/team.html.haml b/app/views/team_members/index.html.haml similarity index 79% rename from app/views/projects/team.html.haml rename to app/views/team_members/index.html.haml index e8a825c7..b3b7b72a 100644 --- a/app/views/projects/team.html.haml +++ b/app/views/team_members/index.html.haml @@ -1,4 +1,4 @@ -= render "project_head" += render "projects/project_head" %h3.page_title Team Members %small (#{@project.users_projects.count}) @@ -10,6 +10,4 @@ Read more about project permissions %strong= link_to "here", help_permissions_path, class: "vlink" - -= render partial: "team", locals: {project: @project} - += render partial: "team_members/team", locals: {project: @project} diff --git a/app/views/team_members/show.html.haml b/app/views/team_members/show.html.haml index 3b5c78a8..9d03cd2c 100644 --- a/app/views/team_members/show.html.haml +++ b/app/views/team_members/show.html.haml @@ -14,7 +14,7 @@ %hr .back_link %br - = link_to team_project_path(@project), class: "" do + = link_to project_team_index_path(@project), class: "" do ← To team list %br .row diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml new file mode 100644 index 00000000..bded53b2 --- /dev/null +++ b/app/views/votes/_votes_block.html.haml @@ -0,0 +1,6 @@ +.votes.votes-block + .progress + .bar.bar-success{style: "width: #{votable.upvotes_in_percent}%;"} + .bar.bar-danger{style: "width: #{votable.downvotes_in_percent}%;"} + .upvotes= "#{votable.upvotes} up" + .downvotes= "#{votable.downvotes} down" diff --git a/app/views/votes/_votes_inline.html.haml b/app/views/votes/_votes_inline.html.haml new file mode 100644 index 00000000..91bd200d --- /dev/null +++ b/app/views/votes/_votes_inline.html.haml @@ -0,0 +1,6 @@ +.votes.votes-inline + .upvotes= votable.upvotes + .progress + .bar.bar-success{style: "width: #{votable.upvotes_in_percent}%;"} + .bar.bar-danger{style: "width: #{votable.downvotes_in_percent}%;"} + .downvotes= votable.downvotes diff --git a/app/views/wikis/show.html.haml b/app/views/wikis/show.html.haml index fc235227..579ea1b3 100644 --- a/app/views/wikis/show.html.haml +++ b/app/views/wikis/show.html.haml @@ -21,4 +21,4 @@ Delete this page %hr -.wiki_notes#notes= render "notes/notes", tid: @wiki.id, tt: "wiki" +.wiki_notes#notes= render "notes/notes_with_form", tid: @wiki.id, tt: "wiki" diff --git a/config/cucumber.yml b/config/cucumber.yml deleted file mode 100644 index 19b288df..00000000 --- a/config/cucumber.yml +++ /dev/null @@ -1,8 +0,0 @@ -<% -rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" -rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" -std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip" -%> -default: <%= std_opts %> features -wip: --tags @wip:3 --wip features -rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 08e3427f..28323484 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -25,8 +25,43 @@ app: # backup_keep_time: 604800 # default: 0 (forever) (in seconds) # disable_gravatar: true # default: false - Disable user avatars from Gravatar.com + # -# 2. Advanced settings: +# 2. Auth settings +# ========================== +ldap: + enabled: false + host: '_your_ldap_server' + base: '_the_base_where_you_search_for_users' + port: 636 + uid: 'sAMAccountName' + method: 'ssl' # plain + bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' + password: '_the_password_of_the_bind_user' + +omniauth: + # Enable ability for users + # to login via twitter, google .. + enabled: false + + # IMPORTANT! + # It allows user to login without having user account + allow_single_sign_on: false + block_auto_created_users: true + + # Auth providers + providers: + # - { name: 'google_oauth2', app_id: 'YOUR APP ID', + # app_secret: 'YOUR APP SECRET', + # args: { access_type: 'offline', approval_prompt: '' } } + # - { name: 'twitter', app_id: 'YOUR APP ID', + # app_secret: 'YOUR APP SECRET'} + # - { name: 'github', app_id: 'YOUR APP ID', + # app_secret: 'YOUR APP SECRET' } + + +# +# 3. Advanced settings: # ========================== # Git Hosting configuration diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index df9ccf32..7a7ca43f 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -6,7 +6,7 @@ class Settings < Settingslogic self.web['protocol'] ||= web.https ? "https" : "http" end - def web_host + def web_host self.web['host'] ||= 'localhost' end @@ -14,11 +14,11 @@ class Settings < Settingslogic self.email['from'] ||= ("notify@" + web_host) end - def url + def url self['url'] ||= build_url - end + end - def web_port + def web_port if web.https web['port'] = 443 else @@ -36,7 +36,7 @@ class Settings < Settingslogic raw_url << web_host if web_custom_port? - raw_url << ":#{web_port}" + raw_url << ":#{web_port}" end raw_url @@ -120,6 +120,22 @@ class Settings < Settingslogic app['backup_keep_time'] || 0 end + def ldap_enabled? + ldap && ldap['enabled'] + rescue Settingslogic::MissingSetting + false + end + + def omniauth_enabled? + omniauth && omniauth['enabled'] + rescue Settingslogic::MissingSetting + false + end + + def omniauth_providers + (omniauth_enabled? && omniauth['providers']) || [] + end + def disable_gravatar? app['disable_gravatar'] || false end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 54011ba5..8f3cef5a 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -204,4 +204,21 @@ Devise.setup do |config| # manager.intercept_401 = false # manager.default_strategies(:scope => :user).unshift :some_external_strategy # end + + gl = Gitlab.config + + if gl.ldap_enabled? + config.omniauth :ldap, + :host => gl.ldap['host'], + :base => gl.ldap['base'], + :uid => gl.ldap['uid'], + :port => gl.ldap['port'], + :method => gl.ldap['method'], + :bind_dn => gl.ldap['bind_dn'], + :password => gl.ldap['password'] + end + + gl.omniauth_providers.each do |gl_provider| + config.omniauth gl_provider['name'].to_sym, gl_provider['app_id'], gl_provider['app_secret'] + end end diff --git a/config/initializers/omniauth.rb.sample b/config/initializers/omniauth.rb.sample deleted file mode 100644 index 6e844efd..00000000 --- a/config/initializers/omniauth.rb.sample +++ /dev/null @@ -1,15 +0,0 @@ -# Copy this file to 'omniauth.rb' and configure it as necessary. -# The wiki has further details on configuring each provider. - -Devise.setup do |config| - # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' - - # config.omniauth :ldap, - # :host => 'YOUR_LDAP_SERVER', - # :base => 'THE_BASE_WHERE_YOU_SEARCH_FOR_USERS', - # :uid => 'sAMAccountName', - # :port => 389, - # :method => :plain, - # :bind_dn => 'THE_FULL_DN_OF_THE_USER_YOU_WILL_BIND_WITH', - # :password => 'THE_PASSWORD_OF_THE_BIND_USER' -end diff --git a/config/initializers/resque.rb b/config/initializers/resque.rb new file mode 100644 index 00000000..b333ceee --- /dev/null +++ b/config/initializers/resque.rb @@ -0,0 +1,8 @@ +rails_root = ENV['RAILS_ROOT'] || File.dirname(__FILE__) + '/../..' +rails_env = ENV['RAILS_ENV'] || 'development' +config_file = File.join(rails_root, 'config', 'resque.yml') + +if File.exists?(config_file) + resque_config = YAML.load_file(config_file) + Resque.redis = resque_config[rails_env] +end diff --git a/config/resque.yml.example b/config/resque.yml.example new file mode 100644 index 00000000..cd3d4874 --- /dev/null +++ b/config/resque.yml.example @@ -0,0 +1,3 @@ +development: localhost:6379 +test: localhost:6379 +production: redis.example.com:6379 diff --git a/config/routes.rb b/config/routes.rb index ed5eac0d..cfb9bdb9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,7 +10,7 @@ Gitlab::Application.routes.draw do # Optionally, enable Resque here require 'resque/server' - mount Resque::Server.new, at: '/info/resque', as: 'resque' + mount Resque::Server => '/info/resque', as: 'resque' # Enable Grack support mount Grack::Bundle.new({ @@ -23,14 +23,14 @@ Gitlab::Application.routes.draw do # # Help # - get 'help' => 'help#index' - get 'help/permissions' => 'help#permissions' - get 'help/workflow' => 'help#workflow' - get 'help/api' => 'help#api' - get 'help/web_hooks' => 'help#web_hooks' + get 'help' => 'help#index' + get 'help/permissions' => 'help#permissions' + get 'help/workflow' => 'help#workflow' + get 'help/api' => 'help#api' + get 'help/web_hooks' => 'help#web_hooks' get 'help/system_hooks' => 'help#system_hooks' - get 'help/markdown' => 'help#markdown' - get 'help/ssh' => 'help#ssh' + get 'help/markdown' => 'help#markdown' + get 'help/ssh' => 'help#ssh' # # Admin Area @@ -43,19 +43,19 @@ Gitlab::Application.routes.draw do put :unblock end end - resources :projects, :constraints => { :id => /[^\/]+/ } do + resources :projects, constraints: { id: /[^\/]+/ } do member do get :team put :team_update end end - resources :team_members, :only => [:edit, :update, :destroy] - resources :hooks, :only => [:index, :create, :destroy] do + resources :team_members, only: [:edit, :update, :destroy] + resources :hooks, only: [:index, :create, :destroy] do get :test end - resource :logs - resource :resque, :controller => 'resque' - root :to => "dashboard#index" + resource :logs, only: [:show] + resource :resque, controller: 'resque', only: [:show] + root to: "dashboard#index" end get "errors/githost" @@ -63,38 +63,39 @@ Gitlab::Application.routes.draw do # # Profile Area # - get "profile/password", :to => "profile#password" - put "profile/password", :to => "profile#password_update" - get "profile/token", :to => "profile#token" - put "profile/reset_private_token", :to => "profile#reset_private_token" - get "profile", :to => "profile#show" - get "profile/design", :to => "profile#design" - put "profile/update", :to => "profile#update" + get "profile/account" => "profile#account" + get "profile/history" => "profile#history" + put "profile/password" => "profile#password_update" + get "profile/token" => "profile#token" + put "profile/reset_private_token" => "profile#reset_private_token" + get "profile" => "profile#show" + get "profile/design" => "profile#design" + put "profile/update" => "profile#update" + resources :keys # # Dashboard Area # - get "dashboard", :to => "dashboard#index" - get "dashboard/issues", :to => "dashboard#issues" - get "dashboard/merge_requests", :to => "dashboard#merge_requests" + get "dashboard" => "dashboard#index" + get "dashboard/issues" => "dashboard#issues" + get "dashboard/merge_requests" => "dashboard#merge_requests" - resources :projects, :constraints => { :id => /[^\/]+/ }, :only => [:new, :create] + resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] - devise_for :users, :controllers => { :omniauth_callbacks => :omniauth_callbacks } + devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks } # # Project Area # - resources :projects, :constraints => { :id => /[^\/]+/ }, :except => [:new, :create, :index], :path => "/" do + resources :projects, constraints: { id: /[^\/]+/ }, except: [:new, :create, :index], path: "/" do member do - get "team" get "wall" get "graph" get "files" end - resources :wikis, :only => [:show, :edit, :destroy, :create] do + resources :wikis, only: [:show, :edit, :destroy, :create] do collection do get :pages end @@ -113,46 +114,45 @@ Gitlab::Application.routes.draw do end resources :deploy_keys - resources :protected_branches, :only => [:index, :create, :destroy] + resources :protected_branches, only: [:index, :create, :destroy] - resources :refs, :only => [], :path => "/" do + resources :refs, only: [], path: "/" do collection do get "switch" end member do - get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } - get "logs_tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } + get "tree", constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } + get "logs_tree", constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } get "blob", - :constraints => { - :id => /[a-zA-Z.0-9\/_\-]+/, - :path => /.*/ + constraints: { + id: /[a-zA-Z.0-9\/_\-]+/, + path: /.*/ } - # tree viewer get "tree/:path" => "refs#tree", - :as => :tree_file, - :constraints => { - :id => /[a-zA-Z.0-9\/_\-]+/, - :path => /.*/ + as: :tree_file, + constraints: { + id: /[a-zA-Z.0-9\/_\-]+/, + path: /.*/ } # tree viewer get "logs_tree/:path" => "refs#logs_tree", - :as => :logs_file, - :constraints => { - :id => /[a-zA-Z.0-9\/_\-]+/, - :path => /.*/ + as: :logs_file, + constraints: { + id: /[a-zA-Z.0-9\/_\-]+/, + path: /.*/ } # blame get "blame/:path" => "refs#blame", - :as => :blame_file, - :constraints => { - :id => /[a-zA-Z.0-9\/_\-]+/, - :path => /.*/ + as: :blame_file, + constraints: { + id: /[a-zA-Z.0-9\/_\-]+/, + path: /.*/ } end end @@ -177,7 +177,7 @@ Gitlab::Application.routes.draw do end end - resources :hooks, :only => [:index, :create, :destroy] do + resources :hooks, only: [:index, :create, :destroy] do member do get :test end @@ -191,9 +191,10 @@ Gitlab::Application.routes.draw do get :patch end end + resources :team, controller: 'team_members', only: [:index] resources :team_members resources :milestones - resources :labels, :only => [:index] + resources :labels, only: [:index] resources :issues do collection do @@ -202,11 +203,12 @@ Gitlab::Application.routes.draw do get :search end end - resources :notes, :only => [:index, :create, :destroy] do + resources :notes, only: [:index, :create, :destroy] do collection do post :preview end end end - root :to => "dashboard#index" + + root to: "dashboard#index" end diff --git a/config/unicorn.rb.orig b/config/unicorn.rb.example similarity index 100% rename from config/unicorn.rb.orig rename to config/unicorn.rb.example diff --git a/doc/api/README.md b/doc/api/README.md index 93919b42..9741072c 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -34,3 +34,4 @@ When listing resources you can pass the following parameters: + [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) ++ [SSH Keys](https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/keys.md) diff --git a/doc/api/keys.md b/doc/api/keys.md new file mode 100644 index 00000000..d22b22e2 --- /dev/null +++ b/doc/api/keys.md @@ -0,0 +1,79 @@ +## List keys + +Get a list of currently authenticated user's keys. + +``` +GET /keys +``` + +```json +[ + { + "id": 1, + "title" : "Public key" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + }, + { + "id": 3, + "title" : "Another Public key" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + } +] +``` + +## Single key + +Get a single key. + +``` +GET /keys/:id +``` + +Parameters: + ++ `id` (required) - The ID of a key + +```json +{ + "id": 1, + "title" : "Public key" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4 + 596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4 + soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" +} +``` +## Add key + +Create new key owned by currently authenticated user + +``` +POST /keys +``` + +Parameters: + ++ `title` (required) - new SSH Key's title ++ `key` (required) - new SSH key + +Will return created key with status `201 Created` on success, or `404 Not +found` on fail. + +## Delete key + +Delete key owned by currently authenticated user + +``` +DELETE /keys/:id +``` + +Parameters: + ++ `id` (required) - key ID + +Will return `200 OK` on success, or `404 Not Found` on fail. + + diff --git a/doc/api/projects.md b/doc/api/projects.md index 72874e59..73d6adc9 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -173,6 +173,50 @@ Parameters: Will return status `200 OK` on success, or `404 Not found` on fail. +## Get project hooks + +Get hooks for project + +``` +GET /projects/:id/hooks +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project + +Will return hooks with status `200 OK` on success, or `404 Not found` on fail. + +## Add project hook + +Add hook to project + +``` +POST /projects/:id/hooks +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `url` (required) - The hook URL + +Will return status `201 Created` on success, or `404 Not found` on fail. + +## Delete project hook + +Delete hook from project + +``` +DELETE /projects/:id/hooks +``` + +Parameters: + ++ `id` (required) - The ID or code name of a project ++ `hook_id` (required) - The ID of hook to delete + +Will return status `200 OK` on success, or `404 Not found` on fail. + ## Project repository branches Get a list of repository branches from a project, sorted by name alphabetically. diff --git a/doc/development.md b/doc/development.md index 55be2bc3..67bcb8e1 100644 --- a/doc/development.md +++ b/doc/development.md @@ -36,10 +36,10 @@ ### 3. Run Tests # All in one - bundle exec gitlab:test + bundle exec rake gitlab:test # Rspec bundle exec rake spec - # Cucumber - bundle exec rake cucumber + # Spinach + bundle exec rake spinach diff --git a/doc/installation.md b/doc/installation.md index af169d81..865cde3c 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -152,7 +152,14 @@ and ensure you have followed all of the above steps carefully. sudo pip install pygments sudo gem install bundler cd /home/gitlab + + # Get gitlab code. Use this for stable setup sudo -H -u gitlab git clone -b stable https://github.com/gitlabhq/gitlabhq.git gitlab + + # Skip this for stable setup. + # Master branch (recent changes, less stable) + sudo -H -u gitlab git clone -b master https://github.com/gitlabhq/gitlabhq.git gitlab + cd gitlab # Rename config files @@ -244,6 +251,14 @@ You can login via web using admin generated with setup: # if you run this as root /home/gitlab/gitlab/tmp/pids/resque_worker.pid will be owned by root # causing the resque worker not to start via init script on next boot/service restart +## Customizing Resque's Redis connection + +If you'd like Resque to connect to a Redis server on a non-standard port or on +a different host, you can configure its connection string in the +**config/resque.yml** file: + + production: redis.example.com:6379 + **Ok - we have a working application now. ** **But keep going - there are some things that should be done ** @@ -252,7 +267,7 @@ You can login via web using admin generated with setup: ## 1. Unicorn cd /home/gitlab/gitlab - sudo -u gitlab cp config/unicorn.rb.orig config/unicorn.rb + sudo -u gitlab cp config/unicorn.rb.example config/unicorn.rb sudo -u gitlab bundle exec unicorn_rails -c config/unicorn.rb -E production -D ## 2. Nginx @@ -269,7 +284,6 @@ You can login via web using admin generated with setup: # of the host serving GitLab. sudo vim /etc/nginx/sites-enabled/gitlab - # Restart nginx: /etc/init.d/nginx restart diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index 98bb4980..9756bc7f 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -1,9 +1,9 @@ Feature: Dashboard - Background: - Given I signin as a user + Background: + Given I sign in as a user And I own project "Shop" And project "Shop" has push event - And I visit dashboard page + And I visit dashboard page Scenario: I should see projects list Then I should see "New Project" link @@ -25,4 +25,3 @@ Feature: Dashboard And user with name "John Doe" left project "Shop" When I visit dashboard page Then I should see "John Doe left project Shop" event - diff --git a/features/dashboard/issues.feature b/features/dashboard/issues.feature index c3361bb3..895b89aa 100644 --- a/features/dashboard/issues.feature +++ b/features/dashboard/issues.feature @@ -1,8 +1,8 @@ Feature: Dashboard Issues - Background: - Given I signin as a user + Background: + Given I sign in as a user And I have assigned issues - And I visit dashboard issues page + And I visit dashboard issues page Scenario: I should see issues list Then I should see issues assigned to me diff --git a/features/dashboard/merge_requests.feature b/features/dashboard/merge_requests.feature index 90b8749c..cad65b0d 100644 --- a/features/dashboard/merge_requests.feature +++ b/features/dashboard/merge_requests.feature @@ -1,8 +1,8 @@ -Feature: Dashboard MR - Background: - Given I signin as a user +Feature: Dashboard Merge Requests + Background: + Given I sign in as a user And I have authored merge requests - And I visit dashboard merge requests page + And I visit dashboard merge requests page Scenario: I should see projects list Then I should see my merge requests diff --git a/features/dashboard/search.feature b/features/dashboard/search.feature index f053fe86..91d870f4 100644 --- a/features/dashboard/search.feature +++ b/features/dashboard/search.feature @@ -1,11 +1,9 @@ Feature: Dashboard Search - Background: - Given I signin as a user + Background: + Given I sign in as a user And I own project "Shop" - And I visit dashboard search page + And I visit dashboard search page - Scenario: I should see project i'm looking for + Scenario: I should see project I am looking for Given I search for "Sho" Then I should see "Shop" project link - - diff --git a/features/profile/profile.feature b/features/profile/profile.feature index afda4b55..134cabb5 100644 --- a/features/profile/profile.feature +++ b/features/profile/profile.feature @@ -1,6 +1,6 @@ Feature: Profile - Background: - Given I signin as a user + Background: + Given I sign in as a user Scenario: I look at my profile Given I visit profile page @@ -12,11 +12,11 @@ Feature: Profile And I should see new contact info Scenario: I change my password - Given I visit profile password page + Given I visit profile account page Then I change my password And I should be redirected to sign in page Scenario: I reset my token - Given I visit profile token page + Given I visit profile account page Then I reset my token And I should see new token diff --git a/features/profile/ssh_keys.feature b/features/profile/ssh_keys.feature index c81503ed..018d124e 100644 --- a/features/profile/ssh_keys.feature +++ b/features/profile/ssh_keys.feature @@ -1,13 +1,10 @@ -Feature: SSH Keys - Background: - Given I signin as a user - And I have ssh keys: - | title | - | ssh-rsa Work | - | ssh-rsa Home | +Feature: Profile SSH Keys + Background: + Given I sign in as a user + And I have ssh key "ssh-rsa Work" And I visit profile keys page - Scenario: I should see SSH keys + Scenario: I should see ssh keys Then I should see my ssh keys Scenario: Add new ssh key diff --git a/features/projects/commits/branches.feature b/features/project/commits/branches.feature similarity index 66% rename from features/projects/commits/branches.feature rename to features/project/commits/branches.feature index 74575c51..4fa4dc26 100644 --- a/features/projects/commits/branches.feature +++ b/features/project/commits/branches.feature @@ -1,6 +1,6 @@ -Feature: Browse branches - Background: - Given I signin as a user +Feature: Project Browse branches + Background: + Given I sign in as a user And I own project "Shop" And project "Shop" has protected branches Given I visit project branches page @@ -16,8 +16,11 @@ Feature: Browse branches Given I click link "Protected" Then I should see "Shop" protected branches list - Scenario: I can download project by branch + # @wip + # Scenario: I can download project by branch - Scenario: I can view protected branches + # @wip + # Scenario: I can view protected branches - Scenario: I can manage protected branches + # @wip + # Scenario: I can manage protected branches diff --git a/features/projects/commits/commit_comments.feature b/features/project/commits/commit_comments.feature similarity index 72% rename from features/projects/commits/commit_comments.feature rename to features/project/commits/commit_comments.feature index 9bd56d29..5acf541a 100644 --- a/features/projects/commits/commit_comments.feature +++ b/features/project/commits/commit_comments.feature @@ -1,6 +1,6 @@ -Feature: Comment commit - Background: - Given I signin as a user +Feature: Project Comment commit + Background: + Given I sign in as a user And I own project "Shop" Given I visit project commit page diff --git a/features/projects/commits/commits.feature b/features/project/commits/commits.feature similarity index 82% rename from features/projects/commits/commits.feature rename to features/project/commits/commits.feature index 69d39d78..53de6e6a 100644 --- a/features/projects/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -1,6 +1,6 @@ -Feature: Browse commits - Background: - Given I signin as a user +Feature: Project Browse commits + Background: + Given I sign in as a user And I own project "Shop" Given I visit project commits page @@ -18,5 +18,4 @@ Feature: Browse commits Scenario: I compare refs Given I visit compare refs page And I fill compare fields with refs - And I see compared refs - + And I see compared refs diff --git a/features/projects/commits/tags.feature b/features/project/commits/tags.feature similarity index 53% rename from features/projects/commits/tags.feature rename to features/project/commits/tags.feature index f7899fc3..1ac0f8bf 100644 --- a/features/projects/commits/tags.feature +++ b/features/project/commits/tags.feature @@ -1,10 +1,11 @@ -Feature: Browse tags - Background: - Given I signin as a user +Feature: Project Browse tags + Background: + Given I sign in as a user And I own project "Shop" Given I visit project tags page Scenario: I can see all git tags Then I should see "Shop" all tags list - Scenario: I can download project by tag + # @wip + # Scenario: I can download project by tag diff --git a/features/projects/create_project.feature b/features/project/create_project.feature similarity index 91% rename from features/projects/create_project.feature rename to features/project/create_project.feature index 42d25b3f..b7cdfdb8 100644 --- a/features/projects/create_project.feature +++ b/features/project/create_project.feature @@ -4,7 +4,7 @@ Feature: Create Project Should be able to create a new one Scenario: User create a project - Given I signin as a user + Given I sign in as a user When I visit new project page And fill project form with valid data Then I should see project page diff --git a/features/projects/issues/issues.feature b/features/project/issues/issues.feature similarity index 97% rename from features/projects/issues/issues.feature rename to features/project/issues/issues.feature index b2301b3f..596e8bd7 100644 --- a/features/projects/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -1,6 +1,6 @@ -Feature: Issues +Feature: Project Issues Background: - Given I signin as a user + Given I sign in as a user And I own project "Shop" And project "Shop" have "Release 0.4" open issue And project "Shop" have "Release 0.3" closed issue @@ -79,4 +79,3 @@ Feature: Issues When I select first assignee from "Shop" project And I click link "New Issue" Then I should see first assignee from "Shop" as selected assignee - diff --git a/features/project/issues/labels.feature b/features/project/issues/labels.feature new file mode 100644 index 00000000..e601a41b --- /dev/null +++ b/features/project/issues/labels.feature @@ -0,0 +1,10 @@ +Feature: Project Labels + Background: + Given I sign in as a user + And I own project "Shop" + And project "Shop" have issues tags: "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/issues/milestones.feature b/features/project/issues/milestones.feature similarity index 79% rename from features/projects/issues/milestones.feature rename to features/project/issues/milestones.feature index d78096a4..a57f67d6 100644 --- a/features/projects/issues/milestones.feature +++ b/features/project/issues/milestones.feature @@ -1,9 +1,9 @@ -Feature: Milestones +Feature: Project Milestones Background: - Given I signin as a user + Given I sign in as a user And I own project "Shop" And project "Shop" has milestone "v2.2" - Given I visit project "Shop" milestones page + Given I visit project "Shop" milestones page Scenario: I should see active milestones Then I should see milestone "v2.2" diff --git a/features/projects/merge_requests.feature b/features/project/merge_requests.feature similarity index 92% rename from features/projects/merge_requests.feature rename to features/project/merge_requests.feature index 54b6ccde..80f00986 100644 --- a/features/projects/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -1,10 +1,10 @@ -Feature: Merge Requests +Feature: Project Merge Requests Background: - Given I signin as a user + Given I sign in as a user And I own project "Shop" And project "Shop" have "Bug NS-04" open merge request And project "Shop" have "Feature NS-03" closed merge request - And I visit project "Shop" merge requests page + And I visit project "Shop" merge requests page Scenario: I should see open merge requests Then I should see "Bug NS-04" in merge requests diff --git a/features/projects/network.feature b/features/project/network.feature similarity index 81% rename from features/projects/network.feature rename to features/project/network.feature index 61c05eb3..31ce5ad3 100644 --- a/features/projects/network.feature +++ b/features/project/network.feature @@ -1,10 +1,9 @@ -@javascript Feature: Project Network Graph - Background: - Given I signin as a user + Given I sign in as a user And I own project "Shop" And I visit project "Shop" network page + @javascript Scenario: I should see project network Then page should have network graph diff --git a/features/project/project.feature b/features/project/project.feature new file mode 100644 index 00000000..1c9f201d --- /dev/null +++ b/features/project/project.feature @@ -0,0 +1,14 @@ +Feature: Projects + Background: + Given I signin as a user + And I own project "Shop" + And I visit project "Shop" page + + # @wip + # Scenario: I should see project activity + + # @wip + # Scenario: I edit project + + # @wip + # Scenario: I visit attachments diff --git a/features/projects/source/browse_files.feature b/features/project/source/browse_files.feature similarity index 71% rename from features/projects/source/browse_files.feature rename to features/project/source/browse_files.feature index 04aebc19..b12b0ee3 100644 --- a/features/projects/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -1,6 +1,6 @@ -Feature: Browse git repo - Background: - Given I signin as a user +Feature: Project Browse files + Background: + Given I sign in as a user And I own project "Shop" Given I visit project source page @@ -12,12 +12,10 @@ Feature: Browse git repo Then I should see files from repository for "8470d70" Scenario: I browse file content - Given I click on file from repo + Given I click on "Gemfile" file in repo Then I should see it content Scenario: I browse raw file - Given I visit blob file from repo - And I click on raw button + Given I visit blob file from repo + And I click link "raw" Then I should see raw file content - - diff --git a/features/project/source/git_blame.feature b/features/project/source/git_blame.feature new file mode 100644 index 00000000..93ed20a8 --- /dev/null +++ b/features/project/source/git_blame.feature @@ -0,0 +1,10 @@ +Feature: Project Browse git repo + Background: + Given I sign in as a user + And I own project "Shop" + Given I visit project source page + + Scenario: I blame file + Given I click on "Gemfile" file in repo + And I click blame button + Then I should see git file blame diff --git a/features/projects/team_management.feature b/features/project/team_management.feature similarity index 78% rename from features/projects/team_management.feature rename to features/project/team_management.feature index b5b485e2..ae0c459f 100644 --- a/features/projects/team_management.feature +++ b/features/project/team_management.feature @@ -1,11 +1,11 @@ Feature: Project Team management - Background: - Given I signin as a user + Background: + Given I sign in as a user And I own project "Shop" - And gitlab user "Mike" - And gitlab user "Sam" + And gitlab user "Mike" + And gitlab user "Sam" And "Sam" is "Shop" developer - And I visit project "Shop" team page + And I visit project "Shop" team page Scenario: See all team members Then I should be able to see myself in team @@ -20,7 +20,7 @@ Feature: Project Team management Scenario: Update user access Given I should see "Sam" in team list as "Developer" And I change "Sam" role to "Reporter" - Then I visit project "Shop" team page + Then I visit project "Shop" team page And I should see "Sam" in team list as "Reporter" Scenario: View team member profile @@ -30,6 +30,5 @@ Feature: Project Team management Scenario: Cancel team member Given I click link "Sam" And I click link "Remove from team" - Then I visit project "Shop" team page + Then I visit project "Shop" team page And I should not see "Sam" in team list - diff --git a/features/projects/wall.feature b/features/project/wall.feature similarity index 63% rename from features/projects/wall.feature rename to features/project/wall.feature index ed675e2c..c38d046a 100644 --- a/features/projects/wall.feature +++ b/features/project/wall.feature @@ -1,17 +1,16 @@ -@javascript Feature: Project Wall In order to use Project Wall - A user - Should be able to read & write messages + A user should be able to read and write messages Background: - Given I signin as a user + Given I sign in as a user And I own project "Shop" - And I visit project "Shop" wall page + And I visit project "Shop" wall page + @javascript Scenario: Write comment Given I write new comment "my special test message" Then I should see project wall note "my special test message" - Then I visit project "Shop" wall page + Then I visit project "Shop" wall page And I should see project wall note "my special test message" diff --git a/features/projects/wiki.feature b/features/project/wiki.feature similarity index 83% rename from features/projects/wiki.feature rename to features/project/wiki.feature index 4441ada2..51370565 100644 --- a/features/projects/wiki.feature +++ b/features/project/wiki.feature @@ -1,6 +1,6 @@ -Feature: Wiki - Background: - Given I signin as a user +Feature: Project Wiki + Background: + Given I sign in as a user And I own project "Shop" Given I visit project wiki page diff --git a/features/projects/deploy_keys.feature b/features/projects/deploy_keys.feature deleted file mode 100644 index e69de29b..00000000 diff --git a/features/projects/issues/labels.feature b/features/projects/issues/labels.feature deleted file mode 100644 index 5a20bfd6..00000000 --- a/features/projects/issues/labels.feature +++ /dev/null @@ -1,13 +0,0 @@ -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/project.feature b/features/projects/project.feature deleted file mode 100644 index 895a928f..00000000 --- a/features/projects/project.feature +++ /dev/null @@ -1,11 +0,0 @@ -Feature: Project - Background: - Given I signin as a user - And I own project "Shop" - And I visit project "Shop" page - - Scenario: I should see project activity - - Scenario: I edit project - - Scenario: I visit attachments diff --git a/features/projects/snippets.feature b/features/projects/snippets.feature deleted file mode 100644 index e69de29b..00000000 diff --git a/features/projects/source/git_blame.feature b/features/projects/source/git_blame.feature deleted file mode 100644 index 6aa6be47..00000000 --- a/features/projects/source/git_blame.feature +++ /dev/null @@ -1,10 +0,0 @@ -Feature: Browse git repo - Background: - Given I signin as a user - And I own project "Shop" - Given I visit project source page - - Scenario: I blame file - Given I click on file from repo - And I click blame button - Then I should see git file blame diff --git a/features/projects/web_hooks.feature b/features/projects/web_hooks.feature deleted file mode 100644 index e69de29b..00000000 diff --git a/features/step_definitions/common_steps.rb b/features/step_definitions/common_steps.rb deleted file mode 100644 index e9023f92..00000000 --- a/features/step_definitions/common_steps.rb +++ /dev/null @@ -1,21 +0,0 @@ -include LoginHelpers - -Given /^I signin as a user$/ do - login_as :user -end - -When /^I click link "(.*?)"$/ do |link| - click_link link -end - -When /^I click button "(.*?)"$/ do |button| - click_button button -end - -When /^I fill in "(.*?)" with "(.*?)"$/ do |field, value| - fill_in field, :with => value -end - -Given /^show me page$/ do - save_and_open_page -end diff --git a/features/step_definitions/dashboard_steps.rb b/features/step_definitions/dashboard_steps.rb deleted file mode 100644 index 3ddc68e9..00000000 --- a/features/step_definitions/dashboard_steps.rb +++ /dev/null @@ -1,136 +0,0 @@ -Then /^I should see "(.*?)" link$/ do |arg1| - page.should have_link(arg1) -end - -Then /^I should see "(.*?)" project link$/ do |arg1| - page.should have_link(arg1) -end - -Then /^I should see project "(.*?)" activity feed$/ do |arg1| - project = Project.find_by_name(arg1) - page.should have_content "#{@user.name} pushed new branch new_design at #{project.name}" -end - -Given /^project "(.*?)" has push event$/ do |arg1| - @project = Project.find_by_name(arg1) - - data = { - :before => "0000000000000000000000000000000000000000", - :after => "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", - :ref => "refs/heads/new_design", - :user_id => @user.id, - :user_name => @user.name, - :repository => { - :name => @project.name, - :url => "localhost/rubinius", - :description => "", - :homepage => "localhost/rubinius", - :private => true - } - } - - @event = Event.create( - :project => @project, - :action => Event::Pushed, - :data => data, - :author_id => @user.id - ) -end - -Then /^I should see last push widget$/ do - page.should have_content "Your pushed to branch new_design" - page.should have_link "Create Merge Request" -end - -Then /^I click "(.*?)" link$/ do |arg1| - click_link arg1 #Create Merge Request" -end - -Then /^I see prefilled new Merge Request page$/ do - current_path.should == new_project_merge_request_path(@project) - find("#merge_request_source_branch").value.should == "new_design" - find("#merge_request_target_branch").value.should == "master" - find("#merge_request_title").value.should == "New Design" -end - -Given /^I visit dashboard search page$/ do - visit search_path -end - -Given /^I search for "(.*?)"$/ do |arg1| - fill_in "dashboard_search", :with => arg1 - click_button "Search" -end - -Then /^I should see issues assigned to me$/ do - issues = @user.issues - issues.each do |issue| - page.should have_content(issue.title[0..10]) - page.should have_content(issue.project.name) - end -end - -Then /^I should see my merge requests$/ do - merge_requests = @user.merge_requests - merge_requests.each do |mr| - page.should have_content(mr.title[0..10]) - page.should have_content(mr.project.name) - end -end - -Given /^I have assigned issues$/ do - project = Factory :project - project.add_access(@user, :read, :write) - - issue1 = Factory :issue, - :author => @user, - :assignee => @user, - :project => project - - issue2 = Factory :issue, - :author => @user, - :assignee => @user, - :project => project -end - -Given /^I have authored merge requests$/ do - project1 = Factory :project - - project2 = Factory :project - - project1.add_access(@user, :read, :write) - project2.add_access(@user, :read, :write) - - merge_request1 = Factory :merge_request, - :author => @user, - :project => project1 - - merge_request2 = Factory :merge_request, - :author => @user, - :project => project2 -end - -Given /^user with name "(.*?)" joined project "(.*?)"$/ do |user_name, project_name| - user = Factory.create(:user, {name: user_name}) - project = Project.find_by_name project_name - Event.create( - project: project, - author_id: user.id, - action: Event::Joined - ) -end - -Given /^user with name "(.*?)" left project "(.*?)"$/ do |user_name, project_name| - user = User.find_by_name user_name - project = Project.find_by_name project_name - Event.create( - project: project, - author_id: user.id, - action: Event::Left - ) -end - -Then /^I should see "(.*?)" event$/ do |event_text| - page.should have_content(event_text) -end - diff --git a/features/step_definitions/profile/profile_keys_steps.rb b/features/step_definitions/profile/profile_keys_steps.rb deleted file mode 100644 index 25926c53..00000000 --- a/features/step_definitions/profile/profile_keys_steps.rb +++ /dev/null @@ -1,34 +0,0 @@ -Given /^I visit profile keys page$/ do - visit keys_path -end - -Then /^I should see my ssh keys$/ do - @user.keys.each do |key| - page.should have_content(key.title) - end -end - -Given /^I have ssh keys:$/ do |table| - table.hashes.each do |row| - Factory :key, :user => @user, :title => row[:title], :key => "jfKLJDFKSFJSHFJ#{row[:title]}" - end -end - -Given /^I submit new ssh key "(.*?)"$/ do |arg1| - fill_in "key_title", :with => arg1 - fill_in "key_key", :with => "ssh-rsa publickey234=" - click_button "Save" -end - -Then /^I should see new ssh key "(.*?)"$/ do |arg1| - key = Key.find_by_title(arg1) - page.should have_content(key.title) - page.should have_content(key.key) - current_path.should == key_path(key) -end - -Then /^I should not see "(.*?)" ssh key$/ do |arg1| - within "#keys-table" do - page.should_not have_content(arg1) - end -end diff --git a/features/step_definitions/profile/profile_steps.rb b/features/step_definitions/profile/profile_steps.rb deleted file mode 100644 index 525d43f5..00000000 --- a/features/step_definitions/profile/profile_steps.rb +++ /dev/null @@ -1,39 +0,0 @@ -Then /^I should see my profile info$/ do - page.should have_content "Profile" - page.should have_content @user.name - page.should have_content @user.email -end - -Then /^I change my password$/ do - fill_in "user_password", :with => "222333" - fill_in "user_password_confirmation", :with => "222333" - click_button "Save" -end - -Then /^I should be redirected to sign in page$/ do - current_path.should == new_user_session_path -end - -Then /^I reset my token$/ do - @old_token = @user.private_token - click_button "Reset" -end - -Then /^I should see new token$/ do - find("#token").value.should_not == @old_token - find("#token").value.should == @user.reload.private_token -end - -Then /^I change my contact info$/ do - fill_in "user_skype", :with => "testskype" - fill_in "user_linkedin", :with => "testlinkedin" - fill_in "user_twitter", :with => "testtwitter" - click_button "Save" - @user.reload -end - -Then /^I should see new contact info$/ do - @user.skype.should == 'testskype' - @user.linkedin.should == 'testlinkedin' - @user.twitter.should == 'testtwitter' -end diff --git a/features/step_definitions/project/browse_code_steps.rb b/features/step_definitions/project/browse_code_steps.rb deleted file mode 100644 index d2ed9a0a..00000000 --- a/features/step_definitions/project/browse_code_steps.rb +++ /dev/null @@ -1,38 +0,0 @@ -Then /^I should see files from repository$/ do - page.should have_content("app") - page.should have_content("History") - page.should have_content("Gemfile") -end - -Then /^I should see files from repository for "(.*?)"$/ do |arg1| - current_path.should == tree_project_ref_path(@project, arg1) - page.should have_content("app") - page.should have_content("History") - page.should have_content("Gemfile") -end - -Given /^I click on file from repo$/ do - click_link "Gemfile" -end - -Then /^I should see it content$/ do - page.should have_content("rubygems.org") -end - -Given /^I click on raw button$/ do - click_link "raw" -end - -Then /^I should see raw file content$/ do - page.source.should == ValidCommit::BLOB_FILE -end - -Given /^I click blame button$/ do - click_link "blame" -end - -Then /^I should see git file blame$/ do - page.should have_content("rubygems.org") - page.should have_content("Dmitriy Zaporozhets") - page.should have_content("bc3735004cb Moving to rails 3.2") -end diff --git a/features/step_definitions/project/project_commits_steps.rb b/features/step_definitions/project/project_commits_steps.rb deleted file mode 100644 index 7f20ade4..00000000 --- a/features/step_definitions/project/project_commits_steps.rb +++ /dev/null @@ -1,64 +0,0 @@ -Then /^I see project commits$/ do - current_path.should == project_commits_path(@project) - - commit = @project.commit - page.should have_content(@project.name) - page.should have_content(commit.message) - page.should have_content(commit.id.to_s[0..5]) -end - -Given /^I click atom feed link$/ do - click_link "Feed" -end - -Then /^I see commits atom feed$/ do - commit = CommitDecorator.decorate(@project.commit) - page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", :text => "Recent commits to #{@project.name}") - page.body.should have_selector("author email", :text => commit.author_email) - page.body.should have_selector("entry summary", :text => commit.description) -end - -Then /^I see commit info$/ do - page.should have_content ValidCommit::MESSAGE - page.should have_content "Showing 1 changed file" -end - -Given /^I fill compare fields with refs$/ do - fill_in "from", :with => "master" - fill_in "to", :with => "stable" - click_button "Compare" -end - -Given /^I see compared refs$/ do - page.should have_content "Commits (27)" - page.should have_content "Compare View" - page.should have_content "Showing 73 changed files" -end - -Then /^I should see "(.*?)" recent branches list$/ do |arg1| - page.should have_content("Branches") - page.should have_content("master") -end - -Then /^I should see "(.*?)" all branches list$/ do |arg1| - page.should have_content("Branches") - page.should have_content("master") -end - -Then /^I should see "(.*?)" all tags list$/ do |arg1| - page.should have_content("Tags") - page.should have_content("v1.2.1") -end - -Then /^I should see "(.*?)" protected branches list$/ do |arg1| - within "table" do - page.should have_content "stable" - page.should_not have_content "master" - end -end - -Given /^project "(.*?)" has protected branches$/ do |arg1| - project = Project.find_by_name(arg1) - project.protected_branches.create(:name => "stable") -end diff --git a/features/step_definitions/project/project_issues_steps.rb b/features/step_definitions/project/project_issues_steps.rb deleted file mode 100644 index d78da53c..00000000 --- a/features/step_definitions/project/project_issues_steps.rb +++ /dev/null @@ -1,81 +0,0 @@ -Given /^project "(.*?)" have "(.*?)" open issue$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - Factory.create(:issue, :title => arg2, :project => project, :author => project.users.first) -end - -Given /^project "(.*?)" have "(.*?)" closed issue$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - Factory.create(:issue, :title => arg2, :project => project, :author => project.users.first, :closed => true) -end - -Given /^I should see "(.*?)" in issues$/ do |arg1| - page.should have_content arg1 -end - -Given /^I should not see "(.*?)" in issues$/ do |arg1| - page.should_not have_content arg1 -end - -Then /^I should see issue "(.*?)"$/ do |arg1| - issue = Issue.find_by_title(arg1) - page.should have_content issue.title - page.should have_content issue.author_name - page.should have_content issue.project.name -end - -Given /^I submit new issue "(.*?)"$/ do |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 - -Given /^I fill in issue search with "(.*?)"$/ do |arg1| - # Because fill_in, with: "" triggers nothing - # we need to trigger a keyup event - if arg1 == '' - page.execute_script("$('.issue_search').val('').keyup();"); - end - fill_in 'issue_search', with: arg1 -end - -When /^I select milestone "(.*?)"$/ do |milestone_title| - select milestone_title, from: "milestone_id" -end - -Then /^I should see selected milestone with title "(.*?)"$/ do |milestone_title| - issues_milestone_selector = "#issue_milestone_id_chzn/a" - wait_until{ page.has_content?("Details") } - page.find(issues_milestone_selector).should have_content(milestone_title) -end - -When /^I select first assignee from "(.*?)" project$/ do |project_name| - project = Project.find_by_name project_name - first_assignee = project.users.first - select first_assignee.name, from: "assignee_id" -end - -Then /^I should see first assignee from "(.*?)" as selected assignee$/ do |project_name| - issues_assignee_selector = "#issue_assignee_id_chzn/a" - wait_until{ page.has_content?("Details") } - project = Project.find_by_name project_name - assignee_name = project.users.first.name - page.find(issues_assignee_selector).should have_content(assignee_name) -end diff --git a/features/step_definitions/project/project_merge_requests_steps.rb b/features/step_definitions/project/project_merge_requests_steps.rb deleted file mode 100644 index fddb18ad..00000000 --- a/features/step_definitions/project/project_merge_requests_steps.rb +++ /dev/null @@ -1,38 +0,0 @@ -Given /^project "(.*?)" have "(.*?)" open merge request$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - Factory.create(:merge_request, :title => arg2, :project => project, :author => project.users.first) -end - -Given /^project "(.*?)" have "(.*?)" closed merge request$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - Factory.create(:merge_request, :title => arg2, :project => project, :author => project.users.first, :closed => true) -end - -Then /^I should see "(.*?)" in merge requests$/ do |arg1| - page.should have_content arg1 -end - -Then /^I should not see "(.*?)" in merge requests$/ do |arg1| - page.should_not have_content arg1 -end - -Then /^I should see merge request "(.*?)"$/ do |arg1| - merge_request = MergeRequest.find_by_title(arg1) - page.should have_content(merge_request.title[0..10]) - page.should have_content(merge_request.target_branch) - page.should have_content(merge_request.source_branch) -end - -Given /^I submit new merge request "(.*?)"$/ do |arg1| - fill_in "merge_request_title", :with => arg1 - select "master", :from => "merge_request_source_branch" - select "stable", :from => "merge_request_target_branch" - click_button "Save" -end - -Then /^I should see closed merge request "(.*?)"$/ do |arg1| - mr = MergeRequest.find_by_title(arg1) - mr.closed.should be_true - page.should have_content "Closed by" -end - diff --git a/features/step_definitions/project/project_milestones_steps.rb b/features/step_definitions/project/project_milestones_steps.rb deleted file mode 100644 index 936c52df..00000000 --- a/features/step_definitions/project/project_milestones_steps.rb +++ /dev/null @@ -1,33 +0,0 @@ -Given /^project "(.*?)" has milestone "(.*?)"$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - - milestone = Factory :milestone, - :title => arg2, - :project => project - - 3.times do |i| - issue = Factory :issue, - :project => project, - :milestone => milestone - end -end - -Then /^I should see active milestones$/ do - milestone = @project.milestones.first - page.should have_content(milestone.title[0..10]) - page.should have_content(milestone.expires_at) - page.should have_content("Browse Issues") -end - -Then /^I should see milestone "(.*?)"$/ do |arg1| - milestone = @project.milestones.find_by_title(arg1) - page.should have_content(milestone.title[0..10]) - page.should have_content(milestone.expires_at) - page.should have_content("Browse Issues") -end - -Given /^I submit new milestone "(.*?)"$/ do |arg1| - fill_in "milestone_title", :with => arg1 - click_button "Create milestone" -end - diff --git a/features/step_definitions/project/project_team_steps.rb b/features/step_definitions/project/project_team_steps.rb deleted file mode 100644 index 91885e46..00000000 --- a/features/step_definitions/project/project_team_steps.rb +++ /dev/null @@ -1,55 +0,0 @@ -Given /^gitlab user "(.*?)"$/ do |arg1| - Factory :user, :name => arg1 -end - -Given /^"(.*?)" is "(.*?)" developer$/ do |arg1, arg2| - user = User.find_by_name(arg1) - project = Project.find_by_name(arg2) - project.add_access(user, :write) -end - -Then /^I should be able to see myself in team$/ do - page.should have_content(@user.name) - page.should have_content(@user.email) -end - -Then /^I should see "(.*?)" in team list$/ do |arg1| - user = User.find_by_name(arg1) - page.should have_content(user.name) - page.should have_content(user.email) -end - -Given /^I select "(.*?)" as "(.*?)"$/ do |arg1, arg2| - user = User.find_by_name(arg1) - within "#new_team_member" do - select user.name, :from => "user_ids" - select arg2, :from => "project_access" - end - click_button "Save" -end - -Then /^I should see "(.*?)" in team list as "(.*?)"$/ do |arg1, arg2| - user = User.find_by_name(arg1) - role_id = find(".user_#{user.id} #team_member_project_access").value - role_id.should == UsersProject.access_roles[arg2].to_s -end - -Given /^I change "(.*?)" role to "(.*?)"$/ do |arg1, arg2| - user = User.find_by_name(arg1) - within ".user_#{user.id}" do - select arg2, :from => "team_member_project_access" - end -end - -Then /^I should see "(.*?)" team profile$/ do |arg1| - user = User.find_by_name(arg1) - page.should have_content(user.name) - page.should have_content(user.email) - page.should have_content("To team list") -end - -Then /^I should not see "(.*?)" in team list$/ do |arg1| - user = User.find_by_name(arg1) - page.should_not have_content(user.name) - page.should_not have_content(user.email) -end diff --git a/features/step_definitions/project/project_wiki_steps.rb b/features/step_definitions/project/project_wiki_steps.rb deleted file mode 100644 index 31fc050a..00000000 --- a/features/step_definitions/project/project_wiki_steps.rb +++ /dev/null @@ -1,14 +0,0 @@ -Given /^I create Wiki page$/ do - fill_in "Title", :with => 'Test title' - fill_in "Content", :with => '[link test](test)' - click_on "Save" -end - -Then /^I should see newly created wiki page$/ do - page.should have_content("Test title") - page.should have_content("link test") - - click_link "link test" - - page.should have_content("Editing page") -end diff --git a/features/step_definitions/project/projects_steps.rb b/features/step_definitions/project/projects_steps.rb deleted file mode 100644 index d22b805f..00000000 --- a/features/step_definitions/project/projects_steps.rb +++ /dev/null @@ -1,77 +0,0 @@ -When /^I visit new project page$/ do - visit new_project_path -end - -When /^fill project form with valid data$/ do - fill_in 'project_name', :with => 'NewProject' - fill_in 'project_code', :with => 'NPR' - fill_in 'project_path', :with => 'newproject' - click_button "Create project" -end - -Then /^I should see project page$/ do - current_path.should == project_path(Project.last) - page.should have_content('NewProject') -end - -Then /^I should see empty project instuctions$/ do - page.should have_content("git init") - page.should have_content("git remote") - page.should have_content(Project.last.url_to_repo) -end - -Given /^I own project "(.*?)"$/ do |arg1| - @project = Factory :project, :name => arg1 - @project.add_access(@user, :admin) -end - -Given /^I visit project "(.*?)" wall page$/ do |arg1| - project = Project.find_by_name(arg1) - visit wall_project_path(project) -end - -Then /^I should see project wall note "(.*?)"$/ do |arg1| - page.should have_content arg1 -end - -Given /^project "(.*?)" has comment "(.*?)"$/ do |arg1, arg2| - project = Project.find_by_name(arg1) - project.notes.create(:note => arg1, :author => project.users.first) -end - -Given /^I write new comment "(.*?)"$/ do |arg1| - fill_in "note_note", :with => arg1 - click_button "Add Comment" -end - -Given /^I visit project "(.*?)" page$/ do |arg1| - project = Project.find_by_name(arg1) - visit project_path(project) -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 - -Given /^page should have network graph$/ do - page.should have_content "Project Network Graph" - within ".graph" do - page.should have_content "master" - page.should have_content "scss_refactor..." - end -end - -Given /^I leave a comment like "(.*?)"$/ do |arg1| - fill_in "note_note", :with => arg1 - click_button "Add Comment" -end - -Then /^I should see comment "(.*?)"$/ do |arg1| - page.should have_content(arg1) -end diff --git a/features/step_definitions/visit_steps.rb b/features/step_definitions/visit_steps.rb deleted file mode 100644 index 35fc6d44..00000000 --- a/features/step_definitions/visit_steps.rb +++ /dev/null @@ -1,91 +0,0 @@ -Given /^I visit project "(.*?)" issues page$/ do |arg1| - visit project_issues_path(Project.find_by_name(arg1)) -end - -Given /^I visit issue page "(.*?)"$/ do |arg1| - issue = Issue.find_by_title(arg1) - visit project_issue_path(issue.project, issue) -end - -Given /^I visit project "(.*?)" merge requests page$/ do |arg1| - visit project_merge_requests_path(Project.find_by_name(arg1)) -end - -Given /^I visit merge request page "(.*?)"$/ do |arg1| - mr = MergeRequest.find_by_title(arg1) - visit project_merge_request_path(mr.project, mr) -end - -Given /^I visit project "(.*?)" milestones page$/ do |arg1| - @project = Project.find_by_name(arg1) - visit project_milestones_path(@project) -end - -Given /^I visit project commits page$/ do - visit project_commits_path(@project) -end - -Given /^I visit compare refs page$/ do - visit compare_project_commits_path(@project) -end - -Given /^I visit project branches page$/ do - visit branches_project_repository_path(@project) -end - -Given /^I visit project commit page$/ do - visit project_commit_path(@project, ValidCommit::ID) -end - -Given /^I visit project tags page$/ do - visit tags_project_repository_path(@project) -end - -Given /^I click on commit link$/ do - visit project_commit_path(@project, ValidCommit::ID) -end - -Given /^I visit project source page$/ do - visit tree_project_ref_path(@project, @project.root_ref) -end - -Given /^I visit project source page for "(.*?)"$/ do |arg1| - visit tree_project_ref_path(@project, arg1) -end - -Given /^I visit blob file from repo$/ do - visit tree_project_ref_path(@project, ValidCommit::ID, :path => ValidCommit::BLOB_FILE_PATH) -end - -Given /^I visit project "(.*?)" team page$/ do |arg1| - visit team_project_path(Project.find_by_name(arg1)) -end - -Given /^I visit project wiki page$/ do - visit project_wiki_path(@project, :index) -end - -Given /^I visit profile page$/ do - visit profile_path -end - -Given /^I visit profile token page$/ do - visit profile_token_path -end - -Given /^I visit profile password page$/ do - visit profile_password_path -end - -Given /^I visit dashboard page$/ do - visit dashboard_path -end - -Given /^I visit dashboard issues page$/ do - visit dashboard_issues_path -end - -Given /^I visit dashboard merge requests page$/ do - visit dashboard_merge_requests_path -end - diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb new file mode 100644 index 00000000..6c603bbe --- /dev/null +++ b/features/steps/dashboard/dashboard.rb @@ -0,0 +1,92 @@ +class Dashboard < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + Then 'I should see "New Project" link' do + page.should have_link "New Project" + end + + Then 'I should see "Shop" project link' do + page.should have_link "Shop" + end + + Then 'I should see project "Shop" activity feed' do + project = Project.find_by_name("Shop") + page.should have_content "#{@user.name} pushed new branch new_design at #{project.name}" + end + + Then 'I should see last push widget' do + page.should have_content "Your pushed to branch new_design" + page.should have_link "Create Merge Request" + end + + And 'I click "Create Merge Request" link' do + click_link "Create Merge Request" + end + + Then 'I see prefilled new Merge Request page' do + current_path.should == new_project_merge_request_path(@project) + find("#merge_request_source_branch").value.should == "new_design" + find("#merge_request_target_branch").value.should == "master" + find("#merge_request_title").value.should == "New Design" + end + + Given 'user with name "John Doe" joined project "Shop"' do + user = Factory.create(:user, {name: "John Doe"}) + project = Project.find_by_name "Shop" + Event.create( + project: project, + author_id: user.id, + action: Event::Joined + ) + end + + Then 'I should see "John Doe joined project Shop" event' do + page.should have_content "John Doe joined project Shop" + end + + And 'user with name "John Doe" left project "Shop"' do + user = User.find_by_name "John Doe" + project = Project.find_by_name "Shop" + Event.create( + project: project, + author_id: user.id, + action: Event::Left + ) + end + + Then 'I should see "John Doe left project Shop" event' do + page.should have_content "John Doe left project Shop" + end + + And 'I own project "Shop"' do + @project = Factory :project, :name => 'Shop' + @project.add_access(@user, :admin) + end + + And 'project "Shop" has push event' do + @project = Project.find_by_name("Shop") + + data = { + :before => "0000000000000000000000000000000000000000", + :after => "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", + :ref => "refs/heads/new_design", + :user_id => @user.id, + :user_name => @user.name, + :repository => { + :name => @project.name, + :url => "localhost/rubinius", + :description => "", + :homepage => "localhost/rubinius", + :private => true + } + } + + @event = Event.create( + :project => @project, + :action => Event::Pushed, + :data => data, + :author_id => @user.id + ) + end +end diff --git a/features/steps/dashboard/dashboard_issues.rb b/features/steps/dashboard/dashboard_issues.rb new file mode 100644 index 00000000..9368782b --- /dev/null +++ b/features/steps/dashboard/dashboard_issues.rb @@ -0,0 +1,19 @@ +class DashboardIssues < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + Then 'I should see issues assigned to me' do + issues = @user.issues + issues.each do |issue| + page.should have_content(issue.title[0..10]) + page.should have_content(issue.project.name) + end + end + + And 'I have assigned issues' do + project = Factory :project + project.add_access(@user, :read, :write) + + 2.times { Factory :issue, :author => @user, :assignee => @user, :project => project } + end +end diff --git a/features/steps/dashboard/dashboard_merge_requests.rb b/features/steps/dashboard/dashboard_merge_requests.rb new file mode 100644 index 00000000..fc339e75 --- /dev/null +++ b/features/steps/dashboard/dashboard_merge_requests.rb @@ -0,0 +1,23 @@ +class DashboardMergeRequests < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + Then 'I should see my merge requests' do + merge_requests = @user.merge_requests + merge_requests.each do |mr| + page.should have_content(mr.title[0..10]) + page.should have_content(mr.project.name) + end + end + + And 'I have authored merge requests' do + project1 = Factory :project + project2 = Factory :project + + project1.add_access(@user, :read, :write) + project2.add_access(@user, :read, :write) + + merge_request1 = Factory :merge_request, :author => @user, :project => project1 + merge_request2 = Factory :merge_request, :author => @user, :project => project2 + end +end diff --git a/features/steps/dashboard/dashboard_search.rb b/features/steps/dashboard/dashboard_search.rb new file mode 100644 index 00000000..e3585898 --- /dev/null +++ b/features/steps/dashboard/dashboard_search.rb @@ -0,0 +1,18 @@ +class DashboardSearch < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + Given 'I search for "Sho"' do + fill_in "dashboard_search", :with => "Sho" + click_button "Search" + end + + Then 'I should see "Shop" project link' do + page.should have_link "Shop" + end + + And 'I own project "Shop"' do + @project = Factory :project, :name => "Shop" + @project.add_access(@user, :admin) + end +end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb new file mode 100644 index 00000000..d3261a16 --- /dev/null +++ b/features/steps/profile/profile.rb @@ -0,0 +1,44 @@ +class Profile < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + Then 'I should see my profile info' do + page.should have_content "Profile" + page.should have_content @user.name + page.should have_content @user.email + end + + Then 'I change my contact info' do + fill_in "user_skype", :with => "testskype" + fill_in "user_linkedin", :with => "testlinkedin" + fill_in "user_twitter", :with => "testtwitter" + click_button "Save" + @user.reload + end + + And 'I should see new contact info' do + @user.skype.should == 'testskype' + @user.linkedin.should == 'testlinkedin' + @user.twitter.should == 'testtwitter' + end + + Then 'I change my password' do + fill_in "user_password", :with => "222333" + fill_in "user_password_confirmation", :with => "222333" + click_button "Save" + end + + And 'I should be redirected to sign in page' do + current_path.should == new_user_session_path + end + + Then 'I reset my token' do + @old_token = @user.private_token + click_button "Reset" + end + + And 'I should see new token' do + find("#token").value.should_not == @old_token + find("#token").value.should == @user.reload.private_token + end +end diff --git a/features/steps/profile/profile_ssh_keys.rb b/features/steps/profile/profile_ssh_keys.rb new file mode 100644 index 00000000..96df2d73 --- /dev/null +++ b/features/steps/profile/profile_ssh_keys.rb @@ -0,0 +1,48 @@ +class ProfileSshKeys < Spinach::FeatureSteps + include SharedAuthentication + + Then 'I should see my ssh keys' do + @user.keys.each do |key| + page.should have_content(key.title) + end + end + + Given 'I click link "Add new"' do + click_link "Add new" + end + + And 'I submit new ssh key "Laptop"' do + fill_in "key_title", :with => "Laptop" + fill_in "key_key", :with => "ssh-rsa publickey234=" + click_button "Save" + end + + Then 'I should see new ssh key "Laptop"' do + key = Key.find_by_title("Laptop") + page.should have_content(key.title) + page.should have_content(key.key) + current_path.should == key_path(key) + end + + Given 'I click link "Work"' do + click_link "Work" + end + + And 'I click link "Remove"' do + click_link "Remove" + end + + Then 'I visit profile keys page' do + visit keys_path + end + + And 'I should not see "Work" ssh key' do + within "#keys-table" do + page.should_not have_content "Work" + end + end + + And 'I have ssh key "ssh-rsa Work"' do + Factory :key, :user => @user, :title => "ssh-rsa Work", :key => "jfKLJDFKSFJSHFJssh-rsa Work" + end +end diff --git a/features/steps/project/create_project.rb b/features/steps/project/create_project.rb new file mode 100644 index 00000000..6d2ca3f9 --- /dev/null +++ b/features/steps/project/create_project.rb @@ -0,0 +1,22 @@ +class CreateProject < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + And 'fill project form with valid data' do + fill_in 'project_name', :with => 'NewProject' + fill_in 'project_code', :with => 'NPR' + fill_in 'project_path', :with => 'newproject' + click_button "Create project" + end + + Then 'I should see project page' do + current_path.should == project_path(Project.last) + page.should have_content "NewProject" + end + + And 'I should see empty project instuctions' do + page.should have_content "git init" + page.should have_content "git remote" + page.should have_content Project.last.url_to_repo + end +end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb new file mode 100644 index 00000000..f33f12eb --- /dev/null +++ b/features/steps/project/project.rb @@ -0,0 +1,5 @@ +class Projects < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths +end diff --git a/features/steps/project/project_browse_branches.rb b/features/steps/project/project_browse_branches.rb new file mode 100644 index 00000000..2f6e185d --- /dev/null +++ b/features/steps/project/project_browse_branches.rb @@ -0,0 +1,35 @@ +class ProjectBrowseBranches < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should see "Shop" recent branches list' do + page.should have_content "Branches" + page.should have_content "master" + end + + Given 'I click link "All"' do + click_link "All" + end + + Then 'I should see "Shop" all branches list' do + page.should have_content "Branches" + page.should have_content "master" + end + + Given 'I click link "Protected"' do + click_link "Protected" + end + + Then 'I should see "Shop" protected branches list' do + within "table" do + page.should have_content "stable" + page.should_not have_content "master" + end + end + + And 'project "Shop" has protected branches' do + project = Project.find_by_name("Shop") + project.protected_branches.create(:name => "stable") + end +end diff --git a/features/steps/project/project_browse_commits.rb b/features/steps/project/project_browse_commits.rb new file mode 100644 index 00000000..01479987 --- /dev/null +++ b/features/steps/project/project_browse_commits.rb @@ -0,0 +1,47 @@ +class ProjectBrowseCommits < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I see project commits' do + current_path.should == project_commits_path(@project) + + commit = @project.commit + page.should have_content(@project.name) + page.should have_content(commit.message) + page.should have_content(commit.id.to_s[0..5]) + end + + Given 'I click atom feed link' do + click_link "Feed" + end + + Then 'I see commits atom feed' do + commit = CommitDecorator.decorate(@project.commit) + page.response_headers['Content-Type'].should have_content("application/atom+xml") + page.body.should have_selector("title", :text => "Recent commits to #{@project.name}") + page.body.should have_selector("author email", :text => commit.author_email) + page.body.should have_selector("entry summary", :text => commit.description) + end + + Given 'I click on commit link' do + visit project_commit_path(@project, ValidCommit::ID) + end + + Then 'I see commit info' do + page.should have_content ValidCommit::MESSAGE + page.should have_content "Showing 1 changed file" + end + + And 'I fill compare fields with refs' do + fill_in "from", :with => "master" + fill_in "to", :with => "stable" + click_button "Compare" + end + + And 'I see compared refs' do + page.should have_content "Commits (27)" + page.should have_content "Compare View" + page.should have_content "Showing 73 changed files" + end +end diff --git a/features/steps/project/project_browse_files.rb b/features/steps/project/project_browse_files.rb new file mode 100644 index 00000000..67c553ce --- /dev/null +++ b/features/steps/project/project_browse_files.rb @@ -0,0 +1,34 @@ +class ProjectBrowseFiles < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should see files from repository' do + page.should have_content "app" + page.should have_content "History" + page.should have_content "Gemfile" + end + + Then 'I should see files from repository for "8470d70"' do + current_path.should == tree_project_ref_path(@project, "8470d70") + page.should have_content "app" + page.should have_content "History" + page.should have_content "Gemfile" + end + + Given 'I click on "Gemfile" file in repo' do + click_link "Gemfile" + end + + Then 'I should see it content' do + page.should have_content "rubygems.org" + end + + And 'I click link "raw"' do + click_link "raw" + end + + Then 'I should see raw file content' do + page.source.should == ValidCommit::BLOB_FILE + end +end diff --git a/features/steps/project/project_browse_git_repo.rb b/features/steps/project/project_browse_git_repo.rb new file mode 100644 index 00000000..e966f407 --- /dev/null +++ b/features/steps/project/project_browse_git_repo.rb @@ -0,0 +1,19 @@ +class ProjectBrowseGitRepo < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Given 'I click on "Gemfile" file in repo' do + click_link "Gemfile" + end + + And 'I click blame button' do + click_link "blame" + end + + Then 'I should see git file blame' do + page.should have_content "rubygems.org" + page.should have_content "Dmitriy Zaporozhets" + page.should have_content "bc3735004cb Moving to rails 3.2" + end +end diff --git a/features/steps/project/project_browse_tags.rb b/features/steps/project/project_browse_tags.rb new file mode 100644 index 00000000..0cbfa0d8 --- /dev/null +++ b/features/steps/project/project_browse_tags.rb @@ -0,0 +1,10 @@ +class ProjectBrowseTags < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should see "Shop" all tags list' do + page.should have_content "Tags" + page.should have_content "v1.2.1" + end +end diff --git a/features/steps/project/project_comment_commit.rb b/features/steps/project/project_comment_commit.rb new file mode 100644 index 00000000..cb8385e1 --- /dev/null +++ b/features/steps/project/project_comment_commit.rb @@ -0,0 +1,6 @@ +class ProjectCommentCommit < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedNote + include SharedPaths +end diff --git a/features/steps/project/project_issues.rb b/features/steps/project/project_issues.rb new file mode 100644 index 00000000..64af2449 --- /dev/null +++ b/features/steps/project/project_issues.rb @@ -0,0 +1,134 @@ +class ProjectIssues < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedNote + include SharedPaths + + Given 'I should see "Release 0.4" in issues' do + page.should have_content "Release 0.4" + end + + And 'I should not see "Release 0.3" in issues' do + page.should_not have_content "Release 0.3" + end + + Given 'I click link "Closed"' do + click_link "Closed" + end + + Then 'I should see "Release 0.3" in issues' do + page.should have_content "Release 0.3" + end + + And 'I should not see "Release 0.4" in issues' do + page.should_not have_content "Release 0.4" + end + + Given 'I click link "All"' do + click_link "All" + end + + Given 'I click link "Release 0.4"' do + click_link "Release 0.4" + end + + Then 'I should see issue "Release 0.4"' do + page.should have_content "Release 0.4" + end + + Given 'I click link "New Issue"' do + click_link "New Issue" + end + + And 'I submit new issue "500 error on profile"' do + fill_in "issue_title", :with => "500 error on profile" + click_button "Submit new issue" + end + + Given 'I click link "500 error on profile"' do + click_link "500 error on profile" + end + + Then 'I should see issue "500 error on profile"' do + issue = Issue.find_by_title("500 error on profile") + page.should have_content issue.title + page.should have_content issue.author_name + page.should have_content issue.project.name + end + + Given 'I fill in issue search with "Release"' do + fill_in 'issue_search', with: "Release" + end + + Given 'I fill in issue search with "Bug"' do + fill_in 'issue_search', with: "Bug" + end + + And 'I fill in issue search with "0.3"' do + fill_in 'issue_search', with: "0.3" + end + + And 'I fill in issue search with "Something"' do + fill_in 'issue_search', with: "Something" + end + + And 'I fill in issue search with ""' do + page.execute_script("$('.issue_search').val('').keyup();"); + fill_in 'issue_search', with: "" + end + + Given 'project "Shop" has milestone "v2.2"' do + project = Project.find_by_name("Shop") + milestone = Factory :milestone, :title => "v2.2", :project => project + + 3.times { Factory :issue, :project => project, :milestone => milestone } + end + + And 'project "Shop" has milestone "v3.0"' do + project = Project.find_by_name("Shop") + milestone = Factory :milestone, :title => "v3.0", :project => project + + 3.times { Factory :issue, :project => project, :milestone => milestone } + end + + When 'I select milestone "v3.0"' do + select "v3.0", from: "milestone_id" + end + + Then 'I should see selected milestone with title "v3.0"' do + issues_milestone_selector = "#issue_milestone_id_chzn/a" + wait_until { page.has_content?("Details") } + page.find(issues_milestone_selector).should have_content("v3.0") + end + + When 'I select first assignee from "Shop" project' do + project = Project.find_by_name "Shop" + first_assignee = project.users.first + select first_assignee.name, from: "assignee_id" + end + + Then 'I should see first assignee from "Shop" as selected assignee' do + issues_assignee_selector = "#issue_assignee_id_chzn/a" + wait_until { page.has_content?("Details") } + project = Project.find_by_name "Shop" + assignee_name = project.users.first.name + page.find(issues_assignee_selector).should have_content(assignee_name) + end + + And 'project "Shop" have "Release 0.4" open issue' do + project = Project.find_by_name("Shop") + Factory.create(:issue, + :title => "Release 0.4", + :project => project, + :author => project.users.first) + end + + And 'project "Shop" have "Release 0.3" closed issue' do + project = Project.find_by_name("Shop") + Factory.create(:issue, + :title => "Release 0.3", + :project => project, + :author => project.users.first, + :closed => true) + end +end diff --git a/features/steps/project/project_labels.rb b/features/steps/project/project_labels.rb new file mode 100644 index 00000000..1a347bf3 --- /dev/null +++ b/features/steps/project/project_labels.rb @@ -0,0 +1,24 @@ +class ProjectLabels < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should see label "bug"' do + within ".labels-table" do + page.should have_content "bug" + end + end + + And 'I should see label "feature"' do + within ".labels-table" do + page.should have_content "feature" + end + end + + And 'project "Shop" have issues tags: "bug", "feature"' do + project = Project.find_by_name("Shop") + ['bug', 'feature'].each do |label| + Factory :issue, project: project, label_list: label + end + end +end diff --git a/features/steps/project/project_merge_requests.rb b/features/steps/project/project_merge_requests.rb new file mode 100644 index 00000000..80e83906 --- /dev/null +++ b/features/steps/project/project_merge_requests.rb @@ -0,0 +1,80 @@ +class ProjectMergeRequests < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedNote + include SharedPaths + + Then 'I should see "Bug NS-04" in merge requests' do + page.should have_content "Bug NS-04" + end + + And 'I should not see "Feature NS-03" in merge requests' do + page.should_not have_content "Feature NS-03" + end + + Given 'I click link "Closed"' do + click_link "Closed" + end + + Then 'I should see "Feature NS-03" in merge requests' do + page.should have_content "Feature NS-03" + end + + And 'I should not see "Bug NS-04" in merge requests' do + page.should_not have_content "Bug NS-04" + end + + Given 'I click link "All"' do + click_link "All" + end + + Given 'I click link "Bug NS-04"' do + click_link "Bug NS-04" + end + + Then 'I should see merge request "Bug NS-04"' do + page.should have_content "Bug NS-04" + end + + And 'I click link "Close"' do + click_link "Close" + end + + Then 'I should see closed merge request "Bug NS-04"' do + mr = MergeRequest.find_by_title("Bug NS-04") + mr.closed.should be_true + page.should have_content "Closed by" + end + + Given 'I click link "New Merge Request"' do + click_link "New Merge Request" + end + + And 'I submit new merge request "Wiki Feature"' do + fill_in "merge_request_title", :with => "Wiki Feature" + select "master", :from => "merge_request_source_branch" + select "stable", :from => "merge_request_target_branch" + click_button "Save" + end + + Then 'I should see merge request "Wiki Feature"' do + page.should have_content "Wiki Feature" + end + + And 'project "Shop" have "Bug NS-04" open merge request' do + project = Project.find_by_name("Shop") + Factory.create(:merge_request, + :title => "Bug NS-04", + :project => project, + :author => project.users.first) + end + + And 'project "Shop" have "Feature NS-03" closed merge request' do + project = Project.find_by_name("Shop") + Factory.create(:merge_request, + :title => "Feature NS-03", + :project => project, + :author => project.users.first, + :closed => true) + end +end diff --git a/features/steps/project/project_milestones.rb b/features/steps/project/project_milestones.rb new file mode 100644 index 00000000..83ed6859 --- /dev/null +++ b/features/steps/project/project_milestones.rb @@ -0,0 +1,39 @@ +class ProjectMilestones < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should see milestone "v2.2"' do + milestone = @project.milestones.find_by_title("v2.2") + page.should have_content(milestone.title[0..10]) + page.should have_content(milestone.expires_at) + page.should have_content("Browse Issues") + end + + Given 'I click link "v2.2"' do + click_link "v2.2" + end + + Given 'I click link "New Milestone"' do + click_link "New Milestone" + end + + And 'I submit new milestone "v2.3"' do + fill_in "milestone_title", :with => "v2.3" + click_button "Create milestone" + end + + Then 'I should see milestone "v2.3"' do + milestone = @project.milestones.find_by_title("v2.3") + page.should have_content(milestone.title[0..10]) + page.should have_content(milestone.expires_at) + page.should have_content("Browse Issues") + end + + And 'project "Shop" has milestone "v2.2"' do + project = Project.find_by_name("Shop") + milestone = Factory :milestone, :title => "v2.2", :project => project + + 3.times { Factory :issue, :project => project, :milestone => milestone } + end +end diff --git a/features/steps/project/project_network_graph.rb b/features/steps/project/project_network_graph.rb new file mode 100644 index 00000000..f34a81a4 --- /dev/null +++ b/features/steps/project/project_network_graph.rb @@ -0,0 +1,22 @@ +class ProjectNetworkGraph < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + + Then 'page should have network graph' do + page.should have_content "Project Network Graph" + within ".graph" do + page.should have_content "master" + page.should have_content "scss_refactor..." + end + end + + And 'I visit project "Shop" network page' do + project = Project.find_by_name("Shop") + + # 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 +end diff --git a/features/steps/project/project_team_management.rb b/features/steps/project/project_team_management.rb new file mode 100644 index 00000000..7beca257 --- /dev/null +++ b/features/steps/project/project_team_management.rb @@ -0,0 +1,89 @@ +class ProjectTeamManagement < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + Then 'I should be able to see myself in team' do + page.should have_content(@user.name) + page.should have_content(@user.email) + end + + And 'I should see "Sam" in team list' do + user = User.find_by_name("Sam") + page.should have_content(user.name) + page.should have_content(user.email) + end + + Given 'I click link "New Team Member"' do + click_link "New Team Member" + end + + And 'I select "Mike" as "Reporter"' do + user = User.find_by_name("Mike") + within "#new_team_member" do + select user.name, :from => "user_ids" + select "Reporter", :from => "project_access" + end + click_button "Save" + end + + Then 'I should see "Mike" in team list as "Reporter"' do + user = User.find_by_name("Mike") + role_id = find(".user_#{user.id} #team_member_project_access").value + role_id.should == UsersProject.access_roles["Reporter"].to_s + end + + Given 'I should see "Sam" in team list as "Developer"' do + user = User.find_by_name("Sam") + role_id = find(".user_#{user.id} #team_member_project_access").value + role_id.should == UsersProject.access_roles["Developer"].to_s + end + + And 'I change "Sam" role to "Reporter"' do + user = User.find_by_name("Sam") + within ".user_#{user.id}" do + select "Reporter", :from => "team_member_project_access" + end + end + + And 'I should see "Sam" in team list as "Reporter"' do + user = User.find_by_name("Sam") + role_id = find(".user_#{user.id} #team_member_project_access").value + role_id.should == UsersProject.access_roles["Reporter"].to_s + end + + Given 'I click link "Sam"' do + click_link "Sam" + end + + Then 'I should see "Sam" team profile' do + user = User.find_by_name("Sam") + page.should have_content(user.name) + page.should have_content(user.email) + page.should have_content("To team list") + end + + And 'I click link "Remove from team"' do + click_link "Remove from team" + end + + And 'I should not see "Sam" in team list' do + user = User.find_by_name("Sam") + page.should_not have_content(user.name) + page.should_not have_content(user.email) + end + + And 'gitlab user "Mike"' do + Factory :user, :name => "Mike" + end + + And 'gitlab user "Sam"' do + Factory :user, :name => "Sam" + end + + And '"Sam" is "Shop" developer' do + user = User.find_by_name("Sam") + project = Project.find_by_name("Shop") + project.add_access(user, :write) + end +end diff --git a/features/steps/project/project_wall.rb b/features/steps/project/project_wall.rb new file mode 100644 index 00000000..ba9d3533 --- /dev/null +++ b/features/steps/project/project_wall.rb @@ -0,0 +1,6 @@ +class ProjectWall < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedNote + include SharedPaths +end diff --git a/features/steps/project/project_wiki.rb b/features/steps/project/project_wiki.rb new file mode 100644 index 00000000..902e9ce1 --- /dev/null +++ b/features/steps/project/project_wiki.rb @@ -0,0 +1,20 @@ +class ProjectWiki < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedNote + include SharedPaths + + Given 'I create Wiki page' do + fill_in "Title", :with => 'Test title' + fill_in "Content", :with => '[link test](test)' + click_on "Save" + end + + Then 'I should see newly created wiki page' do + page.should have_content "Test title" + page.should have_content "link test" + + click_link "link test" + page.should have_content "Editing page" + end +end diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb new file mode 100644 index 00000000..77d9839f --- /dev/null +++ b/features/steps/shared/authentication.rb @@ -0,0 +1,10 @@ +require Rails.root.join('spec', 'support', 'login_helpers') + +module SharedAuthentication + include Spinach::DSL + include LoginHelpers + + Given 'I sign in as a user' do + login_as :user + end +end diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb new file mode 100644 index 00000000..923e69b6 --- /dev/null +++ b/features/steps/shared/note.rb @@ -0,0 +1,21 @@ +module SharedNote + include Spinach::DSL + + Given 'I leave a comment like "XML attached"' do + fill_in "note_note", :with => "XML attached" + click_button "Add Comment" + end + + Then 'I should see comment "XML attached"' do + page.should have_content "XML attached" + end + + Given 'I write new comment "my special test message"' do + fill_in "note_note", :with => "my special test message" + click_button "Add Comment" + end + + Then 'I should see project wall note "my special test message"' do + page.should have_content "my special test message" + end +end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb new file mode 100644 index 00000000..93ad0219 --- /dev/null +++ b/features/steps/shared/paths.rb @@ -0,0 +1,112 @@ +module SharedPaths + include Spinach::DSL + + And 'I visit dashboard search page' do + visit search_path + end + + And 'I visit dashboard merge requests page' do + visit dashboard_merge_requests_path + end + + And 'I visit dashboard issues page' do + visit dashboard_issues_path + end + + When 'I visit dashboard page' do + visit dashboard_path + end + + Given 'I visit profile page' do + visit profile_path + end + + Given 'I visit profile account page' do + visit profile_account_path + end + + Given 'I visit profile token page' do + visit profile_token_path + end + + When 'I visit new project page' do + visit new_project_path + end + + And 'I visit project "Shop" page' do + project = Project.find_by_name("Shop") + visit project_path(project) + end + + Given 'I visit project branches page' do + visit branches_project_repository_path(@project) + end + + Given 'I visit compare refs page' do + visit compare_project_commits_path(@project) + end + + Given 'I visit project commits page' do + visit project_commits_path(@project) + end + + Given 'I visit project source page' do + visit tree_project_ref_path(@project, @project.root_ref) + end + + Given 'I visit blob file from repo' do + visit tree_project_ref_path(@project, ValidCommit::ID, :path => ValidCommit::BLOB_FILE_PATH) + end + + Given 'I visit project source page for "8470d70"' do + visit tree_project_ref_path(@project, "8470d70") + end + + Given 'I visit project tags page' do + visit tags_project_repository_path(@project) + end + + Given 'I visit project commit page' do + visit project_commit_path(@project, ValidCommit::ID) + end + + And 'I visit project "Shop" issues page' do + visit project_issues_path(Project.find_by_name("Shop")) + end + + Given 'I visit issue page "Release 0.4"' do + issue = Issue.find_by_title("Release 0.4") + visit project_issue_path(issue.project, issue) + end + + Given 'I visit project "Shop" labels page' do + visit project_labels_path(Project.find_by_name("Shop")) + end + + Given 'I visit merge request page "Bug NS-04"' do + mr = MergeRequest.find_by_title("Bug NS-04") + visit project_merge_request_path(mr.project, mr) + end + + And 'I visit project "Shop" merge requests page' do + visit project_merge_requests_path(Project.find_by_name("Shop")) + end + + Given 'I visit project "Shop" milestones page' do + @project = Project.find_by_name("Shop") + visit project_milestones_path(@project) + end + + Then 'I visit project "Shop" team page' do + visit project_team_index_path(Project.find_by_name("Shop")) + end + + Then 'I visit project "Shop" wall page' do + project = Project.find_by_name("Shop") + visit wall_project_path(project) + end + + Given 'I visit project wiki page' do + visit project_wiki_path(@project, :index) + end +end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb new file mode 100644 index 00000000..9b64ca59 --- /dev/null +++ b/features/steps/shared/project.rb @@ -0,0 +1,8 @@ +module SharedProject + include Spinach::DSL + + And 'I own project "Shop"' do + @project = Factory :project, :name => "Shop" + @project.add_access(@user, :admin) + end +end diff --git a/features/support/env.rb b/features/support/env.rb index 53578152..9c6cef07 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,59 +1,27 @@ -unless ENV['CI'] - require 'simplecov' - SimpleCov.start 'rails' +ENV['RAILS_ENV'] = 'test' +require './config/environment' + +require 'rspec' +require 'database_cleaner' +require 'spinach/capybara' + +%w(gitolite_stub stubbed_repository valid_commit).each do |f| + require Rails.root.join('spec', 'support', f) end -require 'cucumber/rails' -require 'webmock/cucumber' - -WebMock.allow_net_connect! - -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 -Capybara.javascript_driver = :webkit - -# By default, any exception happening in your Rails application will bubble up -# to Cucumber so that your scenario will fail. This is a different from how -# your application behaves in the production environment, where an error page will -# be rendered instead. -# -# Sometimes we want to override this default behaviour and allow Rails to rescue -# exceptions and display an error page (just like when the app is running in production). -# Typical scenarios where you want to do this is when you test your error pages. -# There are two ways to allow Rails to rescue exceptions: -# -# 1) Tag your scenario (or feature) with @allow-rescue -# -# 2) Set the value below to true. Beware that doing this globally is not -# recommended as it will mask a lot of errors for you! -# -ActionController::Base.allow_rescue = false - -# Remove/comment out the lines below if your app doesn't have a database. -# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. -begin - DatabaseCleaner.strategy = :transaction -rescue NameError - raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." -end - -Cucumber::Rails::Database.javascript_strategy = :truncation - -require 'headless' - -headless = Headless.new -headless.start - -require 'cucumber/rspec/doubles' +Dir["#{Rails.root}/features/steps/shared/*.rb"].each {|file| require file} include GitoliteStub -Before do +WebMock.allow_net_connect! +Capybara.javascript_driver = :webkit + +DatabaseCleaner.strategy = :truncation +Spinach.hooks.before_scenario { DatabaseCleaner.start } +Spinach.hooks.after_scenario { DatabaseCleaner.clean } + +Spinach.hooks.before_run do + RSpec::Mocks::setup self + stub_gitolite! end - -World(FactoryGirl::Syntax::Methods) diff --git a/lib/api.rb b/lib/api.rb index be04701c..37e03849 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -17,5 +17,6 @@ module Gitlab mount Projects mount Issues mount Milestones + mount Keys end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index fef5328d..13a48e12 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -9,6 +9,10 @@ module Gitlab expose :id, :email, :name, :blocked, :created_at end + class Hook < Grape::Entity + expose :id, :url + end + class Project < Grape::Entity expose :id, :code, :name, :description, :path, :default_branch expose :owner, using: Entities::UserBasic @@ -44,5 +48,11 @@ module Gitlab expose :assignee, :author, using: Entities::UserBasic expose :closed, :updated_at, :created_at end + + class Key < Grape::Entity + expose :id, + :title, + :key + end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index c0ba8747..9a08b995 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -8,7 +8,7 @@ module Gitlab if @project ||= current_user.projects.find_by_id(params[:id]) || current_user.projects.find_by_code(params[:id]) else - error!({'message' => '404 Not found'}, 404) + not_found! end @project @@ -19,15 +19,48 @@ module Gitlab end def authenticate! - error!({'message' => '401 Unauthorized'}, 401) unless current_user + unauthorized! unless current_user end def authorize! action, subject unless abilities.allowed?(current_user, action, subject) - error!({'message' => '403 Forbidden'}, 403) + forbidden! end end + def attributes_for_keys(keys) + attrs = {} + keys.each do |key| + attrs[key] = params[key] if params[key].present? + end + attrs + end + + # error helpers + + def forbidden! + render_api_error!('403 Forbidden', 403) + end + + def not_found!(resource = nil) + message = ["404"] + message << resource if resource + message << "Not Found" + render_api_error!(message.join(' '), 404) + end + + def unauthorized! + render_api_error!('401 Unauthorized', 401) + end + + def not_allowed! + render_api_error!('Method Not Allowed', 405) + end + + def render_api_error!(message, status) + error!({'message' => message}, status) + end + private def abilities diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 4cfa7500..4ee2d11f 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -48,19 +48,14 @@ module Gitlab # Example Request: # POST /projects/:id/issues post ":id/issues" do - @issue = user_project.issues.new( - title: params[:title], - description: params[:description], - assignee_id: params[:assignee_id], - milestone_id: params[:milestone_id], - label_list: params[:labels] - ) + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] + attrs[:label_list] = params[:labels] if params[:labels].present? + @issue = user_project.issues.new attrs @issue.author = current_user - if @issue.save present @issue, with: Entities::Issue else - error!({'message' => '404 Not found'}, 404) + not_found! end end @@ -81,19 +76,12 @@ module Gitlab @issue = user_project.issues.find(params[:issue_id]) authorize! :modify_issue, @issue - parameters = { - title: (params[:title] || @issue.title), - description: (params[:description] || @issue.description), - assignee_id: (params[:assignee_id] || @issue.assignee_id), - milestone_id: (params[:milestone_id] || @issue.milestone_id), - label_list: (params[:labels] || @issue.label_list), - closed: (params[:closed] || @issue.closed) - } - - if @issue.update_attributes(parameters) + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :closed] + attrs[:label_list] = params[:labels] if params[:labels].present? + if @issue.update_attributes attrs present @issue, with: Entities::Issue else - error!({'message' => '404 Not found'}, 404) + not_found! end end @@ -105,7 +93,7 @@ module Gitlab # Example Request: # DELETE /projects/:id/issues/:issue_id delete ":id/issues/:issue_id" do - error!({'message' => 'method not allowed'}, 405) + not_allowed! end end end diff --git a/lib/api/keys.rb b/lib/api/keys.rb new file mode 100644 index 00000000..4c302727 --- /dev/null +++ b/lib/api/keys.rb @@ -0,0 +1,50 @@ +module Gitlab + # Keys API + class Keys < Grape::API + before { authenticate! } + resource :keys do + # Get currently authenticated user's keys + # + # Example Request: + # GET /keys + get do + present current_user.keys, with: Entities::Key + end + # Get single key owned by currently authenticated user + # + # Example Request: + # GET /keys/:id + get "/:id" do + key = current_user.keys.find params[:id] + present key, with: Entities::Key + end + # Add new ssh key to currently authenticated user + # + # Parameters: + # key (required) - New SSH Key + # title (required) - New SSH Key's title + # Example Request: + # POST /keys + post do + attrs = attributes_for_keys [:title, :key] + key = current_user.keys.new attrs + if key.save + present key, with: Entities::Key + else + not_found! + end + end + # Delete existed ssh key of currently authenticated user + # + # Parameters: + # id (required) - SSH Key ID + # Example Request: + # DELETE /keys/:id + delete "/:id" do + key = current_user.keys.find params[:id] + key.delete + end + end + end +end + diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 7c684667..daaff940 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -36,16 +36,12 @@ module Gitlab # 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] - ) - + attrs = attributes_for_keys [:title, :description, :due_date] + @milestone = user_project.milestones.new attrs if @milestone.save present @milestone, with: Entities::Milestone else - error!({'message' => '404 Not found'}, 404) + not_found! end end @@ -64,17 +60,11 @@ module Gitlab authorize! :admin_milestone, user_project @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) + attrs = attributes_for_keys [:title, :description, :due_date, :closed] + if @milestone.update_attributes attrs present @milestone, with: Entities::Milestone else - error!({'message' => '404 Not found'}, 404) + not_found! end end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 05b07e8d..1d9004f8 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -40,17 +40,20 @@ module Gitlab post do params[:code] ||= params[:name] params[:path] ||= params[:name] - project_attrs = {} - params.each_pair do |k ,v| - if Project.attribute_names.include? k - project_attrs[k] = v - end - end - @project = Project.create_by_user(project_attrs, current_user) + attrs = attributes_for_keys [:code, + :path, + :name, + :description, + :default_branch, + :issues_enabled, + :wall_enabled, + :merge_requests_enabled, + :wiki_enabled] + @project = Project.create_by_user(attrs, current_user) if @project.saved? present @project, with: Entities::Project else - error!({'message' => '404 Not found'}, 404) + not_found! end end @@ -106,6 +109,49 @@ module Gitlab nil end + # Get project hooks + # + # Parameters: + # id (required) - The ID or code name of a project + # Example Request: + # GET /projects/:id/hooks + get ":id/hooks" do + authorize! :admin_project, user_project + @hooks = paginate user_project.hooks + present @hooks, with: Entities::Hook + end + + # Add hook to project + # + # Parameters: + # id (required) - The ID or code name of a project + # url (required) - The hook URL + # Example Request: + # POST /projects/:id/hooks + post ":id/hooks" do + authorize! :admin_project, user_project + @hook = user_project.hooks.new({"url" => params[:url]}) + if @hook.save + present @hook, with: Entities::Hook + else + error!({'message' => '404 Not found'}, 404) + end + end + + # Delete project hook + # + # Parameters: + # id (required) - The ID or code name of a project + # hook_id (required) - The ID of hook to delete + # Example Request: + # DELETE /projects/:id/hooks + delete ":id/hooks" do + authorize! :admin_project, user_project + @hook = user_project.hooks.find(params[:hook_id]) + @hook.destroy + nil + end + # Get a project repository branches # # Parameters: @@ -161,18 +207,16 @@ module Gitlab # Example Request: # POST /projects/:id/snippets post ":id/snippets" do - @snippet = user_project.snippets.new( - title: params[:title], - file_name: params[:file_name], - expires_at: params[:lifetime], - content: params[:code] - ) + attrs = attributes_for_keys [:title, :file_name] + attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? + attrs[:content] = params[:code] if params[:code].present? + @snippet = user_project.snippets.new attrs @snippet.author = current_user if @snippet.save present @snippet, with: Entities::ProjectSnippet else - error!({'message' => '404 Not found'}, 404) + not_found! end end @@ -191,17 +235,14 @@ module Gitlab @snippet = user_project.snippets.find(params[:snippet_id]) authorize! :modify_snippet, @snippet - parameters = { - title: (params[:title] || @snippet.title), - file_name: (params[:file_name] || @snippet.file_name), - expires_at: (params[:lifetime] || @snippet.expires_at), - content: (params[:code] || @snippet.content) - } + attrs = attributes_for_keys [:title, :file_name] + attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? + attrs[:content] = params[:code] if params[:code].present? - if @snippet.update_attributes(parameters) + if @snippet.update_attributes attrs present @snippet, with: Entities::ProjectSnippet else - error!({'message' => '404 Not found'}, 404) + not_found! end end @@ -244,10 +285,10 @@ module Gitlab ref = params[:sha] commit = user_project.commit ref - error!('404 Commit Not Found', 404) unless commit + not_found! "Commit" unless commit tree = Tree.new commit.tree, user_project, ref, params[:filepath] - error!('404 File Not Found', 404) unless tree.try(:tree) + not_found! "File" unless tree.try(:tree) if tree.text? encoding = Gitlab::Encode.detect_encoding(tree.data) diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb new file mode 100644 index 00000000..8e4717b4 --- /dev/null +++ b/lib/gitlab/app_logger.rb @@ -0,0 +1,11 @@ +module Gitlab + class AppLogger < Gitlab::Logger + def self.file_name + 'application.log' + end + + def format_message(severity, timestamp, progname, msg) + "#{timestamp.to_s(:long)}: #{msg}\n" + end + end +end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb new file mode 100644 index 00000000..90bd5d74 --- /dev/null +++ b/lib/gitlab/auth.rb @@ -0,0 +1,66 @@ +module Gitlab + class Auth + def find_for_ldap_auth(auth, signed_in_resource = nil) + uid = auth.info.uid + provider = auth.provider + email = auth.info.email.downcase unless auth.info.email.nil? + raise OmniAuth::Error, "LDAP accounts must provide an uid and email address" if uid.nil? or email.nil? + + if @user = User.find_by_extern_uid_and_provider(uid, provider) + @user + elsif @user = User.find_by_email(email) + log.info "Updating legacy LDAP user #{email} with extern_uid => #{uid}" + @user.update_attributes(:extern_uid => uid, :provider => provider) + @user + else + create_from_omniauth(auth, true) + end + end + + def create_from_omniauth(auth, ldap = false) + provider = auth.provider + uid = auth.info.uid || auth.uid + name = auth.info.name.force_encoding("utf-8") + email = auth.info.email.downcase unless auth.info.email.nil? + + ldap_prefix = ldap ? '(LDAP) ' : '' + raise OmniAuth::Error, "#{ldap_prefix}#{provider} does not provide an email"\ + " address" if auth.info.email.blank? + + log.info "#{ldap_prefix}Creating user from #{provider} login"\ + " {uid => #{uid}, name => #{name}, email => #{email}}" + password = Devise.friendly_token[0, 8].downcase + @user = User.new( + extern_uid: uid, + provider: provider, + name: name, + email: email, + password: password, + password_confirmation: password, + projects_limit: Gitlab.config.default_projects_limit, + ) + if Gitlab.config.omniauth['block_auto_created_users'] && !ldap + @user.blocked = true + end + @user.save! + @user + end + + def find_or_new_for_omniauth(auth) + provider, uid = auth.provider, auth.uid + + if @user = User.find_by_provider_and_extern_uid(provider, uid) + @user + else + if Gitlab.config.omniauth['allow_single_sign_on'] + @user = create_from_omniauth(auth) + @user + end + end + end + + def log + Gitlab::AppLogger + end + end +end diff --git a/lib/gitlab/backend/gitolite_config.rb b/lib/gitlab/backend/gitolite_config.rb index 60eef8e8..f51e8efc 100644 --- a/lib/gitlab/backend/gitolite_config.rb +++ b/lib/gitlab/backend/gitolite_config.rb @@ -58,18 +58,22 @@ module Gitlab end end rescue PullError => ex - Gitlab::Logger.error("Pull error -> " + ex.message) + log("Pull error -> " + ex.message) raise Gitolite::AccessDenied, ex.message rescue PushError => ex - Gitlab::Logger.error("Push error -> " + " " + ex.message) + log("Push error -> " + " " + ex.message) raise Gitolite::AccessDenied, ex.message rescue Exception => ex - Gitlab::Logger.error(ex.class.name + " " + ex.message) + log(ex.class.name + " " + ex.message) raise Gitolite::AccessDenied.new("gitolite timeout") end + def log message + Gitlab::GitLogger.error(message) + end + def destroy_project(project) FileUtils.rm_rf(project.path_to_repo) conf.rm_repo(project.path) @@ -148,7 +152,7 @@ module Gitlab # Enable access to all repos for gitolite admin. # We use it for accept merge request feature def admin_all_repo - owner_name = Gitlab.settings.gitolite_admin_key + owner_name = Gitlab.config.gitolite_admin_key # @ALL repos premission for gitolite owner repo_name = "@all" diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 4f77c327..43a75cc3 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -12,21 +12,22 @@ module Grack # Pass Gitolite update hook ENV['GL_BYPASS_UPDATE_HOOK'] = "true" - # Need this patch because the rails mount - @env['PATH_INFO'] = @env['REQUEST_PATH'] + # Need this patch due to the rails mount + @env['PATH_INFO'] = @request.path + @env['SCRIPT_NAME'] = "" # Find project by PATH_INFO from env - if m = /^\/([\w-]+).git/.match(@env['PATH_INFO']).to_a + if m = /^\/([\w-]+).git/.match(@request.path_info).to_a return false unless project = Project.find_by_path(m.last) end # Git upload and receive - if @env['REQUEST_METHOD'] == 'GET' + if @request.get? true - elsif @env['REQUEST_METHOD'] == 'POST' - if @env['REQUEST_URI'].end_with?('git-upload-pack') + elsif @request.post? + if @request.path_info.end_with?('git-upload-pack') return project.dev_access_for?(user) - elsif @env['REQUEST_URI'].end_with?('git-receive-pack') + elsif @request.path_info.end_with?('git-receive-pack') if project.protected_branches.map(&:name).include?(current_ref) project.master_access_for?(user) else diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb new file mode 100644 index 00000000..fbfed205 --- /dev/null +++ b/lib/gitlab/git_logger.rb @@ -0,0 +1,11 @@ +module Gitlab + class GitLogger < Gitlab::Logger + def self.file_name + 'githost.log' + end + + def format_message(severity, timestamp, progname, msg) + "#{timestamp.to_s(:long)} -> #{severity} -> #{msg}\n" + end + end +end diff --git a/lib/gitlab/graph_commit.rb b/lib/gitlab/graph_commit.rb index b9859d79..e14d670e 100644 --- a/lib/gitlab/graph_commit.rb +++ b/lib/gitlab/graph_commit.rb @@ -5,6 +5,8 @@ module Gitlab attr_accessor :time, :space attr_accessor :refs + include ActionView::Helpers::SanitizeHelper + def self.to_graph(project) @repo = project.repo commits = Grit::Commit.find_all(@repo, nil, {max_count: 650}) @@ -164,7 +166,7 @@ module Gitlab h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil? h[:id] = sha h[:date] = date - h[:message] = Gitlab::Encode.utf8(message) + h[:message] = sanitize(Gitlab::Encode.utf8(message)) h[:login] = author.email h end diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index c3a19e71..9405163d 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -9,17 +9,13 @@ module Gitlab end def self.read_latest - path = Rails.root.join("log/githost.log") + path = Rails.root.join("log", file_name) self.build unless File.exist?(path) logs = File.read(path).split("\n") end def self.build - new(File.join(Rails.root, "log/githost.log")) + new(File.join(Rails.root, "log", file_name)) end - - def format_message(severity, timestamp, progname, msg) - "#{timestamp.to_s(:long)} -> #{severity} -> #{msg}\n" - end end end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 4fc0c392..9201003e 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -26,13 +26,13 @@ module Gitlab # => "\":trollface:\" module Markdown REFERENCE_PATTERN = %r{ - ([^\w&;])? # Prefix (1) + (\W)? # Prefix (1) ( # Reference (2) @([\w\._]+) # User name (3) |[#!$](\d+) # Issue/MR/Snippet ID (4) |([\h]{6,40}) # Commit ID (5) ) - ([^\w&;])? # Suffix (6) + (\W)? # Suffix (6) }x.freeze EMOJI_PATTERN = %r{(:(\S+):)}.freeze @@ -48,8 +48,10 @@ module Gitlab def gfm(text, html_options = {}) return text if text.nil? - # prevents the string supplied through the _text_ argument to be altered - text = text.dup + # Duplicate the string so we don't alter the original, then call to_str + # to cast it back to a String instead of a SafeBuffer. This is required + # for gsub calls to work as we need them to. + text = text.dup.to_str @html_options = html_options @@ -84,6 +86,13 @@ module Gitlab # # Returns parsed text def parse(text) + parse_references(text) if @project + parse_emoji(text) + + text + end + + def parse_references(text) # parse reference links text.gsub!(REFERENCE_PATTERN) do |match| prefix = $1 || '' @@ -91,13 +100,18 @@ module Gitlab identifier = $3 || $4 || $5 suffix = $6 || '' - if ref_link = reference_link(reference, identifier) + # Avoid HTML entities + if prefix.ends_with?('&') || suffix.starts_with?(';') + match + elsif ref_link = reference_link(reference, identifier) prefix + ref_link + suffix else match end - end if @project + end + end + def parse_emoji(text) # parse emoji text.gsub!(EMOJI_PATTERN) do |match| if valid_emoji?($2) @@ -106,8 +120,6 @@ module Gitlab match end end - - text end # Private: Checks if an emoji icon exists in the image asset directory diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake deleted file mode 100644 index 83f79471..00000000 --- a/lib/tasks/cucumber.rake +++ /dev/null @@ -1,65 +0,0 @@ -# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. -# It is recommended to regenerate this file in the future when you upgrade to a -# newer version of cucumber-rails. Consider adding your own code to a new file -# instead of editing this one. Cucumber will automatically load all features/**/*.rb -# files. - - -unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks - -vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first -$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? - -begin - require 'cucumber/rake/task' - - namespace :cucumber do - Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| - t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. - t.fork = true # You may get faster startup if you set this to false - t.profile = 'default' - end - - Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| - t.binary = vendored_cucumber_bin - t.fork = true # You may get faster startup if you set this to false - t.profile = 'wip' - end - - Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| - t.binary = vendored_cucumber_bin - t.fork = true # You may get faster startup if you set this to false - t.profile = 'rerun' - end - - desc 'Run all features' - task :all => [:ok, :wip] - - task :statsetup do - require 'rails/code_statistics' - ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') - ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') - end - end - desc 'Alias for cucumber:ok' - task :cucumber => 'cucumber:ok' - - task :default => :cucumber - - task :features => :cucumber do - STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" - end - - # In case we don't have ActiveRecord, append a no-op task that we can depend upon. - task 'db:test:prepare' do - end - - task :stats => 'cucumber:statsetup' -rescue LoadError - desc 'cucumber rake task not available (cucumber not installed)' - task :cucumber do - abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' - end -end - -end diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake index 77e148cf..ad1bfb2e 100644 --- a/lib/tasks/gitlab/test.rake +++ b/lib/tasks/gitlab/test.rake @@ -1,5 +1,4 @@ namespace :gitlab do - desc "GITLAB | Run both cucumber & rspec" - task :test => ['cucumber', 'spec'] + desc "GITLAB | Run both spinach and rspec" + task :test => ['spinach', 'spec'] end - diff --git a/lib/tasks/travis.rake b/lib/tasks/travis.rake index 58767e10..13e32135 100644 --- a/lib/tasks/travis.rake +++ b/lib/tasks/travis.rake @@ -1,5 +1,5 @@ task :travis do - ["cucumber", "rspec spec"].each do |cmd| + ["spinach", "rspec spec"].each do |cmd| puts "Starting to run #{cmd}..." system("export DISPLAY=:99.0 && bundle exec #{cmd}") raise "#{cmd} failed!" unless $?.exitstatus == 0 diff --git a/script/cucumber b/script/cucumber deleted file mode 100755 index 7fa5c920..00000000 --- a/script/cucumber +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env ruby - -vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first -if vendored_cucumber_bin - load File.expand_path(vendored_cucumber_bin) -else - require 'rubygems' unless ENV['NO_RUBYGEMS'] - require 'cucumber' - load Cucumber::BINARY -end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 4dd3802a..a6708a7a 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -31,6 +31,7 @@ describe GitlabMarkdownHelper do end it "should not touch HTML entities" do + @project.issues.stub(:where).with(id: '39').and_return([issue]) actual = expected = "We'll accept good pull requests." gfm(actual).should == expected end @@ -291,11 +292,18 @@ describe GitlabMarkdownHelper do actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo') actual.should have_selector 'a.gfm.gfm-commit.foo' end + + it "escapes HTML passed in as the body" do + actual = "This is a

test

- see ##{issues[0].id}" + link_to_gfm(actual, commit_path).should match('<h1>test</h1>') + end end describe "#markdown" do it "should handle references in paragraphs" do - markdown("\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. #{commit.id} Nam pulvinar sapien eget odio adipiscing at faucibus orci vestibulum.\n").should == "

Lorem ipsum dolor sit amet, consectetur adipiscing elit. #{link_to commit.id, project_commit_path(project, commit), title: commit.link_title, class: "gfm gfm-commit "} Nam pulvinar sapien eget odio adipiscing at faucibus orci vestibulum.

\n" + actual = "\n\nLorem ipsum dolor sit amet. #{commit.id} Nam pulvinar sapien eget.\n" + expected = project_commit_path(project, commit) + markdown(actual).should match(expected) end it "should handle references in headers" do diff --git a/spec/lib/auth_spec.rb b/spec/lib/auth_spec.rb new file mode 100644 index 00000000..1e03bc59 --- /dev/null +++ b/spec/lib/auth_spec.rb @@ -0,0 +1,95 @@ +require 'spec_helper' + +describe Gitlab::Auth do + let(:gl_auth) { Gitlab::Auth.new } + + before do + Gitlab.config.stub(omniauth: {}) + + @info = mock( + uid: '12djsak321', + name: 'John', + email: 'john@mail.com' + ) + end + + describe :find_for_ldap_auth do + before do + @auth = mock( + uid: '12djsak321', + info: @info, + provider: 'ldap' + ) + end + + it "should find by uid & provider" do + User.should_receive :find_by_extern_uid_and_provider + gl_auth.find_for_ldap_auth(@auth) + end + + it "should update credentials by email if missing uid" do + user = double('User') + User.stub find_by_extern_uid_and_provider: nil + User.stub find_by_email: user + user.should_receive :update_attributes + gl_auth.find_for_ldap_auth(@auth) + end + + + it "should create from auth if user doesnot exist"do + User.stub find_by_extern_uid_and_provider: nil + User.stub find_by_email: nil + gl_auth.should_receive :create_from_omniauth + gl_auth.find_for_ldap_auth(@auth) + end + end + + describe :find_or_new_for_omniauth do + before do + @auth = mock( + info: @info, + provider: 'twitter', + uid: '12djsak321', + ) + end + + it "should find user"do + User.should_receive :find_by_provider_and_extern_uid + gl_auth.should_not_receive :create_from_omniauth + gl_auth.find_or_new_for_omniauth(@auth) + end + + it "should not create user"do + User.stub find_by_provider_and_extern_uid: nil + gl_auth.should_not_receive :create_from_omniauth + gl_auth.find_or_new_for_omniauth(@auth) + end + + it "should create user if single_sing_on"do + Gitlab.config.omniauth['allow_single_sign_on'] = true + User.stub find_by_provider_and_extern_uid: nil + gl_auth.should_receive :create_from_omniauth + gl_auth.find_or_new_for_omniauth(@auth) + end + end + + describe :create_from_omniauth do + it "should create user from LDAP" do + @auth = mock(info: @info, provider: 'ldap') + user = gl_auth.create_from_omniauth(@auth, true) + + user.should be_valid + user.extern_uid.should == @info.uid + user.provider.should == 'ldap' + end + + it "should create user from Omniauth" do + @auth = mock(info: @info, provider: 'twitter') + user = gl_auth.create_from_omniauth(@auth, false) + + user.should be_valid + user.extern_uid.should == @info.uid + user.provider.should == 'twitter' + end + end +end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index ee022e95..5cb68761 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -14,12 +14,12 @@ describe Event do it { should respond_to(:commits) } end - describe "Push event" do - before do + describe "Push event" do + before do project = Factory :project @user = project.owner - data = { + data = { before: "0000000000000000000000000000000000000000", after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", ref: "refs/heads/master", @@ -50,25 +50,24 @@ describe Event do it { @event.author.should == @user } end - describe "Joined project team" do - let(:project) {Factory.create :project} - let(:new_user) {Factory.create :user} - it "should create event" do - UsersProject.observers.enable :users_project_observer - expect{ - UsersProject.bulk_import(project, [new_user.id], UsersProject::DEVELOPER) - }.to change{Event.count}.by(1) + describe 'Team events' do + let(:user_project) { stub.as_null_object } + let(:observer) { UsersProjectObserver.instance } + + before { + Event.should_receive :create + } + + describe "Joined project team" do + it "should create event" do + observer.after_create user_project + end end - end - describe "Left project team" do - let(:project) {Factory.create :project} - let(:new_user) {Factory.create :user} - it "should create event" do - UsersProject.bulk_import(project, [new_user.id], UsersProject::DEVELOPER) - UsersProject.observers.enable :users_project_observer - expect{ - UsersProject.bulk_delete(project, [new_user.id]) - }.to change{Event.count}.by(1) + + describe "Left project team" do + it "should create event" do + observer.after_destroy user_project + end end end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index ca6307e7..34192da9 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -12,7 +12,7 @@ describe Issue do describe 'modules' do it { should include_module(IssueCommonality) } - it { should include_module(Upvote) } + it { should include_module(Votes) } end subject { Factory.create(:issue) } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index d1253b35..523e823d 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -8,6 +8,6 @@ describe MergeRequest do describe 'modules' do it { should include_module(IssueCommonality) } - it { should include_module(Upvote) } + it { should include_module(Votes) } end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index dddfd34c..7809953f 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -24,6 +24,13 @@ describe Note do it "recognizes a neutral note" do note = Factory(:note, note: "This is not a +1 note") note.should_not be_upvote + note.should_not be_downvote + end + + it "recognizes a neutral emoji note" do + note = build(:note, note: "I would :+1: this, but I don't want to") + note.should_not be_upvote + note.should_not be_downvote end it "recognizes a +1 note" do @@ -31,19 +38,19 @@ describe Note do note.should be_upvote end - it "recognizes a -1 note as no vote" do - note = Factory(:note, note: "-1 for this") - note.should_not be_upvote - end - it "recognizes a +1 emoji as a vote" do note = build(:note, note: ":+1: for this") note.should be_upvote end - it "recognizes a neutral emoji note" do - note = build(:note, note: "I would :+1: this, but I don't want to") - note.should_not be_upvote + it "recognizes a -1 note" do + note = Factory(:note, note: "-1 for this") + note.should be_downvote + end + + it "recognizes a -1 emoji as a vote" do + note = build(:note, note: ":-1: for this") + note.should be_downvote end end diff --git a/spec/observers/user_observer_spec.rb b/spec/observers/user_observer_spec.rb index 23dac98b..0420a250 100644 --- a/spec/observers/user_observer_spec.rb +++ b/spec/observers/user_observer_spec.rb @@ -13,7 +13,7 @@ describe UserObserver do end context 'when a new user is created' do - let(:user) { double(:user, id: 42, password: 'P@ssword!') } + let(:user) { double(:user, id: 42, password: 'P@ssword!', name: 'John', email: 'u@mail.local') } let(:notification) { double :notification } it 'sends an email' do @@ -22,5 +22,10 @@ describe UserObserver do subject.after_create(user) end + + it 'trigger logger' do + Gitlab::AppLogger.should_receive(:info) + 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 index 650321ce..2009b85f 100644 --- a/spec/observers/users_project_observer_spec.rb +++ b/spec/observers/users_project_observer_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe UsersProjectObserver do +# let(:users_project) { stub.as_null_object } let(:user) { Factory.create :user } let(:project) { Factory.create(:project, code: "Fuu", @@ -14,21 +15,23 @@ describe UsersProjectObserver do it "should called when UsersProject created" do subject.should_receive(:after_commit).once UsersProject.observers.enable :users_project_observer do - Factory.create(:users_project, - project: project, - user: user) + create(:users_project) 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_commit(users_project) + Event.stub(:create => true) end + it "should create new event" do Event.should_receive(:create).with( - project_id: users_project.project.id, - action: Event::Joined, + project_id: users_project.project.id, + action: Event::Joined, author_id: users_project.user.id ) + subject.after_create(users_project) end end @@ -37,9 +40,10 @@ describe UsersProjectObserver do it "should called when UsersProject updated" do subject.should_receive(:after_commit).once UsersProject.observers.enable :users_project_observer do - users_project.update_attribute(:project_access, 40) + create(:users_project).update_attribute(:project_access, UsersProject::MASTER) 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_commit(users_project) @@ -51,20 +55,20 @@ describe UsersProjectObserver do end end end + describe "#after_destroy" do it "should called when UsersProject destroyed" do subject.should_receive(:after_destroy) + UsersProject.observers.enable :users_project_observer do - UsersProject.bulk_delete( - users_project.project, - [users_project.user.id] - ) + create(:users_project).destroy end end + it "should create new event" do Event.should_receive(:create).with( - project_id: users_project.project.id, - action: Event::Left, + project_id: users_project.project.id, + action: Event::Left, author_id: users_project.user.id ) subject.after_destroy(users_project) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 293ea83a..442e9c73 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -9,12 +9,14 @@ describe Gitlab::API do before { project.add_access(user, :read) } describe "GET /issues" do - it "should return authentication error" do - get api("/issues") - response.status.should == 401 + context "when unauthenticated" do + it "should return authentication error" do + get api("/issues") + response.status.should == 401 + end end - describe "authenticated GET /issues" do + context "when authenticated" do it "should return an array of issues" do get api("/issues", user) response.status.should == 200 diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 439aecce..b46380b2 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -6,6 +6,7 @@ describe Gitlab::API do let(:user) { Factory :user } let(:user2) { Factory.create(:user) } let(:user3) { Factory.create(:user) } + let!(:hook) { Factory :project_hook, project: project, url: "http://example.com" } let!(:project) { Factory :project, owner: user } let!(:snippet) { Factory :snippet, author: user, project: project, title: 'example' } let!(:users_project) { Factory :users_project, user: user, project: project, project_access: UsersProject::MASTER } @@ -13,12 +14,14 @@ describe Gitlab::API do before { project.add_access(user, :read) } describe "GET /projects" do - it "should return authentication error" do - get api("/projects") - response.status.should == 401 + context "when unauthenticated" do + it "should return authentication error" do + get api("/projects") + response.status.should == 401 + end end - describe "authenticated GET /projects" do + context "when authenticated" do it "should return an array of projects" do get api("/projects", user) response.status.should == 200 @@ -85,7 +88,7 @@ describe Gitlab::API do 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' + json_response['message'].should == '404 Not Found' end end @@ -147,6 +150,36 @@ describe Gitlab::API do end end + describe "GET /projects/:id/hooks" do + it "should return project hooks" do + get api("/projects/#{project.code}/hooks", user) + + response.status.should == 200 + + json_response.should be_an Array + json_response.count.should == 1 + json_response.first['url'].should == "http://example.com" + end + end + + describe "POST /projects/:id/users" do + it "should add hook to project" do + expect { + post api("/projects/#{project.code}/hooks", user), + "url" => "http://example.com" + }.to change {project.hooks.count}.by(1) + end + end + + describe "DELETE /projects/:id/hooks" do + it "should delete hook from project" do + expect { + delete api("/projects/#{project.code}/hooks", user), + hook_id: hook.id + }.to change {project.hooks.count}.by(-1) + 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) diff --git a/spec/requests/api/ssh_keys_spec.rb b/spec/requests/api/ssh_keys_spec.rb new file mode 100644 index 00000000..7fb8c920 --- /dev/null +++ b/spec/requests/api/ssh_keys_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe Gitlab::Keys do + include ApiHelpers + let(:user) { + user = Factory.create :user + user.reset_authentication_token! + user + } + let(:key) { Factory.create :key, { user: user}} + + describe "GET /keys" do + context "when unauthenticated" do + it "should return authentication error" do + get api("/keys") + response.status.should == 401 + end + end + context "when authenticated" do + it "should return array of ssh keys" do + user.keys << key + user.save + get api("/keys", user) + response.status.should == 200 + json_response.should be_an Array + json_response.first["title"].should == key.title + end + end + end + + describe "GET /keys/:id" do + it "should returm single key" do + user.keys << key + user.save + get api("/keys/#{key.id}", user) + response.status.should == 200 + json_response["title"].should == key.title + end + it "should return 404 Not Found within invalid ID" do + get api("/keys/42", user) + response.status.should == 404 + end + end + + describe "POST /keys" do + it "should not create invalid ssh key" do + post api("/keys", user), { title: "invalid key" } + response.status.should == 404 + end + it "should create ssh key" do + key_attrs = Factory.attributes :key + expect { + post api("/keys", user), key_attrs + }.to change{ user.keys.count }.by(1) + end + end + + describe "DELETE /keys/:id" do + it "should delete existed key" do + user.keys << key + user.save + expect { + delete api("/keys/#{key.id}", user) + }.to change{user.keys.count}.by(-1) + end + it "should return 404 Not Found within invalid ID" do + delete api("/keys/42", user) + response.status.should == 404 + end + end + +end + diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index d791962a..e25fe134 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -6,12 +6,14 @@ describe Gitlab::API do let(:user) { Factory :user } describe "GET /users" do - it "should return authentication error" do - get api("/users") - response.status.should == 401 + context "when unauthenticated" do + it "should return authentication error" do + get api("/users") + response.status.should == 401 + end end - describe "authenticated GET /users" do + context "when authenticated" do it "should return an array of users" do get api("/users", user) response.status.should == 200 diff --git a/spec/requests/atom/dashboard_issues_spec.rb b/spec/requests/atom/dashboard_issues_spec.rb index 79a9b8ef..8d1111fc 100644 --- a/spec/requests/atom/dashboard_issues_spec.rb +++ b/spec/requests/atom/dashboard_issues_spec.rb @@ -1,42 +1,23 @@ require 'spec_helper' -describe "User Issues Dashboard" do +describe "Dashboard Issues Feed" do describe "GET /issues" do - before do + let!(:user) { Factory :user } + let!(:project1) { Factory :project } + let!(:project2) { Factory :project } + let!(:issue1) { Factory :issue, author: user, assignee: user, project: project1 } + let!(:issue2) { Factory :issue, author: user, assignee: user, project: project2 } - login_as :user - - @project1 = Factory :project - - @project2 = Factory :project - - @project1.add_access(@user, :read, :write) - @project2.add_access(@user, :read, :write) - - @issue1 = Factory :issue, - author: @user, - assignee: @user, - project: @project1 - - @issue2 = Factory :issue, - author: @user, - assignee: @user, - project: @project2 - - visit dashboard_issues_path - end - - describe "atom feed", js: false do + describe "atom feed" do it "should render atom feed via private token" do - logout - visit dashboard_issues_path(:atom, private_token: @user.private_token) + visit dashboard_issues_path(:atom, private_token: user.private_token) page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", text: "#{@user.name} issues") - page.body.should have_selector("author email", text: @issue1.author_email) - page.body.should have_selector("entry summary", text: @issue1.title) - page.body.should have_selector("author email", text: @issue2.author_email) - page.body.should have_selector("entry summary", text: @issue2.title) + page.body.should have_selector("title", text: "#{user.name} issues") + page.body.should have_selector("author email", text: issue1.author_email) + page.body.should have_selector("entry summary", text: issue1.title) + page.body.should have_selector("author email", text: issue2.author_email) + page.body.should have_selector("entry summary", text: issue2.title) end end end diff --git a/spec/requests/atom/dashboard_spec.rb b/spec/requests/atom/dashboard_spec.rb index 00c7a525..9459dd01 100644 --- a/spec/requests/atom/dashboard_spec.rb +++ b/spec/requests/atom/dashboard_spec.rb @@ -1,27 +1,21 @@ require 'spec_helper' -describe "User Dashboard" do - before { login_as :user } - +describe "Dashboard Feed" do describe "GET /" do - before do - @project = Factory :project, owner: @user - @project.add_access(@user, :read) - visit dashboard_path + let!(:user) { Factory :user } + + context "projects atom feed via private token" do + it "should render projects atom feed" do + visit dashboard_path(:atom, private_token: user.private_token) + page.body.should have_selector("feed title") + end end - it "should render projects atom feed via private token" do - logout - - visit dashboard_path(:atom, private_token: @user.private_token) - page.body.should have_selector("feed title") - end - - it "should not render projects page via private token" do - logout - - visit dashboard_path(private_token: @user.private_token) - current_path.should == new_user_session_path + context "projects page via private token" do + it "should redirect to login page" do + visit dashboard_path(private_token: user.private_token) + current_path.should == new_user_session_path + end end end end diff --git a/spec/requests/atom/issues_spec.rb b/spec/requests/atom/issues_spec.rb index 468d1b22..c8671979 100644 --- a/spec/requests/atom/issues_spec.rb +++ b/spec/requests/atom/issues_spec.rb @@ -1,40 +1,34 @@ require 'spec_helper' -describe "Issues" do - let(:project) { Factory :project } - - before do - login_as :user - project.add_access(@user, :read, :write) - end - +describe "Issues Feed" do describe "GET /issues" do - before do - @issue = Factory :issue, - author: @user, - assignee: @user, - project: project + let!(:user) { Factory :user } + let!(:project) { Factory :project, owner: user } + let!(:issue) { Factory :issue, author: user, project: project } - visit project_issues_path(project) + before { project.add_access(user, :read, :write) } + + context "when authenticated" do + it "should render atom feed" do + login_with user + visit project_issues_path(project, :atom) + + page.response_headers['Content-Type'].should have_content("application/atom+xml") + page.body.should have_selector("title", text: "#{project.name} issues") + page.body.should have_selector("author email", text: issue.author_email) + page.body.should have_selector("entry summary", text: issue.title) + end end - it "should render atom feed" do - visit project_issues_path(project, :atom) + context "when authenticated via private token" do + it "should render atom feed" do + visit project_issues_path(project, :atom, private_token: user.private_token) - page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", text: "#{project.name} issues") - page.body.should have_selector("author email", text: @issue.author_email) - page.body.should have_selector("entry summary", text: @issue.title) - end - - it "should render atom feed via private token" do - logout - visit project_issues_path(project, :atom, private_token: @user.private_token) - - page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", text: "#{project.name} issues") - page.body.should have_selector("author email", text: @issue.author_email) - page.body.should have_selector("entry summary", text: @issue.title) + page.response_headers['Content-Type'].should have_content("application/atom+xml") + page.body.should have_selector("title", text: "#{project.name} issues") + page.body.should have_selector("author email", text: issue.author_email) + page.body.should have_selector("entry summary", text: issue.title) + end end end end diff --git a/spec/requests/gitlab_flavored_markdown_spec.rb b/spec/requests/gitlab_flavored_markdown_spec.rb index 1076e90c..68d354b7 100644 --- a/spec/requests/gitlab_flavored_markdown_spec.rb +++ b/spec/requests/gitlab_flavored_markdown_spec.rb @@ -25,6 +25,7 @@ describe "Gitlab Flavored Markdown" do @tag_name = "gfm-test-tag" r.git.native(:tag, {}, @tag_name, commit.id) end + after do # delete test branch and tag project.repo.git.native(:branch, {D: true}, @branch_name) diff --git a/spec/requests/security/profile_access_spec.rb b/spec/requests/security/profile_access_spec.rb index 9f6fe6a2..69c1c29c 100644 --- a/spec/requests/security/profile_access_spec.rb +++ b/spec/requests/security/profile_access_spec.rb @@ -28,8 +28,8 @@ describe "Users Security" do it { should be_denied_for :visitor } end - describe "GET /profile/password" do - subject { profile_password_path } + describe "GET /profile/account" do + subject { profile_account_path } it { should be_allowed_for @u1 } it { should be_allowed_for :admin } diff --git a/spec/requests/security/project_access_spec.rb b/spec/requests/security/project_access_spec.rb index 0cdf43bf..af0d5fcd 100644 --- a/spec/requests/security/project_access_spec.rb +++ b/spec/requests/security/project_access_spec.rb @@ -70,7 +70,7 @@ describe "Application access" do end describe "GET /project_code/team" do - subject { team_project_path(@project) } + subject { project_team_index_path(@project) } it { should be_allowed_for @u1 } it { should be_allowed_for @u3 } diff --git a/spec/roles/upvote_spec.rb b/spec/roles/upvote_spec.rb deleted file mode 100644 index 24288ada..00000000 --- a/spec/roles/upvote_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -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/roles/votes_spec.rb b/spec/roles/votes_spec.rb new file mode 100644 index 00000000..98666022 --- /dev/null +++ b/spec/roles/votes_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' + +describe Issue do + let(:issue) { create(:issue) } + + describe "#upvotes" do + 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 + + describe "#downvotes" do + it "with no notes has a 0/0 score" do + issue.downvotes.should == 0 + end + + it "should recognize non--1 notes" do + issue.notes << create(:note, note: "Almost got a -1") + issue.should have(1).note + issue.notes.first.downvote?.should be_false + issue.downvotes.should == 0 + end + + it "should recognize a single -1 note" do + issue.notes << create(:note, note: "-1 This is bad") + issue.downvotes.should == 1 + end + + it "should recognize multiple -1 notes" do + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "-1 Away with this") + issue.downvotes.should == 2 + end + end + + describe "#votes_count" do + it "with no notes has a 0/0 score" do + issue.votes_count.should == 0 + end + + it "should recognize non notes" do + issue.notes << create(:note, note: "No +1 here") + issue.should have(1).note + issue.votes_count.should == 0 + end + + it "should recognize a single +1 note" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.votes_count.should == 1 + end + + it "should recognize a single -1 note" do + issue.notes << create(:note, note: "-1 This is bad") + issue.votes_count.should == 1 + end + + it "should recognize multiple notes" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "+1 I want this") + issue.votes_count.should == 3 + end + end + + describe "#upvotes_in_percent" do + it "with no notes has a 0% score" do + issue.upvotes_in_percent.should == 0 + end + + it "should count a single 1 note as 100%" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.upvotes_in_percent.should == 100 + end + + it "should count multiple +1 notes as 100%" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.upvotes_in_percent.should == 100 + end + + it "should count fractions for multiple +1 and -1 notes correctly" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "+1 me too") + issue.upvotes_in_percent.should == 75 + end + end + + describe "#downvotes_in_percent" do + it "with no notes has a 0% score" do + issue.downvotes_in_percent.should == 0 + end + + it "should count a single -1 note as 100%" do + issue.notes << create(:note, note: "-1 This is bad") + issue.downvotes_in_percent.should == 100 + end + + it "should count multiple -1 notes as 100%" do + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "-1 Away with this") + issue.downvotes_in_percent.should == 100 + end + + it "should count fractions for multiple +1 and -1 notes correctly" do + issue.notes << create(:note, note: "+1 This is awesome") + issue.notes << create(:note, note: "+1 I want this") + issue.notes << create(:note, note: "-1 This is bad") + issue.notes << create(:note, note: "+1 me too") + issue.downvotes_in_percent.should == 25 + end + end +end diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb new file mode 100644 index 00000000..60261c7a --- /dev/null +++ b/spec/routing/admin_routing_spec.rb @@ -0,0 +1,166 @@ +require 'spec_helper' + +# team_update_admin_user PUT /admin/users/:id/team_update(.:format) admin/users#team_update +# block_admin_user PUT /admin/users/:id/block(.:format) admin/users#block +# unblock_admin_user PUT /admin/users/:id/unblock(.:format) admin/users#unblock +# admin_users GET /admin/users(.:format) admin/users#index +# POST /admin/users(.:format) admin/users#create +# new_admin_user GET /admin/users/new(.:format) admin/users#new +# edit_admin_user GET /admin/users/:id/edit(.:format) admin/users#edit +# admin_user GET /admin/users/:id(.:format) admin/users#show +# PUT /admin/users/:id(.:format) admin/users#update +# DELETE /admin/users/:id(.:format) admin/users#destroy +describe Admin::UsersController, "routing" do + it "to #team_update" do + put("/admin/users/1/team_update").should route_to('admin/users#team_update', id: '1') + end + + it "to #block" do + put("/admin/users/1/block").should route_to('admin/users#block', id: '1') + end + + it "to #unblock" do + put("/admin/users/1/unblock").should route_to('admin/users#unblock', id: '1') + end + + it "to #index" do + get("/admin/users").should route_to('admin/users#index') + end + + it "to #show" do + get("/admin/users/1").should route_to('admin/users#show', id: '1') + end + + it "to #create" do + post("/admin/users").should route_to('admin/users#create') + end + + it "to #new" do + get("/admin/users/new").should route_to('admin/users#new') + end + + it "to #edit" do + get("/admin/users/1/edit").should route_to('admin/users#edit', id: '1') + end + + it "to #show" do + get("/admin/users/1").should route_to('admin/users#show', id: '1') + end + + it "to #update" do + put("/admin/users/1").should route_to('admin/users#update', id: '1') + end + + it "to #destroy" do + delete("/admin/users/1").should route_to('admin/users#destroy', id: '1') + end +end + +# team_admin_project GET /admin/projects/:id/team(.:format) admin/projects#team {:id=>/[^\/]+/} +# team_update_admin_project PUT /admin/projects/:id/team_update(.:format) admin/projects#team_update {:id=>/[^\/]+/} +# admin_projects GET /admin/projects(.:format) admin/projects#index {:id=>/[^\/]+/} +# POST /admin/projects(.:format) admin/projects#create {:id=>/[^\/]+/} +# new_admin_project GET /admin/projects/new(.:format) admin/projects#new {:id=>/[^\/]+/} +# edit_admin_project GET /admin/projects/:id/edit(.:format) admin/projects#edit {:id=>/[^\/]+/} +# admin_project GET /admin/projects/:id(.:format) admin/projects#show {:id=>/[^\/]+/} +# PUT /admin/projects/:id(.:format) admin/projects#update {:id=>/[^\/]+/} +# DELETE /admin/projects/:id(.:format) admin/projects#destroy {:id=>/[^\/]+/} +describe Admin::ProjectsController, "routing" do + it "to #team" do + get("/admin/projects/gitlab/team").should route_to('admin/projects#team', id: 'gitlab') + end + + it "to #team_update" do + put("/admin/projects/gitlab/team_update").should route_to('admin/projects#team_update', id: 'gitlab') + end + + it "to #index" do + get("/admin/projects").should route_to('admin/projects#index') + end + + it "to #create" do + post("/admin/projects").should route_to('admin/projects#create') + end + + it "to #new" do + get("/admin/projects/new").should route_to('admin/projects#new') + end + + it "to #edit" do + get("/admin/projects/gitlab/edit").should route_to('admin/projects#edit', id: 'gitlab') + end + + it "to #show" do + get("/admin/projects/gitlab").should route_to('admin/projects#show', id: 'gitlab') + end + + it "to #update" do + put("/admin/projects/gitlab").should route_to('admin/projects#update', id: 'gitlab') + end + + it "to #destroy" do + delete("/admin/projects/gitlab").should route_to('admin/projects#destroy', id: 'gitlab') + end +end + +# edit_admin_team_member GET /admin/team_members/:id/edit(.:format) admin/team_members#edit +# admin_team_member PUT /admin/team_members/:id(.:format) admin/team_members#update +# DELETE /admin/team_members/:id(.:format) admin/team_members#destroy +describe Admin::TeamMembersController, "routing" do + it "to #edit" do + get("/admin/team_members/1/edit").should route_to('admin/team_members#edit', id: '1') + end + + it "to #update" do + put("/admin/team_members/1").should route_to('admin/team_members#update', id: '1') + end + + it "to #destroy" do + delete("/admin/team_members/1").should route_to('admin/team_members#destroy', id: '1') + end +end + +# admin_hook_test GET /admin/hooks/:hook_id/test(.:format) admin/hooks#test +# admin_hooks GET /admin/hooks(.:format) admin/hooks#index +# POST /admin/hooks(.:format) admin/hooks#create +# admin_hook DELETE /admin/hooks/:id(.:format) admin/hooks#destroy +describe Admin::HooksController, "routing" do + it "to #test" do + get("/admin/hooks/1/test").should route_to('admin/hooks#test', hook_id: '1') + end + + it "to #index" do + get("/admin/hooks").should route_to('admin/hooks#index') + end + + it "to #create" do + post("/admin/hooks").should route_to('admin/hooks#create') + end + + it "to #destroy" do + delete("/admin/hooks/1").should route_to('admin/hooks#destroy', id: '1') + end + +end + +# admin_logs GET /admin/logs(.:format) admin/logs#show +describe Admin::LogsController, "routing" do + it "to #show" do + get("/admin/logs").should route_to('admin/logs#show') + end +end + +# admin_resque GET /admin/resque(.:format) admin/resque#show +describe Admin::ResqueController, "routing" do + it "to #show" do + get("/admin/resque").should route_to('admin/resque#show') + end +end + +# admin_root /admin(.:format) admin/dashboard#index +describe Admin::DashboardController, "routing" do + it "to #index" do + get("/admin").should route_to('admin/dashboard#index') + end +end + diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb new file mode 100644 index 00000000..b3f9db01 --- /dev/null +++ b/spec/routing/project_routing_spec.rb @@ -0,0 +1,398 @@ +require 'spec_helper' + +# Shared examples for a resource inside a Project +# +# By default it tests all the default REST actions: index, create, new, edit, +# show, update, and destroy. You can remove actions by customizing the +# `actions` variable. +# +# It also expects a `controller` variable to be available which defines both +# the path to the resource as well as the controller name. +# +# Examples +# +# # Default behavior +# it_behaves_like "RESTful project resources" do +# let(:controller) { 'issues' } +# end +# +# # Customizing actions +# it_behaves_like "RESTful project resources" do +# let(:actions) { [:index] } +# let(:controller) { 'issues' } +# end +shared_examples "RESTful project resources" do + let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] } + + it "to #index" do + get("/gitlabhq/#{controller}").should route_to("#{controller}#index", project_id: 'gitlabhq') if actions.include?(:index) + end + + it "to #create" do + post("/gitlabhq/#{controller}").should route_to("#{controller}#create", project_id: 'gitlabhq') if actions.include?(:create) + end + + it "to #new" do + get("/gitlabhq/#{controller}/new").should route_to("#{controller}#new", project_id: 'gitlabhq') if actions.include?(:new) + end + + it "to #edit" do + get("/gitlabhq/#{controller}/1/edit").should route_to("#{controller}#edit", project_id: 'gitlabhq', id: '1') if actions.include?(:edit) + end + + it "to #show" do + get("/gitlabhq/#{controller}/1").should route_to("#{controller}#show", project_id: 'gitlabhq', id: '1') if actions.include?(:show) + end + + it "to #update" do + put("/gitlabhq/#{controller}/1").should route_to("#{controller}#update", project_id: 'gitlabhq', id: '1') if actions.include?(:update) + end + + it "to #destroy" do + delete("/gitlabhq/#{controller}/1").should route_to("#{controller}#destroy", project_id: 'gitlabhq', id: '1') if actions.include?(:destroy) + end +end + +# projects POST /projects(.:format) projects#create +# new_project GET /projects/new(.:format) projects#new +# wall_project GET /:id/wall(.:format) projects#wall +# graph_project GET /:id/graph(.:format) projects#graph +# files_project GET /:id/files(.:format) projects#files +# edit_project GET /:id/edit(.:format) projects#edit +# project GET /:id(.:format) projects#show +# PUT /:id(.:format) projects#update +# DELETE /:id(.:format) projects#destroy +describe ProjectsController, "routing" do + it "to #create" do + post("/projects").should route_to('projects#create') + end + + it "to #new" do + get("/projects/new").should route_to('projects#new') + end + + it "to #wall" do + get("/gitlabhq/wall").should route_to('projects#wall', id: 'gitlabhq') + end + + it "to #graph" do + get("/gitlabhq/graph").should route_to('projects#graph', id: 'gitlabhq') + end + + it "to #files" do + get("/gitlabhq/files").should route_to('projects#files', id: 'gitlabhq') + end + + it "to #edit" do + get("/gitlabhq/edit").should route_to('projects#edit', id: 'gitlabhq') + end + + it "to #show" do + get("/gitlabhq").should route_to('projects#show', id: 'gitlabhq') + end + + it "to #update" do + put("/gitlabhq").should route_to('projects#update', id: 'gitlabhq') + end + + it "to #destroy" do + delete("/gitlabhq").should route_to('projects#destroy', id: 'gitlabhq') + end +end + +# pages_project_wikis GET /:project_id/wikis/pages(.:format) wikis#pages +# history_project_wiki GET /:project_id/wikis/:id/history(.:format) wikis#history +# project_wikis POST /:project_id/wikis(.:format) wikis#create +# edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) wikis#edit +# project_wiki GET /:project_id/wikis/:id(.:format) wikis#show +# DELETE /:project_id/wikis/:id(.:format) wikis#destroy +describe WikisController, "routing" do + it "to #pages" do + get("/gitlabhq/wikis/pages").should route_to('wikis#pages', project_id: 'gitlabhq') + end + + it "to #history" do + get("/gitlabhq/wikis/1/history").should route_to('wikis#history', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like "RESTful project resources" do + let(:actions) { [:create, :edit, :show, :destroy] } + let(:controller) { 'wikis' } + end +end + +# branches_project_repository GET /:project_id/repository/branches(.:format) repositories#branches +# tags_project_repository GET /:project_id/repository/tags(.:format) repositories#tags +# archive_project_repository GET /:project_id/repository/archive(.:format) repositories#archive +# project_repository POST /:project_id/repository(.:format) repositories#create +# new_project_repository GET /:project_id/repository/new(.:format) repositories#new +# edit_project_repository GET /:project_id/repository/edit(.:format) repositories#edit +# GET /:project_id/repository(.:format) repositories#show +# PUT /:project_id/repository(.:format) repositories#update +# DELETE /:project_id/repository(.:format) repositories#destroy +describe RepositoriesController, "routing" do + it "to #branches" do + get("/gitlabhq/repository/branches").should route_to('repositories#branches', project_id: 'gitlabhq') + end + + it "to #tags" do + get("/gitlabhq/repository/tags").should route_to('repositories#tags', project_id: 'gitlabhq') + end + + it "to #archive" do + get("/gitlabhq/repository/archive").should route_to('repositories#archive', project_id: 'gitlabhq') + end + + it "to #create" do + post("/gitlabhq/repository").should route_to('repositories#create', project_id: 'gitlabhq') + end + + it "to #new" do + get("/gitlabhq/repository/new").should route_to('repositories#new', project_id: 'gitlabhq') + end + + it "to #edit" do + get("/gitlabhq/repository/edit").should route_to('repositories#edit', project_id: 'gitlabhq') + end + + it "to #show" do + get("/gitlabhq/repository").should route_to('repositories#show', project_id: 'gitlabhq') + end + + it "to #update" do + put("/gitlabhq/repository").should route_to('repositories#update', project_id: 'gitlabhq') + end + + it "to #destroy" do + delete("/gitlabhq/repository").should route_to('repositories#destroy', project_id: 'gitlabhq') + end +end + +# project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index +# POST /:project_id/deploy_keys(.:format) deploy_keys#create +# new_project_deploy_key GET /:project_id/deploy_keys/new(.:format) deploy_keys#new +# edit_project_deploy_key GET /:project_id/deploy_keys/:id/edit(.:format) deploy_keys#edit +# project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show +# PUT /:project_id/deploy_keys/:id(.:format) deploy_keys#update +# DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy +describe DeployKeysController, "routing" do + it_behaves_like "RESTful project resources" do + let(:controller) { 'deploy_keys' } + end +end + +# project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index +# POST /:project_id/protected_branches(.:format) protected_branches#create +# project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy +describe ProtectedBranchesController, "routing" do + it_behaves_like "RESTful project resources" do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'protected_branches' } + end +end + +# switch_project_refs GET /:project_id/switch(.:format) refs#switch +# tree_project_ref GET /:project_id/:id/tree(.:format) refs#tree +# logs_tree_project_ref GET /:project_id/:id/logs_tree(.:format) refs#logs_tree +# blob_project_ref GET /:project_id/:id/blob(.:format) refs#blob +# tree_file_project_ref GET /:project_id/:id/tree/:path(.:format) refs#tree +# logs_file_project_ref GET /:project_id/:id/logs_tree/:path(.:format) refs#logs_tree +# blame_file_project_ref GET /:project_id/:id/blame/:path(.:format) refs#blame +describe RefsController, "routing" do + it "to #switch" do + get("/gitlabhq/switch").should route_to('refs#switch', project_id: 'gitlabhq') + end + + it "to #tree" do + get("/gitlabhq/stable/tree").should route_to('refs#tree', project_id: 'gitlabhq', id: 'stable') + get("/gitlabhq/stable/tree/foo/bar/baz").should route_to('refs#tree', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') + end + + it "to #logs_tree" do + get("/gitlabhq/stable/logs_tree").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable') + get("/gitlabhq/stable/logs_tree/foo/bar/baz").should route_to('refs#logs_tree', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') + end + + it "to #blob" do + get("/gitlabhq/stable/blob").should route_to('refs#blob', project_id: 'gitlabhq', id: 'stable') + end + + it "to #blame" do + get("/gitlabhq/stable/blame/foo/bar/baz").should route_to('refs#blame', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') + end +end + +# diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) merge_requests#diffs +# automerge_project_merge_request GET /:project_id/merge_requests/:id/automerge(.:format) merge_requests#automerge +# automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) merge_requests#automerge_check +# raw_project_merge_request GET /:project_id/merge_requests/:id/raw(.:format) merge_requests#raw +# branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) merge_requests#branch_from +# branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) merge_requests#branch_to +# project_merge_requests GET /:project_id/merge_requests(.:format) merge_requests#index +# POST /:project_id/merge_requests(.:format) merge_requests#create +# new_project_merge_request GET /:project_id/merge_requests/new(.:format) merge_requests#new +# edit_project_merge_request GET /:project_id/merge_requests/:id/edit(.:format) merge_requests#edit +# project_merge_request GET /:project_id/merge_requests/:id(.:format) merge_requests#show +# PUT /:project_id/merge_requests/:id(.:format) merge_requests#update +# DELETE /:project_id/merge_requests/:id(.:format) merge_requests#destroy +describe MergeRequestsController, "routing" do + it "to #diffs" do + get("/gitlabhq/merge_requests/1/diffs").should route_to('merge_requests#diffs', project_id: 'gitlabhq', id: '1') + end + + it "to #automerge" do + get("/gitlabhq/merge_requests/1/automerge").should route_to('merge_requests#automerge', project_id: 'gitlabhq', id: '1') + end + + it "to #automerge_check" do + get("/gitlabhq/merge_requests/1/automerge_check").should route_to('merge_requests#automerge_check', project_id: 'gitlabhq', id: '1') + end + + it "to #raw" do + get("/gitlabhq/merge_requests/1/raw").should route_to('merge_requests#raw', project_id: 'gitlabhq', id: '1') + end + + it "to #branch_from" do + get("/gitlabhq/merge_requests/branch_from").should route_to('merge_requests#branch_from', project_id: 'gitlabhq') + end + + it "to #branch_to" do + get("/gitlabhq/merge_requests/branch_to").should route_to('merge_requests#branch_to', project_id: 'gitlabhq') + end + + it_behaves_like "RESTful project resources" do + let(:controller) { 'merge_requests' } + end +end + +# raw_project_snippet GET /:project_id/snippets/:id/raw(.:format) snippets#raw +# project_snippets GET /:project_id/snippets(.:format) snippets#index +# POST /:project_id/snippets(.:format) snippets#create +# new_project_snippet GET /:project_id/snippets/new(.:format) snippets#new +# edit_project_snippet GET /:project_id/snippets/:id/edit(.:format) snippets#edit +# project_snippet GET /:project_id/snippets/:id(.:format) snippets#show +# PUT /:project_id/snippets/:id(.:format) snippets#update +# DELETE /:project_id/snippets/:id(.:format) snippets#destroy +describe SnippetsController, "routing" do + it "to #raw" do + get("/gitlabhq/snippets/1/raw").should route_to('snippets#raw', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like "RESTful project resources" do + let(:controller) { 'snippets' } + end +end + +# test_project_hook GET /:project_id/hooks/:id/test(.:format) hooks#test +# project_hooks GET /:project_id/hooks(.:format) hooks#index +# POST /:project_id/hooks(.:format) hooks#create +# project_hook DELETE /:project_id/hooks/:id(.:format) hooks#destroy +describe HooksController, "routing" do + it "to #test" do + get("/gitlabhq/hooks/1/test").should route_to('hooks#test', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like "RESTful project resources" do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'hooks' } + end +end + +# compare_project_commits GET /:project_id/commits/compare(.:format) commits#compare +# patch_project_commit GET /:project_id/commits/:id/patch(.:format) commits#patch +# project_commits GET /:project_id/commits(.:format) commits#index +# POST /:project_id/commits(.:format) commits#create +# new_project_commit GET /:project_id/commits/new(.:format) commits#new +# edit_project_commit GET /:project_id/commits/:id/edit(.:format) commits#edit +# project_commit GET /:project_id/commits/:id(.:format) commits#show +# PUT /:project_id/commits/:id(.:format) commits#update +# DELETE /:project_id/commits/:id(.:format) commits#destroy +describe CommitsController, "routing" do + it "to #compare" do + get("/gitlabhq/commits/compare").should route_to('commits#compare', project_id: 'gitlabhq') + end + + it "to #patch" do + get("/gitlabhq/commits/1/patch").should route_to('commits#patch', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like "RESTful project resources" do + let(:controller) { 'commits' } + end +end + +# project_team_members GET /:project_id/team_members(.:format) team_members#index +# POST /:project_id/team_members(.:format) team_members#create +# new_project_team_member GET /:project_id/team_members/new(.:format) team_members#new +# edit_project_team_member GET /:project_id/team_members/:id/edit(.:format) team_members#edit +# project_team_member GET /:project_id/team_members/:id(.:format) team_members#show +# PUT /:project_id/team_members/:id(.:format) team_members#update +# DELETE /:project_id/team_members/:id(.:format) team_members#destroy +describe TeamMembersController, "routing" do + it_behaves_like "RESTful project resources" do + let(:controller) { 'team_members' } + end +end + +# project_milestones GET /:project_id/milestones(.:format) milestones#index +# POST /:project_id/milestones(.:format) milestones#create +# new_project_milestone GET /:project_id/milestones/new(.:format) milestones#new +# edit_project_milestone GET /:project_id/milestones/:id/edit(.:format) milestones#edit +# project_milestone GET /:project_id/milestones/:id(.:format) milestones#show +# PUT /:project_id/milestones/:id(.:format) milestones#update +# DELETE /:project_id/milestones/:id(.:format) milestones#destroy +describe MilestonesController, "routing" do + it_behaves_like "RESTful project resources" do + let(:controller) { 'milestones' } + end +end + +# project_labels GET /:project_id/labels(.:format) labels#index +describe LabelsController, "routing" do + it "to #index" do + get("/gitlabhq/labels").should route_to('labels#index', project_id: 'gitlabhq') + end +end + +# sort_project_issues POST /:project_id/issues/sort(.:format) issues#sort +# bulk_update_project_issues POST /:project_id/issues/bulk_update(.:format) issues#bulk_update +# search_project_issues GET /:project_id/issues/search(.:format) issues#search +# project_issues GET /:project_id/issues(.:format) issues#index +# POST /:project_id/issues(.:format) issues#create +# new_project_issue GET /:project_id/issues/new(.:format) issues#new +# edit_project_issue GET /:project_id/issues/:id/edit(.:format) issues#edit +# project_issue GET /:project_id/issues/:id(.:format) issues#show +# PUT /:project_id/issues/:id(.:format) issues#update +# DELETE /:project_id/issues/:id(.:format) issues#destroy +describe IssuesController, "routing" do + it "to #sort" do + post("/gitlabhq/issues/sort").should route_to('issues#sort', project_id: 'gitlabhq') + end + + it "to #bulk_update" do + post("/gitlabhq/issues/bulk_update").should route_to('issues#bulk_update', project_id: 'gitlabhq') + end + + it "to #search" do + get("/gitlabhq/issues/search").should route_to('issues#search', project_id: 'gitlabhq') + end + + it_behaves_like "RESTful project resources" do + let(:controller) { 'issues' } + end +end + +# preview_project_notes POST /:project_id/notes/preview(.:format) notes#preview +# project_notes GET /:project_id/notes(.:format) notes#index +# POST /:project_id/notes(.:format) notes#create +# project_note DELETE /:project_id/notes/:id(.:format) notes#destroy +describe NotesController, "routing" do + it "to #preview" do + post("/gitlabhq/notes/preview").should route_to('notes#preview', project_id: 'gitlabhq') + end + + it_behaves_like "RESTful project resources" do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'notes' } + end +end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb new file mode 100644 index 00000000..cb8dbf37 --- /dev/null +++ b/spec/routing/routing_spec.rb @@ -0,0 +1,186 @@ +require 'spec_helper' + +# search GET /search(.:format) search#show +describe SearchController, "routing" do + it "to #show" do + get("/search").should route_to('search#show') + end +end + +# gitlab_api /api Gitlab::API +# resque /info/resque Resque::Server +# /:path Grack +describe "Mounted Apps", "routing" do + it "to API" do + get("/api").should be_routable + end + + it "to Resque" do + pending + get("/info/resque").should be_routable + end + + it "to Grack" do + get("/gitlabhq.git").should be_routable + end +end + +# help GET /help(.:format) help#index +# help_permissions GET /help/permissions(.:format) help#permissions +# help_workflow GET /help/workflow(.:format) help#workflow +# help_api GET /help/api(.:format) help#api +# help_web_hooks GET /help/web_hooks(.:format) help#web_hooks +# help_system_hooks GET /help/system_hooks(.:format) help#system_hooks +# help_markdown GET /help/markdown(.:format) help#markdown +# help_ssh GET /help/ssh(.:format) help#ssh +describe HelpController, "routing" do + it "to #index" do + get("/help").should route_to('help#index') + end + + it "to #permissions" do + get("/help/permissions").should route_to('help#permissions') + end + + it "to #workflow" do + get("/help/workflow").should route_to('help#workflow') + end + + it "to #api" do + get("/help/api").should route_to('help#api') + end + + it "to #web_hooks" do + get("/help/web_hooks").should route_to('help#web_hooks') + end + + it "to #system_hooks" do + get("/help/system_hooks").should route_to('help#system_hooks') + end + + it "to #markdown" do + get("/help/markdown").should route_to('help#markdown') + end + + it "to #ssh" do + get("/help/ssh").should route_to('help#ssh') + end +end + +# errors_githost GET /errors/githost(.:format) errors#githost +describe ErrorsController, "routing" do + it "to #githost" do + get("/errors/githost").should route_to('errors#githost') + end +end + +# profile_account GET /profile/account(.:format) profile#account +# profile_history GET /profile/history(.:format) profile#history +# profile_password PUT /profile/password(.:format) profile#password_update +# profile_token GET /profile/token(.:format) profile#token +# profile_reset_private_token PUT /profile/reset_private_token(.:format) profile#reset_private_token +# profile GET /profile(.:format) profile#show +# profile_design GET /profile/design(.:format) profile#design +# profile_update PUT /profile/update(.:format) profile#update +describe ProfileController, "routing" do + it "to #account" do + get("/profile/account").should route_to('profile#account') + end + + it "to #history" do + get("/profile/history").should route_to('profile#history') + end + + it "to #password_update" do + put("/profile/password").should route_to('profile#password_update') + end + + it "to #token" do + get("/profile/token").should route_to('profile#token') + end + + it "to #reset_private_token" do + put("/profile/reset_private_token").should route_to('profile#reset_private_token') + end + + it "to #show" do + get("/profile").should route_to('profile#show') + end + + it "to #design" do + get("/profile/design").should route_to('profile#design') + end + + it "to #update" do + put("/profile/update").should route_to('profile#update') + end +end + +# keys GET /keys(.:format) keys#index +# POST /keys(.:format) keys#create +# new_key GET /keys/new(.:format) keys#new +# edit_key GET /keys/:id/edit(.:format) keys#edit +# key GET /keys/:id(.:format) keys#show +# PUT /keys/:id(.:format) keys#update +# DELETE /keys/:id(.:format) keys#destroy +describe KeysController, "routing" do + it "to #index" do + get("/keys").should route_to('keys#index') + end + + it "to #create" do + post("/keys").should route_to('keys#create') + end + + it "to #new" do + get("/keys/new").should route_to('keys#new') + end + + it "to #edit" do + get("/keys/1/edit").should route_to('keys#edit', id: '1') + end + + it "to #show" do + get("/keys/1").should route_to('keys#show', id: '1') + end + + it "to #update" do + put("/keys/1").should route_to('keys#update', id: '1') + end + + it "to #destroy" do + delete("/keys/1").should route_to('keys#destroy', id: '1') + end +end + +# dashboard GET /dashboard(.:format) dashboard#index +# dashboard_issues GET /dashboard/issues(.:format) dashboard#issues +# dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests +# root / dashboard#index +describe DashboardController, "routing" do + it "to #index" do + get("/dashboard").should route_to('dashboard#index') + get("/").should route_to('dashboard#index') + end + + it "to #issues" do + get("/dashboard/issues").should route_to('dashboard#issues') + end + + it "to #merge_requests" do + get("/dashboard/merge_requests").should route_to('dashboard#merge_requests') + end +end + +# new_user_session GET /users/sign_in(.:format) devise/sessions#new +# user_session POST /users/sign_in(.:format) devise/sessions#create +# destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy +# user_omniauth_authorize /users/auth/:provider(.:format) omniauth_callbacks#passthru +# user_omniauth_callback /users/auth/:action/callback(.:format) omniauth_callbacks#(?-mix:(?!)) +# user_password POST /users/password(.:format) devise/passwords#create +# new_user_password GET /users/password/new(.:format) devise/passwords#new +# edit_user_password GET /users/password/edit(.:format) devise/passwords#edit +# PUT /users/password(.:format) devise/passwords#update +describe "Authentication", "routing" do + # pending +end diff --git a/spec/support/gitolite_stub.rb b/spec/support/gitolite_stub.rb index 037b09cd..574bb5a1 100644 --- a/spec/support/gitolite_stub.rb +++ b/spec/support/gitolite_stub.rb @@ -5,42 +5,16 @@ module GitoliteStub 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_admin = double('Gitolite::GitoliteAdmin') + gitolite_admin.as_null_object Gitolite::GitoliteAdmin.stub(new: gitolite_admin) - end def stub_gitlab_gitolite gitolite_config = double('Gitlab::GitoliteConfig') - gitolite_config.stub( - apply: ->() { yield(self) }, - write_key: true, - rm_key: true, - update_projects: true, - update_project: true, - update_project!: true, - destroy_project: true, - destroy_project!: true, - admin_all_repo: true, - admin_all_repo!: true, - - ) + gitolite_config.stub(apply: ->() { yield(self) }) + gitolite_config.as_null_object Gitlab::GitoliteConfig.stub(new: gitolite_config) end diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb index cb1dcba3..809453c4 100644 --- a/spec/support/matchers.rb +++ b/spec/support/matchers.rb @@ -73,11 +73,7 @@ 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 + is_at_least(range.min) && is_at_most(range.max) end end end diff --git a/vendor/assets/images/authbuttons/github_32.png b/vendor/assets/images/authbuttons/github_32.png new file mode 100644 index 00000000..247e52a5 Binary files /dev/null and b/vendor/assets/images/authbuttons/github_32.png differ diff --git a/vendor/assets/images/authbuttons/github_64.png b/vendor/assets/images/authbuttons/github_64.png new file mode 100644 index 00000000..fca7bf44 Binary files /dev/null and b/vendor/assets/images/authbuttons/github_64.png differ diff --git a/vendor/assets/images/authbuttons/google_32.png b/vendor/assets/images/authbuttons/google_32.png new file mode 100644 index 00000000..3909e9de Binary files /dev/null and b/vendor/assets/images/authbuttons/google_32.png differ diff --git a/vendor/assets/images/authbuttons/google_64.png b/vendor/assets/images/authbuttons/google_64.png new file mode 100644 index 00000000..e55f34f1 Binary files /dev/null and b/vendor/assets/images/authbuttons/google_64.png differ diff --git a/vendor/assets/images/authbuttons/twitter_32.png b/vendor/assets/images/authbuttons/twitter_32.png new file mode 100644 index 00000000..daadcffd Binary files /dev/null and b/vendor/assets/images/authbuttons/twitter_32.png differ diff --git a/vendor/assets/images/authbuttons/twitter_64.png b/vendor/assets/images/authbuttons/twitter_64.png new file mode 100644 index 00000000..68b74530 Binary files /dev/null and b/vendor/assets/images/authbuttons/twitter_64.png differ