5d63d1205274771af2ce4cc48a4125c252a42a4d
Author: Robin Luckey
Date: 2009-02-17 14:27:39 -0800
diff --git a/lib/scm.rb b/lib/scm.rb
index 3089c09..8702a65 100644
--- a/lib/scm.rb
+++ b/lib/scm.rb
@@ -13,6 +13,7 @@ require 'lib/scm/diff'
require 'lib/scm/adapters/abstract_adapter'
require 'lib/scm/adapters/cvs_adapter'
require 'lib/scm/adapters/svn_adapter'
+require 'lib/scm/adapters/svn_chain_adapter'
require 'lib/scm/adapters/git_adapter'
require 'lib/scm/adapters/hg_adapter'
require 'lib/scm/adapters/bzr_adapter'
diff --git a/lib/scm/adapters/svn/cat_file.rb b/lib/scm/adapters/svn/cat_file.rb
index fb2a8d1..45a8739 100644
--- a/lib/scm/adapters/svn/cat_file.rb
+++ b/lib/scm/adapters/svn/cat_file.rb
@@ -9,10 +9,6 @@ module Scm::Adapters
end
def cat(path, revision)
- parent_svn(revision) ? parent_svn.cat(path, revision) : base_cat(path, revision)
- end
-
- def base_cat(path, revision)
begin
run "svn cat -r #{revision} '#{SvnAdapter.uri_encode(File.join(self.root, self.branch_name.to_s, path.to_s))}@#{revision}'"
rescue
diff --git a/lib/scm/adapters/svn/chain.rb b/lib/scm/adapters/svn/chain.rb
deleted file mode 100644
index 5f6435a..0000000
--- a/lib/scm/adapters/svn/chain.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-module Scm::Adapters
- class SvnAdapter < AbstractAdapter
-
- # Some explanation is in order about "chaining."
- #
- # First, realize that a base SvnAdapter only tracks the history of a single
- # subdirectory. If you point an adapter at /trunk, then that adapter is
- # going to ignore eveything in /branches and /tags.
- #
- # The problem with this is that directories often get moved about. What is
- # called "/trunk" today might have been in a branch directory at some point
- # in the past. But since we completely ignore other directories, we never see
- # that old history.
- #
- # Suppose for example that from revisions 1 to 100, development occured in
- # /branches/beta. Then at revision 101, /trunk was created by copying
- # /branches/beta, and this /trunk lives on to this day.
- #
- # The log for revision 101 is going to look something like this:
- #
- # Changed paths:
- # D /branches/beta
- # A /trunk (from /branches/beta:100)
- #
- # A single SvnAdapter pointed at today's /trunk will only see revisions 101
- # through HEAD, because /trunk didn't even exist before revision 101.
- #
- # To capture the prior history, we need to create *another* SvnAdapter
- # which points at /branches/beta, and which considers revisions from 1 to 100.
- #
- # That's what chaining is: when we find that the first commit of an adapter
- # indicates the wholesale renaming or copying of the entire tree from
- # another location, then we generate a new SvnAdapter that points to that
- # prior location, and process that SvnAdapter as well.
- #
- # This behavior recurses ("chains") all the way back to revision 1.
- #
- # It only works if the *entire branch* moves. We don't chain when
- # subdirectories or individual files are copied.
-
- # Returns the entire SvnAdapter ancestry chain as a simple array.
- def chain
- (parent_svn ? parent_svn.chain : []) << self
- end
-
- # If this adapter's branch was created by copying or renaming another branch,
- # then return a new adapter that points to that prior branch.
- #
- # Only commits following +since+ are considered, so if the copy or rename
- # occured on or before +since+, then no parent will be found or returned.
- def parent_svn(since=0)
- @parent_svn ||={} # Poor man's memoize
-
- @parent_svn[since] ||= begin
- parent = nil
- c = first_commit(since)
- if c
- c.diffs.each do |d|
- if (b = parent_branch_name(d))
- parent = SvnAdapter.new(:url => File.join(root, b), :branch_name => b,
- :username => username, :password => password,
- :final_token => d.from_revision).normalize
- break
- end
- end
- end
- parent
- end
- end
-
- def first_token(since=0)
- c = first_commit(since)
- c && c.token
- end
-
- def first_commit(since=0)
- Scm::Parsers::SvnXmlParser.parse(next_revision_xml(since)).first
- end
-
- # Returns the first commit with a revision number greater than the provided revision number
- def next_revision_xml(since=0)
- return "<?xml?>" if since.to_i >= head_token
- run "svn log --verbose --xml --stop-on-copy -r #{since.to_i+1}:#{final_token || 'HEAD'} --limit 1 #{opt_auth} '#{SvnAdapter.uri_encode(File.join(self.root, self.branch_name))}@#{final_token || 'HEAD'}'"
- end
-
- # If the passed diff represents the wholesale movement of the entire
- # code tree from one directory to another, this method returns the name
- # of the previous directory.
- def parent_branch_name(d)
- if d.action == 'A' && branch_name[0, d.path.size] == d.path && d.from_path && d.from_revision
- d.from_path + branch_name[d.path.size..-1]
- end
- end
- end
-end
diff --git a/lib/scm/adapters/svn/commits.rb b/lib/scm/adapters/svn/commits.rb
index 9170f85..29d6462 100644
--- a/lib/scm/adapters/svn/commits.rb
+++ b/lib/scm/adapters/svn/commits.rb
@@ -19,56 +19,15 @@ module Scm::Adapters
# this adapter ever return information regarding commits after this point.
attr_accessor :final_token
- #------------------------------------------------------------------
- # Recursive or "chained" versions of the commit accessors.
- #
- # These methods recurse through the chain of ancestors for this
- # adapter, calling the base_* method in turn for each ancestor.
- #------------------------------------------------------------------
-
# Returns the count of commits following revision number 'since'.
def commit_count(since=0)
- (parent_svn ? parent_svn.commit_count(since) : 0) + base_commit_count(since)
- end
-
- # Returns an array of revision numbers for all commits following revision number 'since'.
- def commit_tokens(since=0)
- (parent_svn(since) ? parent_svn.commit_tokens(since) : []) + base_commit_tokens(since)
- end
-
- # Returns an array of commits following revision number 'since'.
- def commits(since=0)
- (parent_svn(since) ? parent_svn.commits(since) : []) + base_commits(since)
- end
-
- # Yield verbose commits following revision number 'since', one at a time.
- def each_commit(since=0, &block)
- parent_svn.each_commit(since, &block) if parent_svn
- base_each_commit(since) do |commit|
- block.call commit
- end
- end
-
- def verbose_commit(since=0)
- parent_svn(since) ? parent_svn.verbose_commit(since) : base_verbose_commit(since)
- end
-
- #------------------------------------------------------------------
- # Base versions of the commit accessors.
- #
- # These are the original, simple commit accessors that are
- # unaware of branch "chaining".
- #------------------------------------------------------------------
-
- # Returns the count of commits following revision number 'since'.
- def base_commit_count(since=0)
since ||= 0
return 0 if final_token && since >= final_token
run("svn log -q -r #{since.to_i + 1}:#{final_token || 'HEAD'} --stop-on-copy '#{SvnAdapter.uri_encode(File.join(root, branch_name.to_s))}@#{final_token || 'HEAD'}' | grep -E -e '^r[0-9]+ ' | wc -l").strip.to_i
end
# Returns an array of revision numbers for all commits following revision number 'since'.
- def base_commit_tokens(since=0)
+ def commit_tokens(since=0)
since ||= 0
return [] if final_token && since >= final_token
cmd = "svn log -q -r #{since.to_i + 1}:#{final_token || 'HEAD'} --stop-on-copy '#{SvnAdapter.uri_encode(File.join(root, branch_name.to_s))}@#{final_token || 'HEAD'}' | grep -E -e '^r[0-9]+ ' | cut -f 1 -d '|' | cut -c 2-"
@@ -77,7 +36,7 @@ module Scm::Adapters
# Returns an array of commits following revision number 'since'.
# These commit objects do not include diffs.
- def base_commits(since=0)
+ def commits(since=0)
list = []
open_log_file(since) do |io|
list = Scm::Parsers::SvnXmlParser.parse(io)
@@ -93,9 +52,9 @@ module Scm::Adapters
# directories, the complexity (and time) of this method comes in expanding directories with a recursion
# through every file in the directory.
#
- def base_each_commit(since=nil)
- base_commit_tokens(since).each do |rev|
- yield base_verbose_commit(rev)
+ def each_commit(since=nil)
+ commit_tokens(since).each do |rev|
+ yield verbose_commit(rev)
end
end
@@ -134,12 +93,7 @@ module Scm::Adapters
# Note that if the directory was deleted, we have to look at the previous revision to see what it held.
recurse_rev = (diff.action == 'D') ? rev-1 : rev
- if diff.action == 'A' && diff.path == '' && rev == first_token && parent_svn
- # A very special case. This is the first commit, and the entire tree is being
- # copied from somewhere else. In this case, there isn't actually any change, just
- # a change of branch_name. Return no diffs at all.
- nil
- elsif (diff.action == 'D' or diff.action == 'A') && is_directory?(diff.path, recurse_rev)
+ if (diff.action == 'D' or diff.action == 'A') && is_directory?(diff.path, recurse_rev)
# Deleting or adding a directory. Expand it out to show every file.
recurse_files(diff.path, recurse_rev).collect do |f|
Scm::Diff.new(:action => diff.action, :path => File.join(diff.path, f))
@@ -178,7 +132,7 @@ module Scm::Adapters
end
end
- def base_verbose_commit(rev)
+ def verbose_commit(rev)
c = Scm::Parsers::SvnXmlParser.parse(single_revision_xml(rev)).first
c.scm = self
deepen_commit(strip_commit_branch(c))
diff --git a/lib/scm/adapters/svn_adapter.rb b/lib/scm/adapters/svn_adapter.rb
index bd57f41..cb2569f 100644
--- a/lib/scm/adapters/svn_adapter.rb
+++ b/lib/scm/adapters/svn_adapter.rb
@@ -13,4 +13,3 @@ require 'lib/scm/adapters/svn/push'
require 'lib/scm/adapters/svn/pull'
require 'lib/scm/adapters/svn/head'
require 'lib/scm/adapters/svn/misc'
-require 'lib/scm/adapters/svn/chain'
diff --git a/lib/scm/adapters/svn_chain/cat_file.rb b/lib/scm/adapters/svn_chain/cat_file.rb
new file mode 100644
index 0000000..26657f7
--- /dev/null
+++ b/lib/scm/adapters/svn_chain/cat_file.rb
@@ -0,0 +1,8 @@
+module Scm::Adapters
+ class SvnChainAdapter < SvnAdapter
+ def cat(path, revision)
+ parent_svn(revision) ? parent_svn.cat(path, revision) : super(path, revision)
+ end
+ end
+end
+
diff --git a/lib/scm/adapters/svn_chain/chain.rb b/lib/scm/adapters/svn_chain/chain.rb
new file mode 100644
index 0000000..210add2
--- /dev/null
+++ b/lib/scm/adapters/svn_chain/chain.rb
@@ -0,0 +1,59 @@
+module Scm::Adapters
+ class SvnChainAdapter < SvnAdapter
+
+ # Returns the entire SvnAdapter ancestry chain as a simple array.
+ def chain
+ (parent_svn ? parent_svn.chain : []) << self
+ end
+
+ # If this adapter's branch was created by copying or renaming another branch,
+ # then return a new adapter that points to that prior branch.
+ #
+ # Only commits following +since+ are considered, so if the copy or rename
+ # occured on or before +since+, then no parent will be found or returned.
+ def parent_svn(since=0)
+ @parent_svn ||={} # Poor man's memoize
+
+ @parent_svn[since] ||= begin
+ parent = nil
+ c = first_commit(since)
+ if c
+ c.diffs.each do |d|
+ if (b = parent_branch_name(d))
+ parent = SvnChainAdapter.new(
+ :url => File.join(root, b), :branch_name => b,
+ :username => username, :password => password,
+ :final_token => d.from_revision).normalize
+ break
+ end
+ end
+ end
+ parent
+ end
+ end
+
+ def first_token(since=0)
+ c = first_commit(since)
+ c && c.token
+ end
+
+ def first_commit(since=0)
+ Scm::Parsers::SvnXmlParser.parse(next_revision_xml(since)).first
+ end
+
+ # Returns the first commit with a revision number greater than the provided revision number
+ def next_revision_xml(since=0)
+ return "<?xml?>" if since.to_i >= head_token
+ run "svn log --verbose --xml --stop-on-copy -r #{since.to_i+1}:#{final_token || 'HEAD'} --limit 1 #{opt_auth} '#{SvnAdapter.uri_encode(File.join(self.root, self.branch_name))}@#{final_token || 'HEAD'}'"
+ end
+
+ # If the passed diff represents the wholesale movement of the entire
+ # code tree from one directory to another, this method returns the name
+ # of the previous directory.
+ def parent_branch_name(d)
+ if d.action == 'A' && branch_name[0, d.path.size] == d.path && d.from_path && d.from_revision
+ d.from_path + branch_name[d.path.size..-1]
+ end
+ end
+ end
+end
diff --git a/lib/scm/adapters/svn_chain/commits.rb b/lib/scm/adapters/svn_chain/commits.rb
new file mode 100644
index 0000000..4c9c6a2
--- /dev/null
+++ b/lib/scm/adapters/svn_chain/commits.rb
@@ -0,0 +1,37 @@
+module Scm::Adapters
+ class SvnChainAdapter < SvnAdapter
+
+ # Returns the count of commits following revision number 'since'.
+ def commit_count(since=0)
+ (parent_svn ? parent_svn.commit_count(since) : 0) + super(since)
+ end
+
+ # Returns an array of revision numbers for all commits following revision number 'since'.
+ def commit_tokens(since=0)
+ (parent_svn(since) ? parent_svn.commit_tokens(since) : []) + super(since)
+ end
+
+ # Returns an array of commits following revision number 'since'.
+ def commits(since=0)
+ (parent_svn(since) ? parent_svn.commits(since) : []) + super(since)
+ end
+
+ def verbose_commit(since=0)
+ parent_svn(since) ? parent_svn.verbose_commit(since) : super(since)
+ end
+
+ # If the diff points to a file, simply returns the diff.
+ # If the diff points to a directory, returns an array of diffs for every file in the directory.
+ def deepen_diff(diff, rev)
+ if diff.action == 'A' && diff.path == '' && parent_svn && rev == first_token
+ # A very special case that is important for chaining.
+ # This is the first commit, and the entire tree is being created by copying from parent_svn.
+ # In this case, there isn't actually any change, just
+ # a change of branch_name. Return no diffs at all.
+ nil
+ else
+ super(diff, rev)
+ end
+ end
+ end
+end
diff --git a/lib/scm/adapters/svn_chain_adapter.rb b/lib/scm/adapters/svn_chain_adapter.rb
new file mode 100644
index 0000000..674e6f0
--- /dev/null
+++ b/lib/scm/adapters/svn_chain_adapter.rb
@@ -0,0 +1,44 @@
+module Scm::Adapters
+ # Some explanation is in order about "chaining."
+ #
+ # First, realize that a base SvnAdapter only tracks the history of a single
+ # subdirectory. If you point an adapter at /trunk, then that adapter is
+ # going to ignore eveything in /branches and /tags.
+ #
+ # The problem with this is that directories often get moved about. What is
+ # called "/trunk" today might have been in a branch directory at some point
+ # in the past. But since we completely ignore other directories, we never see
+ # that old history.
+ #
+ # Suppose for example that from revisions 1 to 100, development occured in
+ # /branches/beta. Then at revision 101, /trunk was created by copying
+ # /branches/beta, and this /trunk lives on to this day.
+ #
+ # The log for revision 101 is going to look something like this:
+ #
+ # Changed paths:
+ # D /branches/beta
+ # A /trunk (from /branches/beta:100)
+ #
+ # A single SvnAdapter pointed at today's /trunk will only see revisions 101
+ # through HEAD, because /trunk didn't even exist before revision 101.
+ #
+ # To capture the prior history, we need to create *another* SvnAdapter
+ # which points at /branches/beta, and which considers revisions from 1 to 100.
+ #
+ # That's what chaining is: when we find that the first commit of an adapter
+ # indicates the wholesale renaming or copying of the entire tree from
+ # another location, then we generate a new SvnAdapter that points to that
+ # prior location, and process that SvnAdapter as well.
+ #
+ # This behavior recurses ("chains") all the way back to revision 1.
+ #
+ # It only works if the *entire branch* moves. We don't chain when
+ # subdirectories or individual files are copied.
+ class SvnChainAdapter < SvnAdapter
+ end
+end
+
+require 'lib/scm/adapters/svn_chain/chain'
+require 'lib/scm/adapters/svn_chain/commits'
+require 'lib/scm/adapters/svn_chain/cat_file'
diff --git a/test/test_helper.rb b/test/test_helper.rb
index c84fa24..3c8651c 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -69,6 +69,15 @@ class Scm::Test < Test::Unit::TestCase
end
end
+ def with_svn_chain_repository(name, branch_name='')
+ with_repository(Scm::Adapters::SvnChainAdapter, name) do |svn|
+ svn.branch_name = branch_name
+ svn.url = File.join(svn.root, svn.branch_name)
+ svn.url = svn.url[0..-2] if svn.url[-1..-1] == '/' # Strip trailing /
+ yield svn
+ end
+ end
+
def with_cvs_repository(name)
with_repository(Scm::Adapters::CvsAdapter, name) { |cvs| yield cvs }
end
diff --git a/test/unit/svn_cat_file_test.rb b/test/unit/svn_cat_file_test.rb
index 188ad62..f7e47ea 100644
--- a/test/unit/svn_cat_file_test.rb
+++ b/test/unit/svn_cat_file_test.rb
@@ -18,23 +18,5 @@ EXPECTED
assert_equal nil, svn.cat_file(Scm::Commit.new(:token => 1), Scm::Diff.new(:path => "file not found"))
end
end
-
- def test_cat_file_with_chaining
-goodbye = <<-EXPECTED
-#include <stdio.h>
-main()
-{
- printf("Goodbye, world!\\n");
-}
-EXPECTED
- with_svn_repository('svn_with_branching', '/trunk') do |svn|
- # The first case asks for the file on the HEAD, so it should easily be found
- assert_equal goodbye, svn.cat_file(Scm::Commit.new(:token => 8), Scm::Diff.new(:path => "goodbyeworld.c"))
-
- # The next test asks for the file as it appeared before /branches/development was moved to /trunk,
- # so this request requires traversal up the chain to the parent SvnAdapter.
- assert_equal goodbye, svn.cat_file(Scm::Commit.new(:token => 5), Scm::Diff.new(:path => "goodbyeworld.c"))
- end
- end
end
end
diff --git a/test/unit/svn_chain_cat_file_test.rb b/test/unit/svn_chain_cat_file_test.rb
new file mode 100644
index 0000000..579397f
--- /dev/null
+++ b/test/unit/svn_chain_cat_file_test.rb
@@ -0,0 +1,24 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+ class SvnChainCatFileTest < Scm::Test
+
+ def test_cat_file_with_chaining
+goodbye = <<-EXPECTED
+#include <stdio.h>
+main()
+{
+ printf("Goodbye, world!\\n");
+}
+EXPECTED
+ with_svn_chain_repository('svn_with_branching', '/trunk') do |svn|
+ # The first case asks for the file on the HEAD, so it should easily be found
+ assert_equal goodbye, svn.cat_file(Scm::Commit.new(:token => 8), Scm::Diff.new(:path => "goodbyeworld.c"))
+
+ # The next test asks for the file as it appeared before /branches/development was moved to /trunk,
+ # so this request requires traversal up the chain to the parent SvnAdapter.
+ assert_equal goodbye, svn.cat_file(Scm::Commit.new(:token => 5), Scm::Diff.new(:path => "goodbyeworld.c"))
+ end
+ end
+ end
+end
diff --git a/test/unit/svn_chain_commits_test.rb b/test/unit/svn_chain_commits_test.rb
new file mode 100644
index 0000000..77266b2
--- /dev/null
+++ b/test/unit/svn_chain_commits_test.rb
@@ -0,0 +1,168 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Parsers
+ class SvnChainTest < Scm::Test
+
+ def test_chained_commit_tokens
+ with_svn_chain_repository('svn_with_branching', '/trunk') do |svn|
+ assert_equal [1,2,4,5,8,9], svn.commit_tokens
+ assert_equal [2,4,5,8,9], svn.commit_tokens(1)
+ assert_equal [4,5,8,9], svn.commit_tokens(2)
+ assert_equal [4,5,8,9], svn.commit_tokens(3)
+ assert_equal [5,8,9], svn.commit_tokens(4)
+ assert_equal [8,9], svn.commit_tokens(5)
+ assert_equal [8,9], svn.commit_tokens(6)
+ assert_equal [8,9], svn.commit_tokens(7)
+ assert_equal [9], svn.commit_tokens(8)
+ assert_equal [], svn.commit_tokens(9)
+ assert_equal [], svn.commit_tokens(10)
+ end
+ end
+
+ def test_chained_commit_count
+ with_svn_chain_repository('svn_with_branching', '/trunk') do |svn|
+ assert_equal 6, svn.commit_count
+ assert_equal 5, svn.commit_count(1)
+ assert_equal 4, svn.commit_count(2)
+ assert_equal 4, svn.commit_count(3)
+ assert_equal 3, svn.commit_count(4)
+ assert_equal 2, svn.commit_count(5)
+ assert_equal 2, svn.commit_count(6)
+ assert_equal 2, svn.commit_count(7)
+ assert_equal 1, svn.commit_count(8)
+ assert_equal 0, svn.commit_count(9)
+ end
+ end
+
+ def test_chained_commits
+ with_svn_chain_repository('svn_with_branching', '/trunk') do |svn|
+ assert_equal [1,2,4,5,8,9], svn.commits.collect { |c| c.token }
+ assert_equal [2,4,5,8,9], svn.commits(1).collect { |c| c.token }
+ assert_equal [4,5,8,9], svn.commits(2).collect { |c| c.token }
+ assert_equal [4,5,8,9], svn.commits(3).collect { |c| c.token }
+ assert_equal [5,8,9], svn.commits(4).collect { |c| c.token }
+ assert_equal [8,9], svn.commits(5).collect { |c| c.token }
+ assert_equal [8,9], svn.commits(6).collect { |c| c.token }
+ assert_equal [8,9], svn.commits(7).collect { |c| c.token }
+ assert_equal [9], svn.commits(8).collect { |c| c.token }
+ assert_equal [], svn.commits(9).collect { |c| c.token }
+ end
+ end
+
+ # This test is primarly concerned with the checking the diffs
+ # of commits. Specifically, when an entire branch is moved
+ # to a new name, we should not see any diffs. From our
+ # point of view the code is unchanged; only the base directory
+ # has moved.
+ def test_chained_each_commit
+ commits = []
+ with_svn_chain_repository('svn_with_branching', '/trunk') do |svn|
+ svn.each_commit do |c|
+ assert c.scm # To support checkout of chained commits, the
+ # commit must include a link to its containing adapter.
+ commits << c
+ end
+ end
+
+ assert_equal [1,2,4,5,8,9], commits.collect { |c| c.token }
+
+ # This repository spends a lot of energy moving directories around.
+ # File edits actually occur in just 3 commits.
+
+ # Revision 1: /trunk directory created, but it is empty
+ assert_equal 0, commits[0].diffs.size
+
+ # Revision 2: /trunk/helloworld.c is added
+ assert_equal 1, commits[1].diffs.size
+ assert_equal 'A', commits[1].diffs.first.action
+ assert_equal '/helloworld.c', commits[1].diffs.first.path
+
+ # Revision 3: /trunk is deleted. We can't see this revision.
+
+ # Revision 4: /trunk is re-created by copying it from revision 2.
+ # From our point of view, there has been no change at all, and thus no diffs.
+ assert_equal 0, commits[2].diffs.size
+
+ # Revision 5: /branches/development is created by copying /trunk.
+ # From our point of view, the contents of the repository are unchanged, so
+ # no diffs result from the copy.
+ # However, /branches/development/goodbyeworld.c is also created, so we should
+ # have a diff for that.
+ assert_equal 1, commits[3].diffs.size
+ assert_equal 'A', commits[3].diffs.first.action
+ assert_equal '/goodbyeworld.c', commits[3].diffs.first.path
+
+ # Revision 6: /trunk/goodbyeworld.c is created, but we only see activity
+ # on /branches/development, so no commit reported.
+
+ # Revision 7: /trunk is deleted, but again we don't see it.
+
+ # Revision 8: /branches/development is moved to become the new /trunk.
+ # The directory contents are unchanged, so no diffs result.
+ assert_equal 0, commits[4].diffs.size
+
+ # Revision 9: an edit to /trunk/helloworld.c
+ assert_equal 1, commits[5].diffs.size
+ assert_equal 'M', commits[5].diffs.first.action
+ assert_equal '/helloworld.c', commits[5].diffs.first.path
+ end
+
+ # Specifically tests this case:
+ # Suppose we're importing /myproject/trunk, and the log
+ # contains the following:
+ #
+ # A /myproject (from /all/myproject:1)
+ # D /all/myproject
+ #
+ # We need to make sure we detect the move here, even though
+ # "/myproject" is not an exact match for "/myproject/trunk".
+ def test_tree_move
+ with_svn_chain_repository('svn_with_tree_move', '/myproject/trunk') do |svn|
+ assert_equal svn.url, svn.root + '/myproject/trunk'
+ assert_equal svn.branch_name, '/myproject/trunk'
+
+ p = svn.parent_svn
+ assert_equal p.url, svn.root + '/all/myproject/trunk'
+ assert_equal p.branch_name, '/all/myproject/trunk'
+ assert_equal p.final_token, 1
+
+ assert_equal [1, 2], svn.commit_tokens
+ end
+ end
+
+ def test_verbose_commit_with_chaining
+ with_svn_chain_repository('svn_with_branching','/trunk') do |svn|
+
+ c = svn.verbose_commit(9)
+ assert_equal 'modified helloworld.c', c.message
+ assert_equal ['/helloworld.c'], c.diffs.collect { |d| d.path }
+ assert_equal '/trunk', c.scm.branch_name
+
+ c = svn.verbose_commit(8)
+ assert_equal [], c.diffs
+ assert_equal '/trunk', c.scm.branch_name
+
+ # Reaching these commits requires chaining
+ c = svn.verbose_commit(5)
+ assert_equal 'add a new branch, with goodbyeworld.c', c.message
+ assert_equal ['/goodbyeworld.c'], c.diffs.collect { |d| d.path }
+ assert_equal '/branches/development', c.scm.branch_name
+
+ # Reaching these commits requires chaining twice
+ c = svn.verbose_commit(4)
+ assert_equal [], c.diffs
+ assert_equal '/trunk', c.scm.branch_name
+
+ # And now a fourth chain (to skip over /trunk deletion in rev 3)
+ c = svn.verbose_commit(2)
+ assert_equal 'Added helloworld.c to trunk', c.message
+ assert_equal ['/helloworld.c'], c.diffs.collect { |d| d.path }
+ assert_equal '/trunk', c.scm.branch_name
+
+ c = svn.verbose_commit(1)
+ assert_equal [], c.diffs
+ assert_equal '/trunk', c.scm.branch_name
+ end
+ end
+ end
+end
diff --git a/test/unit/svn_chain_test.rb b/test/unit/svn_chain_test.rb
index eaaef8e..22a74da 100644
--- a/test/unit/svn_chain_test.rb
+++ b/test/unit/svn_chain_test.rb
@@ -4,7 +4,7 @@ module Scm::Parsers
class SvnChainTest < Scm::Test
def test_chain
- with_svn_repository('svn_with_branching', '/trunk') do |svn|
+ with_svn_chain_repository('svn_with_branching', '/trunk') do |svn|
chain = svn.chain
assert_equal 4, chain.size
@@ -33,12 +33,10 @@ module Scm::Parsers
end
def test_parent_svn
- with_svn_repository('svn_with_branching', '/trunk') do |svn|
+ with_svn_chain_repository('svn_with_branching', '/trunk') do |svn|
# In this repository, /branches/development becomes
- # the /trunk in revision 8. So there should be no record
- # before revision 8 in the 'traditional' base commit parser.
- assert_equal [8,9], svn.base_commit_tokens
-
+ # the /trunk in revision 8. So there should be a parent
+ # will final_token 7.
p1 = svn.parent_svn
assert_equal p1.url, svn.root + '/branches/development'
assert_equal p1.branch_name, '/branches/development'
@@ -53,131 +51,8 @@ module Scm::Parsers
end
end
- def test_chained_commit_tokens
- with_svn_repository('svn_with_branching', '/trunk') do |svn|
- assert_equal [1,2,4,5,8,9], svn.commit_tokens
- assert_equal [2,4,5,8,9], svn.commit_tokens(1)
- assert_equal [4,5,8,9], svn.commit_tokens(2)
- assert_equal [4,5,8,9], svn.commit_tokens(3)
- assert_equal [5,8,9], svn.commit_tokens(4)
- assert_equal [8,9], svn.commit_tokens(5)
- assert_equal [8,9], svn.commit_tokens(6)
- assert_equal [8,9], svn.commit_tokens(7)
- assert_equal [9], svn.commit_tokens(8)
- assert_equal [], svn.commit_tokens(9)
- assert_equal [], svn.commit_tokens(10)
- end
- end
-
- def test_chained_commit_count
- with_svn_repository('svn_with_branching', '/trunk') do |svn|
- assert_equal 6, svn.commit_count
- assert_equal 5, svn.commit_count(1)
- assert_equal 4, svn.commit_count(2)
- assert_equal 4, svn.commit_count(3)
- assert_equal 3, svn.commit_count(4)
- assert_equal 2, svn.commit_count(5)
- assert_equal 2, svn.commit_count(6)
- assert_equal 2, svn.commit_count(7)
- assert_equal 1, svn.commit_count(8)
- assert_equal 0, svn.commit_count(9)
- end
- end
-
- def test_chained_commits
- with_svn_repository('svn_with_branching', '/trunk') do |svn|
- assert_equal [1,2,4,5,8,9], svn.commits.collect { |c| c.token }
- assert_equal [2,4,5,8,9], svn.commits(1).collect { |c| c.token }
- assert_equal [4,5,8,9], svn.commits(2).collect { |c| c.token }
- assert_equal [4,5,8,9], svn.commits(3).collect { |c| c.token }
- assert_equal [5,8,9], svn.commits(4).collect { |c| c.token }
- assert_equal [8,9], svn.commits(5).collect { |c| c.token }
- assert_equal [8,9], svn.commits(6).collect { |c| c.token }
- assert_equal [8,9], svn.commits(7).collect { |c| c.token }
- assert_equal [9], svn.commits(8).collect { |c| c.token }
- assert_equal [], svn.commits(9).collect { |c| c.token }
- end
- end
-
- # This test is primarly concerned with the checking the diffs
- # of commits. Specifically, when an entire branch is moved
- # to a new name, we should not see any diffs. From our
- # point of view the code is unchanged; only the base directory
- # has moved.
- def test_chained_each_commit
- commits = []
- with_svn_repository('svn_with_branching', '/trunk') do |svn|
- svn.each_commit { |c| commits << c }
- end
-
- assert_equal [1,2,4,5,8,9], commits.collect { |c| c.token }
-
- # This repository spends a lot of energy moving directories around.
- # File edits actually occur in just 3 commits.
-
- # Revision 1: /trunk directory created, but it is empty
- assert_equal 0, commits[0].diffs.size
-
- # Revision 2: /trunk/helloworld.c is added
- assert_equal 1, commits[1].diffs.size
- assert_equal 'A', commits[1].diffs.first.action
- assert_equal '/helloworld.c', commits[1].diffs.first.path
-
- # Revision 3: /trunk is deleted. We can't see this revision.
-
- # Revision 4: /trunk is re-created by copying it from revision 2.
- # From our point of view, there has been no change at all, and thus no diffs.
- assert_equal 0, commits[2].diffs.size
-
- # Revision 5: /branches/development is created by copying /trunk.
- # From our point of view, the contents of the repository are unchanged, so
- # no diffs result from the copy.
- # However, /branches/development/goodbyeworld.c is also created, so we should
- # have a diff for that.
- assert_equal 1, commits[3].diffs.size
- assert_equal 'A', commits[3].diffs.first.action
- assert_equal '/goodbyeworld.c', commits[3].diffs.first.path
-
- # Revision 6: /trunk/goodbyeworld.c is created, but we only see activity
- # on /branches/development, so no commit reported.
-
- # Revision 7: /trunk is deleted, but again we don't see it.
-
- # Revision 8: /branches/development is moved to become the new /trunk.
- # The directory contents are unchanged, so no diffs result.
- assert_equal 0, commits[4].diffs.size
-
- # Revision 9: an edit to /trunk/helloworld.c
- assert_equal 1, commits[5].diffs.size
- assert_equal 'M', commits[5].diffs.first.action
- assert_equal '/helloworld.c', commits[5].diffs.first.path
- end
-
- # Specifically tests this case:
- # Suppose we're importing /myproject/trunk, and the log
- # contains the following:
- #
- # A /myproject (from /all/myproject:1)
- # D /all/myproject
- #
- # We need to make sure we detect the move here, even though
- # "/myproject" is not an exact match for "/myproject/trunk".
- def test_tree_move
- with_svn_repository('svn_with_tree_move', '/myproject/trunk') do |svn|
- assert_equal svn.url, svn.root + '/myproject/trunk'
- assert_equal svn.branch_name, '/myproject/trunk'
-
- p = svn.parent_svn
- assert_equal p.url, svn.root + '/all/myproject/trunk'
- assert_equal p.branch_name, '/all/myproject/trunk'
- assert_equal p.final_token, 1
-
- assert_equal [1, 2], svn.commit_tokens
- end
- end
-
def test_parent_branch_name
- svn = Scm::Adapters::SvnAdapter.new(:branch_name => "/trunk")
+ svn = Scm::Adapters::SvnChainAdapter.new(:branch_name => "/trunk")
assert_equal "/branches/b", svn.parent_branch_name(Scm::Diff.new(:action => 'A',
:path => "/trunk", :from_revision => 1, :from_path => "/branches/b"))
diff --git a/test/unit/svn_commits_test.rb b/test/unit/svn_commits_test.rb
index 6f051d8..ef90191 100644
--- a/test/unit/svn_commits_test.rb
+++ b/test/unit/svn_commits_test.rb
@@ -121,40 +121,19 @@ module Scm::Adapters
# The full repository contains 4 revisions...
assert_equal 4, svn.commit_count
- # ..however, looking only at the history of /trunk@4 shows 3 revisions.
- #
- # That's because /trunk@3 was created by copying /branches/b@1
+ # ...however, the current trunk contains only revisions 3 and 4.
+ # That's because the branch was moved to replace the trunk at revision 3.
#
- # I hope the following diagram helps to explain:
- #
- # r1 | r2 | r3 | r4
- # ----------------|----------------|---------------------|---------------
- # | | |
- # /trunk(x) ------|-> deleted(x) | /--> /trunk(*) --|--> /trunk(*)
- # | | / |
- # /branches/b(*)--|----------------|--/ |
- # | | |
- #
- # (*) activity we track
- # (x) activity we ignore
+ # Even though there was a different trunk directory present in
+ # revisions 1 and 2, it is not visible to Ohloh.
trunk = SvnAdapter.new(:url => File.join(svn.url,'trunk'), :branch_name => '/trunk').normalize
- assert_equal 3, trunk.commit_count
- assert_equal [1,3,4], trunk.commit_tokens
-
- commits = []
- trunk.each_commit { |c| commits << c }
+ assert_equal 2, trunk.commit_count
+ assert_equal [3,4], trunk.commit_tokens
- # The first commit we should see is /branches/b@1.
- # It should contain two files.
- assert_equal 1, commits.first.token
- assert_equal 2, commits.first.diffs.size # Two files seen
-
- assert_equal 'A', commits.first.diffs[0].action
- assert_equal '/subdir/bar.rb', commits.first.diffs[0].path
- assert_equal 'A', commits.first.diffs[1].action
- assert_equal '/subdir/foo.rb', commits.first.diffs[1].path
+ deep_commits = []
+ trunk.each_commit { |c| deep_commits << c }
# When the branch is moved to replace the trunk in revision 3,
# the Subversion log shows
@@ -162,34 +141,46 @@ module Scm::Adapters
# D /branches/b
# A /trunk (from /branches/b:2)
#
- # The branch_name changes at this point, but none of the file contents
- # actually change. So there are no diffs to report at this point.
- assert_equal 3, commits[1].token
- assert_equal 0, commits[1].diffs.size
+ # However, there are files in those directories. Make sure the commits
+ # that we generate include all of those files not shown by the log.
+ #
+ # Also, our commits do not include diffs for the actual directories;
+ # only the files within those directories.
+ #
+ # Also, since we are only tracking the /trunk and not /branches/b, then
+ # there should not be anything referring to activity in /branches/b.
- # In Revision 4, a subdirectory is renamed. This shows in the Subversion log as
+ assert_equal 3, deep_commits.first.token # Make sure this is the right revision
+ assert_equal 2, deep_commits.first.diffs.size # Two files seen
+
+ assert_equal 'A', deep_commits.first.diffs[0].action
+ assert_equal '/subdir/bar.rb', deep_commits.first.diffs[0].path
+ assert_equal 'A', deep_commits.first.diffs[1].action
+ assert_equal '/subdir/foo.rb', deep_commits.first.diffs[1].path
+
+ # In Revision 4, a directory is renamed. This shows in the Subversion log as
#
# A /trunk/newdir (from /trunk/subdir:3)
# D /trunk/subdir
#
- # There are files in this directory, so make sure our commit includes
+ # Again, there are files in this directory, so make sure our commit includes
# both delete and add events for all of the files in this directory, but does
# not actually refer to the directories themselves.
- assert_equal 4, commits.last.token
+ assert_equal 4, deep_commits.last.token # Make sure we're checking the right revision
# There should be 2 files removed and two files added
- assert_equal 4, commits.last.diffs.size
+ assert_equal 4, deep_commits.last.diffs.size
- assert_equal 'A', commits.last.diffs[0].action
- assert_equal '/newdir/bar.rb', commits.last.diffs[0].path
- assert_equal 'A', commits.last.diffs[1].action
- assert_equal '/newdir/foo.rb', commits.last.diffs[1].path
+ assert_equal 'A', deep_commits.last.diffs[0].action
+ assert_equal '/newdir/bar.rb', deep_commits.last.diffs[0].path
+ assert_equal 'A', deep_commits.last.diffs[1].action
+ assert_equal '/newdir/foo.rb', deep_commits.last.diffs[1].path
- assert_equal 'D', commits.last.diffs[2].action
- assert_equal '/subdir/bar.rb', commits.last.diffs[2].path
- assert_equal 'D', commits.last.diffs[3].action
- assert_equal '/subdir/foo.rb', commits.last.diffs[3].path
+ assert_equal 'D', deep_commits.last.diffs[2].action
+ assert_equal '/subdir/bar.rb', deep_commits.last.diffs[2].path
+ assert_equal 'D', deep_commits.last.diffs[3].action
+ assert_equal '/subdir/foo.rb', deep_commits.last.diffs[3].path
end
end
@@ -209,7 +200,6 @@ module Scm::Adapters
assert d.action.length == 1
assert d.path.length > 0
end
- assert_equal svn, e.scm # Commit points back to its containing scm.
end
assert !FileTest.exist?(svn.log_filename) # Make sure we cleaned up after ourselves
end
@@ -254,40 +244,5 @@ module Scm::Adapters
assert_equal '/trunk/COPYING', commits[4].diffs[1].path
end
- def test_verbose_commit_with_chaining
- with_svn_repository('svn_with_branching','/trunk') do |svn|
-
- c = svn.verbose_commit(9)
- assert_equal 'modified helloworld.c', c.message
- assert_equal ['/helloworld.c'], c.diffs.collect { |d| d.path }
- assert_equal '/trunk', c.scm.branch_name
-
- c = svn.verbose_commit(8)
- assert_equal [], c.diffs
- assert_equal '/trunk', c.scm.branch_name
-
- # Reaching these commits requires chaining
- c = svn.verbose_commit(5)
- assert_equal 'add a new branch, with goodbyeworld.c', c.message
- assert_equal ['/goodbyeworld.c'], c.diffs.collect { |d| d.path }
- assert_equal '/branches/development', c.scm.branch_name
-
- # Reaching these commits requires chaining twice
- c = svn.verbose_commit(4)
- assert_equal [], c.diffs
- assert_equal '/trunk', c.scm.branch_name
-
- # And now a fourth chain (to skip over /trunk deletion in rev 3)
- c = svn.verbose_commit(2)
- assert_equal 'Added helloworld.c to trunk', c.message
- assert_equal ['/helloworld.c'], c.diffs.collect { |d| d.path }
- assert_equal '/trunk', c.scm.branch_name
-
- c = svn.verbose_commit(1)
- assert_equal [], c.diffs
- assert_equal '/trunk', c.scm.branch_name
- end
- end
-
end
end
