gemcutter 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.textile +8 -14
- data/lib/commands/abstract_command.rb +66 -0
- data/lib/commands/migrate.rb +99 -0
- data/lib/commands/push.rb +11 -58
- data/lib/commands/tumble.rb +3 -3
- data/lib/rubygems_plugin.rb +14 -5
- data/test/command_helper.rb +19 -0
- data/test/unit/commands/abstract_command_test.rb +113 -0
- data/test/unit/commands/migrate_command_test.rb +141 -0
- data/test/unit/commands/push_command_test.rb +41 -0
- data/test/unit/commands/tumble_command_test.rb +58 -0
- metadata +30 -10
    
        data/README.textile
    CHANGED
    
    | @@ -23,27 +23,21 @@ No. In fact it will make their lives better: it will be easier to get to project | |
| 23 23 | 
             
            GitHub's gem system is great, but it's not the canonical source by default. The nice part is that it takes the approval process out, but the namespacing on gems makes it hard for those new to the community to determine what is the right copy to use. Also, the goals here are much different and the site as a whole can be improved over time by the community.
         | 
| 24 24 |  | 
| 25 25 | 
             
            *How can I gain access of my gem that was available through RubyForge?*<br />
         | 
| 26 | 
            -
            The  | 
| 26 | 
            +
            The full process is coming soon!
         | 
| 27 27 |  | 
| 28 28 | 
             
            *How can I help?*<br />
         | 
| 29 29 | 
             
            Fork away, and read the next section! Feel free to bug qrush via a message or on FreeNode if you're interested.
         | 
| 30 30 |  | 
| 31 | 
            -
            h2.  | 
| 31 | 
            +
            h2. Contributions
         | 
| 32 32 |  | 
| 33 | 
            -
             | 
| 33 | 
            +
            Gemcutter has a 'commit-bit' policy, much like the Rubinius project. Submit a patch that is accepted, and you'll get full commit access to the project. Please check the wiki for more information.
         | 
| 34 34 |  | 
| 35 | 
            -
             | 
| 36 | 
            -
            * Releasing/updating gems via @gem push@
         | 
| 37 | 
            -
            * Project pages
         | 
| 38 | 
            -
            * Authentication/accounts
         | 
| 39 | 
            -
            * Gem server up and running
         | 
| 35 | 
            +
            If you're looking for things to hack on, please check GitHub Issues. If you've found bugs or have feature ideas don't be afraid to pipe up and ask the mailing list about them.
         | 
| 40 36 |  | 
| 41 | 
            -
             | 
| 37 | 
            +
            h2. Testing
         | 
| 42 38 |  | 
| 43 | 
            -
             | 
| 44 | 
            -
            * Fully fledged API documentation
         | 
| 45 | 
            -
            * Search via the API and site
         | 
| 39 | 
            +
            Make sure you run @rake gemcutter:index:create@ before running the test suite, otherwise it might complain. Better solutions to this are more than welcome.
         | 
| 46 40 |  | 
| 47 | 
            -
            h2.  | 
| 41 | 
            +
            h2. License
         | 
| 48 42 |  | 
| 49 | 
            -
             | 
| 43 | 
            +
            Gemcutter uses the MIT license. Please check the LICENSE file for more details.
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            class Gem::AbstractCommand < Gem::Command
         | 
| 2 | 
            +
              def api_key
         | 
| 3 | 
            +
                Gem.configuration[:gemcutter_key]
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def gemcutter_url
         | 
| 7 | 
            +
                if ENV['test']
         | 
| 8 | 
            +
                  "http://gemcutter.local"
         | 
| 9 | 
            +
                else
         | 
| 10 | 
            +
                  "https://gemcutter.heroku.com"
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def setup
         | 
| 15 | 
            +
                use_proxy! if http_proxy
         | 
| 16 | 
            +
                sign_in unless api_key
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def sign_in
         | 
| 20 | 
            +
                say "Enter your Gemcutter credentials. Don't have an account yet? Create one at #{URL}/sign_up"
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                email = ask("Email: ")
         | 
| 23 | 
            +
                password = ask_for_password("Password: ")
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                url = URI.parse("#{gemcutter_url}/api_key")
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                http = Net::HTTP.new(url.host, url.port)
         | 
| 28 | 
            +
                http.verify_mode = OpenSSL::SSL::VERIFY_NONE
         | 
| 29 | 
            +
                http.use_ssl = (url.scheme == 'https')
         | 
| 30 | 
            +
                request = Net::HTTP::Get.new(url.path)
         | 
| 31 | 
            +
                request.basic_auth email, password
         | 
| 32 | 
            +
                response = http.request(request)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                case response
         | 
| 35 | 
            +
                when Net::HTTPSuccess
         | 
| 36 | 
            +
                  Gem.configuration[:gemcutter_key] = response.body
         | 
| 37 | 
            +
                  Gem.configuration.write
         | 
| 38 | 
            +
                  say "Signed in. Your api key has been stored in ~/.gemrc"
         | 
| 39 | 
            +
                else
         | 
| 40 | 
            +
                  say response.body
         | 
