[NEW] hg.commits() and family. Rake does not pass.
authorRobin Luckey <robin@Tangier.local>
Wed, 21 Jan 2009 00:53:16 +0000 (16:53 -0800)
committerRobin Luckey <robin@Tangier.local>
Wed, 21 Jan 2009 00:53:16 +0000 (16:53 -0800)
lib/scm.rb
lib/scm/adapters/hg/commits.rb [new file with mode: 0644]
lib/scm/adapters/hg_adapter.rb
lib/scm/parsers/hg_parser.rb
lib/scm/parsers/hg_style [new file with mode: 0644]
lib/scm/parsers/hg_styled_parser.rb [new file with mode: 0644]
lib/scm/parsers/hg_verbose_style [new file with mode: 0644]
test/unit/hg_commits_test.rb [new file with mode: 0644]
test/unit/hg_parser_test.rb

index 8d56b4d3e8e13debdf4af39096859479d1a030a2..175871be365f77043f1e770f7a0e7541dc802d0f 100644 (file)
@@ -24,6 +24,8 @@ require 'lib/scm/parsers/cvs_parser'
 require 'lib/scm/parsers/svn_parser'
 require 'lib/scm/parsers/svn_xml_parser'
 require 'lib/scm/parsers/hg_parser'
+require 'lib/scm/parsers/hg_styled_parser'
+
 require 'lib/scm/parsers/array_writer'
 require 'lib/scm/parsers/xml_writer'
 require 'lib/scm/parsers/human_writer'
diff --git a/lib/scm/adapters/hg/commits.rb b/lib/scm/adapters/hg/commits.rb
new file mode 100644 (file)
index 0000000..c477a9b
--- /dev/null
@@ -0,0 +1,37 @@
+module Scm::Adapters
+       class HgAdapter < AbstractAdapter
+
+               # Return the number of commits in the repository following +since+.
+               def commit_count(since=0)
+                       commit_tokens(since).size
+               end
+
+               # Return the list of commit tokens following +since+.
+               def commit_tokens(since=0)
+                       tokens = run("cd '#{self.url}' && hg log -r #{since}:tip --template='{node}\\n'").split("\n")
+
+                       # Hg returns everything after *and including* since.
+                       # We do not want to include it.
+                       if tokens.any? && tokens.first == since 
+                               tokens[1..-1]
+                       else
+                               tokens
+                       end
+               end
+
+               # Returns a list of shallow commits (i.e., the diffs are not populated).
+               # Not including the diffs is meant to be a memory savings when we encounter massive repositories.
+               # If you need all commits including diffs, you should use the each_commit() iterator, which only holds one commit
+               # in memory at a time.
+               def commits(since=0)
+                       log = run("cd '#{self.url}' && hg log -v -r #{since}:tip --style #{Scm::Parsers::HgStyledParser.style_path}")
+                       a = Scm::Parsers::HgStyledParser.parse(log)
+
+                       if a.any? && a.first.token == since
+                               a[1..-1]
+                       else
+                               a
+                       end
+               end
+       end
+end
index a04ea0d043a98e5dd08053c88b38fdf3609e2d6c..28e55a51900a17467d12a815c2ab5c2941855c83 100644 (file)
@@ -8,4 +8,5 @@ end
 
 require 'lib/scm/adapters/hg/validation'
 require 'lib/scm/adapters/hg/cat_file'
+require 'lib/scm/adapters/hg/commits'
 require 'lib/scm/adapters/hg/misc'
index 834e0c4e7e99bd5669694ba1e0a2df4c2036b0c8..b5c67078784c71baa25dcc8a61392eaf85afb4dd 100644 (file)
@@ -1,6 +1,9 @@
 require 'parsedate'
 
 module Scm::Parsers
+       # This parser can process the default hg logs, with or without the --verbose flag.
+       # It is handy for debugging but is not detailed enough for Ohloh analysis.
+       # See the HgStyledParser.
        class HgParser < Parser
                def self.scm
                        'hg'
