first_github_commit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Christoph Olszowka
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,46 @@
1
+ = First Github Commit
2
+
3
+ Makes finding the very first commit to a GitHub repo very simple:
4
+
5
+ require 'first_github_commit'
6
+ require 'pp'
7
+ pp FirstGithubCommit.find('thoughtbot', 'shoulda')
8
+ => {"author"=> { ... }
9
+ "url"=> "http://github.com/thoughtbot/shoulda/commit/37686ae0dadb3a820db039a9ab3d024983034e7f",
10
+ "id"=>"37686ae0dadb3a820db039a9ab3d024983034e7f",
11
+ "committed_date"=>"2007-03-14T11:11:47-07:00",
12
+ "message"=>"Created trunk and tags (...)",
13
+ (...)}
14
+
15
+ The data returned is that from the GitHub commit listing API, see http://develop.github.com/p/commits.html
16
+
17
+ If you also want to know the page number, try this:
18
+
19
+ c = FirstGithubCommit.new('thoughtbot', 'shoulda')
20
+ c.yaml => {"author"=> ... } # Same as above
21
+ c.page => 14
22
+ # Get the url for the github commit listing web interface
23
+ c.github_url => "http://github.com/thoughtbot/shoulda/commits/master?page=14"
24
+
25
+ The page number is being retrieved by first finding the highest non-available page number.
26
+ Starting from page 100, the limit is being doubled on consecutive requests until the GitHub API
27
+ returns a 404. Then, the chunk is split in half and traversed further until the single page is found.
28
+
29
+ The web request limit is 30 consecutive requests, after which a FirstGithubCommit::TooManyRequests
30
+ exception will be thrown.
31
+
32
+ Set FirstGithubCommit::DEBUG to true to get some debug output about the requests and the traversal.
33
+
34
+ == Note on Patches/Pull Requests
35
+
36
+ * Fork the project.
37
+ * Make your feature addition or bug fix.
38
+ * Add tests for it. This is important so I don't break it in a
39
+ future version unintentionally.
40
+ * Commit, do not mess with rakefile, version, or history.
41
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
42
+ * Send me a pull request. Bonus points for topic branches.
43
+
44
+ == Copyright
45
+
46
+ Copyright (c) 2010 Christoph Olszowka. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "first_github_commit"
8
+ gem.summary = %Q{Find the first commit for a given github repository and return the API info for it}
9
+ gem.description = %Q{Find the first commit for a given github repository and return the API info for it by traversing the }
10
+ gem.email = "christoph at olszowka dot de"
11
+ gem.homepage = "http://github.com/colszowka/first_github_commit"
12
+ gem.authors = ["Christoph Olszowka"]
13
+ gem.add_dependency 'redirect_follower', '>= 0.1.1'
14
+ gem.add_development_dependency "shoulda", ">= 2.10.2"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :test => :check_dependencies
43
+
44
+ task :default => :test
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "first_github_commit #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,61 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{first_github_commit}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Christoph Olszowka"]
12
+ s.date = %q{2010-06-03}
13
+ s.description = %q{Find the first commit for a given github repository and return the API info for it by traversing the }
14
+ s.email = %q{christoph at olszowka dot de}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "first_github_commit.gemspec",
27
+ "lib/first_github_commit.rb",
28
+ "test/fast_context.rb",
29
+ "test/helper.rb",
30
+ "test/ruby_ext.rb",
31
+ "test/test_first_github_commit.rb"
32
+ ]
33
+ s.homepage = %q{http://github.com/colszowka/first_github_commit}
34
+ s.rdoc_options = ["--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.3.5}
37
+ s.summary = %q{Find the first commit for a given github repository and return the API info for it}
38
+ s.test_files = [
39
+ "test/fast_context.rb",
40
+ "test/helper.rb",
41
+ "test/ruby_ext.rb",
42
+ "test/test_first_github_commit.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<redirect_follower>, [">= 0.1.1"])
51
+ s.add_development_dependency(%q<shoulda>, [">= 2.10.2"])
52
+ else
53
+ s.add_dependency(%q<redirect_follower>, [">= 0.1.1"])
54
+ s.add_dependency(%q<shoulda>, [">= 2.10.2"])
55
+ end
56
+ else
57
+ s.add_dependency(%q<redirect_follower>, [">= 0.1.1"])
58
+ s.add_dependency(%q<shoulda>, [">= 2.10.2"])
59
+ end
60
+ end
61
+
@@ -0,0 +1,94 @@
1
+ require 'redirect_follower'
2
+ require 'yaml'
3
+
4
+ class FirstGithubCommit
5
+ class TooManyRequests < StandardError; end;
6
+
7
+ attr_reader :user, :repo, :requests_made, :page
8
+
9
+ DEBUG = false
10
+ MAX_REQUESTS = 30
11
+
12
+ def initialize(user, repo)
13
+ @user, @repo = user, repo
14
+ @requests_made = 0
15
+ @range = (1..100)
16
+ find_upper_limit
17
+ find_page_number
18
+ end
19
+
20
+ def yaml
21
+ @yaml ||= YAML.load(last_response.body)["commits"].last
22
+ end
23
+
24
+ def github_url
25
+ @github_url ||= "http://github.com/#{user}/#{repo}/commits/master?page=#{page}"
26
+ end
27
+
28
+ def self.find(user, repo)
29
+ FirstGithubCommit.new(user, repo).yaml
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :last_response, :upper_limit, :lower_limit, :range
35
+ attr_writer :range
36
+
37
+ def upper_limit
38
+ range.last
39
+ end
40
+
41
+ def lower_limit
42
+ range.first
43
+ end
44
+
45
+ def find_upper_limit
46
+ while get_page(range.last) and not last_response.kind_of?(Net::HTTPNotFound)
47
+ debug range.last
48
+ debug last_response.inspect
49
+ self.range = (upper_limit..upper_limit*2)
50
+ end
51
+ end
52
+
53
+ def find_page_number
54
+ while last_response.kind_of?(Net::HTTPNotFound) or range.to_a.length > 2
55
+ self.range = (range.first..((range.last - range.first)/2 + range.first))
56
+ last_response = get_page(range.last)
57
+ debug last_response.inspect
58
+ if last_response.kind_of?(Net::HTTPSuccess)
59
+ self.range = (range.last..(range.last+range.last-range.first))
60
+ end
61
+ debug range.inspect
62
+ end
63
+
64
+ if last_response.kind_of?(Net::HTTPSuccess)
65
+ @page = range.last
66
+ else
67
+ @page = range.first
68
+ last_response = get_page(page)
69
+ end
70
+
71
+ page
72
+ end
73
+
74
+ def get_page(page_number)
75
+ raise FirstGithubCommit::TooManyRequests if @requests_made > MAX_REQUESTS
76
+ @requests_made += 1
77
+ @last_response = RedirectFollower.new(url_for_page(page_number)).response
78
+ end
79
+
80
+ def base_url
81
+ @base_url ||= "http://github.com/api/v2/yaml/commits/list/#{user}/#{repo}/master"
82
+ end
83
+
84
+ def url_for_page(page_number)
85
+ base_url + "?page=#{page_number}"
86
+ end
87
+
88
+ def debug(*args)
89
+ return nil unless DEBUG == true
90
+ args.each do |arg|
91
+ puts arg
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,88 @@
1
+ puts "Loading fast_context"
2
+ require 'shoulda/context'
3
+
4
+ module ShouldaContextExtensions
5
+ def self.included(base)
6
+ base.class_eval do
7
+ #alias_method_chain :build, :fast_context
8
+ #alias_method_chain :am_subcontext?, :fast_context
9
+
10
+ alias_method :build_without_fast_context, :build
11
+ alias_method :build, :build_with_fast_context
12
+ alias_method :am_subcontext_without_fast_context?, :am_subcontext?
13
+ alias_method :am_subcontext?, :am_subcontext_with_fast_context?
14
+ end
15
+ end
16
+
17
+ def fast_context(name, &blk)
18
+ @fast_subcontexts ||= []
19
+ @fast_subcontexts << Shoulda::FastContext.new(name, self, &blk)
20
+ end
21
+
22
+ def build_with_fast_context
23
+ build_without_fast_context
24
+ @fast_subcontexts ||= []
25
+ @fast_subcontexts.each {|f| f.build }
26
+ end
27
+
28
+ def am_subcontext_with_fast_context?
29
+ parent.is_a?(Shoulda::Context) || parent.is_a?(Shoulda::FastContext)
30
+ end
31
+ end
32
+
33
+ module Shoulda
34
+ class FastContext < Context
35
+ def create_test_from_should_hash
36
+ test_name = ["test:", full_name, "should", "run_fast"].flatten.join(' ').to_sym
37
+
38
+ if test_unit_class.instance_methods.include?(test_name.to_s)
39
+ warn " * WARNING: '#{test_name}' is already defined"
40
+ end
41
+
42
+ context = self
43
+ test_unit_class.send(:define_method, test_name) do
44
+ @shoulda_context = context
45
+ @current_should = nil
46
+ begin
47
+ context.run_parent_setup_blocks(self)
48
+ context.shoulds.each do |s|
49
+ @current_should = s
50
+ s[:before].bind(self).call if s[:before]
51
+ end
52
+ context.run_current_setup_blocks(self)
53
+
54
+ context.shoulds.each {|should| should[:block].bind(self).call }
55
+ rescue Test::Unit::AssertionFailedError => e
56
+ error = Test::Unit::AssertionFailedError.new(["FAILED:", context.full_name, "should", "#{@current_should[:name]}:", e.message].flatten.join(' '))
57
+ error.set_backtrace e.backtrace
58
+ raise error
59
+ ensure
60
+ context.run_all_teardown_blocks(self)
61
+ end
62
+ end
63
+ end
64
+
65
+ def build
66
+ create_test_from_should_hash
67
+ subcontexts.each {|context| context.build }
68
+
69
+ @fast_subcontexts ||= []
70
+ @fast_subcontexts.each {|f| f.build }
71
+
72
+ print_should_eventuallys
73
+ end
74
+ end
75
+ end
76
+
77
+ class Test::Unit::TestCase
78
+ def self.fast_context(name, &blk)
79
+ if Shoulda.current_context
80
+ Shoulda.current_context.fast_context(name, &blk)
81
+ else
82
+ context = Shoulda::FastContext.new(name, self, &blk)
83
+ context.build
84
+ end
85
+ end
86
+ end
87
+
88
+ Shoulda::Context.send :include, ShouldaContextExtensions
data/test/helper.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'first_github_commit'
8
+ require 'test/fast_context'
9
+ require 'test/ruby_ext'
10
+
11
+ class Test::Unit::TestCase
12
+
13
+ def self.after_finding(user, repo)
14
+ fast_context "After finding #{user}/#{repo}" do
15
+ setup { @yaml = FirstGithubCommit.find(user, repo) }
16
+ subject { @yaml }
17
+
18
+ yield
19
+ end
20
+ end
21
+
22
+ def self.after_initializing_for(user, repo)
23
+ fast_context "After initializing for #{user}/#{repo}" do
24
+ setup { @commit = FirstGithubCommit.new(user, repo) }
25
+ subject { @commit }
26
+
27
+ yield
28
+ end
29
+ end
30
+
31
+ def self.should_return_a_hash
32
+ should "return a hash" do
33
+ assert subject.instance_of?(Hash)
34
+ end
35
+ end
36
+
37
+ def self.should_have_value(expected_value, options)
38
+ should "have #{expected_value} in #{options[:at]}" do
39
+ assert_equal expected_value, subject.find_nested(options[:at])
40
+ end
41
+ end
42
+ end
data/test/ruby_ext.rb ADDED
@@ -0,0 +1,11 @@
1
+ class Hash
2
+ # {'foo' => {:bar => 'baz'}}.find_nested('foo.bar') => 'baz'
3
+ def find_nested(keys)
4
+ value = self
5
+ keys.split('.').each do |key|
6
+ value = value[key] || value[key.to_sym]
7
+ end
8
+
9
+ value
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ require 'helper'
2
+ require 'pp'
3
+ class TestFirstGithubCommit < Test::Unit::TestCase
4
+ after_finding 'thoughtbot', 'shoulda' do
5
+ should_return_a_hash
6
+ should_have_value 'tsaleh', :at => 'author.name'
7
+ should_have_value '4b825dc642cb6eb9a060e54bf8d69288fbee4904', :at => "tree"
8
+ should_have_value "2007-03-14T11:11:47-07:00", :at => "authored_date"
9
+ end
10
+
11
+ after_finding 'rails', 'rails' do
12
+ should_return_a_hash
13
+ should_have_value 'David Heinemeier Hansson', :at => 'author.name'
14
+ should_have_value 'dhh', :at => "author.login"
15
+ should_have_value "2004-11-25T14:07:29-08:00", :at => "committed_date"
16
+ end
17
+
18
+ after_finding 'lifo', 'fast_context' do
19
+ should_return_a_hash
20
+ should_have_value 'Pratik Naik', :at => 'author.name'
21
+ should_have_value 'lifo', :at => "author.login"
22
+ should_have_value "2009-09-29T04:30:54-07:00", :at => "committed_date"
23
+ end
24
+
25
+ after_initializing_for 'lifo', 'fast_context' do
26
+ should("have page 1 for page number") { assert_equal 1, subject.page }
27
+ should("have yaml hash") { assert subject.yaml.kind_of?(Hash) }
28
+ should "have 'http://github.com/lifo/fast_context/commits/master?page=1' for github_url" do
29
+ assert_equal 'http://github.com/lifo/fast_context/commits/master?page=1', subject.github_url
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: first_github_commit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Christoph Olszowka
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-06-03 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: redirect_follower
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.1.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: shoulda
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.10.2
34
+ version:
35
+ description: "Find the first commit for a given github repository and return the API info for it by traversing the "
36
+ email: christoph at olszowka dot de
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.rdoc
49
+ - Rakefile
50
+ - VERSION
51
+ - first_github_commit.gemspec
52
+ - lib/first_github_commit.rb
53
+ - test/fast_context.rb
54
+ - test/helper.rb
55
+ - test/ruby_ext.rb
56
+ - test/test_first_github_commit.rb
57
+ has_rdoc: true
58
+ homepage: http://github.com/colszowka/first_github_commit
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --charset=UTF-8
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ requirements: []
79
+
80
+ rubyforge_project:
81
+ rubygems_version: 1.3.5
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Find the first commit for a given github repository and return the API info for it
85
+ test_files:
86
+ - test/fast_context.rb
87
+ - test/helper.rb
88
+ - test/ruby_ext.rb
89
+ - test/test_first_github_commit.rb