| 41 | 
            +
                  terminate_interaction
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              def use_proxy!
         | 
| 46 | 
            +
                proxy_uri = http_proxy
         | 
| 47 | 
            +
                @proxy_class = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def proxy_class
         | 
| 51 | 
            +
                @proxy_class || Net::HTTP
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              # @return [URI, nil] the HTTP-proxy as a URI if set; +nil+ otherwise
         | 
| 55 | 
            +
              def http_proxy
         | 
| 56 | 
            +
                proxy = Gem.configuration[:http_proxy]
         | 
| 57 | 
            +
                return nil if proxy.nil? || proxy == :no_proxy
         | 
| 58 | 
            +
                URI.parse(proxy)
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              def ask_for_password(message)
         | 
| 62 | 
            +
                password = ui.ask_for_password(message)
         | 
| 63 | 
            +
                ui.say("\n")
         | 
| 64 | 
            +
                password
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| @@ -0,0 +1,99 @@ | |
| 1 | 
            +
            require 'open-uri'
         | 
| 2 | 
            +
            require 'net/scp'
         | 
| 3 | 
            +
            require 'json'
         | 
| 4 | 
            +
            require 'tempfile'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class Gem::Commands::MigrateCommand < Gem::AbstractCommand
         | 
| 7 | 
            +
              attr_reader :rubygem
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              def description
         | 
| 10 | 
            +
                'Migrate a gem your own from Rubyforge to Gemcutter.'
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def initialize
         | 
| 14 | 
            +
                super 'migrate', description
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def execute
         | 
| 18 | 
            +
                setup
         | 
| 19 | 
            +
                migrate
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def migrate
         | 
| 23 | 
            +
                find(get_one_gem_name)
         | 
| 24 | 
            +
                token = get_token
         | 
| 25 | 
            +
                upload_token(token)
         | 
| 26 | 
            +
                check_for_approved
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def find(name)
         | 
| 30 | 
            +
                begin
         | 
| 31 | 
            +
                  data = open("#{gemcutter_url}/gems/#{name}.json")
         | 
| 32 | 
            +
                  @rubygem = JSON.parse(data.string)
         | 
| 33 | 
            +
                rescue OpenURI::HTTPError
         | 
| 34 | 
            +
                  say "This gem is currently not hosted on Gemcutter."
         | 
| 35 | 
            +
                  terminate_interaction
         | 
| 36 | 
            +
                rescue JSON::ParserError => json_error
         | 
| 37 | 
            +
                  say "There was a problem parsing the data: #{json_error}"
         | 
| 38 | 
            +
                  terminate_interaction
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              def get_token
         | 
| 43 | 
            +
                say "Starting migration of #{rubygem["name"]} from RubyForge..."
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                url = URI.parse("#{gemcutter_url}/gems/#{rubygem["slug"]}/migrate")
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                http = proxy_class.new(url.host, url.port)
         | 
| 48 | 
            +
                http.verify_mode = OpenSSL::SSL::VERIFY_NONE
         | 
| 49 | 
            +
                http.use_ssl = (url.scheme == 'https')
         | 
| 50 | 
            +
                request = proxy_class::Post.new(url.path)
         | 
| 51 | 
            +
                request.add_field("Content-Length", 0)
         | 
| 52 | 
            +
                request.add_field("Authorization", api_key)
         | 
| 53 | 
            +
                response = http.request(request)
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                case response
         | 
| 56 | 
            +
                when Net::HTTPSuccess
         | 
| 57 | 
            +
                  say "A migration token has been created."
         | 
| 58 | 
            +
                  response.body
         | 
| 59 | 
            +
                else
         | 
| 60 | 
            +
                  say response.body
         | 
| 61 | 
            +
                  terminate_interaction
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              def upload_token(token)
         | 
| 66 | 
            +
                url = "#{self.rubygem['rubyforge_project']}.rubyforge.org"
         | 
| 67 | 
            +
                say "Uploading the migration token to #{url}. Please enter your RubyForge login:"
         | 
| 68 | 
            +
                login = ask("Login: ")
         | 
| 69 | 
            +
                password = ask_for_password("Password: ")
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                begin
         | 
| 72 | 
            +
                  Net::SCP.start(url, login, :password => password) do |scp|
         | 
| 73 | 
            +
                    temp_token = Tempfile.new("token")
         | 
| 74 | 
            +
                    temp_token.chmod 0644
         | 
| 75 | 
            +
                    temp_token.write(token)
         | 
| 76 | 
            +
                    temp_token.close
         | 
| 77 | 
            +
                    scp.upload! temp_token.path, "/var/www/gforge-projects/#{rubygem['rubyforge_project']}/migrate-#{rubygem['name']}.html"
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
                  say "Successfully uploaded your token."
         | 
| 80 | 
            +
                rescue Exception => e
         | 
| 81 | 
            +
                  say "There was a problem uploading your token: #{e}"
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
              def check_for_approved
         | 
| 86 | 
            +
                say "Asking Gemcutter to verify the upload..."
         | 