diff --git a/lib/scm/parsers/hg_style b/lib/scm/parsers/hg_style
new file mode 100644 (file)
index 0000000..c92f9d2
--- /dev/null
@@ -0,0 +1 @@
+changeset = '__BEGIN_COMMIT__\nchangeset: {node}\nuser:      {author}\ndate:      {date}\n__BEGIN_COMMENT__\n{desc}\n__END_COMMENT__\n__END_COMMIT__\n'
diff --git a/lib/scm/parsers/hg_styled_parser.rb b/lib/scm/parsers/hg_styled_parser.rb
new file mode 100644 (file)
index 0000000..4018121
--- /dev/null
@@ -0,0 +1,65 @@
+module Scm::Parsers
+       # This parser processes Mercurial logs which have been generated using a custom style.
+       # This custom style provides additional information required by Ohloh.
+       class HgStyledParser < Parser
+               def self.scm
+                       'hg'
+               end
+
+               # Use when you want to include diffs
+               def self.verbose_style_path
+                       File.expand_path(File.join(File.dirname(__FILE__), 'hg_verbose_style'))
+               end
+
+               # Use when you do not want to include diffs
+               def self.style_path
+                       File.expand_path(File.join(File.dirname(__FILE__), 'hg_style'))
+               end
+
+               def self.internal_parse(buffer, opts)
+                       e = nil
+                       state = :data
+
+                       buffer.each_line do |l|
+                               next_state = state
+                               if state == :data
+                                       case l
+                                       when /^changeset:\s+([0-9a-f]+)/
+                                               e = Scm::Commit.new
+                                               e.diffs = []
+                                               e.token = $1
+                                       when /^user:\s+(.+?)(\s+<(.+)>)?$/
+                                               e.committer_name = $1
+                                               e.committer_email = $3
+                                       when /^date:\s+([\d\.]+)/
+                                               e.committer_date = Time.at($1.to_f).utc
+                                       when "__BEGIN_FILES__\n"
+                                               next_state = :files
+                                       when "__BEGIN_COMMENT__\n"
+                                               next_state = :long_comment
+                                       when "__END_COMMIT__\n"
+                                               yield e if block_given?
+                                               e = nil
+                                       end
+
+                               elsif state == :files
+                                       if l == "__END_FILES__\n"
+                                               next_state = :data
+                                       elsif l =~ /^([MAD]) (.+)$/
+                                               e.diffs << Scm::Diff.new(:action => $1, :path => $2)
+                                       end
+
+                               elsif state == :long_comment
+                                       if l == "__END_COMMENT__\n"
+                                               next_state = :data
+                                       else
+                                               e.message ||= ''
+                                               e.message << l
+                                       end
+                               end
+                               state = next_state
+                       end
+               end
+       end
+end
+
diff --git a/lib/scm/parsers/hg_verbose_style b/lib/scm/parsers/hg_verbose_style
new file mode 100644 (file)
index 0000000..f53fec4
--- /dev/null
@@ -0,0 +1,5 @@
+changeset = '__BEGIN_COMMIT__\nchangeset: {node}\nuser:      {author}\ndate:      {date}\n__BEGIN_COMMENT__\n{desc}\n__END_COMMENT__\n__BEGIN_FILES__\n{file_mods}{file_adds}{file_dels}__END_FILES__\n__END_COMMIT__\n'
+file_mod = 'M {file_mod}\n'
+file_add = 'A {file_add}\n'
+file_del = 'D {file_del}\n'
+
diff --git a/test/unit/hg_commits_test.rb b/test/unit/hg_commits_test.rb
new file mode 100644 (file)
index 0000000..faf0032
--- /dev/null
@@ -0,0 +1,38 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class HgCommitsTest < Scm::Test
+
+               def test_commit
+                       with_hg_repository('hg') do |hg|
+                               assert_equal 4, hg.commit_count
+                               assert_equal 2, hg.commit_count('b14fa4692f949940bd1e28da6fb4617de2615484')
+                               assert_equal 0, hg.commit_count('75532c1e1f1de55c2271f6fd29d98efbe35397c4')
+
+                               assert_equal ['01101d8ef3cea7da9ac6e9a226d645f4418f05c9',
+                                                                                       'b14fa4692f949940bd1e28da6fb4617de2615484',
+                                                                                       '468336c6671cbc58237a259d1b7326866afc2817',
+                                                                                       '75532c1e1f1de55c2271f6fd29d98efbe35397c4'], hg.commit_tokens
+
+                               assert_equal ['75532c1e1f1de55c2271f6fd29d98efbe35397c4'],
+                                       hg.commit_tokens('468336c6671cbc58237a259d1b7326866afc2817')
+
+                               assert_equal [], hg.commit_tokens('75532c1e1f1de55c2271f6fd29d98efbe35397c4')
+
+                               assert_equal ['01101d8ef3cea7da9ac6e9a226d645f4418f05c9',
+                                                                                       'b14fa4692f949940bd1e28da6fb4617de2615484',
+                                                                                       '468336c6671cbc58237a259d1b7326866afc2817',
+                                                                                       '75532c1e1f1de55c2271f6fd29d98efbe35397c4'], hg.commits.collect { |c| c.token }
+
+                               assert_equal ['75532c1e1f1de55c2271f6fd29d98efbe35397c4'],
+                                       hg.commits('468336c6671cbc58237a259d1b7326866afc2817').collect { |c| c.token }
+
+                               # Check that the diffs are not populated
+                               assert_equal [], hg.commits('468336c6671cbc58237a259d1b7326866afc2817').first.diffs
+
+                               assert_equal [], hg.commits('75532c1e1f1de55c2271f6fd29d98efbe35397c4')
+                       end
+               end
+       end
+end
+
index 7d83b0d98c83f0794f20ccd91fc39d852b53c4a5..da821e36fb258b0e0135471b8b31a0ce1bae7c0c 100644 (file)
@@ -102,5 +102,51 @@ SAMPLE
                        assert_equal 1, commits[1].diffs.size
                        assert_equal 'helloworld.c', commits[1].diffs[0].path
                end
