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'
--- /dev/null
+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
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'
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'
--- /dev/null
+changeset = '__BEGIN_COMMIT__\nchangeset: {node}\nuser: {author}\ndate: {date}\n__BEGIN_COMMENT__\n{desc}\n__END_COMMENT__\n__END_COMMIT__\n'
--- /dev/null
+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
+
--- /dev/null
+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'
+
--- /dev/null
+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
+
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