| 87 | 
            +
                url = URI.parse("#{gemcutter_url}/gems/#{rubygem["slug"]}/migrate")
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                http = proxy_class.new(url.host, url.port)
         | 
| 90 | 
            +
                http.verify_mode = OpenSSL::SSL::VERIFY_NONE
         | 
| 91 | 
            +
                http.use_ssl = (url.scheme == 'https')
         | 
| 92 | 
            +
                request = proxy_class::Put.new(url.path)
         | 
| 93 | 
            +
                request.add_field("Content-Length", 0)
         | 
| 94 | 
            +
                request.add_field("Authorization", api_key)
         | 
| 95 | 
            +
                response = http.request(request)
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                say response.body
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
            end
         | 
    
        data/lib/commands/push.rb
    CHANGED
    
    | @@ -1,7 +1,11 @@ | |
| 1 1 | 
             
            require 'net/http'
         | 
| 2 2 | 
             
            require 'net/https'
         | 
| 3 | 
            +
            require 'rubygems/local_remote_options'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class Gem::Commands::PushCommand < Gem::AbstractCommand
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              include Gem::LocalRemoteOptions
         | 
| 3 8 |  | 
| 4 | 
            -
            class Gem::Commands::PushCommand < Gem::Command
         | 
| 5 9 | 
             
              def description
         | 
| 6 10 | 
             
                'Push a gem up to Gemcutter'
         | 
| 7 11 | 
             
              end
         | 
| @@ -11,46 +15,29 @@ class Gem::Commands::PushCommand < Gem::Command | |
| 11 15 | 
             
              end
         | 
| 12 16 |  | 
| 13 17 | 
             
              def usage
         | 
| 14 | 
            -
                "#{ | 
| 18 | 
            +
                "#{program_name} GEM"
         | 
| 15 19 | 
             
              end
         | 
| 16 20 |  | 
| 17 21 | 
             
              def initialize
         | 
| 18 22 | 
             
                super 'push', description
         | 
| 23 | 
            +
                add_proxy_option
         | 
| 19 24 | 
             
              end
         | 
| 20 25 |  | 
| 21 26 | 
             
              def execute
         | 
| 22 | 
            -
                 | 
| 27 | 
            +
                setup
         | 
| 23 28 | 
             
                send_gem
         | 
| 24 29 | 
             
              end
         | 
| 25 30 |  | 
| 26 | 
            -
              def ask_for_password(message)
         | 
| 27 | 
            -
                password = ui.ask_for_password(message)
         | 
| 28 | 
            -
                ui.say("\n")
         | 
| 29 | 
            -
                password
         | 
| 30 | 
            -
              end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
              def api_key
         | 
| 33 | 
            -
                Gem.configuration[:gemcutter_key]
         | 
| 34 | 
            -
              end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
              def push_url
         | 
| 37 | 
            -
                if ENV['test']
         | 
| 38 | 
            -
                  "http://gemcutter.local"
         | 
| 39 | 
            -
                else
         | 
| 40 | 
            -
                  "https://gemcutter.heroku.com"
         | 
| 41 | 
            -
                end
         | 
| 42 | 
            -
              end
         | 
| 43 | 
            -
             | 
| 44 31 | 
             
              def send_gem
         | 
| 45 32 | 
             
                say "Pushing gem to Gemcutter..."
         | 
| 46 33 |  | 
| 47 34 | 
             
                name = get_one_gem_name
         | 