+
+               def test_styled_parser
+                       with_hg_repository('hg') do |hg|
+                               assert FileTest.exist?(HgStyledParser.style_path)
+                               log = hg.run("cd #{hg.url} && hg log --style #{Scm::Parsers::HgStyledParser.style_path}")
+                               commits = Scm::Parsers::HgStyledParser.parse(log)
+                               assert_styled_commits(commits, true)
+
+                               assert FileTest.exist?(HgStyledParser.verbose_style_path)
+                               log = hg.run("cd #{hg.url} && hg log --style #{Scm::Parsers::HgStyledParser.verbose_style_path}")
+                               commits = Scm::Parsers::HgStyledParser.parse(log)
+                               assert_styled_commits(commits, true)
+                       end
+               end
+
+               protected
+               def assert_styled_commits(commits, with_diffs=false)
+                       assert_equal 4, commits.size
+
+                       assert_equal '75532c1e1f1de55c2271f6fd29d98efbe35397c4', commits[0].token
+                       assert_equal 'Robin Luckey', commits[0].committer_name
+                       assert_equal 'robin@ohloh.net', commits[0].committer_email
+                       assert Time.utc(2009,1,20,19,34,53) - commits[0].committer_date < 1 # Don't care about milliseconds
+                       assert_equal "deleted helloworld.c\n", commits[0].message
+
+                       if with_diffs
+                               assert_equal 1, commits[0].diffs.size
+                               assert_equal 'D', commits[0].diffs[0].action
+                               assert_equal 'helloworld.c', commits[0].diffs[0].path
+                       else
+                               assert_equal [], commits[0].diffs
+                       end
+
+                       assert_equal '468336c6671cbc58237a259d1b7326866afc2817', commits[1].token
+                       assert Time.utc(2009, 1,20,19,34,04) - commits[1].committer_date < 1
+
+                       if with_diffs
+                               assert_equal 2, commits[1].diffs.size
+                               assert_equal 'M', commits[1].diffs[0].action
+                               assert_equal 'helloworld.c', commits[1].diffs[0].path
+                               assert_equal 'A', commits[1].diffs[1].action
+                               assert_equal 'README', commits[1].diffs[1].path
+                       else
+                               assert_equal [], commits[0].diffs
+                       end
+               end
        end
 end