| 48 | 
            -
                url = URI.parse("#{ | 
| 35 | 
            +
                url = URI.parse("#{gemcutter_url}/gems")
         | 
| 49 36 |  | 
| 50 | 
            -
                http =  | 
| 37 | 
            +
                http = proxy_class.new(url.host, url.port)
         | 
| 51 38 | 
             
                http.verify_mode = OpenSSL::SSL::VERIFY_NONE
         | 
| 52 39 | 
             
                http.use_ssl = (url.scheme == 'https')
         | 
| 53 | 
            -
                request =  | 
| 40 | 
            +
                request = proxy_class::Post.new(url.path)
         | 
| 54 41 | 
             
                request.body = File.open(name).read
         | 
| 55 42 | 
             
                request.add_field("Content-Length", request.body.size)
         | 
| 56 43 | 
             
                request.add_field("Authorization", api_key)
         | 
| @@ -60,38 +47,4 @@ class Gem::Commands::PushCommand < Gem::Command | |
| 60 47 | 
             
                say response.body
         | 
| 61 48 | 
             
              end
         | 
| 62 49 |  | 
| 63 | 
            -
              def sign_in
         | 
| 64 | 
            -
                say "Enter your Gemcutter credentials. Don't have an account yet? Create one at #{URL}/sign_up"
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                email = ask("Email: ")
         | 
| 67 | 
            -
                password = ask_for_password("Password: ")
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                url = URI.parse("#{push_url}/api_key")
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                http = Net::HTTP.new(url.host, url.port)
         | 
| 72 | 
            -
                http.verify_mode = OpenSSL::SSL::VERIFY_NONE
         | 
| 73 | 
            -
                http.use_ssl = (url.scheme == 'https')
         | 
| 74 | 
            -
                request = Net::HTTP::Get.new(url.path)
         | 
| 75 | 
            -
                request.basic_auth email, password
         | 
| 76 | 
            -
                response = http.request(request)
         | 
| 77 | 
            -
             | 
| 78 | 
            -
                case response
         | 
| 79 | 
            -
                when Net::HTTPSuccess
         | 
| 80 | 
            -
                  Gem.configuration[:gemcutter_key] = response.body
         | 
| 81 | 
            -
                  Gem.configuration.write
         | 
| 82 | 
            -
                  say "Signed in. Your api key has been stored in ~/.gemrc"
         | 
| 83 | 
            -
                else
         | 
| 84 | 
            -
                  say response.body
         | 
| 85 | 
            -
                  terminate_interaction
         | 
| 86 | 
            -
                end
         | 
| 87 | 
            -
              end
         | 
| 88 | 
            -
            end
         | 
| 89 | 
            -
             | 
| 90 | 
            -
            class Gem::StreamUI
         | 
| 91 | 
            -
              def ask_for_password(message)
         | 
| 92 | 
            -
                system "stty -echo"
         | 
| 93 | 
            -
                password = ask(message)
         | 
| 94 | 
            -
                system "stty echo"
         | 
| 95 | 
            -
                password
         | 
| 96 | 
            -
              end
         | 
| 97 50 | 
             
            end
         | 
    
        data/lib/commands/tumble.rb
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            class Gem::Commands::TumbleCommand < Gem:: | 
| 1 | 
            +
            class Gem::Commands::TumbleCommand < Gem::AbstractCommand
         | 
| 2 2 | 
             
              def description
         | 
| 3 3 | 
             
                'Enable or disable Gemcutter as your primary gem source.'
         | 
| 4 4 | 
             
              end
         | 
| @@ -24,9 +24,9 @@ class Gem::Commands::TumbleCommand < Gem::Command | |
| 24 24 | 
             
              end
         | 
| 25 25 |  | 
| 26 26 | 
             
              def show_sources
         | 
| 27 | 
            -
                 | 
| 27 | 
            +
                say "Your gem sources are now:"
         | 
| 28 28 | 
             
                Gem.sources.each do |source|
         | 
| 29 | 
            -
                   | 
| 29 | 
            +
                  say "- #{source}"
         | 
| 30 30 | 
             
                end
         | 
| 31 31 | 
             
              end
         | 
| 32 32 | 
             
            end
         | 
    
        data/lib/rubygems_plugin.rb
    CHANGED
    
    | @@ -1,11 +1,20 @@ | |
| 1 1 | 
             
            $:.unshift File.dirname(__FILE__)     # For use/testing when no gem is installed
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'rubygems/command_manager'
         | 
| 4 | 
            +
            require 'commands/abstract_command'
         | 
| 4 5 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
            require  | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
            Gem::CommandManager.instance.register_command :tumble
         | 
| 6 | 
            +
            %w[migrate push tumble].each do |command|
         | 
| 7 | 
            +
              require "commands/#{command}"
         | 
| 8 | 
            +
              Gem::CommandManager.instance.register_command command.to_sym
         | 
| 9 | 
            +
            end
         | 
| 10 10 |  | 
| 11 11 | 
             
            URL = "http://gemcutter.org" unless defined?(URL)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            class Gem::StreamUI
         | 
| 14 | 
            +
              def ask_for_password(message)
         | 
| 15 | 
            +
                system "stty -echo"
         | 
| 16 | 
            +
                password = ask(message)
         | 
| 17 | 
            +
                system "stty echo"
         | 
| 18 | 
            +
                password
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            require 'rubygems'
         | 
| 2 | 
            +
            require 'test/unit'
         | 
| 3 | 
            +
            require 'shoulda'
         | 
| 4 | 
            +
            require 'redgreen'
         | 
| 5 | 
            +
            require 'active_support'
         | 
| 6 | 
            +
            require 'active_support/test_case'
         | 
| 7 | 
            +
            gem 'fakeweb', '>= 1.2.5'
         | 
| 8 | 
            +
            require 'fakeweb'
         | 
| 9 | 
            +
            require 'rr'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            FakeWeb.allow_net_connect = false
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            require "rubygems_plugin"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            class CommandTest < ActiveSupport::TestCase
         | 
| 18 | 
            +
              include RR::Adapters::TestUnit unless include?(RR::Adapters::TestUnit)
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,113 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/../../command_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Gem::Commands::FakeCommand < Gem::AbstractCommand
         | 
| 4 | 
            +
              def description
         | 
| 5 | 
            +
                'fake command'
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def initialize
         | 
| 9 | 
            +
                super 'fake', description
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def execute
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            class AbstractCommandTest < CommandTest
         | 
| 17 | 
            +
              context "with an fake command" do
         | 
| 18 | 
            +
                setup do
         | 
| 19 | 
            +
                  @command = Gem::Commands::FakeCommand.new
         | 
| 20 | 
            +
                  stub(@command).say
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                context "parsing the proxy" do
         | 
| 24 | 
            +
                  should "return nil if no proxy is set" do
         | 
| 25 | 
            +
                    stub(Gem).configuration { { :http_proxy => nil } }
         | 
| 26 | 
            +
                    assert_equal nil, @command.http_proxy
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  should "return nil if the proxy is set to :no_proxy" do
         | 
| 30 | 
            +
                    stub(Gem).configuration { { :http_proxy => :no_proxy } }
         | 
| 31 | 
            +
                    assert_equal nil, @command.http_proxy
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  should "return a proxy as a URI if set" do
         | 
| 35 | 
            +
                    stub(Gem).configuration { { :http_proxy => 'http://proxy.example.org:9192' } }
         | 
| 36 | 
            +
                    assert_equal 'proxy.example.org', @command.http_proxy.host
         | 
| 37 | 
            +
                    assert_equal 9192, @command.http_proxy.port
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                # TODO: rewrite all of this junk using spies
         | 
| 42 | 
            +
                #should "use a proxy if specified" do
         | 
| 43 | 
            +
                #  stub(@command).http_proxy { 'http://some.proxy' }
         | 
| 44 | 
            +
                #  mock(@command).use_proxy!
         | 
| 45 | 
            +
                #  mock(@command).sign_in
         | 
| 46 | 
            +
                #  @command.setup
         | 
| 47 | 
            +
                #end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                #should "not use a proxy if unspecified" do
         | 
| 50 | 
            +
                #  stub(@command).http_proxy { nil }
         | 
| 51 | 
            +
                #  mock(@command).use_proxy!.never
         | 
| 52 | 
            +
                #  mock(@command).sign_in
         | 
| 53 | 
            +
                #  @command.setup
         | 
| 54 | 
            +
                #end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                should "sign in if no api key" do
         | 
| 57 | 
            +
                  stub(Gem).configuration { {:gemcutter_key => nil} }
         | 
| 58 | 
            +
                  mock(@command).sign_in
         | 
| 59 | 
            +
                  @command.setup
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                should "not sign in if api key exists" do
         | 
| 63 | 
            +
                  stub(Gem).configuration { {:gemcutter_key => "1234567890"} }
         | 
| 64 | 
            +
                  mock(@command).sign_in.never
         | 
| 65 | 
            +
                  @command.setup
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                context "using the proxy" do
         | 
| 69 | 
            +
                  setup do
         | 
| 70 | 
            +
                    stub(Gem).configuration { { :http_proxy => "http://gilbert:sekret@proxy.example.org:8081" } }
         | 
| 71 | 
            +
                    @proxy_class = Object.new
         | 
| 72 | 
            +
                    mock(Net::HTTP).Proxy('proxy.example.org', 8081, 'gilbert', 'sekret') { @proxy_class }
         | 
| 73 | 
            +
                    @command.use_proxy!
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  should "replace Net::HTTP with a proxy version" do
         | 
| 77 | 
            +
                    assert_equal @proxy_class, @command.proxy_class
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                context "signing in" do
         | 
| 82 | 
            +
                  setup do
         | 
| 83 | 
            +
                    @email = "email"
         | 
| 84 | 
            +
                    @password = "password"
         | 
| 85 | 
            +
                    @key = "key"
         | 
| 86 | 
            +
                    mock(@command).say("Enter your Gemcutter credentials. Don't have an account yet? Create one at #{URL}/sign_up")
         | 
| 87 | 
            +
                    mock(@command).ask("Email: ") { @email }
         | 
| 88 | 
            +
                    mock(@command).ask_for_password("Password: ") { @password }
         | 
| 89 | 
            +
                    FakeWeb.register_uri :get, "https://#{@email}:#{@password}@gemcutter.heroku.com/api_key", :body => @key
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    @config = Object.new
         | 
| 92 | 
            +
                    stub(Gem).configuration { @config }
         | 
| 93 | 
            +
                    stub(@config)[:gemcutter_key] = @key
         | 
| 94 | 
            +
                    stub(@config).write
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  should "sign in" do
         | 
| 98 | 
            +
                    mock(@command).say("Signed in. Your api key has been stored in ~/.gemrc")
         | 
| 99 | 
            +
                    @command.sign_in
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  should "let the user know if there was a problem" do
         | 
| 103 | 
            +
                    @problem = "Access Denied"
         | 
| 104 | 
            +
                    mock(@command).say(@problem)
         | 
| 105 | 
            +
                    mock(@command).terminate_interaction
         | 
| 106 | 
            +
                    mock(@config).write.never
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    FakeWeb.register_uri :get, "https://#{@email}:#{@password}@gemcutter.heroku.com/api_key", :body => @problem, :status => 401
         | 
| 109 | 
            +
                    @command.sign_in
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
            end
         | 
| @@ -0,0 +1,141 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/../../command_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class MigrateCommandTest < CommandTest
         | 
| 4 | 
            +
              context "executing the command" do
         | 
| 5 | 
            +
                setup do
         | 
| 6 | 
            +
                  @command = Gem::Commands::MigrateCommand.new
         | 
| 7 | 
            +
                  stub(@command).setup
         | 
| 8 | 
            +
                  stub(@command).migrate
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                should "setup and send the gem" do
         | 
| 12 | 
            +
                  @command.execute
         | 
| 13 | 
            +
                  assert_received(@command) { |subject| subject.setup }
         | 
| 14 | 
            +
                  assert_received(@command) { |subject| subject.migrate }
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              context "migrating" do
         | 
| 19 | 
            +
                setup do
         | 
| 20 | 
            +
                  @command = Gem::Commands::MigrateCommand.new
         | 
| 21 | 
            +
                  @token = "deadbeef"
         | 
| 22 | 
            +
                  stub(@command).say
         | 
| 23 | 
            +
                  stub(@command).find
         | 
| 24 | 
            +
                  stub(@command).get_token { @token }
         | 
| 25 | 
            +
                  stub(@command).upload_token
         | 
| 26 | 
            +
                  stub(@command).check_for_approved
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                should "raise an error with no arguments" do
         | 
| 30 | 
            +
                  assert_raise Gem::CommandLineError do
         | 
| 31 | 
            +
                    @command.migrate
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                should "migrate the gem" do
         | 
| 36 | 
            +
                  stub(@command).get_one_gem_name { "mygem" }
         | 
| 37 | 
            +
                  @command.migrate
         | 
| 38 | 
            +
                  assert_received(@command) { |subject| subject.find("mygem") }
         | 
| 39 | 
            +
                  assert_received(@command) { |subject| subject.get_token }
         | 
| 40 | 
            +
                  assert_received(@command) { |subject| subject.upload_token(@token) }
         | 
| 41 | 
            +
                  assert_received(@command) { |subject| subject.check_for_approved }
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              context "ask about the gem" do
         | 
| 46 | 
            +
                setup do
         | 
| 47 | 
            +
                  @command = Gem::Commands::MigrateCommand.new
         | 
| 48 | 
            +
                  stub(@command).say
         | 
| 49 | 
            +
                  stub(@command).terminate_interaction
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  @name = "rails"
         | 
| 52 | 
            +
                  @json = "{\"downloads\":4,\"name\":\"rails\",\"slug\":\"rails\",\"authors\":\"David Heinemeier Hansson\",\"version\":\"2.3.3\",\"rubyforge_project\":\"rails\",\"info\":\"Rails is a framework for building web-application using CGI, FCGI, mod_ruby, or WEBrick on top of either MySQL, PostgreSQL, SQLite, DB2, SQL Server, or Oracle with eRuby- or Builder-based templates.\"}"
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                should "find gem info if it exists" do
         | 
| 56 | 
            +
                  FakeWeb.register_uri :get, "https://gemcutter.heroku.com/gems/#{@name}.json", :body => @json
         | 
| 57 | 
            +
                  @command.find(@name)
         | 
| 58 | 
            +
                  assert_equal JSON.parse(@json), @command.rubygem
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                should "dump out if the gem couldn't be found" do
         | 
| 62 | 
            +
                  FakeWeb.register_uri :get, "https://gemcutter.heroku.com/gems/#{@name}.json", :body => "Not hosted here.", :status => 404
         | 
| 63 | 
            +
                  @command.find(@name)
         | 
| 64 | 
            +
                  assert_received(@command) { |subject| subject.say(anything) }
         | 
| 65 | 
            +
                  assert_received(@command) { |subject| subject.terminate_interaction }
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                should "dump out if bad json is returned" do
         | 
| 69 | 
            +
                  FakeWeb.register_uri :get, "https://gemcutter.heroku.com/gems/#{@name}.json", :body => "bad data is bad"
         | 
| 70 | 
            +
                  @command.find(@name)
         | 
| 71 | 
            +
                  assert_received(@command) { |subject| subject.say(anything) }
         | 
| 72 | 
            +
                  assert_received(@command) { |subject| subject.terminate_interaction }
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              context "getting the token" do
         | 
| 77 | 
            +
                setup do
         | 
| 78 | 
            +
                  @command = Gem::Commands::MigrateCommand.new
         | 
| 79 | 
            +
                  @name = "SomeGem"
         | 
| 80 | 
            +
                  @name = "somegem"
         | 
| 81 | 
            +
                  stub(@command).say
         | 
| 82 | 
            +
                  stub(@command).terminate_interaction
         | 
| 83 | 
            +
                  stub(@command).rubygem { { "name" => @name, "slug" => @slug } }
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                should "ask gemcutter to start the migration" do
         | 
| 87 | 
            +
                  token = "SECRET TOKEN"
         | 
| 88 | 
            +
                  FakeWeb.register_uri :post, "https://gemcutter.heroku.com/gems/#{@slug}/migrate", :body => token
         | 
| 89 | 
            +
                  assert_equal token, @command.get_token
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                should "dump out if gem could not be found" do
         | 
| 93 | 
            +
                  FakeWeb.register_uri :post, "https://gemcutter.heroku.com/gems/#{@slug}/migrate", :status => 404, :body => "not found"
         | 
| 94 | 
            +
                  @command.get_token
         | 
| 95 | 
            +
                  assert_received(@command) { |subject| subject.say("not found") }
         | 
| 96 | 
            +
                  assert_received(@command) { |subject| subject.terminate_interaction }
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                should "dump out if migration has already been completed" do
         | 
| 100 | 
            +
                  FakeWeb.register_uri :post, "https://gemcutter.heroku.com/gems/#{@slug}/migrate", :status => 403, :body => "already migrated"
         | 
| 101 | 
            +
                  @command.get_token
         | 
| 102 | 
            +
                  assert_received(@command) { |subject| subject.say("already migrated") }
         | 
| 103 | 
            +
                  assert_received(@command) { |subject| subject.terminate_interaction }
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
              context "uploading the token" do
         | 
| 108 | 
            +
                setup do
         | 
| 109 | 
            +
                  @command = Gem::Commands::MigrateCommand.new
         | 
| 110 | 
            +
                  @token = "deadbeef"
         | 
| 111 | 
            +
                  stub(@command).say
         | 
| 112 | 
            +
                  stub(@command).rubygem { { "rubyforge_project" => "bostonrb" } }
         | 
| 113 | 
            +
                  stub(Net::SCP).start
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                should "connect to rubyforge and upload away" do
         | 
| 117 | 
            +
                  stub(@command).ask { "user" }
         | 
| 118 | 
            +
                  stub(@command).ask_for_password { "secret" }
         | 
| 119 | 
            +
                  @command.upload_token(@token)
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  # TODO: figure out how to test the upload! in the block
         | 
| 122 | 
            +
                  assert_received(Net::SCP) { |subject| subject.start("bostonrb.rubyforge.org", "user", :password => "secret") }
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
              end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
              context "checking if the rubygem was approved" do
         | 
| 127 | 
            +
                setup do
         | 
| 128 | 
            +
                  @command = Gem::Commands::MigrateCommand.new
         | 
| 129 | 
            +
                  @slug = "rails"
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  stub(@command).say
         | 
| 132 | 
            +
                  stub(@command).rubygem { { "slug" => @slug } }
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                should "let the server decide the status" do
         | 
| 136 | 
            +
                  FakeWeb.register_uri :put, "https://gemcutter.heroku.com/gems/#{@slug}/migrate", :body => "Success!", :status => 400
         | 
| 137 | 
            +
                  @command.check_for_approved
         | 
| 138 | 
            +
                  assert_received(@command) { |subject| subject.say("Success!") }
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
              end
         | 
| 141 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/../../command_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class PushCommandTest < CommandTest
         | 
| 4 | 
            +
              context "pushing" do
         | 
| 5 | 
            +
                setup do
         | 
| 6 | 
            +
                  @command = Gem::Commands::PushCommand.new
         | 
| 7 | 
            +
                  stub(@command).say
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                should "setup and send the gem" do
         | 
| 11 | 
            +
                  mock(@command).setup
         | 
| 12 | 
            +
                  mock(@command).send_gem
         | 
| 13 | 
            +
                  @command.execute
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                should "raise an error with no arguments" do
         | 
| 17 | 
            +
                  assert_raise Gem::CommandLineError do
         | 
| 18 | 
            +
                    @command.send_gem
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                should "push a gem" do
         | 
| 23 | 
            +
                  mock(@command).say("Pushing gem to Gemcutter...")
         | 
| 24 | 
            +
                  @response = "success"
         | 
| 25 | 
            +
                  FakeWeb.register_uri :post, "https://gemcutter.heroku.com/gems", :body => @response
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  @gem = "test"
         | 
| 28 | 
            +
                  @io = "io"
         | 
| 29 | 
            +
                  @config = { :gemcutter_key => "key" }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  stub(File).open(@gem) { @io }
         | 
| 32 | 
            +
                  stub(@io).read.stub!.size
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  stub(@command).options { {:args => [@gem]} }
         | 
| 35 | 
            +
                  stub(Gem).configuration { @config }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  mock(@command).say(@response)
         | 
| 38 | 
            +
                  @command.send_gem
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/../../command_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TumbleCommandTest < CommandTest
         | 
| 4 | 
            +
              context "with a tumbler and some sources" do
         | 
| 5 | 
            +
                setup do
         | 
| 6 | 
            +
                  @sources = ["gems.rubyforge.org", URL]
         | 
| 7 | 
            +
                  stub(Gem).sources { @sources }
         | 
| 8 | 
            +
                  @command = Gem::Commands::TumbleCommand.new
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                should "show sources" do
         | 
| 12 | 
            +
                  mock(@command).puts("Your gem sources are now:")
         | 
| 13 | 
            +
                  mock(@command).puts("- #{@sources.first}")
         | 
| 14 | 
            +
                  mock(@command).puts("- #{URL}")
         | 
| 15 | 
            +
                  @command.show_sources
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              context "tumbling the gem sources" do
         | 
| 20 | 
            +
                setup do
         | 
| 21 | 
            +
                  @sources = ["http://rubyforge.org"]
         | 
| 22 | 
            +
                  stub(Gem).sources { @sources }
         | 
| 23 | 
            +
                  @config = Object.new
         | 
| 24 | 
            +
                  stub(Gem).configuration { @config }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  @command = Gem::Commands::TumbleCommand.new
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                should "add gemcutter as first source" do
         | 
| 30 | 
            +
                  mock(@sources).unshift(URL)
         | 
| 31 | 
            +
                  mock(@config).write
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  @command.tumble
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                should "remove gemcutter if it's in the sources" do
         | 
| 37 | 
            +
                  mock(@sources).include?(URL) { true }
         | 
| 38 | 
            +
                  mock(@config).write
         | 
| 39 | 
            +
                  mock(@sources).delete(URL)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  @command.tumble
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              context "executing the tumbler" do
         | 
| 46 | 
            +
                setup do
         | 
| 47 | 
            +
                  @command = Gem::Commands::TumbleCommand.new
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                should "say thanks, tumble and show the sources" do
         | 
| 51 | 
            +
                  mock(@command).say(anything)
         | 
| 52 | 
            +
                  mock(@command).tumble
         | 
| 53 | 
            +
                  mock(@command).show_sources
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  @command.execute
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: gemcutter
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              version: 0.0. | 
| 4 | 
            +
              version: 0.0.7
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors: 
         | 
| 7 7 | 
             
            - Nick Quaranto
         | 
| @@ -9,10 +9,19 @@ autorequire: | |
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 11 |  | 
| 12 | 
            -
            date: 2009- | 
| 12 | 
            +
            date: 2009-08-10 00:00:00 -04:00
         | 
| 13 13 | 
             
            default_executable: 
         | 
| 14 | 
            -
            dependencies:  | 
| 15 | 
            -
             | 
| 14 | 
            +
            dependencies: 
         | 
| 15 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 16 | 
            +
              name: json
         | 
| 17 | 
            +
              type: :runtime
         | 
| 18 | 
            +
              version_requirement: 
         | 
| 19 | 
            +
              version_requirements: !ruby/object:Gem::Requirement 
         | 
| 20 | 
            +
                requirements: 
         | 
| 21 | 
            +
                - - ">="
         | 
| 22 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 23 | 
            +
                    version: "0"
         | 
| 24 | 
            +
                version: 
         | 
| 16 25 | 
             
            description: 
         | 
| 17 26 | 
             
            email: nick@quaran.to
         | 
| 18 27 | 
             
            executables: []
         | 
| @@ -22,6 +31,8 @@ extensions: [] | |
| 22 31 | 
             
            extra_rdoc_files: 
         | 
| 23 32 | 
             
            - README.textile
         | 
| 24 33 | 
             
            files: 
         | 
| 34 | 
            +
            - lib/commands/abstract_command.rb
         | 
| 35 | 
            +
            - lib/commands/migrate.rb
         | 
| 25 36 | 
             
            - lib/commands/push.rb
         | 
| 26 37 | 
             
            - lib/commands/tumble.rb
         | 
| 27 38 | 
             
            - lib/rubygems_plugin.rb
         | 
| @@ -32,9 +43,14 @@ licenses: [] | |
| 32 43 |  | 
| 33 44 | 
             
            post_install_message: |+
         | 
| 34 45 |  | 
| 35 | 
            -
               | 
| 36 | 
            -
             | 
| 37 | 
            -
               | 
| 46 | 
            +
              ========================================================================
         | 
| 47 | 
            +
                         Thanks for installing Gemcutter! You can now run:
         | 
| 48 | 
            +
              
         | 
| 49 | 
            +
                  gem tumble        use Gemcutter as your primary RubyGem source
         | 
| 50 | 
            +
                  gem push          publish your gems for the world to use and enjoy
         | 
| 51 | 
            +
                  gem migrate       take over your gem from RubyForge on Gemcutter
         | 
| 52 | 
            +
              
         | 
| 53 | 
            +
              ========================================================================
         | 
| 38 54 |  | 
| 39 55 | 
             
            rdoc_options: 
         | 
| 40 56 | 
             
            - --charset=UTF-8
         | 
| @@ -55,9 +71,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 55 71 | 
             
            requirements: []
         | 
| 56 72 |  | 
| 57 73 | 
             
            rubyforge_project: gemcutter
         | 
| 58 | 
            -
            rubygems_version: 1.3. | 
| 74 | 
            +
            rubygems_version: 1.3.3
         | 
| 59 75 | 
             
            signing_key: 
         | 
| 60 76 | 
             
            specification_version: 3
         | 
| 61 77 | 
             
            summary: Awesome gem hosting
         | 
| 62 | 
            -
            test_files:  | 
| 63 | 
            -
             | 
| 78 | 
            +
            test_files: 
         | 
| 79 | 
            +
            - test/command_helper.rb
         | 
| 80 | 
            +
            - test/unit/commands/migrate_command_test.rb
         | 
| 81 | 
            +
            - test/unit/commands/tumble_command_test.rb
         | 
| 82 | 
            +
            - test/unit/commands/push_command_test.rb
         | 
| 83 | 
            +
            - test/unit/commands/abstract_command_test.rb
         |