[NEW] Initial Revision
authorRobin Luckey <robin@Tangier.local>
Mon, 19 Jan 2009 23:21:49 +0000 (15:21 -0800)
committerRobin Luckey <robin@Tangier.local>
Mon, 19 Jan 2009 23:21:49 +0000 (15:21 -0800)
224 files changed:
COPYING [new file with mode: 0644]
README [new file with mode: 0644]
Rakefile [new file with mode: 0644]
bin/ohlog [new file with mode: 0755]
lib/scm.rb [new file with mode: 0644]
lib/scm/adapters/abstract/system.rb [new file with mode: 0644]
lib/scm/adapters/abstract/validation.rb [new file with mode: 0644]
lib/scm/adapters/abstract_adapter.rb [new file with mode: 0644]
lib/scm/adapters/cvs/commits.rb [new file with mode: 0644]
lib/scm/adapters/cvs/misc.rb [new file with mode: 0644]
lib/scm/adapters/cvs/validation.rb [new file with mode: 0644]
lib/scm/adapters/cvs_adapter.rb [new file with mode: 0644]
lib/scm/adapters/git/cat_file.rb [new file with mode: 0644]
lib/scm/adapters/git/commit_all.rb [new file with mode: 0644]
lib/scm/adapters/git/commits.rb [new file with mode: 0644]
lib/scm/adapters/git/log_parser.rb [new file with mode: 0644]
lib/scm/adapters/git/misc.rb [new file with mode: 0644]
lib/scm/adapters/git/pull.rb [new file with mode: 0644]
lib/scm/adapters/git/push.rb [new file with mode: 0644]
lib/scm/adapters/git/token.rb [new file with mode: 0644]
lib/scm/adapters/git/validation.rb [new file with mode: 0644]
lib/scm/adapters/git_adapter.rb [new file with mode: 0644]
lib/scm/adapters/svn/cat_file.rb [new file with mode: 0644]
lib/scm/adapters/svn/commits.rb [new file with mode: 0644]
lib/scm/adapters/svn/misc.rb [new file with mode: 0644]
lib/scm/adapters/svn/pre-revprop-change [new file with mode: 0755]
lib/scm/adapters/svn/pull.rb [new file with mode: 0644]
lib/scm/adapters/svn/push.rb [new file with mode: 0644]
lib/scm/adapters/svn/validation.rb [new file with mode: 0644]
lib/scm/adapters/svn_adapter.rb [new file with mode: 0644]
lib/scm/commit.rb [new file with mode: 0644]
lib/scm/diff.rb [new file with mode: 0644]
lib/scm/parsers/array_writer.rb [new file with mode: 0644]
lib/scm/parsers/branch_number.rb [new file with mode: 0644]
lib/scm/parsers/cvs_parser.rb [new file with mode: 0644]
lib/scm/parsers/human_writer.rb [new file with mode: 0644]
lib/scm/parsers/parser.rb [new file with mode: 0644]
lib/scm/parsers/svn_parser.rb [new file with mode: 0644]
lib/scm/parsers/svn_xml_parser.rb [new file with mode: 0644]
lib/scm/parsers/xml_writer.rb [new file with mode: 0644]
lib/scm/scratch_dir.rb [new file with mode: 0644]
lib/scm/systemu.rb [new file with mode: 0644]
log/.gitignore [new file with mode: 0644]
test/data/basic.ohlog [new file with mode: 0644]
test/data/basic.rlog [new file with mode: 0644]
test/data/file_created_on_branch.rlog [new file with mode: 0644]
test/data/helloworld.log [new file with mode: 0644]
test/data/intelliglue.rlog [new file with mode: 0644]
test/data/multiple_commits.rlog [new file with mode: 0644]
test/data/multiple_revisions.rlog [new file with mode: 0644]
test/data/simple.ohlog [new file with mode: 0644]
test/data/simple.svn_log [new file with mode: 0644]
test/data/simple.svn_xml_log [new file with mode: 0644]
test/data/simultaneous_checkins.rlog [new file with mode: 0644]
test/data/simultaneous_checkins_2.rlog [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#checkoutlist [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#commit_email [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#commitinfo [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#config [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#cvsrc [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#cvswrappers [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#historyinfo [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#keywords [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#loginfo [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#modules [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#modules2 [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#notify [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#notify_email [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#postcommand [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#postmodule [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#precommand [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#premodule [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#rcsinfo [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#shadow [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#tag_email [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#taginfo [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#triggers [new file with mode: 0644]
test/repositories/cvs/CVSROOT/.#verifymsg [new file with mode: 0644]
test/repositories/cvs/CVSROOT/CVS/fileattr.xml [new file with mode: 0644]
test/repositories/cvs/CVSROOT/checkoutlist [new file with mode: 0644]
test/repositories/cvs/CVSROOT/checkoutlist,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/commit_email [new file with mode: 0644]
test/repositories/cvs/CVSROOT/commit_email,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/commitinfo [new file with mode: 0644]
test/repositories/cvs/CVSROOT/commitinfo,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/config [new file with mode: 0644]
test/repositories/cvs/CVSROOT/config,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/cvsrc [new file with mode: 0644]
test/repositories/cvs/CVSROOT/cvsrc,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/cvswrappers [new file with mode: 0644]
test/repositories/cvs/CVSROOT/cvswrappers,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/historyinfo [new file with mode: 0644]
test/repositories/cvs/CVSROOT/historyinfo,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/keywords [new file with mode: 0644]
test/repositories/cvs/CVSROOT/keywords,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/loginfo [new file with mode: 0644]
test/repositories/cvs/CVSROOT/loginfo,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/modules [new file with mode: 0644]
test/repositories/cvs/CVSROOT/modules,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/modules2 [new file with mode: 0644]
test/repositories/cvs/CVSROOT/modules2,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/notify [new file with mode: 0644]
test/repositories/cvs/CVSROOT/notify,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/notify_email [new file with mode: 0644]
test/repositories/cvs/CVSROOT/notify_email,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/postcommand [new file with mode: 0644]
test/repositories/cvs/CVSROOT/postcommand,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/postmodule [new file with mode: 0644]
test/repositories/cvs/CVSROOT/postmodule,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/precommand [new file with mode: 0644]
test/repositories/cvs/CVSROOT/precommand,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/premodule [new file with mode: 0644]
test/repositories/cvs/CVSROOT/premodule,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/rcsinfo [new file with mode: 0644]
test/repositories/cvs/CVSROOT/rcsinfo,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/shadow [new file with mode: 0644]
test/repositories/cvs/CVSROOT/shadow,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/tag_email [new file with mode: 0644]
test/repositories/cvs/CVSROOT/tag_email,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/taginfo [new file with mode: 0644]
test/repositories/cvs/CVSROOT/taginfo,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/triggers [new file with mode: 0644]
test/repositories/cvs/CVSROOT/triggers,v [new file with mode: 0644]
test/repositories/cvs/CVSROOT/val-tags [new file with mode: 0644]
test/repositories/cvs/CVSROOT/verifymsg [new file with mode: 0644]
test/repositories/cvs/CVSROOT/verifymsg,v [new file with mode: 0644]
test/repositories/cvs/simple/CVS/fileattr.xml [new file with mode: 0644]
test/repositories/cvs/simple/another_file.rb,v [new file with mode: 0644]
test/repositories/cvs/simple/foo.rb,v [new file with mode: 0644]
test/repositories/cvs/simple/late_addition.rb,v [new file with mode: 0644]
test/repositories/cvs/simple/new_file.rb,v [new file with mode: 0644]
test/repositories/deep_svn/README.txt [new file with mode: 0644]
test/repositories/deep_svn/conf/authz [new file with mode: 0644]
test/repositories/deep_svn/conf/passwd [new file with mode: 0644]
test/repositories/deep_svn/conf/svnserve.conf [new file with mode: 0644]
test/repositories/deep_svn/db/current [new file with mode: 0644]
test/repositories/deep_svn/db/format [new file with mode: 0644]
test/repositories/deep_svn/db/fs-type [new file with mode: 0644]
test/repositories/deep_svn/db/revprops/0 [new file with mode: 0644]
test/repositories/deep_svn/db/revprops/1 [new file with mode: 0644]
test/repositories/deep_svn/db/revprops/2 [new file with mode: 0644]
test/repositories/deep_svn/db/revprops/3 [new file with mode: 0644]
test/repositories/deep_svn/db/revprops/4 [new file with mode: 0644]
test/repositories/deep_svn/db/revs/0 [new file with mode: 0644]
test/repositories/deep_svn/db/revs/1 [new file with mode: 0644]
test/repositories/deep_svn/db/revs/2 [new file with mode: 0644]
test/repositories/deep_svn/db/revs/3 [new file with mode: 0644]
test/repositories/deep_svn/db/revs/4 [new file with mode: 0644]
test/repositories/deep_svn/db/uuid [new file with mode: 0644]
test/repositories/deep_svn/db/write-lock [new file with mode: 0644]
test/repositories/deep_svn/format [new file with mode: 0644]
test/repositories/deep_svn/hooks/post-commit.tmpl [new file with mode: 0644]
test/repositories/deep_svn/hooks/post-lock.tmpl [new file with mode: 0644]
test/repositories/deep_svn/hooks/post-revprop-change.tmpl [new file with mode: 0644]
test/repositories/deep_svn/hooks/post-unlock.tmpl [new file with mode: 0644]
test/repositories/deep_svn/hooks/pre-commit.tmpl [new file with mode: 0644]
test/repositories/deep_svn/hooks/pre-lock.tmpl [new file with mode: 0644]
test/repositories/deep_svn/hooks/pre-revprop-change.tmpl [new file with mode: 0644]
test/repositories/deep_svn/hooks/pre-unlock.tmpl [new file with mode: 0644]
test/repositories/deep_svn/hooks/start-commit.tmpl [new file with mode: 0644]
test/repositories/deep_svn/locks/db-logs.lock [new file with mode: 0644]
test/repositories/deep_svn/locks/db.lock [new file with mode: 0644]
test/repositories/git.tgz [new file with mode: 0644]
test/repositories/svn/README.txt [new file with mode: 0644]
test/repositories/svn/conf/authz [new file with mode: 0644]
test/repositories/svn/conf/passwd [new file with mode: 0644]
test/repositories/svn/conf/svnserve.conf [new file with mode: 0644]
test/repositories/svn/db/current [new file with mode: 0644]
test/repositories/svn/db/format [new file with mode: 0644]
test/repositories/svn/db/fs-type [new file with mode: 0644]
test/repositories/svn/db/revprops/0 [new file with mode: 0644]
test/repositories/svn/db/revprops/1 [new file with mode: 0644]
test/repositories/svn/db/revprops/2 [new file with mode: 0644]
test/repositories/svn/db/revprops/3 [new file with mode: 0644]
test/repositories/svn/db/revprops/4 [new file with mode: 0644]
test/repositories/svn/db/revprops/5 [new file with mode: 0644]
test/repositories/svn/db/revs/0 [new file with mode: 0644]
test/repositories/svn/db/revs/1 [new file with mode: 0644]
test/repositories/svn/db/revs/2 [new file with mode: 0644]
test/repositories/svn/db/revs/3 [new file with mode: 0644]
test/repositories/svn/db/revs/4 [new file with mode: 0644]
test/repositories/svn/db/revs/5 [new file with mode: 0644]
test/repositories/svn/db/uuid [new file with mode: 0644]
test/repositories/svn/db/write-lock [new file with mode: 0644]
test/repositories/svn/format [new file with mode: 0644]
test/repositories/svn/hooks/post-commit.tmpl [new file with mode: 0644]
test/repositories/svn/hooks/post-lock.tmpl [new file with mode: 0644]
test/repositories/svn/hooks/post-revprop-change.tmpl [new file with mode: 0644]
test/repositories/svn/hooks/post-unlock.tmpl [new file with mode: 0644]
test/repositories/svn/hooks/pre-commit.tmpl [new file with mode: 0644]
test/repositories/svn/hooks/pre-lock.tmpl [new file with mode: 0644]
test/repositories/svn/hooks/pre-revprop-change.tmpl [new file with mode: 0644]
test/repositories/svn/hooks/pre-unlock.tmpl [new file with mode: 0644]
test/repositories/svn/hooks/start-commit.tmpl [new file with mode: 0644]
test/repositories/svn/locks/db-logs.lock [new file with mode: 0644]
test/repositories/svn/locks/db.lock [new file with mode: 0644]
test/test_helper.rb [new file with mode: 0644]
test/unit/abstract_adapter_test.rb [new file with mode: 0644]
test/unit/array_writer_test.rb [new file with mode: 0644]
test/unit/cvs_branch_number_test.rb [new file with mode: 0644]
test/unit/cvs_commits_test.rb [new file with mode: 0644]
test/unit/cvs_convert_test.rb [new file with mode: 0644]
test/unit/cvs_misc_test.rb [new file with mode: 0644]
test/unit/cvs_parser_test.rb [new file with mode: 0644]
test/unit/cvs_validation_test.rb [new file with mode: 0644]
test/unit/git_cat_file_test.rb [new file with mode: 0644]
test/unit/git_commit_all_test.rb [new file with mode: 0644]
test/unit/git_commits_test.rb [new file with mode: 0644]
test/unit/git_log_parser_test.rb [new file with mode: 0644]
test/unit/git_misc_test.rb [new file with mode: 0644]
test/unit/git_pull_test.rb [new file with mode: 0644]
test/unit/git_push_test.rb [new file with mode: 0644]
test/unit/git_token_test.rb [new file with mode: 0644]
test/unit/git_validation_test.rb [new file with mode: 0644]
test/unit/ohlog_command_line_test.rb [new file with mode: 0644]
test/unit/svn_cat_file_test.rb [new file with mode: 0644]
test/unit/svn_commits_test.rb [new file with mode: 0644]
test/unit/svn_convert_test.rb [new file with mode: 0644]
test/unit/svn_misc_test.rb [new file with mode: 0644]
test/unit/svn_parser_test.rb [new file with mode: 0644]
test/unit/svn_pull_test.rb [new file with mode: 0644]
test/unit/svn_push_test.rb [new file with mode: 0644]
test/unit/svn_validation_test.rb [new file with mode: 0644]
test/unit/svn_xml_parser_test.rb [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..d511905
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e55cc91
--- /dev/null
+++ b/README
@@ -0,0 +1,99 @@
+= Ohloh SCM
+
+The Ohloh source control management library
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License Version 2 as
+published by the Free Software Foundation.
+
+Ohcount is specifically licensed under GPL v2.0, and no later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+== Overview
+
+Ohloh SCM is an abstraction layer for source control management systems,
+allowing an application to interoperate with various SCMs using a
+single interface.
+
+It was originally developed at Ohloh, and is used to generate
+the reports at www.ohloh.net.
+
+== System Requirements
+
+Ohloh SCM is developed on Mac OS X 10.5 and Ubuntu 6.06 LTS. Other Linux
+environments should also work, but your mileage may vary.
+
+Ohloh SCM does not support Windows.
+
+Ohloh SCM targets Ruby 1.8.6 and Rake 0.8.1
+
+Ohloh SCM interfaces with CVSNT, Subversion, and Git through the shell.
+In order to pass the unit tests, all three systems must be installed and
+on your path. Ohloh uses the following versions, and other versions are
+totally unsupported at this time:
+
+cvsnt 2.5.03
+svn 1.4.2
+git 1.6.0.4
+
+If you are using CVS instead of CVSNT, you can potentially try creating
+a shell alias or symlink mapping 'cvsnt' to 'cvs'.
+
+== Running
+
+Ensure that cvsnt, svn, svnadmin, svnsync, and git are all on your path.
+Then you can run the unit tests:
+
+$ rake
+
+You can load the library into your own Ruby application by requiring lib/scm.rb.
+
+= Functionality
+
+For each tracked repository, Ohloh uses the SCM library to maintain a private
+local mirror. The SCM library hides the differences between source control
+systems. The SCM library manages all required updates to a mirror, and reports
+the contents of the mirror in standardized ways.
+
+Each mirror is assigned a dedicated directory, and the SCM library adapter may
+store any content it desires in that directory. Usually, it's a direct clone of
+the original repository, but in the case of CVS or some Subversion servers, it
+is a conversion of the original repository to Git.
+
+The main Ohloh application orchestrates the scheduling of all updates and
+backups. On demand, the SCM library adapter then performs the following basic
+tasks on the local mirror:
+
+1. Pull changes -- From a remote repository URL, pull any changes to the local
+mirror. This step may involve conversion from one system to another.
+
+2. Push changes -- From the local mirror, push any changes to another Ohloh
+server. This is required to create backup copies and perform load balancing on
+the Ohloh cluster, and typically occurs over ssh.
+
+3. Commit log -- Given the last known commit, report the list of new commits,
+if any, including their diffs.
+
+4. Cat file or parent -- Given a commit, return either the contents of a
+single file, or that file's previous contents.
+
+5. Export tree -- Given a commit, export the entire contents of the source tree
+to a specified temp directory.
+
+The adapter must also implement validation routines used to filter user inputs
+and confirm the presence of the remote server.
+
+= Contact Ohloh
+
+For more information visit the Ohloh website:
+   http://labs.ohloh.net
+
+You can reach Ohloh via email at:
+   info@ohloh.net
diff --git a/Rakefile b/Rakefile
new file mode 100644 (file)
index 0000000..efe749b
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,10 @@
+require 'rake'
+require 'rake/clean'
+require 'rake/testtask'
+
+
+Rake::TestTask.new :unit_tests do |t|
+       t.test_files = FileList[File.dirname(__FILE__) + '/test/unit/**/*_test.rb']
+end
+
+task :default => :unit_tests
diff --git a/bin/ohlog b/bin/ohlog
new file mode 100755 (executable)
index 0000000..b9bb746
--- /dev/null
+++ b/bin/ohlog
@@ -0,0 +1,118 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../lib/scm'
+
+# This is a simple command line tool which parses CVS and Subversion logs.
+# It is not used by the main Ohloh system.
+#
+# I use it primarily to help debug Ohloh behavior. It's a convenient way
+# to turn an enormously long CVS log into something readable.
+module Scm::Parsers
+       class CommandLine
+               attr_accessor :paths, :writer
+
+               def initialize(args=[])
+                       args = args.clone # Because shift is destructive
+                       set_option(args.shift) while args.first =~ /^-/
+                       self.paths = args
+               end
+
+               def help
+                       puts <<HELP
+Usage: ohlog [option] [paths]
+
+Ohloh source control log parser
+   http://www.ohloh.net/
+
+[option] can be one of the following:
+
+  --cvs             Parse a CVS rlog
+  --svn             Parse a Subversion log
+  --svn-xml         Parse a Subversion XML log
+
+  -h, --human       Output result as a human-readable log (default)
+  -x, --xml         Output result as an XML log
+
+  -?, --help        Display this message
+
+[paths] must be one or more source control log filenames.
+   If no path is given, input will be read from STDIN.
+
+Examples:
+
+   cvsnt -d :pserver:anonymous:@cvs-mirror.mozilla.org:/cvsroot rlog mozilla/browser | ohlog --cvs
+
+   svn log --xml --verbose -r2:3 http://svn.collab.net/repos/svn/trunk | ohlog --svn-xml
+
+HELP
+               end
+
+               def cvs
+                       parse CvsParser
+               end
+
+               def svn
+                       parse SvnParser
+               end
+
+               def svn_xml
+                       parse SvnXmlParser
+               end
+
+               def parse(parser)
+                       self.writer ||= HumanWriter.new(STDOUT)
+
+                       if self.paths.any?
+                               self.paths.each do |path|
+                                       parser.parse File.new(path,'r'), :writer => self.writer
+                               end
+                       else
+                               parser.parse STDIN, :writer => self.writer
+                       end
+               end
+
+               def subcommand=(s)
+                       if @subcommand
+                               STDERR.puts "Error: Multiple commands specified."
+                               exit 1
+                       else
+                               @subcommand=s
+                       end
+               end
+
+               def subcommand
+                       @subcommand
+               end
+
+               def set_option(option)
+                       case option
+                       when '--cvs'
+                               self.subcommand = :cvs
+                       when '--svn'
+                               self.subcommand = :svn
+                       when '--svn-xml'
+                               self.subcommand = :svn_xml
+                       when '-h', '--human'
+                               self.writer = HumanWriter.new(STDOUT)
+                       when '-x', '--xml'
+                               self.writer = XmlWriter.new(STDOUT)
+                       when '-?', '--help'
+                               self.subcommand = :help
+                       else
+                               STDERR.puts "Type 'ohlog -?' for usage."
+                               exit 1
+                       end
+               end
+
+               def run!
+                       self.subcommand ||= :summary
+                       if self.respond_to?(self.subcommand)
+                               self.send(self.subcommand)
+                       else
+                               STDERR.puts "Type 'ohlog -?' for usage."
+                               exit 1
+                       end
+               end
+       end
+
+       CommandLine.new(ARGV).run!
+end
diff --git a/lib/scm.rb b/lib/scm.rb
new file mode 100644 (file)
index 0000000..76c81d0
--- /dev/null
@@ -0,0 +1,27 @@
+module Scm
+end
+
+require 'rbconfig'
+
+$: << File.join(File.dirname(__FILE__),"..")
+
+NULL_SHA1 = '0000000000000000000000000000000000000000' unless defined?(NULL_SHA1)
+
+require 'lib/scm/systemu'
+require 'lib/scm/scratch_dir'
+require 'lib/scm/commit'
+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/git_adapter'
+
+require 'lib/scm/parsers/parser'
+require 'lib/scm/parsers/branch_number'
+require 'lib/scm/parsers/cvs_parser'
+require 'lib/scm/parsers/svn_parser'
+require 'lib/scm/parsers/svn_xml_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/abstract/system.rb b/lib/scm/adapters/abstract/system.rb
new file mode 100644 (file)
index 0000000..d3b452a
--- /dev/null
@@ -0,0 +1,41 @@
+module Scm::Adapters
+       require 'logger'
+       class AbstractAdapter
+               def self.logger
+                       @@logger ||= Logger.new(STDERR)
+               end
+
+               def self.logger=(val)
+                       @@logger = val
+               end
+
+               def logger
+                       self.class.logger
+               end
+
+               # Custom implementation of shell execution, does not block when the "pipe is full."
+               # Raises an exception if the shell returns non-zero exit code.
+               def self.run(cmd)
+                       logger.debug { cmd }
+                       status, out, err = systemu(cmd)
+                       raise RuntimeError.new("#{cmd} failed: #{out}\n#{err}") if status.exitstatus != 0
+                       out
+               end
+
+               def run(cmd)
+                       AbstractAdapter::run(cmd)
+               end
+
+               # As above, but does not raise an exception when an error occurs.
+               # Returns two values, stdout and stderr.
+               def self.run_with_err(cmd)
+                       logger.debug { cmd }
+                       status, out, err = systemu(cmd)
+                       [out, err]
+               end
+
+               def run_with_err(cmd)
+                       AbstractAdapter::run_with_err(cmd)
+               end
+       end
+end
diff --git a/lib/scm/adapters/abstract/validation.rb b/lib/scm/adapters/abstract/validation.rb
new file mode 100644 (file)
index 0000000..aa3b3c3
--- /dev/null
@@ -0,0 +1,77 @@
+module Scm::Adapters
+       class AbstractAdapter
+               # The full regex that permits all possible URLs supported by the source control system.
+               def self.url_regex
+                       /.+/
+               end
+
+               # A limited regex that permits only URLs that are publicly addressable on the web.
+               # This regex should refuse access to local disk files.
+               def self.public_url_regex
+                       /.+/
+               end
+
+               def validate
+                       @errors = []
+                       @errors << validate_url
+                       @errors << validate_branch_name
+                       @errors << validate_username
+                       @errors << validate_password
+                       @errors.compact!
+               end
+
+               def validate_url
+                       return [:url, "The URL can't be blank."] unless @url and @url.length > 0
+                       return [:url, "The URL must not be longer than 120 characters."] unless @url.length <= 120
+
+                       regex = @public_urls_only ? self.class.public_url_regex : self.class.url_regex
+                       return [:url, "The URL does not appear to be a valid server connection string."] unless @url =~ regex
+               end
+
+               def validate_branch_name
+                       return nil if @branch_name.to_s == ''
+                       return [:branch_name, "The branch name must not be longer than 50 characters."] unless @branch_name.length <= 50
+                       return [:branch_name, "The branch name may contain only letters, numbers, spaces, and the special characters '_', '-', '+', '/', '^', and '.'"] unless @branch_name =~ /^[A-Za-z0-9_^\-\+\.\/\ ]+$/
+               end
+
+               def validate_username
+                       return nil unless @username
+                       return [:username, "The username must not be longer than 32 characters."] unless @username.length <= 32
+                       return [:username, "The username may contain only A-Z, a-z, 0-9, and underscore (_)"] unless @username =~ /^\w*$/
+               end
+
+               def validate_password
+                       return nil unless @password
+                       return [:password, "The password must not be longer than 32 characters."] unless @password.length <= 32
+                       return [:password, "The password contains illegal characters"] unless @password =~ /^[\w!@\#$%^&*\(\)\{\}\[\]\;\?\|\+\-\=]*$/
+               end
+
+               def valid?
+                       validate
+                       errors.empty?
+               end
+
+               def exists?
+                       exist?
+               end
+
+               # Ping the remote server to ensure it is responding and that the desired branch exists.
+               def validate_server_connection
+               end
+
+               # Give the object a chance to massage/cleanup its input attributes
+               def normalize
+                       @url.strip! if @url
+                       @branch_name.strip! if @branch_name
+                       @username.strip! if @username
+                       @password.strip! if @password
+                       self
+               end
+
+               # Based on the URL, return the domain name of the forge hosting this code.
+               def guess_forge
+                       # This is a very general rule for systems using HTTP-style URLs.
+                       url =~ /:\/\/([^\/]+)\// ? $1 : nil
+               end
+       end
+end
diff --git a/lib/scm/adapters/abstract_adapter.rb b/lib/scm/adapters/abstract_adapter.rb
new file mode 100644 (file)
index 0000000..1dde224
--- /dev/null
@@ -0,0 +1,24 @@
+module Scm::Adapters
+       class AbstractAdapter
+               attr_accessor :url, :branch_name, :username, :password, :errors, :public_urls_only
+
+               def initialize(params={})
+                       @url = params[:url]
+                       @branch_name = params[:branch_name]
+                       @username = params[:username]
+                       @password = params[:password]
+                       @public_urls_only = params[:public_urls_only]
+               end
+
+               # Handy for test overrides
+               def metaclass
+                       class << self
+                               self
+                       end
+               end
+
+       end
+end
+
+require 'lib/scm/adapters/abstract/system'
+require 'lib/scm/adapters/abstract/validation'
diff --git a/lib/scm/adapters/cvs/commits.rb b/lib/scm/adapters/cvs/commits.rb
new file mode 100644 (file)
index 0000000..f17721a
--- /dev/null
@@ -0,0 +1,89 @@
+module Scm::Adapters
+       class CvsAdapter
+               def commits(since=nil)
+                       result = []
+                       open_log_file(since) do |io|
+                               result = Scm::Parsers::CvsParser.parse(io, :branch_name => branch_name)
+                       end
+
+                       return result if result.size == 0 # Nothing found; we're done here.
+                       return result if since.to_s == '' # We requested everything, so just return everything.
+
+                       # We must now remove any duplicates caused by timestamp fudge factors,
+                       # and only return commits with timestamp > since.
+
+                       # If the first commit is newer than since, then the whole list is new and we can simply return.
+                       return result if parse_time(result.first.token) > parse_time(since)
+
+                       # Walk the list of commits to find the first new one, throwing away all of the old ones.
+
+                       # I want to string-compare timestamps without converting to dates objects (I think it's faster).
+                       # Some CVS servers print dates as 2006/01/02 03:04:05, others as 2006-01-02 03:04:05.
+                       # To work around this, we'll build a regex that matches either date format.
+                       re = Regexp.new(since.gsub(/[\/-]/, '.'))
+
+                       result.each_index do |i|
+                               if result[i].token =~ re # We found the match for since
+                                       if i == result.size-1
+                                               return [] # There aren't any new commits.
+                                       else
+                                               return result[i+1..-1]
+                                       end
+                               end
+                       end
+
+                       # Something bad is going on: 'since' does not match any timestamp in the rlog.
+                       # This is very rare, but it can happen.
+                       #
+                       # Often this means that the *last* time we ran commits(), there was some kind of
+                       # undetected problem (CVS was in an intermediate state?) so the list of timestamps we
+                       # calculated last time does not match the list of timestamps we calculated this time.
+                       #
+                       # There's no work around for this condition here in the code, but there are some things
+                       # you can try manually to fix the problem. Typically, you can try throwing way the
+                       # commit associated with 'since' and fetching it again (git reset --hard HEAD^).
+                       raise RuntimeError.new("token '#{since}' not found in rlog.")
+               end
+
+               # Gets the rlog of the repository and saves it in a temporary file.
+               # If you pass a timestamp token, then only commits after the timestamp will be returned.
+               #
+               # Warning!
+               #
+               # CVS servers are apparently unreliable when you truncate the log by timestamp -- perhaps round-off error?
+               # In any case, to be sure not to miss any commits, this method subtracts 10 seconds from the provided timestamp.
+               # This means that the returned log might actually contain a few revisions that predate the requested time.
+               # That's better than missing revisions completely! Just be sure to check for duplicates.
+               def open_log_file(since=nil)
+                       begin
+                               run "cvsnt -d #{self.url} rlog #{opt_branch} #{opt_time(since)} '#{self.module_name}' > #{rlog_filename}"
+                               File.open(rlog_filename, 'r') do |file|
+                                       yield file
+                               end
+                       ensure
+                               File.delete rlog_filename if FileTest.exists?(rlog_filename)
+                       end
+               end
+
+               def opt_time(since=nil)
+                       if since
+                               most_recent_time = parse_time(since) - 10
+                               " -d '#{most_recent_time.strftime('%Y-%m-%d %H:%M:%S')}Z<#{Time.now.utc.strftime('%Y-%m-%d %H:%M:%S')}Z' "
+                       else
+                               ""
+                       end
+               end
+
+               def rlog_filename
+                 File.join('/tmp', (self.url + self.module_name.to_s + self.branch_name.to_s).gsub(/\W/,'') + '.rlog')
+               end
+
+               # Converts a CVS time string to a Ruby Time object
+               def parse_time(token)
+                       case token
+                       when /(\d\d\d\d).(\d\d).(\d\d) (\d\d):(\d\d):(\d\d)/
+                               Time.gm( $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i )
+                       end
+               end
+       end
+end
diff --git a/lib/scm/adapters/cvs/misc.rb b/lib/scm/adapters/cvs/misc.rb
new file mode 100644 (file)
index 0000000..c95d885
--- /dev/null
@@ -0,0 +1,160 @@
+module Scm::Adapters
+       class CvsAdapter
+               # Returns an array of file and directory names from the remote server.
+               # Directory names will end with a trailing '/' character.
+               #
+               # Directories named "CVSROOT" are always ignored, and thus never returned.
+               #
+               # An empty array means that the call succeeded, but the remote directory is empty.
+               # A nil result means that the call failed and the remote server could not be queried.
+               def ls(path=nil)
+                       path = File.join(@module_name, path.to_s)
+
+                       cmd = "cvsnt -q -d #{url} ls -e '#{path}'"
+
+                       stdout, stderr = run_with_err(cmd)
+
+                       files = []
+                       stdout.each_line do |s|
+                               s.strip!
+                               s = $1 + '/' if s =~ /^D\/(.*)\/\/\/\/$/
+                               s = $1 if s =~ /^\/(.*)\/.*\/.*\/.*\/$/
+                               next if s == "CVSROOT/"
+                               files << s if s and s.length > 0
+                       end
+
+                       # Some of the cvs 'errors' are just harmless problems with some directories.
+                       # If we recognize all the error messages, then nothing is really wrong.
+                       # If some error messages go unhandled, then there really is an error.
+                       stderr.each_line do |s|
+                               s.strip!
+                               error_handled = false
+
+                               ignored_error_messages = [
+                                       /Listing modules on server/,
+                                       /Listing module: #{Regexp.escape(path.to_s)}/,
+                                       /-m wrapper option is not supported remotely; ignored/,
+                                       /cannot open directory .* No such file or directory/,
+                                       /ignoring module/,
+                                       /skipping directory/,
+                                       /existing repository .* does not match/,
+                                       /nothing known about/
+                               ]
+
+                               # The signal 11 error should not really be ignored, but dev.eclipse.org
+                               # returns it at the end of every ls. Yes, this sucks.
+                               ignored_error_messages << /Terminated with fatal signal 11/ if guess_forge == 'eclipse.org'
+
+                               if s.length == 0
+                                       error_handled = true
+                               elsif s =~ /cvs server: New directory `(#{Regexp.escape(path.to_s)}\/)?(.*)' -- ignored/
+                                       files << "#{$2}/"
+                                       error_handled = true
+                               end
+
+                               ignored_error_messages.each do |m|
+                                       error_handled = true if s =~ m
+                               end
+
+                               logger.warn {   "'#{cmd}' resulted in unhandled error '#{s}'"   } unless error_handled
+                               return nil unless error_handled
+                       end
+
+                       files.sort
+               end
+
+               def log(most_recent_token=nil)
+                       run "cvsnt -d #{self.url} rlog #{opt_branch} #{opt_time(most_recent_token)} '#{self.module_name}'"
+               end
+
+               def checkout(r, local_directory)
+                       opt_D = r.token ? "-D'#{r.token}Z'" : ""
+
+                       if FileTest.exists?(local_directory + '/CVS/Root')
+                               # We already have a local enlistment, so do a quick update.
+                               if r.directories.size > 0
+                                       build_ordered_directory_list(r.directories).each do |d|
+                                               if d.length == 0
+                                                       run "cd #{local_directory} && cvsnt update -d -l -C #{opt_D} ."
+                                               else
+                                                       run "cd #{local_directory} && cvsnt update -d -l -C #{opt_D} '#{d}'"
+                                               end
+                                       end
+                               else
+                                       # Brute force: get all updates
+                                       logger.warn("Revision #{r.token} did not contain any directories. Using brute force update of entire module.")
+                                       run "cd #{local_directory} && cvsnt update -d -R -C #{opt_D}"
+                               end
+                       else
+                               # We do not have a local enlistment, so do a slow checkout to create one.
+                               # Silly cvsnt won't accept an absolute path. We'll have to play some games and cd to the parent directory.
+                               parent_path, checkout_dir = File.split(local_directory)
+                               FileUtils.mkdir_p(parent_path) unless FileTest.exist?(parent_path)
+                               run "cd #{parent_path} && cvsnt -d #{self.url} checkout #{opt_D} -A -d'#{checkout_dir}' '#{self.module_name}'"
+                       end
+               end
+
+               # A revision can contain an arbitrary collection of directories.
+               # We need to ensure that for every directory we want to fetch, we also have its parent directories.
+               def build_ordered_directory_list(directories)
+                       # Integration Test Limitation
+                       # cvsnt has problems with absolute path names, so we are stuck with
+                       # using cvs modules that are only a single directory deep when testing.
+                       # We'll check if the url begins with '/' to detect an integration test,
+                       # then return an empty string (ie, the default root directory) if so.
+                       return [''] if self.url =~ /^\//
+
+                               list = []
+                       directories.collect{ |a| trim_directory(a.to_s).to_s }.each do |d|
+                               # We always ignore Attic directories, which just contain deleted files
+                               # Update the parent directory of the Attic instead.
+                               if d =~ /^(.*)Attic$/
+                                       d = $1
+                                       d = d[0..-2] if d.length > 0 and d[-1,1]=='/'
+                               end
+
+                               unless list.include? d
+                                       list << d
+                                       # We also need to include every parent directory of the directory
+                                       # we are interested in, all the way up to the root.
+                                       while d.rindex('/') and d.rindex('/') > 0 do
+                                               d = d[0..(d.rindex('/')-1)]
+                                               if list.include? d
+                                                       break
+                                               else
+                                                       list << d
+                                               end
+                                       end
+                               end
+                       end
+                       # Sort the list by length because we need to update parent directories before children
+                       list.sort! { |a,b| a.length <=> b.length }
+               end
+
+               def trim_directory(d)
+                       # If we are connecting to a remote server (basically anytime we are not
+                       # running the integration test) then we need to create a relative path
+                       # by trimming the prefix from the directory.
+                       # The prefix can be determined by examining the url and the module name.
+                       # For example, if url = ':pserver:anonymous:@moodle.cvs.sourceforge.net:/cvsroot/moodle'
+                       # and module = 'contrib', then the directory prefix = '/cvsroot/moodle/contrib/'
+                       if root
+                               d[root.length..-1]
+                       else
+                               d # If not remote, just leave the directory name as-is
+                       end
+               end
+
+               def root
+                       "#{$2}/#{self.module_name}/" if self.url =~ /^:pserver:.*@[^:]+:(\d+)?(\/.*)$/
+               end
+
+               def opt_branch
+                       if branch_name != nil and branch_name.length > 0 and branch_name != 'HEAD'
+               "-r'#{branch_name}'"
+                       else
+               "-b -r1:"
+                       end
+               end
+       end
+end
diff --git a/lib/scm/adapters/cvs/validation.rb b/lib/scm/adapters/cvs/validation.rb
new file mode 100644 (file)
index 0000000..3cd8ff8
--- /dev/null
@@ -0,0 +1,77 @@
+module Scm::Adapters
+       class CvsAdapter
+               def self.url_regex
+                       /^(:pserver:[\w\-\+\_]*(:[\w\-\+\_]*)?@[A-Za-z0-9_\-\+\.]+:[0-9]*)?\/[A-Za-z0-9_\-\+\.\/]*$/
+               end
+
+               def self.public_url_regex
+                       /^:pserver:[\w\-\+\_]*(:[\w\-\+\_]*)?@[A-Za-z0-9_\-\+\.]+:[0-9]*\/[A-Za-z0-9_\-\+\.\/]*$/
+               end
+
+               def validate
+                       super
+                       @errors << validate_module_name
+                       @errors.compact!
+               end
+
+               def validate_module_name
+                       return [:module_name, "The module name can't be blank."] if @module_name.to_s.length == 0
+                       return [:module_name, "The module name must not be longer than 120 characters."] unless @module_name.length <= 120
+                       return [:module_name, "The module name may contain only letters, numbers, spaces, and the special characters '_', '-', '+', '/', and '.'"] unless @module_name =~ /^[A-Za-z0-9_\-\+\.\/\ ]+$/
+               end
+
+               def normalize
+                       super
+                       @module_name = @module_name.strip if @module_name
+
+                       # Some CVS forges publish an URL which is actually a symlink, which causes CVSNT to crash.
+                       # For some forges, we can work around this by using an alternate directory.
+                       case guess_forge
+                       when 'java.net', 'netbeans.org'
+                               @url.gsub!(/:\/cvs\/?$/, ':/shared/data/ccvs/repository')
+                       when 'gna.org'
+                               @url.gsub!(/:\/cvs\b/, ':/var/cvs')
+                       end
+
+                       sync_pserver_username_password
+
+                       self
+               end
+
+               # This bit of code patches up any inconsistencies that may arise because there
+               # is both a @password attribute and a password embedded in the :pserver: url.
+               # This method guarantees that they are both the same.
+               #
+               # It's assumed that if the user specified a @password attribute, then that is
+               # the preferred value and it should take precedence over any password found
+               # in the :pserver: url.
+               #
+               # If the user did not specify a @password attribute, then the value
+               # found in the :pserver: url is assigned to both.
+               def sync_pserver_username_password
+                       # Do nothing unless pserver connection string is well-formed.
+                       return unless self.url =~ /:pserver:([\w\-\_]*)(:([\w\-\_]*))?@(.*)$/
+
+                       pserver_username = $1
+                       pserver_password = $3
+                       pserver_remainder = $4
+
+                       @username = pserver_username if @username.to_s == ''
+                       @password = pserver_password if @password.to_s == ''
+
+                       self.url = ":pserver:#{@username}:#{password}@#{pserver_remainder}"
+               end
+
+               def validate_server_connection
+                       return unless valid?
+                       if ls.nil?
+                               @errors << [:failed, "The cvs server did not respond to an 'ls' command. Are the URL and module name correct?"]
+                       end
+               end
+
+               # Based on the URL, take a guess about which forge this code is hosted on.
+               def guess_forge
+                       @url =~ /.*pserver.*@(([^\.]+\.)?cvs\.)?(dev\.)?([^:]+):\//i ? $4.downcase : nil
+               end
+       end
+end
diff --git a/lib/scm/adapters/cvs_adapter.rb b/lib/scm/adapters/cvs_adapter.rb
new file mode 100644 (file)
index 0000000..ce78e70
--- /dev/null
@@ -0,0 +1,18 @@
+module Scm::Adapters
+       class CvsAdapter < AbstractAdapter
+               attr_accessor :module_name
+
+               def english_name
+                       'CVS'
+               end
+
+               def initialize(params={})
+                       super
+                       @module_name = params[:module_name]
+               end
+       end
+end
+
+require 'lib/scm/adapters/cvs/validation'
+require 'lib/scm/adapters/cvs/commits'
+require 'lib/scm/adapters/cvs/misc'
diff --git a/lib/scm/adapters/git/cat_file.rb b/lib/scm/adapters/git/cat_file.rb
new file mode 100644 (file)
index 0000000..ee8cd1b
--- /dev/null
@@ -0,0 +1,15 @@
+module Scm::Adapters
+       class GitAdapter < AbstractAdapter
+               def cat_file(commit, diff)
+                       cat(diff.sha1)
+               end
+
+               def cat_file_parent(commit, diff)
+                       cat(diff.parent_sha1)
+               end
+
+               def cat(sha1)
+                       run "cd '#{url}' && git cat-file -p #{sha1}"
+               end
+       end
+end
diff --git a/lib/scm/adapters/git/commit_all.rb b/lib/scm/adapters/git/commit_all.rb
new file mode 100644 (file)
index 0000000..acf06cf
--- /dev/null
@@ -0,0 +1,141 @@
+module Scm::Adapters
+       class GitAdapter < AbstractAdapter
+
+               #---------------------------------------------------------------------------
+               # COMMIT-RELATED CODE
+               #-------------------------------------------------------------------------
+
+               # Commit all changes in the working directory, using metadata from the passed commit.
+               def commit_all(commit=Commit.new)
+                       init_db
+                       ensure_gitignore
+                       write_token(commit.token)
+
+                       # Establish the author, email, message, etc. for the git-commit.
+                       message_filename = set_commit_metadata(commit)
+
+                       run "cd '#{self.url}' && git add ."
+                       if anything_to_commit?
+                               run " cd '#{self.url}' && git commit -a -F #{message_filename}"
+                       else
+                               logger.info { "nothing to commit" }
+                       end
+               end
+
+               # Store all of the commit metadata in the GIT environment variables
+               # where they will be picked up by the git-commit command.
+               #
+               # Commit info is required.
+               # Author info is optional, and defaults to committer info.
+               def set_commit_metadata(commit)
+
+                       ENV['GIT_COMMITTER_NAME'] = commit.committer_name || '[anonymous]'
+                       ENV['GIT_AUTHOR_NAME'] = commit.author_name || ENV['GIT_COMMITTER_NAME']
+
+                       ENV['GIT_COMMITTER_EMAIL'] = commit.committer_email || ENV['GIT_COMMITTER_NAME']
+                       ENV['GIT_AUTHOR_EMAIL'] = commit.author_email || ENV['GIT_AUTHOR_NAME']
+
+                       ENV['GIT_COMMITTER_DATE'] = commit.committer_date.to_s
+                       ENV['GIT_AUTHOR_DATE'] = (commit.author_date || commit.committer_date).to_s
+
+                       # This is a one-off fix for DrJava, which includes some escape characters
+                       # in one of its Subversion messages. This might lead to a more generalized
+                       # cleanup of message text, but for now...
+                       commit.message.gsub!(/\\027/,'') if commit.message
+
+                       # Git requires a non-empty message
+                       if commit.message.nil? || commit.message =~ /\A\s*\z/
+                               commit.message = '[no message]'
+                       end
+
+                       # We need to store the message in a file in case it contains crazy characters
+                       #    that would corrupt a bash command line.
+                       File.open(message_filename, 'w') do |f|
+                               f.write commit.message
+                       end
+                       message_filename
+               end
+
+               # By hiding the message file inside the .git directory, we
+               #    avoid it being found by the commit-all.
+               def message_filename
+                       File.expand_path(File.join(git_path, 'ohloh_message'))
+               end
+
+               # True if there are pending changes to commit.
+               def anything_to_commit?
+                       run("cd '#{self.url}' && git status | tail -1") =~ /nothing to commit / ? false : true
+               end
+
+               # Ensures that the repository directory exists, and that the git database has been initialized.
+               def init_db
+                       unless FileTest.exist? url
+                               run "mkdir -p '#{url}'"
+                       end
+                       unless FileTest.exist? git_path
+                               run "cd '#{url}' && git init-db"
+                       end
+               end
+
+
+               #-------------------------------------------------------------------------
+               # GIT IGNORE CODE
+               # Ensures that we do not waste storage space on non-source code files
+               #-------------------------------------------------------------------------
+
+               unless defined?(IGNORE)
+                       IGNORE = [
+                               ".svn",
+                               "CVS",
+                               "*.jar",
+                               "*.tar",
+                               "*.gz",
+                               "*.tgz",
+                               "*.zip",
+                               "*.gif",
+                               "*.jpg",
+                               "*.jpeg",
+                               "*.bmp",
+                               "*.png",
+                               "*.tif",
+                               "*.tiff",
+                               "*.ogg",
+                               "*.aiff",
+                               "*.wav",
+                               "*.mp3",
+                               "*.au",
+                               "*.ra",
+                               "*.m4a",
+                               "*.pdf",
+                               "*.mpg",
+                               "*.mov",
+                               "*.qt",
+                               "*.avi",
+                               "*.xbm"
+                       ]
+               end
+
+               # The .gitignore file will be created if it does not exist.
+               # If our desired filespec is not found in .gitignore, it will be appended
+               # to the end of .gitignore.
+               def ensure_gitignore
+                       IGNORE.each do |ignore|
+                               gitignore_filename = File.join(self.url, '.gitignore')
+                               found = false
+                               File.open(gitignore_filename, File::CREAT | File::RDONLY) do |io|
+                                       io.readlines.each do |l|
+                                               if l.chomp == ignore
+                                                       found = true
+                                                       break
+                                               end
+                                       end
+                               end
+                               unless found
+                                       File.open(gitignore_filename, File::APPEND | File::WRONLY) do |io|
+                                               io.puts ignore
+                                       end
+                               end
+                       end
+               end
+       end
+end
diff --git a/lib/scm/adapters/git/commits.rb b/lib/scm/adapters/git/commits.rb
new file mode 100644 (file)
index 0000000..68045a2
--- /dev/null
@@ -0,0 +1,60 @@
+module Scm::Adapters
+       class GitAdapter < AbstractAdapter
+
+               # Returns the number of commits in the repository following the commit with SHA1 'since'.
+               def commit_count(since=nil)
+                       run("#{rev_list_command(since)} | wc -l").to_i
+               end
+
+               # Returns the SHA1 hash for every commit in the repository following the commit with SHA1 'since'.
+               def commit_tokens(since=nil)
+                       run(rev_list_command(since)).split("\n")
+               end
+
+               # Yields each commit following the commit with SHA1 'since'.
+               # Officially, this method isn't required to provide diffs with these commits, and the Subversion equivalent of this method does not,
+               # so if you really require the diffs you should be using each_commit() instead.
+               def commits(since=nil)
+                       result = []
+                       each_commit(since) { |c| result << c }
+                       result
+               end
+
+               # Yields each commit following the commit with SHA1 'since'.
+               # These commits are populated with diffs.
+               def each_commit(since=nil)
+                       Git::LogParser.parse(log(since)) do |e|
+                               yield e
+                       end
+               end
+
+               # Command line to output a single commit.
+               # We use a special format string so that we can get the date in a format Ruby can parse.
+               WHATCHANGED="git whatchanged --root --abbrev=40 --max-count=1 --pretty=format:'__BEGIN_COMMIT__%nCommit: %H%nAuthor: %an%nAuthorEmail: %ae%nDate: %aD%n__BEGIN_COMMENT__%n%s%n%b%n__END_COMMENT__'" unless defined?(WHATCHANGED)
+
+               REV_LIST_OPTIONS=" --root --reverse --no-merges --topo-order " unless defined?(REV_LIST_OPTIONS)
+
+               # Retrieves the git log in the format expected by Git::LogParser.
+               # We get the log forward chronological order (oldest first)
+               def log(since=nil)
+                       if has_branch?
+                               if since && since==self.head
+                                       '' # Nothing new.
+                               else
+                                       run "#{rev_list_command(since)} | xargs -n 1 #{WHATCHANGED}"
+                               end
+                       else
+                               ''
+                       end
+               end
+
+               def rev_list_command(since=nil)
+                       if since
+                               "cd '#{self.url}' && git rev-list #{REV_LIST_OPTIONS} #{since}..HEAD #{self.branch_name}"
+                       else
+                               "cd '#{self.url}' && git rev-list #{REV_LIST_OPTIONS} #{self.branch_name}"
+                       end
+               end
+
+       end
+end
diff --git a/lib/scm/adapters/git/log_parser.rb b/lib/scm/adapters/git/log_parser.rb
new file mode 100644 (file)
index 0000000..9f5f2c0
--- /dev/null
@@ -0,0 +1,70 @@
+module Scm::Adapters::Git
+       class LogParser
+
+               NO_AUTHOR='(no author)'
+
+               def self.parse io
+                       e = nil
+                       state = :key_values
+
+                       io.each do |line|
+                               line.chomp!
+
+                               # Kind of a hack: the diffs section is not always present.
+                               # If we are expecting a line of diffs, but instead find a line
+                               # starting with "Commit: ", that means the diffs section
+                               # is missing for this commit,  and we need to fix up our state.
+                               if state == :diffs and line =~ /^Commit: ([a-z0-9]+)$/
+                                       state = :key_values
+                               end
+
+                               if state == :key_values
+                                       if line =~ /^Commit: ([a-z0-9]+)$/
+                                               sha1 = $1
+                                               yield e if e
+                                               e = Scm::Commit.new
+                                               e.diffs = []
+                                               e.token = sha1
+                                               e.author_name = NO_AUTHOR
+                                       elsif line =~ /^Author: (.+)$/
+                                               e.author_name = $1
+                                       elsif line =~ /^Date: (.*)$/
+                                               e.author_date = Time.parse($1).utc # Note strongly: MUST be RFC2822 format to parse properly
+                                       elsif line == "__BEGIN_COMMENT__"
+                                               state = :message
+                                       elsif line =~ /^AuthorEmail: (.+)$/
+                                               e.author_email = $1
+                                               # In the rare case that the Git repository does not contain any names (see OpenEmbedded for example)
+                                               # we use the email instead.
+                                               e.author_name = $1 if e.author_name.to_s.empty? || e.author_name == NO_AUTHOR
+                                       end
+
+                               elsif state == :message
+                                       if line == "__END_COMMENT__"
+                                               state = :diffs
+                                       elsif line != "<unknown>"
+                                               if e.message
+                                                       e.message << "\n" << line
+                                               else
+                                                       e.message = line
+                                               end
+                                       end
+
+                               elsif state == :diffs
+                                       if line == "__BEGIN_COMMIT__"
+                                               state = :key_values
+                                       elsif line =~ /:([0-9]+) ([0-9]+) ([a-z0-9]+) ([a-z0-9]+) ([A-Z])\t"?(.+[^"])"?$/
+                                               # Submodules have a file mode of '160000', which indicates a "gitlink"
+                                               # We ignore submodules completely.
+                                               e.diffs << Scm::Diff.new( :action => $5, :path => $6, :sha1 => $4, :parent_sha1 => $3 ) unless $1=='160000' || $2=='160000'
+                                       end
+
+                               else
+                                       raise RuntimeError("Unknown parser state #{state.to_s}")
+                               end
+                       end
+
+                       yield e if e
+               end
+       end
+end
diff --git a/lib/scm/adapters/git/misc.rb b/lib/scm/adapters/git/misc.rb
new file mode 100644 (file)
index 0000000..a854a67
--- /dev/null
@@ -0,0 +1,65 @@
+module Scm::Adapters
+       class GitAdapter < AbstractAdapter
+               def git_path
+                       File.join(self.url, '/.git')
+               end
+
+               def exist?
+                       begin
+                               !!(head)
+                       rescue
+                               logger.debug { $! }
+                               false
+                       end
+               end
+
+               def head
+                       run("git ls-remote --heads '#{url}' #{branch_name}") =~ /^(^[a-z0-9]{40})\s+\S+$/
+                       $1
+               end
+
+               def export(dest_dir, commit_id = 'HEAD')
+                       run "cd #{url} && git archive #{commit_id} | tar -C #{ dest_dir } -x"
+               end
+
+               # Moves us the correct branch and checks out the most recent files.
+               #
+               # Anything not tracked by Git is deleted.
+               #
+               # This method may not seem like the most efficient way to accomplish this,
+               # but we need very high reliability and this sequence gets the job done every time.
+               def checkout
+                       if FileTest.exist? git_path
+                               run "cd '#{url}' && git clean -f -d -x"
+                               if self.has_branch?
+                                       run "cd '#{url}' && git reset --hard #{self.branch_name} --"
+                                       run "cd '#{url}' && git checkout #{self.branch_name} --"
+                               end
+                       end
+               end
+
+               #---------------------------------------------------------------------------
+               # BRANCH-RELATED CODE
+               #-------------------------------------------------------------------------
+
+               # Returns an array of all branch names
+               def branches
+                       run("cd '#{self.url}' && git branch").split.collect { |b| b =~ /\b(.+)$/ ; $1 }.compact
+               end
+
+               def has_branch?(name=self.branch_name)
+                       return false unless FileTest.exist?(self.git_path)
+                       self.branches.include?(name)
+               end
+
+               # Create a new local branch to mirror the remote one
+               # If a branch of this name already exist, nothing happens.
+               def create_tracking_branch(name)
+                       return if name.to_s == ''
+
+                       unless self.branches.include? name
+                               run "cd '#{self.url}' && git branch -f #{name} origin/#{name}"
+                       end
+               end
+       end
+end
diff --git a/lib/scm/adapters/git/pull.rb b/lib/scm/adapters/git/pull.rb
new file mode 100644 (file)
index 0000000..1b6d7f5
--- /dev/null
@@ -0,0 +1,114 @@
+module Scm::Adapters
+       class GitAdapter < AbstractAdapter
+
+               def pull(from, &block)
+                       logger.info { "Pulling #{from.url}" }
+                       case from
+                       when GitAdapter
+                               clone_or_fetch(from, &block)
+                       when CvsAdapter, SvnAdapter
+                               convert(from, &block)
+                       end
+               end
+
+               # Clone source_scm as new repository.
+               # If a local repository already exists, update the local repository with the latest changes.
+               def clone_or_fetch(source_scm)
+                       raise ArgumentError.new("Cannot pull from #{source_scm.inspect}") unless source_scm.is_a?(GitAdapter)
+
+                       yield(0,1) if block_given? # Progress bar callback
+
+                       unless self.exist? && self.has_branch?
+                               run "mkdir -p '#{self.url}'"
+                               run "rm -rf '#{self.url}'"
+                               run "git clone -q -n '#{source_scm.url}' '#{self.url}' >/dev/null 2>&1"
+                               create_tracking_branch(source_scm.branch_name) # ensure the correct branch exist locally
+                               checkout # switch to the correct branch
+                       else
+                               checkout # should already be on correct branch, but some old repositories were stricken by a bug
+                               run "cd '#{self.url}' && git fetch --update-head-ok '#{source_scm.url}' #{self.branch_name}:#{self.branch_name}"
+                       end
+                       clean_up_disk
+
+                       yield(1,1) if block_given? # Progress bar callback
+               end
+
+               # Apply all recent changes from source_scm, converting to Git in the process.
+               #
+               # Progress is reported by yielding [current_step, total_steps] pairs to a provided block.
+               #
+               # There are two design goals here:
+               #
+               # First, minimize adminstrator burden. The conversion process is
+               # idempotent: if it fails, Ohloh's first recourse is always to try again.
+               # For this reason, it is important that multiple pulls do not result in
+               # duplicate data. No matter how badly a previous attempt may be screwed up
+               # the contents on disk, a second attempt should be able to gracefully
+               # resume.
+               #
+               # Second, maximize compatibility with a wild array of public source control
+               # servers.  CVS in particular has a broad spectrum of server capabilities.
+               # Therefore, this conversion algorithm is brute-force simplicity itself. We
+               # require a minimum number of features: a log feature to get the list of
+               # commits, and the checkout feature to get the contents of each commit.
+               #
+               # The basic idea for conversion is that we sequentially check out each
+               # revision from the original repository into a local directory, which also
+               # happens to be a local Git repository. As each revision is checked out
+               # from the original repository, we commit it into the local Git repository.
+               # By walking forward through each commit, we build a Git equivalent to the
+               # original repository.
+               #
+               # It's not fast, but it almost always succeeds.
+               #
+               # The original commit ID (CVS timestamp or Subversion revision number) is
+               # stored in a special file called 'ohloh_token' and checked in as part of
+               # each Git commit. This enables us to match code to original source
+               # commit, and also enables us to pick up where we left off for incremental
+               # updates.
+               #
+               # Only a single branch from the original repository is converted.
+               def convert(source_scm)
+                       yield(0,1) if block_given? # Progress bar callback
+
+                       # Any new work to be done since last time we were here?
+                       commits = source_scm.commits(read_token)
+                       if commits and commits.size > 0
+                               # Start by making sure we are in a known good state. Set up our working directory.
+                               clean_up_disk
+                               checkout
+
+                               commits.each_with_index do |r,i|
+                                       yield(i,commits.size) if block_given? # Progress bar callback
+
+                                       logger.info { "Downloading revision #{r.token} (#{i+1} of #{commits.size})... " }
+                                       begin
+                                               source_scm.checkout(r, url)
+                                       rescue
+                                               logger.error { $!.inspect }
+                                               # If we fail to checkout, it's often because there is junk of some kind
+                                               # in our working directory.
+                                               logger.info { "Checkout failed. Cleaning and trying again..." }
+                                               clean_up_disk
+                                               source_scm.checkout(r, url)
+                                       end
+
+                                       logger.debug { "Committing revision #{r.token} (#{i+1} of #{commits.size})... " }
+                                       commit_all(r)
+                               end
+                               yield(commits.size, commits.size) if block_given?
+                       else
+                               logger.info { "Already up-to-date." }
+                       end
+               end
+
+               # Deletes everything in the working directory.
+               # All pending changes are discarded.
+               # Only the hidden git folder will remain.
+               def clean_up_disk
+                       if FileTest.exist? url
+                               run "cd #{url} && find . -maxdepth 1 -not -name .git -not -name . -print0 | xargs -0 rm -rf --"
+                       end
+               end
+       end
+end
diff --git a/lib/scm/adapters/git/push.rb b/lib/scm/adapters/git/push.rb
new file mode 100644 (file)
index 0000000..11aa891
--- /dev/null
@@ -0,0 +1,39 @@
+require 'socket'
+
+module Scm::Adapters
+       class GitAdapter < AbstractAdapter
+
+               COMMITTER_NAME = 'ohloh_slave' unless defined?(COMMITTER_NAME)
+
+               def push(to)
+                       logger.info { "Pushing to #{to.url}" }
+
+                       if to.exist?
+                               ENV['GIT_COMMITTER_NAME'] = COMMITTER_NAME
+                               run "cd '#{self.url}' && git push '#{to.url}' #{self.branch_name}:#{to.branch_name}"
+                       else
+                               if to.local?
+                                       # Create a new repo on the same local machine. Just use existing pull code in reverse.
+                                       to.pull(self)
+                               else
+                                       run "ssh #{to.hostname} 'mkdir -p #{to.path}'"
+                                       run "scp -rpqB #{git_path} #{to.hostname}:#{to.path}"
+                               end
+                       end
+               end
+
+               def local?
+                       return true if hostname == Socket.gethostname
+                       return false if url =~ /:/
+                       true
+               end
+
+               def hostname
+                       url =~ /^([^:^\/]+):(.+)/ ? $1 : nil
+               end
+
+               def path
+                       url =~ /^([^:^\/]+):(.+)/ ? $2 : nil
+               end
+       end
+end
diff --git a/lib/scm/adapters/git/token.rb b/lib/scm/adapters/git/token.rb
new file mode 100644 (file)
index 0000000..b102af9
--- /dev/null
@@ -0,0 +1,53 @@
+module Scm::Adapters
+       class GitAdapter < AbstractAdapter
+
+               #---------------------------------------------------------------------------
+               # TOKEN-RELATED CODE
+               #
+               # As CVS and Subversion are converted to Git, the unique ID of each commit
+               # from the original source control system is stored in a special token file.
+               #
+               # Whenever Ohloh needs to incrementally update our local copy of the code with
+               # the latest commits, we just check the token file to see at which point we need
+               # to restart the process.
+               #---------------------------------------------------------------------------
+
+               def token_filename
+                       'ohloh_token'
+               end
+
+               def token_path
+                       File.join(self.url, token_filename)
+               end
+
+               # Determine the most recent revision that was safely stored in the GIT archive.
+               # Resets the token file on disk to the most recent version stored in the repository.
+               def read_token
+                       token = nil
+                       if self.exist?
+                               begin
+                                       token = run("cd '#{url}' && git cat-file -p `git ls-tree HEAD #{token_filename} | cut -c 13-51`").strip
+                               rescue RuntimeError => e
+                                       # If the git repository doesn't have a token file yet, it will error out.
+                                       # We want to just quietly return nil.
+                                       if e.message =~ /pathspec '#{token_filename}' did not match any file\(s\) known to git/
+                                               return nil
+                                       else
+                                               raise
+                                       end
+                               end
+                       end
+                       token
+               end
+
+               # Saves the new token in a well-known file.
+               # If the passed token is empty, this method silently does nothing.
+               def write_token(token)
+                       if token and token.to_s.length > 0
+                               File.open(token_path, 'w') do |f|
+                                       f.write token.to_s
+                               end
+                       end
+               end
+       end
+end
diff --git a/lib/scm/adapters/git/validation.rb b/lib/scm/adapters/git/validation.rb
new file mode 100644 (file)
index 0000000..ef5cc60
--- /dev/null
@@ -0,0 +1,22 @@
+module Scm::Adapters
+       class GitAdapter < AbstractAdapter
+               def self.url_regex
+                       /^(http|https|rsync|git|ssh):\/\/(\w+@)?[A-Za-z0-9_\-\.]+(:\d+)?\/[A-Za-z0-9_\-\.\/\~\+]*$/
+               end
+
+               def self.public_url_regex
+                       /^(http|https|git):\/\/(\w+@)?[A-Za-z0-9_\-\.]+(:\d+)?\/[A-Za-z0-9_\-\.\/\~\+]*$/
+               end
+
+               def normalize
+                       super
+                       @branch_name = 'master' if @branch_name.to_s == ''
+                       self
+               end
+
+               def validate_server_connection
+                       return unless valid?
+                       @errors << [:failed, "The server did not respond to the 'git-ls-remote' command. Is the URL correct?"] unless self.exists?
+               end
+       end
+end
diff --git a/lib/scm/adapters/git_adapter.rb b/lib/scm/adapters/git_adapter.rb
new file mode 100644 (file)
index 0000000..f229a1b
--- /dev/null
@@ -0,0 +1,17 @@
+module Scm::Adapters
+       class GitAdapter < AbstractAdapter
+               def english_name
+                       "Git"
+               end
+       end
+end
+
+require 'lib/scm/adapters/git/validation'
+require 'lib/scm/adapters/git/cat_file'
+require 'lib/scm/adapters/git/commits'
+require 'lib/scm/adapters/git/commit_all'
+require 'lib/scm/adapters/git/log_parser'
+require 'lib/scm/adapters/git/token'
+require 'lib/scm/adapters/git/push'
+require 'lib/scm/adapters/git/pull'
+require 'lib/scm/adapters/git/misc'
diff --git a/lib/scm/adapters/svn/cat_file.rb b/lib/scm/adapters/svn/cat_file.rb
new file mode 100644 (file)
index 0000000..635afdf
--- /dev/null
@@ -0,0 +1,15 @@
+module Scm::Adapters
+       class SvnAdapter < AbstractAdapter
+               def cat_file(commit, diff)
+                       cat(diff.path, commit.token)
+               end
+
+               def cat_file_parent(commit, diff)
+                       cat(diff.path, commit.token.to_i-1)
+               end
+
+               def cat(path=nil, revision='HEAD')
+                       run "svn cat -r #{revision} '#{SvnAdapter.uri_encode(File.join(self.root, self.branch_name.to_s, path.to_s))}@#{revision}'"
+               end
+       end
+end
diff --git a/lib/scm/adapters/svn/commits.rb b/lib/scm/adapters/svn/commits.rb
new file mode 100644 (file)
index 0000000..d3d752a
--- /dev/null
@@ -0,0 +1,240 @@
+require 'rexml/document'
+require 'sha1'
+
+module Scm::Adapters
+       class SvnAdapter < AbstractAdapter
+
+               # In all commit- and log-related methods, 'since' refers to the revision number of the last known commit,
+               # and the methods return the commits *following* this commit.
+               #
+               # Examples:
+               #    commits(1) => [rev 2, rev 3, ..., HEAD]
+               #    commits(3) => [rev 4, rev 5, ..., HEAD]
+               #
+               # This is convenient for Ohloh -- Ohloh passes the last commit it is aware of, and these methods return any new commits.
+
+               # Returns the count of commits following revision number 'since'.
+               def commit_count(since=0)
+                       run("svn log -q -r #{since.to_i + 1}:HEAD --stop-on-copy '#{SvnAdapter.uri_encode(File.join(root, branch_name.to_s))}' | 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 commit_tokens(since=0)
+                       cmd = "svn log -q -r #{since.to_i + 1}:HEAD --stop-on-copy '#{SvnAdapter.uri_encode(File.join(root, branch_name.to_s))}' | grep -E -e '^r[0-9]+ ' | cut -f 1 -d '|' | cut -c 2-"
+                       run(cmd).split.collect { |r| r.to_i }
+               end
+
+               # Returns an array of commits following revision number 'since'. These commit objects do not include diffs.
+               def commits(since=0)
+                       c = []
+                       open_log_file(since) do |io|
+                               c = Scm::Parsers::SvnXmlParser.parse(io)
+                       end
+
+                       # We may be using a log saved on disk from a previous fetch.
+                       # If so, exclude the portion of the log up to 'since'.
+                       c.each_index do |i|
+                               if c[i].token.to_i == since.to_i
+                                       if i == commits.size-1
+                                               # We're up to date
+                                               return []
+                                       else
+                                               return c[i+1..-1]
+                                       end
+                               end
+                       end
+                       c
+               end
+
+               # Yields each commit following revision number 'since'. These commit object are populated with diffs.
+               #
+               # With Subversion, populating the diffs can be tricky because when an entire directory is affected,
+               # Subversion abbreviates the log by simply listing the directory name, rather than all of the directory
+               # contents. Since Ohloh requires the list of every individual file affected, and doesn't care about
+               # directories, the complexity (and time) of this method comes in expanding directories with a recursion
+               # through every file in the directory.
+               #
+               # Additionally, Ohloh requires the SHA1 hash of every file, as well as the SHA1 of its prior version.
+               # These must match the SHA1 values that would be generated by Git.
+               #
+               def each_commit(since=nil)
+                       commit_tokens(since).each do |rev|
+                               yield populate_sha1s!(deepen_commit(strip_commit_branch(verbose_commit(rev))))
+                       end
+               end
+
+               # For a given commit, replace any diffs that point to directories with diffs for each file that
+               # the directory contains.
+               def deepen_commit(commit)
+                       deep_commit = commit.clone
+                       deep_commit.diffs = commit.diffs.collect do |diff|
+                               deepen_diff(diff, commit.token)
+                       end.flatten.uniq.sort { |a,b| a.action <=> b.action }.sort { |a,b| a.path <=> b.path }
+                       deep_commit
+               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)
+                       # 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 == 'D' or diff.action == 'A') && is_directory?(diff.path, recurse_rev)
+                               recurse_files(diff.path, recurse_rev).collect do |f|
+                                       Scm::Diff.new(:action => diff.action, :path => File.join(diff.path, f))
+                               end
+                       else
+                               diff
+                       end
+               end
+
+               # Strip all paths in this commit to remove the leading branch_name.
+               # Throw away any diffs in the commit that don't lie in the branch_name we care about.
+               def strip_commit_branch(commit)
+                       stripped_commit = commit.clone
+                       if commit.diffs
+                               stripped_commit.diffs = commit.diffs.collect { |d| strip_diff_branch(d) }.compact
+                       end
+                       stripped_commit
+               end
+
+               # Return a new diff whose path excludes the leading branch_name.
+               # If the diff is not in the branch, return nil.
+               def strip_diff_branch(diff)
+                       stripped_diff = diff.clone
+                       stripped_diff.path = strip_path_branch(diff.path)
+                       stripped_diff.path && stripped_diff
+               end
+
+               # Returns only the portion of the path following branch_name.
+               # Returns nil if the path is not within the branch.
+               def strip_path_branch(path)
+                       if path == self.branch_name.to_s
+                               ''
+                       else
+                               $1 if path =~ /^#{Regexp.escape(self.branch_name.to_s)}(\/.*)$/
+                       end
+               end
+
+               # A single commit, including any changed paths.
+               # Basically equivalent to the data you get back from the Subversion log when you pass the --verbose flag.
+               def verbose_commit(rev)
+                       Scm::Parsers::SvnXmlParser.parse(single_revision_xml(rev)).first
+               end
+
+               #---------------------------------------------------------------------
+               # Log-related code ; get log for entire file or single revision
+               #---------------------------------------------------------------------
+
+               def log(since=0)
+                       run "svn log --xml --stop-on-copy -r #{since.to_i + 1}:HEAD '#{SvnAdapter.uri_encode(self.url)}' #{opt_auth}"
+               end
+
+               def open_log_file(since=0)
+                       begin
+                               if (since.to_i + 1) <= max_revision
+                                       run "svn log --xml --stop-on-copy -r #{since.to_i + 1}:HEAD '#{SvnAdapter.uri_encode(self.url)}' #{opt_auth} > #{log_filename}"
+                               else
+                                       # As a time optimization, just create an empty file rather than fetch a log we know will be empty.
+                                       File.open(log_filename, 'w') { |f| f.puts '<?xml version="1.0"?>' }
+                               end
+                               File.open(log_filename, 'r') { |io| yield io }
+                       rescue
+                               File.delete(log_filename) if FileTest.exist?(log_filename)
+                               raise
+                       end
+               end
+
+               def log_filename
+                 File.join('/tmp', (self.url).gsub(/\W/,'') + '.log')
+               end
+
+               def single_revision_xml(revision)
+                       run "svn log --verbose --xml --stop-on-copy -r #{revision} --limit 1 #{opt_auth} '#{SvnAdapter.uri_encode(self.url)}@#{revision}'"
+               end
+
+               # Recurses the entire repository and returns an array of file names.
+               # Directories are not returned.
+               # Directories named 'CVSROOT' are always ignored and the files they contain are never returned.
+               # An empty array means that the call succeeded, but the remote directory is empty.
+               # A nil result means that the call failed and the remote server could not be queried.
+               def recurse_files(path=nil, revision='HEAD')
+                       begin
+                               stdout = run "svn ls -r #{revision} --recursive #{opt_auth} '#{SvnAdapter.uri_encode(File.join(root, branch_name.to_s, path.to_s))}@#{revision}'"
+                       rescue
+                               puts $!.inspect
+                               return nil
+                       end
+
+                       files = []
+                       stdout.each_line do |s|
+                               s.chomp!
+                               files << s if s.length > 0 and s !~ /CVSROOT\// and s[-1..-1] != '/'
+                       end
+                       files.sort
+               end
+
+               #---------------------------------------------------------------------
+               # SHA1 Computation
+               # Generates SHA1 hashes for file contents, matching Git's method.
+               #---------------------------------------------------------------------
+
+               # Populates the SHA1 values for each diff in a commit.
+               def populate_sha1s!(commit)
+                       commit.diffs.each do |diff|
+                               populate_sha1!(diff, commit.token)
+                       end
+                       commit
+               end
+
+               # Populates the SHA1 values for a single diff.
+               def populate_sha1!(diff, rev)
+                       diff.sha1 =
+                               case diff.action
+                               when 'D'
+                                       NULL_SHA1
+                               else
+                                       get_sha1(diff.path, rev)
+                               end
+
+                       diff.parent_sha1 =
+                               case diff.action
+                               when 'A'
+                                       NULL_SHA1
+                               else
+                                       # It is possible for Subversion to report deletion or modification of a file
+                                       # which didn't previously exist. This happens, for instance, when a directory
+                                       # is copied and some contents of that directory are deleted during
+                                       # the same commit. We don't care about these deletions and don't need to report them.
+                                       # The parent_sha1 computation might fail, so use the 'try' version.
+                                       try_get_sha1(diff.path, rev-1)
+                               end
+
+                       diff
+               end
+
+
+               # Use in cases where we're not sure that the file actually exists.
+               # Returns NULL_SHA1 if the file is not found.
+               def try_get_sha1(path=nil, revision='HEAD')
+                       begin
+                               get_sha1(path, revision)
+                       rescue
+                               if $!.message =~ /svn: (File not found|.* is not a directory in filesystem)/
+                                       NULL_SHA1
+                               else
+                                       raise
+                               end
+                       end
+               end
+
+               # Use in cases where the file should actually exist.
+               # Raises an exception if the file is not found.
+               def get_sha1(path=nil, revision='HEAD')
+                       generate_sha1(cat(path, revision))
+               end
+
+               def generate_sha1(contents)
+                       contents.to_s == '' ? NULL_SHA1 : SHA1.sha1("blob #{contents.length}\0#{contents}").to_s
+               end
+       end
+end
diff --git a/lib/scm/adapters/svn/misc.rb b/lib/scm/adapters/svn/misc.rb
new file mode 100644 (file)
index 0000000..9af0d0a
--- /dev/null
@@ -0,0 +1,136 @@
+require 'open-uri'
+
+module Scm::Adapters
+       class SvnAdapter < AbstractAdapter
+
+               # Converts an URL of form file://local/path to simply /local/path.
+               def path
+                       case url
+                       when /^file:\/\/(.*)/
+                               $1
+                       when /^svn\+ssh:\/\/([^\/]+)(\/.+)/
+                               $2
+                       end
+               end
+
+               def hostname
+                       $1 if url =~ /^svn\+ssh:\/\/([^\/]+)(\/.+)/
+               end
+
+               # Does some simple searching through the server's directory tree for a
+               # good canditate for the trunk. Basically, we are looking for a trunk
+               # in order to avoid the heavy lifting of processing all the branches and tags.
+               #
+               # There are two simple rules to the search:
+               #  (1) If the current directory contains a subdirectory named 'trunk', go there.
+               #  (2) If the current directory is empty except for a single subdirectory, go there.
+               # Repeat until neither rule is satisfied.
+               #
+               # The url of this object will be updated with the selected location.
+               # The url will be unmodified if there is a problem connecting to the server.
+               def restrict_url_to_trunk
+                       list = ls
+                       return self.url unless list
+
+                       if list.include? 'trunk/'
+                               self.url = self.url + '/trunk'
+                               return restrict_url_to_trunk
+                       elsif list.size == 1 and list.first[-1..-1] == '/'
+                               self.url = self.url + '/' + list.first[0..-2]
+                               return restrict_url_to_trunk
+                       end
+                       self.url
+               end
+
+               # It appears that the default URI encoder does not encode some characters.
+               # This fixes it for us.
+               def self.uri_encode(uri)
+                       URI.encode(uri,/#{URI::UNSAFE}|[\[\]';\? ]/) # Add [ ] ' ; ? and space
+               end
+
+               def exist?
+                       # If it's a local directory, first just check that the dir exists
+                       return false if url =~ /^file:\/\/(.+)/ && !FileTest.exist?(File.join($1, 'db'))
+
+                       begin
+                               !!(max_revision)
+                       rescue
+                               logger.debug { $! }
+                               false
+                       end
+               end
+
+               def info(path=nil, revision='HEAD')
+                       if path || (revision != 'HEAD')
+                               run "svn info -r #{revision} #{opt_auth} '#{SvnAdapter.uri_encode(File.join(self.root, self.branch_name.to_s, path.to_s))}@#{revision}'"
+                       else
+                               # Cache the default info query for performance
+                               @info ||= run "svn info -r #{revision} #{opt_auth} '#{SvnAdapter.uri_encode(self.url)}@#{revision}'"
+                       end
+               end
+
+               def root
+                       $1 if self.info =~ /^Repository Root: (.+)$/
+               end
+
+               def uuid
+                       $1 if self.info =~ /^Repository UUID: (.+)$/
+               end
+
+               def max_revision
+                       self.info =~ /^Revision: (\d+)$/ ? $1.to_i : nil
+               end
+
+               # Returns an array of file and directory names.
+               # Directory names will end with a trailing '/' character.
+               # Directories named 'CVSROOT' are always ignored and never returned.
+               # An empty array means that the call succeeded, but the remote directory is empty.
+               # A nil result means that the call failed and the remote server could not be queried.
+               def ls(path=nil, revision='HEAD')
+                       begin
+                               stdout = run "svn ls -r #{revision} #{opt_auth} '#{SvnAdapter.uri_encode(File.join(url, path.to_s))}@#{revision}'"
+                       rescue
+                               return nil
+                       end
+
+                       files = []
+                       stdout.each_line do |s|
+                               s.chomp!
+                               files << s if s.length > 0 and s != 'CVSROOT/'
+                       end
+                       files.sort
+               end
+
+               def node_kind(path=nil, revision='HEAD')
+                       $1 if self.info(path, revision) =~ /^Node Kind: (.+)$/
+               end
+
+               def is_directory?(path=nil, revision='HEAD')
+                       begin
+                               return node_kind(path, revision) == 'directory'
+                       rescue
+                               if $!.message =~ /svn: .* is not a directory in filesystem/
+                                       return false
+                               else
+                                       raise
+                               end
+                       end
+               end
+
+               def checkout(rev, dest_dir)
+                       FileUtils.mkdir_p(File.dirname(dest_dir)) unless FileTest.exist?(File.dirname(dest_dir))
+                       run "svn checkout -r #{rev.token} '#{SvnAdapter.uri_encode(self.url)}@#{rev.token}' '#{dest_dir}' --ignore-externals #{opt_auth}"
+               end
+
+               def export(dest_dir, commit_id = 'HEAD')
+                       FileUtils.mkdir_p(File.dirname(dest_dir)) unless FileTest.exist?(File.dirname(dest_dir))
+                       run "svn export --force -r #{commit_id} '#{SvnAdapter.uri_encode(File.join(root, branch_name.to_s))}' '#{dest_dir}'"
+               end
+
+               def opt_auth
+                       opt_password = ""
+                       opt_password = "--username='#{self.username}' --password='#{self.password}'" if self.username && self.username != ''
+                       " #{opt_password} --no-auth-cache "
+               end
+       end
+end
diff --git a/lib/scm/adapters/svn/pre-revprop-change b/lib/scm/adapters/svn/pre-revprop-change
new file mode 100755 (executable)
index 0000000..742e13d
--- /dev/null
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+exit 0
diff --git a/lib/scm/adapters/svn/pull.rb b/lib/scm/adapters/svn/pull.rb
new file mode 100644 (file)
index 0000000..16f4211
--- /dev/null
@@ -0,0 +1,76 @@
+module Scm::Adapters
+       class SvnAdapter < AbstractAdapter
+
+               def pull(from)
+                       logger.warn { "Pulling #{from.url}" }
+                       yield(0,1) if block_given? # Progress bar callback
+
+                       unless self.exist?
+                               svnadmin_create
+                               svnsync_init(from)
+                       end
+                       SvnAdapter.svnsync_sync(from, self)
+
+                       yield(1,1) if block_given? # Progress bar callback
+               end
+
+               # Initialize a new Subversion repository on disk.
+               #
+               # This method can work either locally (file://) or on another machine
+               # in the server cluster (svn+ssh://).
+               def svnadmin_create
+                       return if exist?
+                       if hostname
+                               svnadmin_create_remote
+                       else
+                               svnadmin_create_local
+                       end
+               end
+
+               # The local template for the Subversion hook
+               def pre_revprop_change_template
+                       File.join(File.dirname(__FILE__), 'pre-revprop-change')
+               end
+
+               # The destination location for the Subversion hook
+               def pre_revprop_change_path
+                       File.join(path, 'hooks', 'pre-revprop-change')
+               end
+
+               def svnadmin_create_local
+                       FileUtils.mkdir_p path
+                       FileUtils.rmdir path
+                       run "svnadmin create #{path}"
+                       FileUtils.cp pre_revprop_change_template, pre_revprop_change_path
+               end
+
+               def svnadmin_create_remote
+                       run "ssh #{hostname} 'mkdir -p #{path} && rmdir #{path} && svnadmin create #{path}'"
+                       run "scp #{pre_revprop_change_template} #{hostname}:#{pre_revprop_change_path}"
+               end
+
+               def svnsync_init(from)
+                       run "svnsync init #{from.opt_auth} '#{url}' #{from.root}"
+               end
+
+               def self.svnsync_sync(src, dest)
+                       # We might not be pulling from the same repository we pulled from last time.
+                       # We use svnsync to manage multiple backups on our server cluster, as well as to
+                       # pull from the well-known public repository.
+                       # Therefore we have to set the root and UUID of the svnsync every time.
+                       dest.propset('sync-from-url', src.root)
+                       dest.propset('sync-from-uuid', src.uuid)
+
+                       run "svnsync sync #{src.opt_auth} --non-interactive '#{SvnAdapter.uri_encode(dest.root)}'"
+               end
+
+               def propget(propname)
+                       run("svn propget #{opt_auth} --revprop -r 0 svn:#{propname} '#{SvnAdapter.uri_encode(root)}'").strip!
+               end
+
+               def propset(propname, value)
+                       run("svn propset #{opt_auth} --revprop -r 0 svn:#{propname} #{value} '#{SvnAdapter.uri_encode(root)}'")
+               end
+
+       end
+end
diff --git a/lib/scm/adapters/svn/push.rb b/lib/scm/adapters/svn/push.rb
new file mode 100644 (file)
index 0000000..1a8f99e
--- /dev/null
@@ -0,0 +1,15 @@
+module Scm::Adapters
+       class SvnAdapter < AbstractAdapter
+
+               def push(to)
+                       logger.warn { "Pushing #{to.url}" }
+
+                       unless to.exist?
+                               to.svnadmin_create
+                               to.svnsync_init(self)
+                       end
+                       SvnAdapter.svnsync_sync(self, to)
+               end
+
+       end
+end
diff --git a/lib/scm/adapters/svn/validation.rb b/lib/scm/adapters/svn/validation.rb
new file mode 100644 (file)
index 0000000..a8149fc
--- /dev/null
@@ -0,0 +1,60 @@
+module Scm::Adapters
+       class SvnAdapter < AbstractAdapter
+               def self.url_regex
+                       /^(file|http|https|svn):\/\/(\/)?[A-Za-z0-9_\-\.]+(:\d+)?(\/[A-Za-z0-9_\-\.\/\+%^~]*)?$/
+               end
+
+               def self.public_url_regex
+                       /^(http|https|svn):\/\/[A-Za-z0-9_\-\.]+(:\d+)?(\/[A-Za-z0-9_\-\.\/\+%^~]*)?$/
+               end
+
+               def normalize
+                       super
+                       @url = path_to_file_url(@url)
+                       @url = force_https_if_sourceforge(@url)
+                       self
+               end
+
+               # If the URL is a simple directory path, make sure it is prefixed by file://
+               def path_to_file_url(path)
+                       url =~ /:\/\// ? url : 'file://' + File.expand_path(path)
+               end
+
+               def force_https_if_sourceforge(url)
+                       # SourceForge requires https for svnsync
+                       url =~ /http(:\/\/.*svn\.sourceforge\.net.*)/ ? "https#{$1}" : url
+               end
+
+               def validate_server_connection
+                       return unless valid?
+                       begin
+                               if max_revision.nil?
+                                       @errors << [:failed, "The server did not respond to a 'svn info' command. Is the URL correct?"]
+                               elsif !self.url.starts_with?(root)
+                                       @errors << [:failed, "The URL did not match the Subversion root #{root}. Is the URL correct?"]
+                               elsif ls.nil?
+                                       @errors << [:failed, "The server did not respond to a 'svn ls' command. Is the URL correct?"]
+                               end
+                       rescue
+                               @errors << [:failed, "An error occured connecting to the server. Check the URL, username, and password."]
+                       end
+               end
+
+               # From the given URL, determine which part of it is the root and which part of it is the branch_name.
+               # The current branch_name is overwritten.
+               def recalc_branch_name
+                       @branch_name = @url ? @url[root.length..-1] : @branch_name
+               end
+
+               def guess_forge
+                       u = @url =~ /:\/\/(.*\.?svn\.)?([^\/^:]+)(:\d+)?\// ? $2 : nil
+                       case u
+                       when /(googlecode\.com$)/, /(tigris\.org$)/, /(sunsource\.net$)/, /(java\.net$)/,
+                               /(openoffice\.org$)/, /(netbeans\.org$)/, /(dev2dev\.bea\.com$)/
+                               $1
+                       else
+                               u
+                       end
+               end
+       end
+end
diff --git a/lib/scm/adapters/svn_adapter.rb b/lib/scm/adapters/svn_adapter.rb
new file mode 100644 (file)
index 0000000..0771e2c
--- /dev/null
@@ -0,0 +1,14 @@
+module Scm::Adapters
+       class SvnAdapter < AbstractAdapter
+               def english_name
+                       "Subversion"
+               end
+       end
+end
+
+require 'lib/scm/adapters/svn/validation'
+require 'lib/scm/adapters/svn/cat_file'
+require 'lib/scm/adapters/svn/commits'
+require 'lib/scm/adapters/svn/push'
+require 'lib/scm/adapters/svn/pull'
+require 'lib/scm/adapters/svn/misc'
diff --git a/lib/scm/commit.rb b/lib/scm/commit.rb
new file mode 100644 (file)
index 0000000..af11911
--- /dev/null
@@ -0,0 +1,46 @@
+module Scm
+       # A commit is a collection of diffs united by a single timestamp, author, and
+       # message.
+       #
+       # Ohloh's internal data model assumes that commits are immutable, and exist
+       # in a singly-linked list. That is, commits can be nicely numbered a la
+       # Subversion, and new commits are always added to the end of the list.
+       #
+       # This works for CVS and Subversion, but unfortunately, does not at all map
+       # to the DAG used by Git, which allows a commit to have multiple parents and
+       # children, and which allows new commits to appear during a pull which have
+       # timestamps older than previously known commits.
+       #
+       # This means that Ohloh's support for systems like Git is crude at best. For
+       # the near future, it is the job of the adapter to make the Git commit chain
+       # appear as much like a single array as possible.
+       #
+       class Commit
+               # This object supports the idea of distinct authors and committers, a la
+               # Git.  However, Ohloh will retain only one of them in its database. It
+               # prefers author, but will fall back to committer if no author is given.
+               attr_accessor :author_name, :author_email, :author_date, :committer_name, :committer_email, :committer_date
+
+               attr_accessor :message
+
+               attr_accessor :diffs
+
+               # The token is used to uniquely identify a commit, and can be any type of
+               # adapter-specific data.
+               #
+               # For Subversion, the token is the revision number.
+               # For Git, the token is the commit SHA1 hash.
+               # For CVS, which does not support atomic commits with unique IDs, we use
+               # the approximate timestamp of the change.
+               attr_accessor :token
+
+               # Hack. To optimize CVS updates, we will store the names of all the
+               # directories that require updating during this commit. Ohloh itself never
+               # actually sees this.
+               attr_accessor :directories
+
+               def initialize(params={})
+                       params.each { |k,v| send(k.to_s + '=', v) if respond_to?(k.to_s + '=') }
+               end
+       end
+end
diff --git a/lib/scm/diff.rb b/lib/scm/diff.rb
new file mode 100644 (file)
index 0000000..82db21a
--- /dev/null
@@ -0,0 +1,43 @@
+module Scm
+       # A +Diff+ represents a change to a single file. It can represent the addition or
+       # deletion of a file, or it can represent a modification of the file contents.
+       # 
+       # Ohloh does not track filename changes. If a file is renamed, Ohloh treats this
+       # as the deletion of one file and the creation of another.
+       # 
+       # Ohloh does not track directories, only the files within directories.
+       #
+       # Don't confuse our use of the word "Diff" with a patch file or the output of the
+       # console tool 'diff'. This object doesn't have anything to do the actual contents
+       # of the file; it's better to think of this object as representing a single line
+       # item from a source control log.
+       class Diff
+               # The filename of the changed file, relative to the root of the repository.
+               attr_accessor :path
+
+               # An action code describing the type of change made to the file.
+               # Action codes are copied directly from the Git standard.
+               # The action code can be...
+               #   'A' added
+               #   'M' modified
+               #   'D' deleted
+               attr_accessor :action
+               
+               # The SHA1 hash of the file contents both before and after the change.
+               # These must be computed using the same method as Git.
+               attr_accessor :parent_sha1, :sha1
+
+               def initialize(params={})
+                       params.each { |k,v| send(k.to_s + '=', v) if respond_to?(k.to_s + '=') }
+               end
+
+               # eql?() and hash() are implemented so that [].uniq() will work on an array of Diffs.
+               def eql?(a)
+                       @action.eql?(a.action) && @path.eql?(a.path) && @sha1.eql?(a.sha1) && @parent_sha1.eql?(a.parent_sha1)
+               end
+
+               def hash
+                       "#{action}|#{path}|#{sha1}|#{parent_sha1}".hash
+               end
+       end
+end
diff --git a/lib/scm/parsers/array_writer.rb b/lib/scm/parsers/array_writer.rb
new file mode 100644 (file)
index 0000000..5371602
--- /dev/null
@@ -0,0 +1,19 @@
+module Scm::Parsers
+       class ArrayWriter
+
+               attr_accessor :buffer
+               def initialize(buffer=[])
+                       @buffer = buffer
+               end
+
+               def write_preamble(opts = {})
+               end
+
+               def write_commit(commit)
+                       @buffer << commit
+               end
+
+               def write_postamble
+               end
+       end
+end
diff --git a/lib/scm/parsers/branch_number.rb b/lib/scm/parsers/branch_number.rb
new file mode 100644 (file)
index 0000000..6b235c7
--- /dev/null
@@ -0,0 +1,60 @@
+module Scm::Parsers
+       class BranchNumber
+               def initialize(s)
+                       @a = s.split('.').collect { |i| i.to_i }
+                       # Accomodate CVS magic branch numbers by swapping the magic zero
+                       # That is, 1.1.0.2 => 1.1.2.0
+                       if @a.size > 2 and @a[-2]==0
+                               @a[-1],@a[-2] = @a[-2],@a[-1]
+                       end
+               end
+
+               def to_s
+                       @a.join('.')
+               end
+
+               def to_a
+                       @a
+               end
+
+               # Returns true if <branch_number> is an ancestor of this object.
+               # Also returns true if <branch_number> is the same as this object.
+               def inherits_from?(branch_number)
+                       b = branch_number.to_a
+
+                       return false if b.size > @a.size
+
+                       if b.size == 2
+                               return false if b[0] > @a[0]
+                               return false if b[0] == @a[0] and b[1] > @a[1]
+                       else
+                               0.upto(b.size-2) do |i|
+                                       return false if b[i] != @a[i]
+                               end
+                               return false if b[-1] > @a[b.size-1]
+                       end
+
+                       true
+               end
+
+               # Returns true if <branch_number> is an ancestor of this object,
+               # or if this object follows <branch_number> on the same line.
+               def on_same_line?(branch_number)
+                       b = branch_number.to_a
+
+                       if b.size > @a.size
+                               # b has been branched more times than this object.
+                               return false
+                       elsif b.size == @a.size
+                               # b and a have the same number of branch events.
+                               # If either one inherits from the other then they
+                               # are on the same line.
+                               return (inherits_from?(branch_number) or branch_number.inherits_from?(self))
+                       elsif b.size < @a.size
+                               # b has not been branched as often as this object.
+                               # That's OK if b is an ancestor of this object.
+                               return inherits_from?(branch_number)
+                       end
+               end
+       end
+end
diff --git a/lib/scm/parsers/cvs_parser.rb b/lib/scm/parsers/cvs_parser.rb
new file mode 100644 (file)
index 0000000..17fd427
--- /dev/null
@@ -0,0 +1,182 @@
+module Scm::Parsers
+       class CvsParser < Parser
+
+               def self.scm
+                       'cvs'
+               end
+
+               # Given an IO to a CVS rlog, returns a list of
+               # commits (developer/date/message).
+               # If a branch_name is specified, only commits along that branch will be returned,
+               # otherwise only commits along the head will be returned.
+               def self.internal_parse(io, opts)
+                       commits = {}
+                       branch_name = opts[:branch_name]
+                       branch_name = nil if branch_name == 'HEAD' or branch_name == ''
+
+                       read_files(io, branch_name) do |c|
+                               # As commits are yielded by the parser, we sort them into bins.
+                               #
+                               # The 'bins' are arrays of timestamps. We keep a separate array of
+                               # timestamps for each developer/message combination.
+                               #
+                               # If a commit lies near in time to another commit with the same
+                               # developer/message combination, then we merge them and store only
+                               # the later of the two timestamps.
+                               #
+                               # Typically, we end up with only a single timestamp for each developer/message
+                               # combination. However, if a developer repeatedly uses the same message
+                               # a number of separate times, we may end up with several timestamps for
+                               # that combination.
+
+                               key = c.committer_name + ":" + c.message
+                               if commits.has_key? key
+                                       # We have already seen this developer/message combination
+                                       match = false
+                                       commits[key].each_index do |i|
+                                               # Does the new commit lie near in time to a known one in our list?
+                                               if near?(commits[key][i].committer_date, c.committer_date)
+                                                       match = true
+                                                       # Yes. Choose the most recent timestamp, and add the new
+                                                       # directory name to our list.
+                                                       if commits[key][i].committer_date < c.committer_date
+                                                               commits[key][i].committer_date = c.committer_date
+                                                               commits[key][i].token = c.token
+                                                       end
+                                                       commits[key][i].directories << c.directories[0] unless commits[key][i].directories.include? c.directories[0]
+                                                       break
+                                               end
+                                       end
+                                       # This commit lies a long time away from any one we know.
+                                       # Add it to the list as a new checkin event.
+                                       commits[key] << c unless match
+                               else
+                                       # We have never seen this developer/message combination. Start a new list.
+                                       commits[key] = [c]
+                               end
+                       end
+                       # Pull all of the commits out of the hash and return them as a single sorted list.
+                       result = commits.values.flatten.sort! { |a,b| a.committer_date <=> b.committer_date }
+
+                       # If we have two commits with identical timestamps, arbitrarily choose the first
+                       (result.size-1).downto(1) do |i|
+                               result.delete_at(i) if result[i].committer_date == result[i-1].committer_date
+                       end
+
+                       if block_given?
+                               result.each { |r| yield r }
+                       end
+               end
+
+               # Accepts two dates and determines wether they are close enough together to consider simultaneous.
+               def self.near?(a,b)
+                       ((a-b).abs < 30*60) # Less than 30 minutes counts as 'near'
+               end
+
+               def self.read_files(io, branch_name, &block)
+                       io.each_line do |l|
+                               if l =~ /^RCS file: (.*),.$/
+                                       filename = $1
+                                       read_file(io, branch_name, filename, &block)
+                               end
+                       end
+               end
+
+               def self.read_file(io, branch_name, filename, &block)
+                       branch_number = nil
+                       io.each_line do |l|
+                               if l =~ /^head: ([\d\.]+)/
+                                       unless branch_name
+                                               branch_number = BranchNumber.new($1)
+                                       end
+                               elsif l =~ /^symbolic names:/
+                                       if branch_name
+                                               branch_number = read_symbolic_names(io, branch_name)
+                                       end
+                               elsif l =~ /^----------------------------/
+                                       read_commits(io, branch_number, filename, &block)
+                                       return
+                               end
+                       end
+               end
+
+               def self.read_symbolic_names(io, branch_name)
+                       branch_number = nil
+                       io.each_line do |l|
+                               if l =~ /^\s+([^:]+): ([\d\.]+)/
+                                       branch_number = BranchNumber.new($2) if $1 == branch_name
+                               else
+                                       return branch_number
+                               end
+                       end
+               end
+
+               def self.read_commits(io, branch_number, filename, &block)
+                       should_yield = nil
+                       io.each_line do |l|
+                               if l =~ /^\s$/
+                                       return
+                               elsif l =~ /^revision ([\d.]+)/
+                                       commit_number = $1
+                                       if branch_number
+                                               should_yield = branch_number.on_same_line?(BranchNumber.new(commit_number))
+                                       else
+                                               should_yield = false
+                                       end
+                                       read_commit(io, filename, commit_number, should_yield, &block)
+                               end
+                       end
+               end
+
+               def self.read_commit(io, filename, commit_number, should_yield)
+                       io.each_line do |l|
+                               if l =~ /^date: (.*);  author: ([^;]+);  state: (\w+);/
+                                       committer_date = $1
+                                       committer_name = $2
+                                       state = $3
+                                       # CVS creates a "phantom" dead file at 1.1 on the head if a file
+                                       #   is created on a branch. Ignore this file.
+                                       should_yield = false if commit_number == '1.1' and state == 'dead'
+                                       message = read_message(io)
+                                       if should_yield
+                                               commit = Scm::Commit.new
+                                               commit.token = committer_date[0..18]
+                                               commit.committer_date = Time.parse(committer_date[0..18] + ' +0000').utc
+                                               commit.committer_name = committer_name
+                                               commit.message = message
+                                               commit.directories = [File.dirname(filename).intern]
+                                               yield commit
+                                       end
+                                       return
+                               end
+                       end
+               end
+
+               def self.read_message(io)
+                       message = ''
+                       first_line = true
+                       io.each_line do |l|
+                               if l =~ /^branches: / and first_line # the first line might be 'branches:', skip it.
+                                       # do nothing
+                               else
+                                       l.chomp!
+                                       if l == '=============================================================================' or
+                                               l == '----------------------------'
+                                               return message
+                                       end
+                                       message += "\n" if message.length != 0
+                                       message += l
+                               end
+                               first_line = false
+                       end
+                       message
+               end
+       end
+
+       if $0 == __FILE__
+               require File.dirname(__FILE__) + '/../../config/environment'
+               LogParser.parse(STDIN).each do |r|
+                       r.pretty_print(STDOUT)
+               end
+       end
+end
diff --git a/lib/scm/parsers/human_writer.rb b/lib/scm/parsers/human_writer.rb
new file mode 100644 (file)
index 0000000..992eb23
--- /dev/null
@@ -0,0 +1,55 @@
+module Scm::Parsers
+       class HumanWriter
+               # Note that we use << instead of write() or puts() in this writer because
+               # the << operator works on both File and String objects.
+
+               attr_accessor :buffer
+               def initialize(buffer='')
+                       @buffer = buffer
+               end
+
+               def write_preamble(opts = {})
+               end
+
+               def write_commit(commit)
+                       @buffer << "-" * 72 + "\n"
+
+                       @buffer << commit.token.to_s
+                       @buffer << ' | Committer: '
+                       @buffer << commit.committer_name.to_s.ljust(24)
+                       @buffer << ' | '
+                       @buffer << commit.committer_date.to_s
+                       @buffer << "\n"
+
+                       if commit.author_name
+                               @buffer << ' ' * commit.token.to_s.length
+                               @buffer << ' | Author:    '
+                               @buffer << commit.author_name.to_s.ljust(24)
+                               @buffer << ' | '
+                               @buffer << commit.author_date.to_s
+                               @buffer << "\n"
+                       end
+
+                       if commit.diffs && commit.diffs.any?
+                               commit.diffs.each { |diff| write_diff(diff) }
+                       end
+
+                       if commit.directories && commit.directories.any?
+                               commit.directories.each do |d|
+                                       @buffer << "\t#{d}\n"
+                               end
+                       end
+
+                       if commit.message
+                               @buffer << "\n#{commit.message}\n"
+                       end
+               end
+
+               def write_diff(diff)
+                       @buffer << "\t#{diff.action} #{diff.path}\n"
+               end
+
+               def write_postamble
+               end
+       end
+end
diff --git a/lib/scm/parsers/parser.rb b/lib/scm/parsers/parser.rb
new file mode 100644 (file)
index 0000000..a321db8
--- /dev/null
@@ -0,0 +1,34 @@
+require 'stringio'
+
+module Scm::Parsers
+       class Parser
+               def self.parse(buffer='', opts={})
+                       buffer = StringIO.new(buffer) if buffer.is_a? String
+                       opts = opts.merge(:scm => self.scm)
+
+                       writer = (opts[:writer] || ArrayWriter.new) unless block_given?
+                       writer.write_preamble(opts) if writer
+
+                       internal_parse(buffer, opts) do |commit|
+                               if commit
+                                       yield commit if block_given?
+                                       writer.write_commit(commit) if writer
+                               end
+                       end
+
+                       if writer
+                               writer.write_postamble
+                               writer.buffer
+                       else
+                               nil
+                       end
+               end
+
+               def self.internal_parse
+               end
+
+               def self.scm
+                       nil
+               end
+       end
+end
diff --git a/lib/scm/parsers/svn_parser.rb b/lib/scm/parsers/svn_parser.rb
new file mode 100644 (file)
index 0000000..6672f8a
--- /dev/null
@@ -0,0 +1,68 @@
+require 'parsedate'
+
+module Scm::Parsers
+       class SvnParser < Parser
+               def self.scm
+                       'svn'
+               end
+
+               def self.internal_parse(buffer, opts)
+                       e = nil
+                       state = :data
+
+                       buffer.each_line do |l|
+                               l.chomp!
+                               next_state = state
+                               if state == :data
+                                       if l =~ /^r(\d+) \| (.*) \| (\d+-\d+-\d+ .*) \(.*\) \| .*/
+                                               e = Scm::Commit.new
+                                               e.token = $1.to_i
+                                               e.committer_name = $2
+                                               e.committer_date = Time.local(*ParseDate.parsedate($3)).utc
+                                       elsif l == "Changed paths:"
+                                               next_state = :diffs
+                                       elsif l.empty?
+                                               next_state = :comment
+                                       end
+
+                               elsif state == :diffs
+                                       if l =~ /^   (\w) ([^\(\)]+)( \(from .+:\d+\))?$/
+                                               e.diffs ||= []
+                                               e.diffs << Scm::Diff.new(:action => $1, :path => $2)
+                                       else
+                                               next_state = :comment
+                                       end
+
+                               # The :log_embedded_within_comment state is special-case code to fix the Wireshark project, which
+                               # includes fragments of svn logs within its comment blocks, which really confuses the parser.
+                               # I am not sure whether only Wireshark does this, but I suspect it happens because there is a tool
+                               # out there somethere to generate these embedded log comments.
+                               elsif state == :log_embedded_within_comment
+                                       e.message << "\n"
+                                       e.message << l
+                                       next_state = :comment if l =~ /============================ .* log end =+/
+
+                               elsif state == :comment
+                                       if l =~ /------------------------------------------------------------------------/
+                                               yield e if block_given?
+                                               e = nil
+                                               next_state = :data
+                                       elsif l =~ /============================ .* log start =+/
+                                               e.message << "\n"
+                                               e.message << l
+                                               next_state = :log_embedded_within_comment
+                                       else
+                                               if e.message
+                                                       e.message << "\n"
+                                                       e.message << l
+                                               else
+                                                       e.message = l
+                                               end
+                                       end
+                               end
+                               state = next_state
+                       end
+                       yield e if block_given?
+               end
+       end
+end
diff --git a/lib/scm/parsers/svn_xml_parser.rb b/lib/scm/parsers/svn_xml_parser.rb
new file mode 100644 (file)
index 0000000..f8b1783
--- /dev/null
@@ -0,0 +1,60 @@
+require 'parsedate'
+require 'rexml/document'
+require 'rexml/streamlistener'
+
+module Scm::Parsers
+       class SubversionListener
+               include REXML::StreamListener
+
+               attr_accessor :callback
+               def initialize(callback)
+                       @callback = callback
+               end
+
+               attr_accessor :text, :commit, :diff
+
+               def tag_start(name, attrs)
+                       case name
+                       when 'logentry'
+                               @commit = Scm::Commit.new
+                               @commit.token = attrs['revision'].to_i
+                       when 'path'
+                               @diff = Scm::Diff.new(:action => attrs['action'])
+                       end
+               end
+
+               def tag_end(name)
+                       case name
+                       when 'logentry'
+                               @callback.call(@commit)
+                       when 'author'
+                               @commit.committer_name = @text
+                       when 'date'
+                               @commit.committer_date = Time.utc(*ParseDate.parsedate(@text))
+                       when 'path'
+                               @diff.path = @text
+                               @commit.diffs ||= []
+                               @commit.diffs << @diff
+                       when 'msg'
+                               @commit.message = @text
+                       end
+               end
+
+               def text(text)
+                       @text = text
+               end
+       end
+
+       class SvnXmlParser < Parser
+               def self.internal_parse(buffer, opts)
+                       begin
+                               REXML::Document.parse_stream(buffer, SubversionListener.new(Proc.new { |c| yield c if block_given? }))
+                       rescue EOFError
+                       end
+               end
+
+               def self.scm
+                       'svn'
+               end
+       end
+end
diff --git a/lib/scm/parsers/xml_writer.rb b/lib/scm/parsers/xml_writer.rb
new file mode 100644 (file)
index 0000000..4442e0e
--- /dev/null
@@ -0,0 +1,62 @@
+module Scm::Parsers
+       class XmlWriter
+               # Note that we use << instead of write() or puts() in this writer because
+               # the << operator works on both File and String objects.
+
+               attr_accessor :buffer
+               def initialize(buffer='')
+                       @buffer = buffer
+               end
+
+               def write_preamble(opts = {})
+                       @buffer << "<?xml version=\"1.0\"?>\n"
+                       @buffer << "<ohloh_log"
+                       opts.each_key do |key|
+                               next if key.to_s == 'writer'
+                               @buffer << " #{key}=\"#{opts[key]}\""
+                       end
+                       @buffer << ">\n"
+               end
+
+               def write_commit(commit)
+                       @buffer << "    <commit token=\"#{commit.token}\">\n"
+
+                       if commit.author_name
+                               @buffer << "        <author name=\"#{commit.author_name}\" date=\"#{xml_time(commit.author_date)}\" />\n"
+                       end
+
+                       if commit.committer_name
+                               @buffer << "        <committer name=\"#{commit.committer_name}\" date=\"#{xml_time(commit.committer_date)}\" />\n"
+                       end
+
+                       if commit.message
+                               @buffer << "        <message>#{commit.message}</message>\n"
+                       end
+
+                       if commit.diffs && commit.diffs.any?
+                               @buffer << "        <diffs>\n"
+                               commit.diffs.each { |diff| write_diff(diff) }
+                               @buffer << "        </diffs>\n"
+                       end
+
+                       @buffer << "    </commit>\n"
+               end
+
+               def write_diff(diff)
+                       @buffer << "            <diff action=\"#{diff.action}\" path=\"#{diff.path}\" />\n"
+               end
+
+               def write_postamble
+                       @buffer << "</ohloh_log>\n"
+               end
+
+               def xml_time(time)
+                       case time
+                       when Time
+                               time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
+                       when String
+                               time
+                       end
+               end
+       end
+end
diff --git a/lib/scm/scratch_dir.rb b/lib/scm/scratch_dir.rb
new file mode 100644 (file)
index 0000000..4eaa06a
--- /dev/null
@@ -0,0 +1,59 @@
+require 'fileutils'
+
+# A utility class to manage the creation and automatic cleanup of temporary directories.
+module Scm
+  class ScratchDir
+    attr_reader :path
+
+    # Creates a uniquely named directory in the system tmp directory.
+    #
+    # If a block is passed to the constructor, the path to the created directory
+    # will be yielded to the block. The directory will then be deleted
+    # when this block returns.
+    #
+    # Sample usage:
+    #
+    #   ScratchDir.new do |path|
+    #     # Do some work in the new directory
+    #     File.new( path + '/foobaz', 'w' ) do
+    #       # ...
+    #     end
+    #   end # Scratch directory is deleted here
+    #
+    def initialize
+      @path = `mktemp -d /tmp/ohloh_scm_XXXXXX`.strip
+      if block_given?
+        begin
+          return yield(@path)
+        ensure
+          FileUtils.rm_rf(@path)
+        end
+      end
+    end
+  end
+
+  if $0 == __FILE__
+    path = nil
+
+    ScratchDir.new do |d|
+      path = d
+      filename = File.join(d,"test")
+      File.open(filename, "w") do |io|
+        io.write "test"
+      end
+    end
+    raise RuntimeError.new("Directory wasn't cleaned up") if FileTest.directory?(path)
+
+    begin
+      ScratchDir.new do |d|
+        path = d
+        STDOUT.puts "Created scratch direcory #{d}"
+        raise RuntimeError.new("This error should not prevent cleanup")
+      end
+    rescue
+    end
+    raise RuntimeError.new("Directory wasn't cleaned up") if FileTest.directory?(path)
+
+    STDOUT.puts "Tests passed."
+  end
+end
diff --git a/lib/scm/systemu.rb b/lib/scm/systemu.rb
new file mode 100644 (file)
index 0000000..27c0323
--- /dev/null
@@ -0,0 +1,299 @@
+# vim: ts=2:sw=2:sts=2:et:fdm=marker
+require 'tmpdir'
+require 'socket'
+require 'fileutils'
+require 'rbconfig'
+require 'thread'
+require 'yaml'
+
+class Object
+  def systemu(*a, &b) SystemUniversal.new(*a, &b).systemu end
+end
+
+class SystemUniversal
+#
+# constants
+#
+  SystemUniversal::VERSION = '1.2.0' unless defined?  SystemUniversal::VERSION
+  def version() SystemUniversal::VERSION end
+#
+# class methods
+#
+
+  @host = Socket.gethostname
+  @ppid = Process.ppid
+  @pid = Process.pid
+  @turd = ENV['SYSTEMU_TURD']
+
+  c = ::Config::CONFIG
+  ruby = File.join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
+  @ruby = if system('%s -e 42' % ruby)
+    ruby
+  else
+    system('%s -e 42' % 'ruby') ? 'ruby' : warn('no ruby in PATH/CONFIG')
+  end
+
+  class << self
+    %w( host ppid pid ruby turd ).each{|a| attr_accessor a}
+  end
+
+#
+# instance methods
+#
+
+  def initialize argv, opts = {}, &block
+    getopt = getopts opts
+
+    @argv = argv
+    @block = block
+
+    @stdin = getopt[ ['stdin', 'in', '0', 0] ]
+    @stdout = getopt[ ['stdout', 'out', '1', 1] ]
+    @stderr = getopt[ ['stderr', 'err', '2', 2] ]
+    @env = getopt[ 'env' ]
+    @cwd = getopt[ 'cwd' ]
+
+    @host = getopt[ 'host', self.class.host ]
+    @ppid = getopt[ 'ppid', self.class.ppid ]
+    @pid = getopt[ 'pid', self.class.pid ]
+    @ruby = getopt[ 'ruby', self.class.ruby ]
+  end
+
+  def systemu
+    tmpdir do |tmp|
+      c = child_setup tmp
+      status = nil
+
+      begin
+        thread = nil
+
+        quietly{
+          IO.popen "#{ @ruby } #{ c['program'] }", 'r+' do |pipe|
+            line = pipe.gets
+            case line
+              when %r/^pid: \d+$/
+                cid = Integer line[%r/\d+/] 
+              else
+                begin
+                  buf = pipe.read
+                  buf = "#{ line }#{ buf }"
+                  e = Marshal.load buf
+                  raise unless Exception === e
+                  raise e
+                rescue
+                  raise "wtf?\n#{ buf }\n"
+                end
+            end
+            thread = new_thread cid, @block if @block
+            pipe.read rescue nil
+          end
+        }
+        status = $?
+      ensure
+        if thread
+          begin
+            class << status
+              attr 'thread'
+            end
+            status.instance_eval{ @thread = thread }
+          rescue
+            42
+          end
+        end
+      end
+
+      if @stdout or @stderr
+        open(c['stdout']){|f| relay f => @stdout} if @stdout
+        open(c['stderr']){|f| relay f => @stderr} if @stderr
+        status
+      else
+        [status, IO.read(c['stdout']), IO.read(c['stderr'])]
+      end
+    end
+  end
+
+  def new_thread cid, block 
+    q = Queue.new
+    Thread.new(cid) do |cid| 
+      current = Thread.current 
+      current.abort_on_exception = true
+      q.push current 
+      block.call cid
+    end
+    q.pop
+  end
+
+  def child_setup tmp
+    stdin = File.expand_path(File.join(tmp, 'stdin'))
+    stdout = File.expand_path(File.join(tmp, 'stdout'))
+    stderr = File.expand_path(File.join(tmp, 'stderr'))
+    program = File.expand_path(File.join(tmp, 'program'))
+    config = File.expand_path(File.join(tmp, 'config'))
+
+    if @stdin
+      open(stdin, 'w'){|f| relay @stdin => f}
+    else
+      FileUtils.touch stdin
+    end
+    FileUtils.touch stdout
+    FileUtils.touch stderr
+
+    c = {}
+    c['argv'] = @argv
+    c['env'] = @env
+    c['cwd'] = @cwd
+    c['stdin'] = stdin 
+    c['stdout'] = stdout 
+    c['stderr'] = stderr 
+    c['program'] = program 
+    open(config, 'w'){|f| YAML.dump c, f}
+
+    open(program, 'w'){|f| f.write child_program(config)}
+
+    c
+  end
+
+  def quietly
+    v = $VERBOSE
+    $VERBOSE = nil
+    yield
+  ensure
+    $VERBOSE = v
+  end
+
+  def child_program config
+    <<-program
+      PIPE = STDOUT.dup
+      begin
+        require 'yaml'
+
+        config = YAML.load(IO.read('#{ config }'))
+
+        argv = config['argv']
+        env = config['env']
+        cwd = config['cwd']
+        stdin = config['stdin']
+        stdout = config['stdout']
+        stderr = config['stderr']
+
+        Dir.chdir cwd if cwd
+        env.each{|k,v| ENV[k.to_s] = v.to_s} if env
+
+        STDIN.reopen stdin
+        STDOUT.reopen stdout
+        STDERR.reopen stderr
+
+        PIPE.puts "pid: \#{ Process.pid }"
+        PIPE.flush                        ### the process is ready yo! 
+        PIPE.close
+
+        exec *argv
+      rescue Exception => e
+        PIPE.write Marshal.dump(e) rescue nil
+        exit 42
+      end
+    program
+  end
+
+  def relay srcdst
+    src, dst, ignored = srcdst.to_a.first
+    if src.respond_to? 'read'
+      while((buf = src.read(8192))); dst << buf; end
+    else
+      src.each{|buf| dst << buf}
+    end
+  end
+
+  def tmpdir d = Dir.tmpdir, max = 42, &b
+    i = -1 and loop{
+      i += 1
+
+      tmp = File.join d, "systemu_#{ @host }_#{ @ppid }_#{ @pid }_#{ rand }_#{ i += 1 }"
+
+      begin
+        Dir.mkdir tmp 
+      rescue Errno::EEXIST
+        raise if i >= max 
+        next
+      end
+
+      break(
+        if b
+          begin
+            b.call tmp
+          ensure
+            FileUtils.rm_rf tmp unless SystemU.turd 
+          end
+        else
+          tmp
+        end
+      )
+    }
+  end
+
+  def getopts opts = {}
+    lambda do |*args|
+      keys, default, ignored = args
+      catch('opt') do
+        [keys].flatten.each do |key|
+          [key, key.to_s, key.to_s.intern].each do |key|
+            throw 'opt', opts[key] if opts.has_key?(key)
+          end
+        end
+        default
+      end
+    end
+  end
+end
+
+SystemU = SystemUniversal unless defined? SystemU
+
+
+
+
+
+
+
+
+
+
+
+
+
+if $0 == __FILE__
+#
+# date
+#
+  date = %q( ruby -e"  t = Time.now; STDOUT.puts t; STDERR.puts t  " )
+
+  status, stdout, stderr = systemu date
+  p [status, stdout, stderr]
+
+  status = systemu date, 1=>(stdout = '')
+  p [status, stdout]
+
+  status = systemu date, 2=>(stderr = '')
+  p [status, stderr]
+#
+# sleep
+#
+  sleep = %q( ruby -e"  p(sleep(1))  " )
+  status, stdout, stderr = systemu sleep 
+  p [status, stdout, stderr]
+
+  sleep = %q( ruby -e"  p(sleep(42))  " )
+  status, stdout, stderr = systemu(sleep){|cid| Process.kill 9, cid}
+  p [status, stdout, stderr]
+#
+# env 
+#
+  env = %q( ruby -e"  p ENV['A']  " )
+  status, stdout, stderr = systemu env, :env => {'A' => 42} 
+  p [status, stdout, stderr]
+#
+# cwd 
+#
+  env = %q( ruby -e"  p Dir.pwd  " )
+  status, stdout, stderr = systemu env, :cwd => Dir.tmpdir
+  p [status, stdout, stderr]
+end
diff --git a/log/.gitignore b/log/.gitignore
new file mode 100644 (file)
index 0000000..397b4a7
--- /dev/null
@@ -0,0 +1 @@
+*.log
diff --git a/test/data/basic.ohlog b/test/data/basic.ohlog
new file mode 100644 (file)
index 0000000..95a281e
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<ohloh_log scm="cvs">
+    <commit token="2005/07/25 17:09:59">
+        <committer name="pizzandre" date="2005-07-25T17:09:59Z" />
+        <message>*** empty log message ***</message>
+    </commit>
+    <commit token="2005/07/25 17:11:06">
+        <committer name="pizzandre" date="2005-07-25T17:11:06Z" />
+        <message>Addin UNL file with using example-</message>
+    </commit>
+</ohloh_log>
diff --git a/test/data/basic.rlog b/test/data/basic.rlog
new file mode 100644 (file)
index 0000000..8e93905
--- /dev/null
@@ -0,0 +1,30 @@
+
+RCS file: /shared/data/ccvs/repository/intelliglue/UML/intelliglue.zuml,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 17:11:06;  author: pizzandre;  state: Exp;
+Addin UNL file with using example-
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/articles/IoC_Regras.pdf,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 17:09:59;  author: pizzandre;  state: Exp;
+*** empty log message ***
+=============================================================================
diff --git a/test/data/file_created_on_branch.rlog b/test/data/file_created_on_branch.rlog
new file mode 100644 (file)
index 0000000..af28a32
--- /dev/null
@@ -0,0 +1,34 @@
+
+RCS file: /Users/robin/cvs_repo//simple/new_file.rb,v
+head: 1.2
+branch:
+locks: strict
+access list:
+symbolic names:
+       another_branch: 1.2.0.2
+       my_branch: 1.1.0.2
+keyword substitution: kv
+total revisions: 5;    selected revisions: 5
+description:
+----------------------------
+revision 1.2
+date: 2006/06/29 18:14:47;  author: robin;  state: Exp;  lines: +1 -0;  kopt: kv;  commitid: c3944a41896430e;  mergepoint: 1.1.2.1;  filename: new_file.rb;
+merged new_file.rb from branch onto the HEAD
+----------------------------
+revision 1.1
+date: 2006/06/29 18:05:27;  author: robin;  state: dead;  kopt: kv;  commitid: bd144a41667e765;  filename: new_file.rb;
+branches:  1.1.2;
+file new_file.rb was initially added on branch my_branch.
+----------------------------
+revision 1.1.2.3
+date: 2006/06/29 18:40:38;  author: robin;  state: dead;  lines: +0 -0;  kopt: kv;  commitid: d4e44a41ea6477e;  filename: new_file.rb;
+removed new_file.rb from the branch only
+----------------------------
+revision 1.1.2.2
+date: 2006/06/29 18:17:49;  author: robin;  state: Exp;  lines: +1 -0;  kopt: kv;  commitid: c7744a4194cefc8;  filename: new_file.rb;
+modifed new_file.rb on the branch only
+----------------------------
+revision 1.1.2.1
+date: 2006/06/29 18:05:27;  author: robin;  state: Exp;  lines: +1 -0;  kopt: kv;  commitid: bd144a41667e765;  filename: new_file.rb;
+added new_file.rb on the branch
+=============================================================================
diff --git a/test/data/helloworld.log b/test/data/helloworld.log
new file mode 100644 (file)
index 0000000..dbfe448
--- /dev/null
@@ -0,0 +1,32 @@
+__BEGIN_COMMIT__
+Commit: 089c527c61235bd0793c49109b5bd34d439848c6
+Author: robin
+Date: Sun, 11 Jun 2006 11:28:00 +0000
+__BEGIN_COMMENT__
+Initial Checkin
+<unknown>
+__END_COMMENT__
+:000000 100644 0000000000000000000000000000000000000000 482d295e4e4f85cdde2e7d8ae7d8ce257192b9e8 A     .gitignore
+:000000 100644 0000000000000000000000000000000000000000 4c734ad53b272c9b3d719f214372ac497ff6c068 A     helloworld.c
+:000000 100644 0000000000000000000000000000000000000000 56a6051ca2b02b04ef92d5150c9ef600403cb1de A     ohloh_token
+__BEGIN_COMMIT__
+Commit: b6e9220c3cabe53a4ed7f32952aeaeb8a822603d
+Author: robin
+Date: Sun, 11 Jun 2006 11:32:13 -0700
+__BEGIN_COMMENT__
+added makefile
+<unknown>
+__END_COMMENT__
+:000000 100644 0000000000000000000000000000000000000000 af2dfd5070b01a19b672861e595de98c101c49cc A     makefile
+:100644 100644 56a6051ca2b02b04ef92d5150c9ef600403cb1de d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 M     ohloh_token
+__BEGIN_COMMIT__
+Commit: 2e9366dd7a786fdb35f211fff1c8ea05c51968b1
+Author: robin
+Date: Sun, 11 Jun 2006 11:34:17 +0200
+__BEGIN_COMMENT__
+added some documentation and licensing info
+<unknown>
+__END_COMMENT__
+:000000 100644 0000000000000000000000000000000000000000 f0547ce063095e66be74618bc410989df226d2d2 A     README
+:100644 100644 4c734ad53b272c9b3d719f214372ac497ff6c068 f6adcae4447809b651c787c078d255b2b4e963c5 M     helloworld.c
+:100644 100644 d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 e440e5c842586965a7fb77deda2eca68612b1f53 M     ohloh_token
diff --git a/test/data/intelliglue.rlog b/test/data/intelliglue.rlog
new file mode 100644 (file)
index 0000000..433563d
--- /dev/null
@@ -0,0 +1,1216 @@
+
+RCS file: /shared/data/ccvs/repository/intelliglue/UML/intelliglue.zuml,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 17:11:06;  author: pizzandre;  state: Exp;
+Addin UNL file with using example-
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/articles/IoC_Regras.pdf,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 17:09:59;  author: pizzandre;  state: Exp;
+*** empty log message ***
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/etc/Configuration.txt,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 20:29:27;  author: pizzandre;  state: Exp;
+Adding Configuration.txt-
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/antlr-2.7.2.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:21;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/drools-base-2.0-beta-17.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:21;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/drools-core-2.0-beta-17.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:22;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/drools-examples-2.0-beta-17.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:22;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/drools-groovy-2.0-beta-17.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:22;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/drools-io-2.0-beta-17.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:22;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/drools-java-2.0-beta-17.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:22;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/drools-jsr94-2.0-beta-17.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:22;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/drools-python-2.0-beta-17.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:23;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/drools-smf-2.0-beta-17.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:23;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/drools-smftest-2.0-beta-17.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:23;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/lib/drools/janino-2.0.5.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:14:23;  author: pizzandre;  state: Exp;
+Adding drools dependencies to rule engine
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/src/components/sequence/common/Sequence.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 20:36:09;  author: pizzandre;  state: Exp;
+Issue number:  1
+Obtained from: intelligent plugin
+Submitted by: andre piza
+Reviewed by:  andre piza
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/src/exceptions/PluginInstanciationException.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 20:36:11;  author: pizzandre;  state: Exp;
+Issue number:  1
+Obtained from: intelligent plugin
+Submitted by: andre piza
+Reviewed by:  andre piza
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/src/execution/Principal.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 20:36:13;  author: pizzandre;  state: Exp;
+Issue number:  1
+Obtained from: intelligent plugin
+Submitted by: andre piza
+Reviewed by:  andre piza
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/src/plugin/factory/PluginFactory.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 20:36:14;  author: pizzandre;  state: Exp;
+Issue number:  1
+Obtained from: intelligent plugin
+Submitted by: andre piza
+Reviewed by:  andre piza
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/src/plugin/factory/PluginFactorySetup.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 20:36:14;  author: pizzandre;  state: Exp;
+Issue number:  1
+Obtained from: intelligent plugin
+Submitted by: andre piza
+Reviewed by:  andre piza
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/src/plugin/factory/rules/integration/IntegrationRules.java.drl,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 20:36:16;  author: pizzandre;  state: Exp;
+Issue number:  1
+Obtained from: intelligent plugin
+Submitted by: andre piza
+Reviewed by:  andre piza
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/src/plugin/factory/rules/integration/ruleset/sequence.java.drl,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 20:36:17;  author: pizzandre;  state: Exp;
+Issue number:  1
+Obtained from: intelligent plugin
+Submitted by: andre piza
+Reviewed by:  andre piza
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/intelliglue_eclipse/src/plugin/handler/ComponentHandler.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/25 20:36:18;  author: pizzandre;  state: Exp;
+Issue number:  1
+Obtained from: intelligent plugin
+Submitted by: andre piza
+Reviewed by:  andre piza
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/index.html,v
+head: 1.9
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 9;    selected revisions: 9
+description:
+----------------------------
+revision 1.9
+date: 2005/07/26 20:39:16;  author: pizzandre;  state: Exp;  lines: +2 -2
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+Completing and fixing milestones texts
+----------------------------
+revision 1.8
+date: 2005/07/26 20:35:13;  author: pizzandre;  state: Exp;  lines: +2 -2
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+Adding current milestones-
+----------------------------
+revision 1.7
+date: 2005/07/26 20:30:41;  author: pizzandre;  state: Exp;  lines: +1 -1
+Issue number:pizzandre
+Obtained from:pizzandre
+Submitted by:pizzandre
+Reviewed by:pizzandre
+Adding link to release 1.0
+----------------------------
+revision 1.6
+date: 2005/07/26 20:16:13;  author: pizzandre;  state: Exp;  lines: +1 -1
+Issue number: 1
+Obtained from: pizzandre
+Submitted by: pizzandre
+Reviewed by: pizzandre
+
+Adding milestone 1 info
+----------------------------
+revision 1.5
+date: 2005/07/15 17:16:15;  author: pizzandre;  state: Exp;  lines: +7 -26
+*** empty log message ***
+----------------------------
+revision 1.4
+date: 2005/07/15 16:46:35;  author: pizzandre;  state: Exp;  lines: +4 -4
+*** empty log message ***
+----------------------------
+revision 1.3
+date: 2005/07/15 16:40:17;  author: pizzandre;  state: Exp;  lines: +1 -282
+*** empty log message ***
+----------------------------
+revision 1.2
+date: 2005/07/15 16:33:54;  author: pizzandre;  state: Exp;  lines: +349 -41
+*** empty log message ***
+----------------------------
+revision 1.1
+date: 2005/07/15 11:53:30;  author: httpd;  state: Exp;
+Initial data for the intelliglue project
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/Attic/sequence.zip,v
+head: 1.2
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1.2
+date: 2006/02/05 23:10:33;  author: pizzandre;  state: dead;  lines: +0 -0
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+----------------------------
+revision 1.1
+date: 2006/01/30 15:53:05;  author: pizzandre;  state: Exp;
+
+Primeira versao do plugin sequence do netbeans
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/build.xml,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:18;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/manifest.mf,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:19;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/nbproject/build-impl.xml,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:19;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/nbproject/genfiles.properties,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:20;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/nbproject/platform.properties,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:21;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/nbproject/project.xml,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:21;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/nbproject/private/platform-private.properties,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:22;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/nbproject/private/private.xml,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:23;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/action/Bundle.properties,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:23;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/action/ModelGeneratorAction.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:24;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/action/Service.gif,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:25;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/resources/Bundle.properties,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:25;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/resources/layer.xml,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:26;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/sequence/SequenceModelGenerator.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:27;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/sequence/callgraph/AbstractSequenceModelGraph.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:27;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/sequence/callgraph/SequenceModelCallGraph.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:28;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/sequence/callgraph/SequenceModelCallGraphFactory.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:28;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/sequence/callgraph/SequenceModelJTreeCallGraph.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:29;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/sequence/model/SequenceMethod.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:30;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/sequence/model/SequenceModelFilter.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:30;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/sequence/ui/SequenceModelUI.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:31;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/plugin/ModelGenerator/src/org/pizzandre/modelgen/util/MethodUtil.java,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2006/02/05 23:10:32;  author: pizzandre;  state: Exp;
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+CVS: ----------------------------------------------------------------------
+CVS: Issue number:
+CVS:   If this change addresses one or more issues,
+CVS:   then enter the issue number(s) here.
+CVS: Obtained from:
+CVS:   If this change has been taken from another system,
+CVS:   then name the system in this line, otherwise delete it.
+CVS: Submitted by:
+CVS:   If this code has been contributed to the project by someone else; i.e.,
+CVS:   they sent us a patch or a set of diffs, then include their name/email
+CVS:   address here. If this is your work then delete this line.
+CVS: Reviewed by:
+CVS:   If we are doing pre-commit code reviews and someone else has
+CVS:   reviewed your changes, include their name(s) here.
+CVS:   If you have not had it reviewed then delete this line.
+=============================================================================
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/release/1.0/intelliglue-1.0.jar,v
+head: 1.1
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: b
+total revisions: 1;    selected revisions: 1
+description:
+----------------------------
+revision 1.1
+date: 2005/07/26 20:26:42;  author: pizzandre;  state: Exp;
+Issue number:1
+Obtained from:pizzandre
+Submitted by:pizzandre
+Reviewed by:pizzandre
+Adding release 1.0 with example but not dependencies
+=============================================================================
diff --git a/test/data/multiple_commits.rlog b/test/data/multiple_commits.rlog
new file mode 100644 (file)
index 0000000..506115b
--- /dev/null
@@ -0,0 +1,64 @@
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/index.html,v
+head: 1.9
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 9;    selected revisions: 9
+description:
+----------------------------
+revision 1.9
+date: 2005/07/26 20:39:16;  author: pizzandre;  state: Exp;  lines: +2 -2
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+Completing and fixing milestones texts
+----------------------------
+revision 1.8
+date: 2005/07/26 20:35:13;  author: pizzandre;  state: Exp;  lines: +2 -2
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+Adding current milestones-
+----------------------------
+revision 1.7
+date: 2005/07/26 20:30:41;  author: pizzandre;  state: Exp;  lines: +1 -1
+Issue number:pizzandre
+Obtained from:pizzandre
+Submitted by:pizzandre
+Reviewed by:pizzandre
+Adding link to release 1.0
+----------------------------
+revision 1.6
+date: 2005/07/26 20:16:13;  author: pizzandre;  state: Exp;  lines: +1 -1
+Issue number: 1
+Obtained from: pizzandre
+Submitted by: pizzandre
+Reviewed by: pizzandre
+
+Adding milestone 1 info
+----------------------------
+revision 1.5
+date: 2005/07/15 17:16:15;  author: pizzandre;  state: Exp;  lines: +7 -26
+*** empty log message ***
+----------------------------
+revision 1.4
+date: 2005/07/15 16:46:35;  author: pizzandre;  state: Exp;  lines: +4 -4
+*** empty log message ***
+----------------------------
+revision 1.3
+date: 2005/07/15 16:40:17;  author: pizzandre;  state: Exp;  lines: +1 -282
+*** empty log message ***
+----------------------------
+revision 1.2
+date: 2005/07/15 16:33:54;  author: pizzandre;  state: Exp;  lines: +349 -41
+*** empty log message ***
+----------------------------
+revision 1.1
+date: 2005/07/15 11:53:30;  author: httpd;  state: Exp;
+Initial data for the intelliglue project
+=============================================================================
diff --git a/test/data/multiple_revisions.rlog b/test/data/multiple_revisions.rlog
new file mode 100644 (file)
index 0000000..506115b
--- /dev/null
@@ -0,0 +1,64 @@
+
+RCS file: /shared/data/ccvs/repository/intelliglue/www/index.html,v
+head: 1.9
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 9;    selected revisions: 9
+description:
+----------------------------
+revision 1.9
+date: 2005/07/26 20:39:16;  author: pizzandre;  state: Exp;  lines: +2 -2
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+Completing and fixing milestones texts
+----------------------------
+revision 1.8
+date: 2005/07/26 20:35:13;  author: pizzandre;  state: Exp;  lines: +2 -2
+Issue number:
+Obtained from:
+Submitted by:
+Reviewed by:
+Adding current milestones-
+----------------------------
+revision 1.7
+date: 2005/07/26 20:30:41;  author: pizzandre;  state: Exp;  lines: +1 -1
+Issue number:pizzandre
+Obtained from:pizzandre
+Submitted by:pizzandre
+Reviewed by:pizzandre
+Adding link to release 1.0
+----------------------------
+revision 1.6
+date: 2005/07/26 20:16:13;  author: pizzandre;  state: Exp;  lines: +1 -1
+Issue number: 1
+Obtained from: pizzandre
+Submitted by: pizzandre
+Reviewed by: pizzandre
+
+Adding milestone 1 info
+----------------------------
+revision 1.5
+date: 2005/07/15 17:16:15;  author: pizzandre;  state: Exp;  lines: +7 -26
+*** empty log message ***
+----------------------------
+revision 1.4
+date: 2005/07/15 16:46:35;  author: pizzandre;  state: Exp;  lines: +4 -4
+*** empty log message ***
+----------------------------
+revision 1.3
+date: 2005/07/15 16:40:17;  author: pizzandre;  state: Exp;  lines: +1 -282
+*** empty log message ***
+----------------------------
+revision 1.2
+date: 2005/07/15 16:33:54;  author: pizzandre;  state: Exp;  lines: +349 -41
+*** empty log message ***
+----------------------------
+revision 1.1
+date: 2005/07/15 11:53:30;  author: httpd;  state: Exp;
+Initial data for the intelliglue project
+=============================================================================
diff --git a/test/data/simple.ohlog b/test/data/simple.ohlog
new file mode 100644 (file)
index 0000000..58fcc4f
--- /dev/null
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<ohloh_log scm="svn">
+    <commit token="5">
+        <committer name="jason" date="2006-07-14T23:07:15Z" />
+        <message>moving COPYING</message>
+        <diffs>
+            <diff action="D" path="/COPYING" />
+            <diff action="A" path="/trunk/COPYING" />
+        </diffs>
+    </commit>
+    <commit token="4">
+        <committer name="jason" date="2006-07-14T22:17:08Z" />
+        <message>added bs COPYING to catch global licenses</message>
+        <diffs>
+            <diff action="A" path="/COPYING" />
+        </diffs>
+    </commit>
+    <commit token="3">
+        <committer name="robin" date="2006-06-11T18:34:17Z" />
+        <message>added some documentation and licensing info</message>
+        <diffs>
+            <diff action="A" path="/trunk/README" />
+            <diff action="M" path="/trunk/helloworld.c" />
+        </diffs>
+    </commit>
+    <commit token="2">
+        <committer name="robin" date="2006-06-11T18:32:13Z" />
+        <message>added makefile</message>
+        <diffs>
+            <diff action="A" path="/trunk/makefile" />
+        </diffs>
+    </commit>
+    <commit token="1">
+        <committer name="robin" date="2006-06-11T18:28:00Z" />
+        <message>Initial Checkin
+</message>
+        <diffs>
+            <diff action="A" path="/branches" />
+            <diff action="A" path="/tags" />
+            <diff action="A" path="/trunk" />
+            <diff action="A" path="/trunk/helloworld.c" />
+        </diffs>
+    </commit>
+</ohloh_log>
diff --git a/test/data/simple.svn_log b/test/data/simple.svn_log
new file mode 100644 (file)
index 0000000..5cdc96c
--- /dev/null
@@ -0,0 +1,37 @@
+------------------------------------------------------------------------
+r5 | jason | 2006-07-14 16:07:15 -0700 (Fri, 14 Jul 2006) | 1 line
+Changed paths:
+   D /COPYING
+   A /trunk/COPYING (from /COPYING:4)
+
+moving COPYING
+------------------------------------------------------------------------
+r4 | jason | 2006-07-14 15:17:08 -0700 (Fri, 14 Jul 2006) | 1 line
+Changed paths:
+   A /COPYING
+
+added bs COPYING to catch global licenses
+------------------------------------------------------------------------
+r3 | robin | 2006-06-11 11:34:17 -0700 (Sun, 11 Jun 2006) | 1 line
+Changed paths:
+   A /trunk/README
+   M /trunk/helloworld.c
+
+added some documentation and licensing info
+------------------------------------------------------------------------
+r2 | robin | 2006-06-11 11:32:13 -0700 (Sun, 11 Jun 2006) | 1 line
+Changed paths:
+   A /trunk/makefile
+
+added makefile
+------------------------------------------------------------------------
+r1 | robin | 2006-06-11 11:28:00 -0700 (Sun, 11 Jun 2006) | 2 lines
+Changed paths:
+   A /branches
+   A /tags
+   A /trunk
+   A /trunk/helloworld.c
+
+Initial Checkin
+
+------------------------------------------------------------------------
diff --git a/test/data/simple.svn_xml_log b/test/data/simple.svn_xml_log
new file mode 100644 (file)
index 0000000..ec87b87
--- /dev/null
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<log>
+<logentry
+   revision="5">
+<author>jason</author>
+<date>2006-07-14T23:07:15Z</date>
+<paths>
+<path
+   action="D">/COPYING</path>
+<path
+   copyfrom-path="/COPYING"
+   copyfrom-rev="4"
+   action="A">/trunk/COPYING</path>
+</paths>
+<msg>moving COPYING</msg>
+</logentry>
+<logentry
+   revision="4">
+<author>jason</author>
+<date>2006-07-14T22:17:08Z</date>
+<paths>
+<path
+   action="A">/COPYING</path>
+</paths>
+<msg>added bs COPYING to catch global licenses</msg>
+</logentry>
+<logentry
+   revision="3">
+<author>robin</author>
+<date>2006-06-11T18:34:17Z</date>
+<paths>
+<path
+   action="A">/trunk/README</path>
+<path
+   action="M">/trunk/helloworld.c</path>
+</paths>
+<msg>added some documentation and licensing info</msg>
+</logentry>
+<logentry
+   revision="2">
+<author>robin</author>
+<date>2006-06-11T18:32:13Z</date>
+<paths>
+<path
+   action="A">/trunk/makefile</path>
+</paths>
+<msg>added makefile</msg>
+</logentry>
+<logentry
+   revision="1">
+<author>robin</author>
+<date>2006-06-11T18:28:00Z</date>
+<paths>
+<path
+   action="A">/branches</path>
+<path
+   action="A">/tags</path>
+<path
+   action="A">/trunk</path>
+<path
+   action="A">/trunk/helloworld.c</path>
+</paths>
+<msg>Initial Checkin
+</msg>
+</logentry>
+</log>
diff --git a/test/data/simultaneous_checkins.rlog b/test/data/simultaneous_checkins.rlog
new file mode 100644 (file)
index 0000000..304172c
--- /dev/null
@@ -0,0 +1,22 @@
+
+RCS file: /cvsroot/wikipedia/extensions/icpagent/Makefile,v
+head: 1.1
+branch: 1.1.1
+locks: strict
+access list:
+symbolic names:
+       start: 1.1.1.1
+       dammit: 1.1.1
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1.1
+date: 2005/01/02 20:07:30;  author: midom;  state: Exp;
+branches:  1.1.1;
+Initial revision
+----------------------------
+revision 1.1.1.1
+date: 2005/01/02 20:07:30;  author: midom;  state: Exp;  lines: +0 -0
+ICP agent, for delayed ICP responses on loaded systems...
+=============================================================================
diff --git a/test/data/simultaneous_checkins_2.rlog b/test/data/simultaneous_checkins_2.rlog
new file mode 100644 (file)
index 0000000..9a18a20
--- /dev/null
@@ -0,0 +1,18 @@
+
+RCS file: /cvsroot/wikipedia/extensions/icpagent/Makefile,v
+head: 1.2
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 2;    selected revisions: 2
+description:
+----------------------------
+revision 1.1
+date: 2005/01/02 20:07:30;  author: midom;  state: Exp;
+Initial revision
+----------------------------
+revision 1.2
+date: 2005/01/02 20:07:30;  author: foo_author;  state: Exp;  lines: +0 -0
+I have the same timestamp as my predecessor
+=============================================================================
diff --git a/test/repositories/cvs/CVSROOT/.#checkoutlist b/test/repositories/cvs/CVSROOT/.#checkoutlist
new file mode 100644 (file)
index 0000000..b04b350
--- /dev/null
@@ -0,0 +1,13 @@
+# The "checkoutlist" file is used to support additional version controlled
+# administrative files in $CVSROOT/CVSROOT, such as template files.
+#
+# The first entry on a line is a filename which will be checked out from
+# the corresponding RCS file in the $CVSROOT/CVSROOT directory.
+# The remainder of the line is an error message to use if the file cannot
+# be checked out.
+#
+# File format:
+#
+#      [<whitespace>]<filename><whitespace><error message><end-of-line>
+#
+# comment lines begin with '#'
diff --git a/test/repositories/cvs/CVSROOT/.#commit_email b/test/repositories/cvs/CVSROOT/.#commit_email
new file mode 100644 (file)
index 0000000..6386361
--- /dev/null
@@ -0,0 +1,10 @@
+# The "commit_email" file is used to control templates for emails sent
+# during commit and import.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
diff --git a/test/repositories/cvs/CVSROOT/.#commitinfo b/test/repositories/cvs/CVSROOT/.#commitinfo
new file mode 100644 (file)
index 0000000..63ea65a
--- /dev/null
@@ -0,0 +1,15 @@
+# The "commitinfo" file is used to control pre-commit checks.
+# The filter on the right is invoked with the repository name.  A list
+# of files to check is passed to the standard input of the script.  A non-zero
+# exit of the filter program will cause the commit to be aborted.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/.#config b/test/repositories/cvs/CVSROOT/.#config
new file mode 100644 (file)
index 0000000..af2a28c
--- /dev/null
@@ -0,0 +1,26 @@
+# Set this to 'no' if pserver shouldn't check system users/passwords
+#SystemAuth=yes
+
+# Set the Acl parsing type (none,compat,normal).
+#AclMode=compat
+
+# Alternate location of CVS LockServer.  Set to 'none' to disable..
+#LockServer=localhost:2402
+
+# Set 'TopLevelAdmin' to 'yes' to create a CVS directory at the top
+# level of the new working directory when using the 'cvs checkout'
+# command.
+#TopLevelAdmin=no
+
+# Set 'LogHistory' to 'all' or 'TOFEWGCMAR' to log all transactions to the
+# history file, or a subset as needed (ie 'TMAR' logs all write operations)
+#LogHistory=TOFEWGCMAR
+
+# Set 'RereadLogAfterVerify' to control rereading of the log file after a verifymsg
+#   'always' or 'yes' to always reread the log regardless
+#   'never' or 'no' (default) to never reread the log
+#RereadLogAfterVerify=no
+
+# Set 'Watcher' to set a user who gets all notify events within the repository whether
+# or not the ifle is watched.
+#Watcher=watch_user
\ No newline at end of file
diff --git a/test/repositories/cvs/CVSROOT/.#cvsrc b/test/repositories/cvs/CVSROOT/.#cvsrc
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/repositories/cvs/CVSROOT/.#cvswrappers b/test/repositories/cvs/CVSROOT/.#cvswrappers
new file mode 100644 (file)
index 0000000..11fa3b3
--- /dev/null
@@ -0,0 +1,18 @@
+# This file affects handling of files based on their names.
+#
+# The -m option specifies whether CVS attempts to merge files.
+#
+# The -k option specifies keyword expansion (e.g. -kb for binary).
+#
+# The -t option overrides the default mime type.
+#
+# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
+#
+#  wildcard    [option value][option value]...
+#
+#  where option is one of
+#  -k          expansion mode          value: b, o, kkv, etc.
+#
+#  and value is a single-quote delimited value.
+# For example:
+#*.gif -kb
diff --git a/test/repositories/cvs/CVSROOT/.#historyinfo b/test/repositories/cvs/CVSROOT/.#historyinfo
new file mode 100644 (file)
index 0000000..889ff1b
--- /dev/null
@@ -0,0 +1,15 @@
+# The "historyinfo" file is used to log the history file output.
+# The filter on the right is invoked with the repository name.  Its 
+# standard input contains the history line that has just been written 
+# to the history file (if it exists)
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/.#keywords b/test/repositories/cvs/CVSROOT/.#keywords
new file mode 100644 (file)
index 0000000..a94adf4
--- /dev/null
@@ -0,0 +1,30 @@
+# The "keywords" file is used to modify the standard set of RCS keywords
+# or define entirely new ones.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  Subsequent lines contain keyword definitions, indented by a space
+# to separate them from module definitions
+.#
+# If the repository name does not match any of the definitions in this
+# file, the "ALL" section is used, if it is specified.
+#
+# Any keyword with an empty definition is ignored.  This can be used to selectively
+# disable individual RCS keywords.
+#
+# The default defintions are:
+#
+#ALL
+#  Author    %a
+#  Date      %d
+#  Header    %r/%p/%f %v %d %a %s %l
+#  CVSHeader %p/%f %v %d %a %s %l
+#  Id        %f %v %d %a %s %l
+#  Locker    %l
+#  Log           %f
+#  Name      %N
+#  RCSfile   %f
+#  Revision  %v
+#  Source    %r/%p/%f
+#  State     %s
+#  CommitId  %C
diff --git a/test/repositories/cvs/CVSROOT/.#loginfo b/test/repositories/cvs/CVSROOT/.#loginfo
new file mode 100644 (file)
index 0000000..f3c4329
--- /dev/null
@@ -0,0 +1,22 @@
+# The "loginfo" file controls where "cvs commit" log information
+# is sent.  The first entry on a line is a regular expression which must match
+# the directory that the change is being made to, relative to the
+# $CVSROOT.  If a match is found, then the remainder of the line is a filter
+# program that should expect log information on its standard input.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name ALL appears as a regular expression it is always used
+# in addition to the first matching regex or DEFAULT.
+#
+# You may specify a format string as part of the
+# filter.  The string is composed of a '%' followed
+# by a single format character, or followed by a set of format
+# characters surrounded by '{' and '}' as separators.  The format
+# characters are:
+#
+#   s = file name
+#   V = old version number (pre-checkin)
+#   v = new version number (post-checkin)
+#
diff --git a/test/repositories/cvs/CVSROOT/.#modules b/test/repositories/cvs/CVSROOT/.#modules
new file mode 100644 (file)
index 0000000..cb9e9ef
--- /dev/null
@@ -0,0 +1,26 @@
+# Three different line formats are valid:
+#      key     -a    aliases...
+#      key [options] directory
+#      key [options] directory files...
+#
+# Where "options" are composed of:
+#      -i prog         Run "prog" on "cvs commit" from top-level of module.
+#      -o prog         Run "prog" on "cvs checkout" of module.
+#      -e prog         Run "prog" on "cvs export" of module.
+#      -t prog         Run "prog" on "cvs rtag" of module.
+#      -u prog         Run "prog" on "cvs update" of module.
+#      -d dir          Place module in directory "dir" instead of module name.
+#      -l              Top-level directory only -- do not recurse.
+#
+# NOTE:  If you change any of the "Run" options above, you'll have to
+# release and re-checkout any working directories of these modules.
+#
+# And "directory" is a path to a directory relative to $CVSROOT.
+#
+# The "-a" option specifies an alias.  An alias is interpreted as if
+# everything on the right of the "-a" had been typed on the command line.
+#
+# You can encode a module within a module by using the special '&'
+# character to interpose another module into the current module.  This
+# can be useful for creating a module that consists of many directories
+# spread out over the entire source repository.
diff --git a/test/repositories/cvs/CVSROOT/.#modules2 b/test/repositories/cvs/CVSROOT/.#modules2
new file mode 100644 (file)
index 0000000..6cdc5ba
--- /dev/null
@@ -0,0 +1,22 @@
+# *** modules2 currently has 'experimental' status.  Testing is encouraged but.
+# for greatest stability use the modules file. ***
+#
+# This file describes the layout of virtual directory structures
+# within the repository.
+#
+# The layout is similar to a Windows .ini file.  For example:
+#
+# [foo]
+# dir1/dir2/dir3 = realdir1/realdir2
+# dir1/dir3 = !realdir1/realdir3 (^*js$|^*cpp$)
+#
+# [bar]
+# / = realdir4
+# dir_to_delete =
+# foo = foo
+#
+# The special character '!' stops recursion to directories below the one specified (-l option).
+# The special character '+' stops parsing of that line, so that you can avoid infinte loops.
+#
+# Items in (...) are an extended regular expression applied to the filenames.  All files which do not.
+# match are ignored.
diff --git a/test/repositories/cvs/CVSROOT/.#notify b/test/repositories/cvs/CVSROOT/.#notify
new file mode 100644 (file)
index 0000000..cdb3320
--- /dev/null
@@ -0,0 +1,22 @@
+# The "notify" file controls where notifications from watches set by
+# "cvs watch add" or "cvs edit" are sent.  The first entry on a line is
+# a regular expression which is tested against the directory that the
+# change is being made to, relative to the $CVSROOT.  If it matches,
+# then the remainder of the line is a filter program that should contain
+# one occurrence of %s for the user to notify, and information on its
+# standard input.
+#
+# "ALL" or "DEFAULT" can be used in place of the regular expression.
+#
+# You may specify a format string as part of the
+# filter.  The format characters are:
+#
+#   s = user being notified
+#   b = Bug identifier
+#   m = Message supplied on command line
+#   d = Date of action
+#   u = User performing the unedit
+#   t = tag or branch being edited
+#
+# For example:
+#ALL mail %s -s "CVS notification for bug %b"
diff --git a/test/repositories/cvs/CVSROOT/.#notify_email b/test/repositories/cvs/CVSROOT/.#notify_email
new file mode 100644 (file)
index 0000000..c673ab4
--- /dev/null
@@ -0,0 +1,10 @@
+# The "notify_email" file is used to control templates for emails sent
+# when notifying users.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
diff --git a/test/repositories/cvs/CVSROOT/.#postcommand b/test/repositories/cvs/CVSROOT/.#postcommand
new file mode 100644 (file)
index 0000000..ab3ef14
--- /dev/null
@@ -0,0 +1,14 @@
+# The "postcommand" file is run after a cvs command has finished.
+# The filter on the right is invoked with the repository name and
+# the name of the command that has been executed.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/.#postmodule b/test/repositories/cvs/CVSROOT/.#postmodule
new file mode 100644 (file)
index 0000000..b6d258b
--- /dev/null
@@ -0,0 +1,14 @@
+# The "postmodule" file is run after a cvs module is processed.
+# The filter on the right is invoked with the repository name, 
+# the name of the command that has been executed, and the module name.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/.#precommand b/test/repositories/cvs/CVSROOT/.#precommand
new file mode 100644 (file)
index 0000000..05c8b89
--- /dev/null
@@ -0,0 +1,18 @@
+# The "precommand" file is run before a cvs command is executed.
+# The filter on the right is invoked with the repository name and
+# the name of the command that has been executed.  A non-zero return
+# value with abort the command with an error.
+#
+# The standard input of the filter receives each command argument,
+# separated by linefeeds.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/.#premodule b/test/repositories/cvs/CVSROOT/.#premodule
new file mode 100644 (file)
index 0000000..4837892
--- /dev/null
@@ -0,0 +1,15 @@
+# The "premodule" file is run before cvs module is processed.
+# The filter on the right is invoked with the repository name, 
+# the name of the command that has been executed, and the module name.
+# A non-zero return value with abort the command with an error.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/.#rcsinfo b/test/repositories/cvs/CVSROOT/.#rcsinfo
new file mode 100644 (file)
index 0000000..27c002f
--- /dev/null
@@ -0,0 +1,10 @@
+# The "rcsinfo" file is used to control templates with which the editor
+# is invoked on commit and import.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
diff --git a/test/repositories/cvs/CVSROOT/.#shadow b/test/repositories/cvs/CVSROOT/.#shadow
new file mode 100644 (file)
index 0000000..7ccd24e
--- /dev/null
@@ -0,0 +1,7 @@
+# The "shadow" file is used to control automatic checkouts.
+#
+# Each line has 3 parts:
+# <module> <tag> <directory>
+#
+# In common with other commit support files, use forward slashes
+# and escape any spaces in filenames.
diff --git a/test/repositories/cvs/CVSROOT/.#tag_email b/test/repositories/cvs/CVSROOT/.#tag_email
new file mode 100644 (file)
index 0000000..48d545f
--- /dev/null
@@ -0,0 +1,10 @@
+# The "tag_email" file is used to control templates for emails sent
+# during tagging operations.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
diff --git a/test/repositories/cvs/CVSROOT/.#taginfo b/test/repositories/cvs/CVSROOT/.#taginfo
new file mode 100644 (file)
index 0000000..3beb822
--- /dev/null
@@ -0,0 +1,21 @@
+# The "taginfo" file is used to control pre-tag checks.
+# The filter on the right is invoked with the following arguments:
+#
+# $1 -- tagname
+# $2 -- operation "add" for tag, "mov" for tag -F, and "del" for tag -d
+# $3 -- repository
+#
+# The filter is passed a series of filename/version pairs on its standard input
+#
+# A non-zero exit of the filter program will cause the tag to be aborted.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/.#triggers b/test/repositories/cvs/CVSROOT/.#triggers
new file mode 100644 (file)
index 0000000..48a9fc3
--- /dev/null
@@ -0,0 +1,5 @@
+# The "triggers" file lists libraries which handle the events for each
+# module.
+#
+# In addition to the lines here, the default_trigger library is loaded, if available.
+#
diff --git a/test/repositories/cvs/CVSROOT/.#verifymsg b/test/repositories/cvs/CVSROOT/.#verifymsg
new file mode 100644 (file)
index 0000000..28b1d6e
--- /dev/null
@@ -0,0 +1,17 @@
+# The "verifymsg" file is used to allow verification of logging
+# information.  It works best when a template (as specified in the
+# rcsinfo file) is provided for the logging procedure.  Given a
+# template with locations for, a bug-id number, a list of people who
+# reviewed the code before it can be checked in, and an external
+# process to catalog the differences that were code reviewed, the
+# following test can be applied to the code:
+#
+#   Making sure that the entered bug-id number is correct.
+#   Validating that the code that was reviewed is indeed the code being
+#       checked in (using the bug-id number or a seperate review
+#       number to identify this particular code set.).
+#
+# If any of the above test failed, then the commit would be aborted.
+#
+# Actions such as mailing a copy of the report to each reviewer are
+# better handled by an entry in the loginfo file.
diff --git a/test/repositories/cvs/CVSROOT/CVS/fileattr.xml b/test/repositories/cvs/CVSROOT/CVS/fileattr.xml
new file mode 100644 (file)
index 0000000..83ce5ca
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fileattr>
+  <directory>
+    <owner>robin</owner>
+    <acl user="robin">
+      <all />
+    </acl>
+    <acl user="admin">
+      <all />
+    </acl>
+    <acl>
+      <all deny="1" />
+    </acl>
+  </directory>
+</fileattr>
diff --git a/test/repositories/cvs/CVSROOT/checkoutlist b/test/repositories/cvs/CVSROOT/checkoutlist
new file mode 100644 (file)
index 0000000..b04b350
--- /dev/null
@@ -0,0 +1,13 @@
+# The "checkoutlist" file is used to support additional version controlled
+# administrative files in $CVSROOT/CVSROOT, such as template files.
+#
+# The first entry on a line is a filename which will be checked out from
+# the corresponding RCS file in the $CVSROOT/CVSROOT directory.
+# The remainder of the line is an error message to use if the file cannot
+# be checked out.
+#
+# File format:
+#
+#      [<whitespace>]<filename><whitespace><error message><end-of-line>
+#
+# comment lines begin with '#'
diff --git a/test/repositories/cvs/CVSROOT/checkoutlist,v b/test/repositories/cvs/CVSROOT/checkoutlist,v
new file mode 100644 (file)
index 0000000..13ddb4f
--- /dev/null
@@ -0,0 +1,39 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "checkoutlist" file is used to support additional version controlled
+# administrative files in $CVSROOT/CVSROOT, such as template files.
+#
+# The first entry on a line is a filename which will be checked out from
+# the corresponding RCS file in the $CVSROOT/CVSROOT directory.
+# The remainder of the line is an error message to use if the file cannot
+# be checked out.
+#
+# File format:
+#
+#      [<whitespace>]<filename><whitespace><error message><end-of-line>
+#
+# comment lines begin with '#'
+@
+
diff --git a/test/repositories/cvs/CVSROOT/commit_email b/test/repositories/cvs/CVSROOT/commit_email
new file mode 100644 (file)
index 0000000..6386361
--- /dev/null
@@ -0,0 +1,10 @@
+# The "commit_email" file is used to control templates for emails sent
+# during commit and import.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
diff --git a/test/repositories/cvs/CVSROOT/commit_email,v b/test/repositories/cvs/CVSROOT/commit_email,v
new file mode 100644 (file)
index 0000000..79135d9
--- /dev/null
@@ -0,0 +1,36 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "commit_email" file is used to control templates for emails sent
+# during commit and import.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+@
+
diff --git a/test/repositories/cvs/CVSROOT/commitinfo b/test/repositories/cvs/CVSROOT/commitinfo
new file mode 100644 (file)
index 0000000..63ea65a
--- /dev/null
@@ -0,0 +1,15 @@
+# The "commitinfo" file is used to control pre-commit checks.
+# The filter on the right is invoked with the repository name.  A list
+# of files to check is passed to the standard input of the script.  A non-zero
+# exit of the filter program will cause the commit to be aborted.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/commitinfo,v b/test/repositories/cvs/CVSROOT/commitinfo,v
new file mode 100644 (file)
index 0000000..62bf695
--- /dev/null
@@ -0,0 +1,41 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "commitinfo" file is used to control pre-commit checks.
+# The filter on the right is invoked with the repository name.  A list
+# of files to check is passed to the standard input of the script.  A non-zero
+# exit of the filter program will cause the commit to be aborted.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
+@
+
diff --git a/test/repositories/cvs/CVSROOT/config b/test/repositories/cvs/CVSROOT/config
new file mode 100644 (file)
index 0000000..af2a28c
--- /dev/null
@@ -0,0 +1,26 @@
+# Set this to 'no' if pserver shouldn't check system users/passwords
+#SystemAuth=yes
+
+# Set the Acl parsing type (none,compat,normal).
+#AclMode=compat
+
+# Alternate location of CVS LockServer.  Set to 'none' to disable..
+#LockServer=localhost:2402
+
+# Set 'TopLevelAdmin' to 'yes' to create a CVS directory at the top
+# level of the new working directory when using the 'cvs checkout'
+# command.
+#TopLevelAdmin=no
+
+# Set 'LogHistory' to 'all' or 'TOFEWGCMAR' to log all transactions to the
+# history file, or a subset as needed (ie 'TMAR' logs all write operations)
+#LogHistory=TOFEWGCMAR
+
+# Set 'RereadLogAfterVerify' to control rereading of the log file after a verifymsg
+#   'always' or 'yes' to always reread the log regardless
+#   'never' or 'no' (default) to never reread the log
+#RereadLogAfterVerify=no
+
+# Set 'Watcher' to set a user who gets all notify events within the repository whether
+# or not the ifle is watched.
+#Watcher=watch_user
\ No newline at end of file
diff --git a/test/repositories/cvs/CVSROOT/config,v b/test/repositories/cvs/CVSROOT/config,v
new file mode 100644 (file)
index 0000000..80bcd01
--- /dev/null
@@ -0,0 +1,51 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# Set this to 'no' if pserver shouldn't check system users/passwords
+#SystemAuth=yes
+
+# Set the Acl parsing type (none,compat,normal).
+#AclMode=compat
+
+# Alternate location of CVS LockServer.  Set to 'none' to disable..
+#LockServer=localhost:2402
+
+# Set 'TopLevelAdmin' to 'yes' to create a CVS directory at the top
+# level of the new working directory when using the 'cvs checkout'
+# command.
+#TopLevelAdmin=no
+
+# Set 'LogHistory' to 'all' or 'TOFEWGCMAR' to log all transactions to the
+# history file, or a subset as needed (ie 'TMAR' logs all write operations)
+#LogHistory=TOFEWGCMAR
+
+# Set 'RereadLogAfterVerify' to control rereading of the log file after a verifymsg
+#   'always' or 'yes' to always reread the log regardless
+#   'never' or 'no' (default) to never reread the log
+#RereadLogAfterVerify=no
+
+# Set 'Watcher' to set a user who gets all notify events within the repository whether
+# or not the ifle is watched.
+#Watcher=watch_user@
+
diff --git a/test/repositories/cvs/CVSROOT/cvsrc b/test/repositories/cvs/CVSROOT/cvsrc
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/repositories/cvs/CVSROOT/cvsrc,v b/test/repositories/cvs/CVSROOT/cvsrc,v
new file mode 100644 (file)
index 0000000..6e50ecf
--- /dev/null
@@ -0,0 +1,26 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@@
+
diff --git a/test/repositories/cvs/CVSROOT/cvswrappers b/test/repositories/cvs/CVSROOT/cvswrappers
new file mode 100644 (file)
index 0000000..11fa3b3
--- /dev/null
@@ -0,0 +1,18 @@
+# This file affects handling of files based on their names.
+#
+# The -m option specifies whether CVS attempts to merge files.
+#
+# The -k option specifies keyword expansion (e.g. -kb for binary).
+#
+# The -t option overrides the default mime type.
+#
+# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
+#
+#  wildcard    [option value][option value]...
+#
+#  where option is one of
+#  -k          expansion mode          value: b, o, kkv, etc.
+#
+#  and value is a single-quote delimited value.
+# For example:
+#*.gif -kb
diff --git a/test/repositories/cvs/CVSROOT/cvswrappers,v b/test/repositories/cvs/CVSROOT/cvswrappers,v
new file mode 100644 (file)
index 0000000..badb225
--- /dev/null
@@ -0,0 +1,44 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# This file affects handling of files based on their names.
+#
+# The -m option specifies whether CVS attempts to merge files.
+#
+# The -k option specifies keyword expansion (e.g. -kb for binary).
+#
+# The -t option overrides the default mime type.
+#
+# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
+#
+#  wildcard    [option value][option value]...
+#
+#  where option is one of
+#  -k          expansion mode          value: b, o, kkv, etc.
+#
+#  and value is a single-quote delimited value.
+# For example:
+#*.gif -kb
+@
+
diff --git a/test/repositories/cvs/CVSROOT/historyinfo b/test/repositories/cvs/CVSROOT/historyinfo
new file mode 100644 (file)
index 0000000..889ff1b
--- /dev/null
@@ -0,0 +1,15 @@
+# The "historyinfo" file is used to log the history file output.
+# The filter on the right is invoked with the repository name.  Its 
+# standard input contains the history line that has just been written 
+# to the history file (if it exists)
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/historyinfo,v b/test/repositories/cvs/CVSROOT/historyinfo,v
new file mode 100644 (file)
index 0000000..645bd87
--- /dev/null
@@ -0,0 +1,41 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "historyinfo" file is used to log the history file output.
+# The filter on the right is invoked with the repository name.  Its 
+# standard input contains the history line that has just been written 
+# to the history file (if it exists)
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
+@
+
diff --git a/test/repositories/cvs/CVSROOT/keywords b/test/repositories/cvs/CVSROOT/keywords
new file mode 100644 (file)
index 0000000..a94adf4
--- /dev/null
@@ -0,0 +1,30 @@
+# The "keywords" file is used to modify the standard set of RCS keywords
+# or define entirely new ones.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  Subsequent lines contain keyword definitions, indented by a space
+# to separate them from module definitions
+.#
+# If the repository name does not match any of the definitions in this
+# file, the "ALL" section is used, if it is specified.
+#
+# Any keyword with an empty definition is ignored.  This can be used to selectively
+# disable individual RCS keywords.
+#
+# The default defintions are:
+#
+#ALL
+#  Author    %a
+#  Date      %d
+#  Header    %r/%p/%f %v %d %a %s %l
+#  CVSHeader %p/%f %v %d %a %s %l
+#  Id        %f %v %d %a %s %l
+#  Locker    %l
+#  Log           %f
+#  Name      %N
+#  RCSfile   %f
+#  Revision  %v
+#  Source    %r/%p/%f
+#  State     %s
+#  CommitId  %C
diff --git a/test/repositories/cvs/CVSROOT/keywords,v b/test/repositories/cvs/CVSROOT/keywords,v
new file mode 100644 (file)
index 0000000..83f97ed
--- /dev/null
@@ -0,0 +1,56 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "keywords" file is used to modify the standard set of RCS keywords
+# or define entirely new ones.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  Subsequent lines contain keyword definitions, indented by a space
+# to separate them from module definitions
+.#
+# If the repository name does not match any of the definitions in this
+# file, the "ALL" section is used, if it is specified.
+#
+# Any keyword with an empty definition is ignored.  This can be used to selectively
+# disable individual RCS keywords.
+#
+# The default defintions are:
+#
+#ALL
+#  Author    %a
+#  Date      %d
+#  Header    %r/%p/%f %v %d %a %s %l
+#  CVSHeader %p/%f %v %d %a %s %l
+#  Id        %f %v %d %a %s %l
+#  Locker    %l
+#  Log           %f
+#  Name      %N
+#  RCSfile   %f
+#  Revision  %v
+#  Source    %r/%p/%f
+#  State     %s
+#  CommitId  %C
+@
+
diff --git a/test/repositories/cvs/CVSROOT/loginfo b/test/repositories/cvs/CVSROOT/loginfo
new file mode 100644 (file)
index 0000000..f3c4329
--- /dev/null
@@ -0,0 +1,22 @@
+# The "loginfo" file controls where "cvs commit" log information
+# is sent.  The first entry on a line is a regular expression which must match
+# the directory that the change is being made to, relative to the
+# $CVSROOT.  If a match is found, then the remainder of the line is a filter
+# program that should expect log information on its standard input.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name ALL appears as a regular expression it is always used
+# in addition to the first matching regex or DEFAULT.
+#
+# You may specify a format string as part of the
+# filter.  The string is composed of a '%' followed
+# by a single format character, or followed by a set of format
+# characters surrounded by '{' and '}' as separators.  The format
+# characters are:
+#
+#   s = file name
+#   V = old version number (pre-checkin)
+#   v = new version number (post-checkin)
+#
diff --git a/test/repositories/cvs/CVSROOT/loginfo,v b/test/repositories/cvs/CVSROOT/loginfo,v
new file mode 100644 (file)
index 0000000..aac350e
--- /dev/null
@@ -0,0 +1,48 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "loginfo" file controls where "cvs commit" log information
+# is sent.  The first entry on a line is a regular expression which must match
+# the directory that the change is being made to, relative to the
+# $CVSROOT.  If a match is found, then the remainder of the line is a filter
+# program that should expect log information on its standard input.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name ALL appears as a regular expression it is always used
+# in addition to the first matching regex or DEFAULT.
+#
+# You may specify a format string as part of the
+# filter.  The string is composed of a '%' followed
+# by a single format character, or followed by a set of format
+# characters surrounded by '{' and '}' as separators.  The format
+# characters are:
+#
+#   s = file name
+#   V = old version number (pre-checkin)
+#   v = new version number (post-checkin)
+#
+@
+
diff --git a/test/repositories/cvs/CVSROOT/modules b/test/repositories/cvs/CVSROOT/modules
new file mode 100644 (file)
index 0000000..cb9e9ef
--- /dev/null
@@ -0,0 +1,26 @@
+# Three different line formats are valid:
+#      key     -a    aliases...
+#      key [options] directory
+#      key [options] directory files...
+#
+# Where "options" are composed of:
+#      -i prog         Run "prog" on "cvs commit" from top-level of module.
+#      -o prog         Run "prog" on "cvs checkout" of module.
+#      -e prog         Run "prog" on "cvs export" of module.
+#      -t prog         Run "prog" on "cvs rtag" of module.
+#      -u prog         Run "prog" on "cvs update" of module.
+#      -d dir          Place module in directory "dir" instead of module name.
+#      -l              Top-level directory only -- do not recurse.
+#
+# NOTE:  If you change any of the "Run" options above, you'll have to
+# release and re-checkout any working directories of these modules.
+#
+# And "directory" is a path to a directory relative to $CVSROOT.
+#
+# The "-a" option specifies an alias.  An alias is interpreted as if
+# everything on the right of the "-a" had been typed on the command line.
+#
+# You can encode a module within a module by using the special '&'
+# character to interpose another module into the current module.  This
+# can be useful for creating a module that consists of many directories
+# spread out over the entire source repository.
diff --git a/test/repositories/cvs/CVSROOT/modules,v b/test/repositories/cvs/CVSROOT/modules,v
new file mode 100644 (file)
index 0000000..bced005
--- /dev/null
@@ -0,0 +1,52 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# Three different line formats are valid:
+#      key     -a    aliases...
+#      key [options] directory
+#      key [options] directory files...
+#
+# Where "options" are composed of:
+#      -i prog         Run "prog" on "cvs commit" from top-level of module.
+#      -o prog         Run "prog" on "cvs checkout" of module.
+#      -e prog         Run "prog" on "cvs export" of module.
+#      -t prog         Run "prog" on "cvs rtag" of module.
+#      -u prog         Run "prog" on "cvs update" of module.
+#      -d dir          Place module in directory "dir" instead of module name.
+#      -l              Top-level directory only -- do not recurse.
+#
+# NOTE:  If you change any of the "Run" options above, you'll have to
+# release and re-checkout any working directories of these modules.
+#
+# And "directory" is a path to a directory relative to $CVSROOT.
+#
+# The "-a" option specifies an alias.  An alias is interpreted as if
+# everything on the right of the "-a" had been typed on the command line.
+#
+# You can encode a module within a module by using the special '&'
+# character to interpose another module into the current module.  This
+# can be useful for creating a module that consists of many directories
+# spread out over the entire source repository.
+@
+
diff --git a/test/repositories/cvs/CVSROOT/modules2 b/test/repositories/cvs/CVSROOT/modules2
new file mode 100644 (file)
index 0000000..6cdc5ba
--- /dev/null
@@ -0,0 +1,22 @@
+# *** modules2 currently has 'experimental' status.  Testing is encouraged but.
+# for greatest stability use the modules file. ***
+#
+# This file describes the layout of virtual directory structures
+# within the repository.
+#
+# The layout is similar to a Windows .ini file.  For example:
+#
+# [foo]
+# dir1/dir2/dir3 = realdir1/realdir2
+# dir1/dir3 = !realdir1/realdir3 (^*js$|^*cpp$)
+#
+# [bar]
+# / = realdir4
+# dir_to_delete =
+# foo = foo
+#
+# The special character '!' stops recursion to directories below the one specified (-l option).
+# The special character '+' stops parsing of that line, so that you can avoid infinte loops.
+#
+# Items in (...) are an extended regular expression applied to the filenames.  All files which do not.
+# match are ignored.
diff --git a/test/repositories/cvs/CVSROOT/modules2,v b/test/repositories/cvs/CVSROOT/modules2,v
new file mode 100644 (file)
index 0000000..0986d95
--- /dev/null
@@ -0,0 +1,48 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# *** modules2 currently has 'experimental' status.  Testing is encouraged but.
+# for greatest stability use the modules file. ***
+#
+# This file describes the layout of virtual directory structures
+# within the repository.
+#
+# The layout is similar to a Windows .ini file.  For example:
+#
+# [foo]
+# dir1/dir2/dir3 = realdir1/realdir2
+# dir1/dir3 = !realdir1/realdir3 (^*js$|^*cpp$)
+#
+# [bar]
+# / = realdir4
+# dir_to_delete =
+# foo = foo
+#
+# The special character '!' stops recursion to directories below the one specified (-l option).
+# The special character '+' stops parsing of that line, so that you can avoid infinte loops.
+#
+# Items in (...) are an extended regular expression applied to the filenames.  All files which do not.
+# match are ignored.
+@
+
diff --git a/test/repositories/cvs/CVSROOT/notify b/test/repositories/cvs/CVSROOT/notify
new file mode 100644 (file)
index 0000000..cdb3320
--- /dev/null
@@ -0,0 +1,22 @@
+# The "notify" file controls where notifications from watches set by
+# "cvs watch add" or "cvs edit" are sent.  The first entry on a line is
+# a regular expression which is tested against the directory that the
+# change is being made to, relative to the $CVSROOT.  If it matches,
+# then the remainder of the line is a filter program that should contain
+# one occurrence of %s for the user to notify, and information on its
+# standard input.
+#
+# "ALL" or "DEFAULT" can be used in place of the regular expression.
+#
+# You may specify a format string as part of the
+# filter.  The format characters are:
+#
+#   s = user being notified
+#   b = Bug identifier
+#   m = Message supplied on command line
+#   d = Date of action
+#   u = User performing the unedit
+#   t = tag or branch being edited
+#
+# For example:
+#ALL mail %s -s "CVS notification for bug %b"
diff --git a/test/repositories/cvs/CVSROOT/notify,v b/test/repositories/cvs/CVSROOT/notify,v
new file mode 100644 (file)
index 0000000..5edf2f5
--- /dev/null
@@ -0,0 +1,48 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "notify" file controls where notifications from watches set by
+# "cvs watch add" or "cvs edit" are sent.  The first entry on a line is
+# a regular expression which is tested against the directory that the
+# change is being made to, relative to the $CVSROOT.  If it matches,
+# then the remainder of the line is a filter program that should contain
+# one occurrence of %s for the user to notify, and information on its
+# standard input.
+#
+# "ALL" or "DEFAULT" can be used in place of the regular expression.
+#
+# You may specify a format string as part of the
+# filter.  The format characters are:
+#
+#   s = user being notified
+#   b = Bug identifier
+#   m = Message supplied on command line
+#   d = Date of action
+#   u = User performing the unedit
+#   t = tag or branch being edited
+#
+# For example:
+#ALL mail %s -s "CVS notification for bug %b"
+@
+
diff --git a/test/repositories/cvs/CVSROOT/notify_email b/test/repositories/cvs/CVSROOT/notify_email
new file mode 100644 (file)
index 0000000..c673ab4
--- /dev/null
@@ -0,0 +1,10 @@
+# The "notify_email" file is used to control templates for emails sent
+# when notifying users.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
diff --git a/test/repositories/cvs/CVSROOT/notify_email,v b/test/repositories/cvs/CVSROOT/notify_email,v
new file mode 100644 (file)
index 0000000..e9fcc95
--- /dev/null
@@ -0,0 +1,36 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "notify_email" file is used to control templates for emails sent
+# when notifying users.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+@
+
diff --git a/test/repositories/cvs/CVSROOT/postcommand b/test/repositories/cvs/CVSROOT/postcommand
new file mode 100644 (file)
index 0000000..ab3ef14
--- /dev/null
@@ -0,0 +1,14 @@
+# The "postcommand" file is run after a cvs command has finished.
+# The filter on the right is invoked with the repository name and
+# the name of the command that has been executed.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/postcommand,v b/test/repositories/cvs/CVSROOT/postcommand,v
new file mode 100644 (file)
index 0000000..b9a194b
--- /dev/null
@@ -0,0 +1,40 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "postcommand" file is run after a cvs command has finished.
+# The filter on the right is invoked with the repository name and
+# the name of the command that has been executed.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
+@
+
diff --git a/test/repositories/cvs/CVSROOT/postmodule b/test/repositories/cvs/CVSROOT/postmodule
new file mode 100644 (file)
index 0000000..b6d258b
--- /dev/null
@@ -0,0 +1,14 @@
+# The "postmodule" file is run after a cvs module is processed.
+# The filter on the right is invoked with the repository name, 
+# the name of the command that has been executed, and the module name.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/postmodule,v b/test/repositories/cvs/CVSROOT/postmodule,v
new file mode 100644 (file)
index 0000000..f70fae0
--- /dev/null
@@ -0,0 +1,40 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "postmodule" file is run after a cvs module is processed.
+# The filter on the right is invoked with the repository name, 
+# the name of the command that has been executed, and the module name.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
+@
+
diff --git a/test/repositories/cvs/CVSROOT/precommand b/test/repositories/cvs/CVSROOT/precommand
new file mode 100644 (file)
index 0000000..05c8b89
--- /dev/null
@@ -0,0 +1,18 @@
+# The "precommand" file is run before a cvs command is executed.
+# The filter on the right is invoked with the repository name and
+# the name of the command that has been executed.  A non-zero return
+# value with abort the command with an error.
+#
+# The standard input of the filter receives each command argument,
+# separated by linefeeds.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/precommand,v b/test/repositories/cvs/CVSROOT/precommand,v
new file mode 100644 (file)
index 0000000..f2a2db5
--- /dev/null
@@ -0,0 +1,44 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "precommand" file is run before a cvs command is executed.
+# The filter on the right is invoked with the repository name and
+# the name of the command that has been executed.  A non-zero return
+# value with abort the command with an error.
+#
+# The standard input of the filter receives each command argument,
+# separated by linefeeds.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
+@
+
diff --git a/test/repositories/cvs/CVSROOT/premodule b/test/repositories/cvs/CVSROOT/premodule
new file mode 100644 (file)
index 0000000..4837892
--- /dev/null
@@ -0,0 +1,15 @@
+# The "premodule" file is run before cvs module is processed.
+# The filter on the right is invoked with the repository name, 
+# the name of the command that has been executed, and the module name.
+# A non-zero return value with abort the command with an error.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/premodule,v b/test/repositories/cvs/CVSROOT/premodule,v
new file mode 100644 (file)
index 0000000..0d1b7c4
--- /dev/null
@@ -0,0 +1,41 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "premodule" file is run before cvs module is processed.
+# The filter on the right is invoked with the repository name, 
+# the name of the command that has been executed, and the module name.
+# A non-zero return value with abort the command with an error.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
+@
+
diff --git a/test/repositories/cvs/CVSROOT/rcsinfo b/test/repositories/cvs/CVSROOT/rcsinfo
new file mode 100644 (file)
index 0000000..27c002f
--- /dev/null
@@ -0,0 +1,10 @@
+# The "rcsinfo" file is used to control templates with which the editor
+# is invoked on commit and import.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
diff --git a/test/repositories/cvs/CVSROOT/rcsinfo,v b/test/repositories/cvs/CVSROOT/rcsinfo,v
new file mode 100644 (file)
index 0000000..a3c803d
--- /dev/null
@@ -0,0 +1,36 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "rcsinfo" file is used to control templates with which the editor
+# is invoked on commit and import.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+@
+
diff --git a/test/repositories/cvs/CVSROOT/shadow b/test/repositories/cvs/CVSROOT/shadow
new file mode 100644 (file)
index 0000000..7ccd24e
--- /dev/null
@@ -0,0 +1,7 @@
+# The "shadow" file is used to control automatic checkouts.
+#
+# Each line has 3 parts:
+# <module> <tag> <directory>
+#
+# In common with other commit support files, use forward slashes
+# and escape any spaces in filenames.
diff --git a/test/repositories/cvs/CVSROOT/shadow,v b/test/repositories/cvs/CVSROOT/shadow,v
new file mode 100644 (file)
index 0000000..a3c14d9
--- /dev/null
@@ -0,0 +1,33 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "shadow" file is used to control automatic checkouts.
+#
+# Each line has 3 parts:
+# <module> <tag> <directory>
+#
+# In common with other commit support files, use forward slashes
+# and escape any spaces in filenames.
+@
+
diff --git a/test/repositories/cvs/CVSROOT/tag_email b/test/repositories/cvs/CVSROOT/tag_email
new file mode 100644 (file)
index 0000000..48d545f
--- /dev/null
@@ -0,0 +1,10 @@
+# The "tag_email" file is used to control templates for emails sent
+# during tagging operations.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
diff --git a/test/repositories/cvs/CVSROOT/tag_email,v b/test/repositories/cvs/CVSROOT/tag_email,v
new file mode 100644 (file)
index 0000000..3fc9444
--- /dev/null
@@ -0,0 +1,36 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "tag_email" file is used to control templates for emails sent
+# during tagging operations.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being made to, relative to the
+# $CVSROOT.  For the first match that is found, then the remainder of the
+# line is the name of the file that contains the template.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+@
+
diff --git a/test/repositories/cvs/CVSROOT/taginfo b/test/repositories/cvs/CVSROOT/taginfo
new file mode 100644 (file)
index 0000000..3beb822
--- /dev/null
@@ -0,0 +1,21 @@
+# The "taginfo" file is used to control pre-tag checks.
+# The filter on the right is invoked with the following arguments:
+#
+# $1 -- tagname
+# $2 -- operation "add" for tag, "mov" for tag -F, and "del" for tag -d
+# $3 -- repository
+#
+# The filter is passed a series of filename/version pairs on its standard input
+#
+# A non-zero exit of the filter program will cause the tag to be aborted.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
diff --git a/test/repositories/cvs/CVSROOT/taginfo,v b/test/repositories/cvs/CVSROOT/taginfo,v
new file mode 100644 (file)
index 0000000..f511bab
--- /dev/null
@@ -0,0 +1,47 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "taginfo" file is used to control pre-tag checks.
+# The filter on the right is invoked with the following arguments:
+#
+# $1 -- tagname
+# $2 -- operation "add" for tag, "mov" for tag -F, and "del" for tag -d
+# $3 -- repository
+#
+# The filter is passed a series of filename/version pairs on its standard input
+#
+# A non-zero exit of the filter program will cause the tag to be aborted.
+#
+# The first entry on a line is a regular expression which is tested
+# against the directory that the change is being committed to, relative
+# to the $CVSROOT.  For the first match that is found, then the remainder
+# of the line is the name of the filter to run.
+#
+# If the repository name does not match any of the regular expressions in this
+# file, the "DEFAULT" line is used, if it is specified.
+#
+# If the name "ALL" appears as a regular expression it is always used
+# in addition to the first matching regex or "DEFAULT".
+@
+
diff --git a/test/repositories/cvs/CVSROOT/triggers b/test/repositories/cvs/CVSROOT/triggers
new file mode 100644 (file)
index 0000000..48a9fc3
--- /dev/null
@@ -0,0 +1,5 @@
+# The "triggers" file lists libraries which handle the events for each
+# module.
+#
+# In addition to the lines here, the default_trigger library is loaded, if available.
+#
diff --git a/test/repositories/cvs/CVSROOT/triggers,v b/test/repositories/cvs/CVSROOT/triggers,v
new file mode 100644 (file)
index 0000000..78a9e62
--- /dev/null
@@ -0,0 +1,31 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "triggers" file lists libraries which handle the events for each
+# module.
+#
+# In addition to the lines here, the default_trigger library is loaded, if available.
+#
+@
+
diff --git a/test/repositories/cvs/CVSROOT/val-tags b/test/repositories/cvs/CVSROOT/val-tags
new file mode 100644 (file)
index 0000000..6f25c26
--- /dev/null
@@ -0,0 +1,2 @@
+my_branch y
+another_branch y
diff --git a/test/repositories/cvs/CVSROOT/verifymsg b/test/repositories/cvs/CVSROOT/verifymsg
new file mode 100644 (file)
index 0000000..28b1d6e
--- /dev/null
@@ -0,0 +1,17 @@
+# The "verifymsg" file is used to allow verification of logging
+# information.  It works best when a template (as specified in the
+# rcsinfo file) is provided for the logging procedure.  Given a
+# template with locations for, a bug-id number, a list of people who
+# reviewed the code before it can be checked in, and an external
+# process to catalog the differences that were code reviewed, the
+# following test can be applied to the code:
+#
+#   Making sure that the entered bug-id number is correct.
+#   Validating that the code that was reviewed is indeed the code being
+#       checked in (using the bug-id number or a seperate review
+#       number to identify this particular code set.).
+#
+# If any of the above test failed, then the commit would be aborted.
+#
+# Actions such as mailing a copy of the report to each reviewer are
+# better handled by an entry in the loginfo file.
diff --git a/test/repositories/cvs/CVSROOT/verifymsg,v b/test/repositories/cvs/CVSROOT/verifymsg,v
new file mode 100644 (file)
index 0000000..78394d8
--- /dev/null
@@ -0,0 +1,43 @@
+head     1.1;
+access   ;
+symbols ;
+locks    ; strict;
+comment  @# @;
+
+
+1.1
+date     2006.06.29.16.19.58;  author robin;  state Exp;
+branches;
+next     ;
+kopt   kv;
+deltatype   text;
+permissions    664;
+
+desc
+@@
+
+
+
+1.1
+log
+@initial checkin@
+text
+@# The "verifymsg" file is used to allow verification of logging
+# information.  It works best when a template (as specified in the
+# rcsinfo file) is provided for the logging procedure.  Given a
+# template with locations for, a bug-id number, a list of people who
+# reviewed the code before it can be checked in, and an external
+# process to catalog the differences that were code reviewed, the
+# following test can be applied to the code:
+#
+#   Making sure that the entered bug-id number is correct.
+#   Validating that the code that was reviewed is indeed the code being
+#       checked in (using the bug-id number or a seperate review
+#       number to identify this particular code set.).
+#
+# If any of the above test failed, then the commit would be aborted.
+#
+# Actions such as mailing a copy of the report to each reviewer are
+# better handled by an entry in the loginfo file.
+@
+
diff --git a/test/repositories/cvs/simple/CVS/fileattr.xml b/test/repositories/cvs/simple/CVS/fileattr.xml
new file mode 100644 (file)
index 0000000..f8767f2
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fileattr>
+  <directory>
+    <owner>robin</owner>
+  </directory>
+</fileattr>
diff --git a/test/repositories/cvs/simple/another_file.rb,v b/test/repositories/cvs/simple/another_file.rb,v
new file mode 100644 (file)
index 0000000..638a8f7
--- /dev/null
@@ -0,0 +1,113 @@
+head   1.2;
+access;
+symbols
+       another_branch:1.1.0.2;
+locks; strict;
+comment        @# @;
+
+
+1.2
+date   2006.06.29.18.45.29;    author robin;   state Exp;
+branches;
+next   1.1;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       db744a41fc8a6ac;
+mergepoint1    1.1.2.2;
+filename       another_file.rb;
+
+1.1
+date   2006.06.29.18.44.27;    author robin;   state dead;
+branches
+       1.1.2.1;
+next   ;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       db044a41f8b01e1;
+filename       another_file.rb;
+
+1.1.2.1
+date   2006.06.29.18.44.27;    author robin;   state Exp;
+branches;
+next   1.1.2.2;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       db044a41f8b01e1;
+filename       another_file.rb;
+
+1.1.2.2
+date   2006.06.29.18.44.56;    author robin;   state Exp;
+branches;
+next   1.1.2.3;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       db244a41fa871cc;
+filename       another_file.rb;
+
+1.1.2.3
+date   2006.06.29.18.50.37;    author robin;   state Exp;
+branches;
+next   ;
+deltatype      text;
+kopt   kv;
+permissions    600;
+commitid       dfc44a420fde53f;
+mergepoint1    1.2;
+filename       another_file.rb;
+
+
+desc
+@@
+
+
+1.2
+log
+@merged another_file from another_branch onto the HEAD
+@
+text
+@puts "this file was created on another branch"
+puts "I edited this file on another branch only"
+@
+
+
+1.1
+log
+@file another_file.rb was initially added on branch another_branch.
+@
+text
+@d1 2
+@
+
+
+1.1.2.1
+log
+@created another_file on another_branch
+@
+text
+@a0 1
+puts "this file was created on another branch"
+@
+
+
+1.1.2.2
+log
+@edited another_file
+@
+text
+@a1 1
+puts "I edited this file on another branch only"
+@
+
+
+1.1.2.3
+log
+@merged from head onto another_branch. this added file late_addition
+@
+text
+@@
+
+
diff --git a/test/repositories/cvs/simple/foo.rb,v b/test/repositories/cvs/simple/foo.rb,v
new file mode 100644 (file)
index 0000000..6ed04b1
--- /dev/null
@@ -0,0 +1,50 @@
+head   1.1;
+branch 1.1.1;
+access;
+symbols
+       another_branch:1.1.1.1.0.4
+       my_branch:1.1.1.1.0.2
+       simple_release_tag:1.1.1.1
+       simple_vendor_tag:1.1.1;
+locks; strict;
+comment        @# @;
+
+
+1.1
+date   2006.06.29.16.21.07;    author robin;   state Exp;
+branches
+       1.1.1.1;
+next   ;
+deltatype      text;
+kopt   kv;
+permissions    644;
+
+1.1.1.1
+date   2006.06.29.16.21.07;    author robin;   state Exp;
+branches;
+next   ;
+deltatype      text;
+kopt   kv;
+permissions    644;
+
+
+desc
+@@
+
+
+
+1.1
+log
+@Initial revision
+@
+text
+@puts "Hi, I'm foo.rb!"
+@
+
+
+1.1.1.1
+log
+@Initial import.
+@
+text
+@@
diff --git a/test/repositories/cvs/simple/late_addition.rb,v b/test/repositories/cvs/simple/late_addition.rb,v
new file mode 100644 (file)
index 0000000..91b4c72
--- /dev/null
@@ -0,0 +1,93 @@
+head   1.2;
+access;
+symbols
+       another_branch:1.1.0.2;
+locks; strict;
+comment        @# @;
+
+
+1.2
+date   2006.06.29.18.52.23;    author robin;   state Exp;
+branches;
+next   1.1;
+deltatype      text;
+kopt   kv;
+permissions    600;
+commitid       e0544a421671465;
+mergepoint1    1.1.2.2;
+filename       late_addition.rb;
+
+1.1
+date   2006.06.29.18.48.54;    author robin;   state Exp;
+branches
+       1.1.2.1;
+next   ;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       df344a420967b0e;
+filename       late_addition.rb;
+
+1.1.2.1
+date   2006.06.29.18.50.37;    author robin;   state Exp;
+branches;
+next   1.1.2.2;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       dfc44a420fde53f;
+mergepoint1    1.1;
+filename       late_addition.rb;
+
+1.1.2.2
+date   2006.06.29.18.51.25;    author robin;   state Exp;
+branches;
+next   ;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       dff44a4212d348f;
+filename       late_addition.rb;
+
+
+desc
+@@
+
+
+1.2
+log
+@merged the change to late_addition from another_branch to the head
+@
+text
+@puts "This file was created on the HEAD after the branches were already made"
+puts "This line of code was added on another_branch after merging from the head"
+@
+
+
+1.1
+log
+@created late_addition on the HEAD
+@
+text
+@d2 1
+@
+
+
+1.1.2.1
+log
+@merged from head onto another_branch. this added file late_addition
+@
+text
+@@
+
+
+1.1.2.2
+log
+@edited late_addition on another_branch
+@
+text
+@a1 1
+puts "This line of code was added on another_branch after merging from the head"
+@
+
+
diff --git a/test/repositories/cvs/simple/new_file.rb,v b/test/repositories/cvs/simple/new_file.rb,v
new file mode 100644 (file)
index 0000000..72d4e57
--- /dev/null
@@ -0,0 +1,112 @@
+head   1.2;
+access;
+symbols
+       another_branch:1.2.0.2
+       my_branch:1.1.0.2;
+locks; strict;
+comment        @# @;
+
+
+1.2
+date   2006.06.29.18.14.47;    author robin;   state Exp;
+branches;
+next   1.1;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       c3944a41896430e;
+mergepoint1    1.1.2.1;
+filename       new_file.rb;
+
+1.1
+date   2006.06.29.18.05.27;    author robin;   state dead;
+branches
+       1.1.2.1;
+next   ;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       bd144a41667e765;
+filename       new_file.rb;
+
+1.1.2.1
+date   2006.06.29.18.05.27;    author robin;   state Exp;
+branches;
+next   1.1.2.2;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       bd144a41667e765;
+filename       new_file.rb;
+
+1.1.2.2
+date   2006.06.29.18.17.49;    author robin;   state Exp;
+branches;
+next   1.1.2.3;
+deltatype      text;
+kopt   kv;
+permissions    644;
+commitid       c7744a4194cefc8;
+filename       new_file.rb;
+
+1.1.2.3
+date   2006.06.29.18.40.38;    author robin;   state dead;
+branches;
+next   ;
+deltatype      text;
+kopt   kv;
+permissions    444;
+commitid       d4e44a41ea6477e;
+filename       new_file.rb;
+
+
+desc
+@@
+
+
+1.2
+log
+@merged new_file.rb from branch onto the HEAD
+@
+text
+@puts "I was created on the branch"
+@
+
+
+1.1
+log
+@file new_file.rb was initially added on branch my_branch.
+@
+text
+@d1 1
+@
+
+
+1.1.2.1
+log
+@added new_file.rb on the branch
+@
+text
+@a0 1
+puts "I was created on the branch"
+@
+
+
+1.1.2.2
+log
+@modifed new_file.rb on the branch only
+@
+text
+@a1 1
+puts "This file was edited on the branch after the orginal was merged onto the HEAD"
+@
+
+
+1.1.2.3
+log
+@removed new_file.rb from the branch only
+@
+text
+@@
+
+
diff --git a/test/repositories/deep_svn/README.txt b/test/repositories/deep_svn/README.txt
new file mode 100644 (file)
index 0000000..3bf5a57
--- /dev/null
@@ -0,0 +1,5 @@
+This is a Subversion repository; use the 'svnadmin' tool to examine
+it.  Do not add, delete, or modify files here unless you know how
+to avoid corrupting the repository.
+
+Visit http://subversion.tigris.org/ for more information.
diff --git a/test/repositories/deep_svn/conf/authz b/test/repositories/deep_svn/conf/authz
new file mode 100644 (file)
index 0000000..78cb28e
--- /dev/null
@@ -0,0 +1,21 @@
+### This file is an example authorization file for svnserve.
+### Its format is identical to that of mod_authz_svn authorization
+### files.
+### As shown below each section defines authorizations for the path and
+### (optional) repository specified by the section name.
+### The authorizations follow. An authorization line can refer to a
+### single user, to a group of users defined in a special [groups]
+### section, or to anyone using the '*' wildcard.  Each definition can
+### grant read ('r') access, read-write ('rw') access, or no access
+### ('').
+
+[groups]
+# harry_and_sally = harry,sally
+
+# [/foo/bar]
+# harry = rw
+# * =
+
+# [repository:/baz/fuz]
+# @harry_and_sally = rw
+# * = r
diff --git a/test/repositories/deep_svn/conf/passwd b/test/repositories/deep_svn/conf/passwd
new file mode 100644 (file)
index 0000000..ecaa08d
--- /dev/null
@@ -0,0 +1,8 @@
+### This file is an example password file for svnserve.
+### Its format is similar to that of svnserve.conf. As shown in the
+### example below it contains one section labelled [users].
+### The name and password for each user follow, one account per line.
+
+[users]
+# harry = harryssecret
+# sally = sallyssecret
diff --git a/test/repositories/deep_svn/conf/svnserve.conf b/test/repositories/deep_svn/conf/svnserve.conf
new file mode 100644 (file)
index 0000000..b52bc5a
--- /dev/null
@@ -0,0 +1,30 @@
+### This file controls the configuration of the svnserve daemon, if you
+### use it to allow access to this repository.  (If you only allow
+### access through http: and/or file: URLs, then this file is
+### irrelevant.)
+
+### Visit http://subversion.tigris.org/ for more information.
+
+[general]
+### These options control access to the repository for unauthenticated
+### and authenticated users.  Valid values are "write", "read",
+### and "none".  The sample settings below are the defaults.
+# anon-access = read
+# auth-access = write
+### The password-db option controls the location of the password
+### database file.  Unless you specify a path starting with a /,
+### the file's location is relative to the conf directory.
+### Uncomment the line below to use the default password file.
+# password-db = passwd
+### The authz-db option controls the location of the authorization
+### rules for path-based access control.  Unless you specify a path
+### starting with a /, the file's location is relative to the conf
+### directory.  If you don't specify an authz-db, no path-based access
+### control is done.
+### Uncomment the line below to use the default authorization file.
+# authz-db = authz
+### This option specifies the authentication realm of the repository.
+### If two repositories have the same authentication realm, they should
+### have the same password database, and vice versa.  The default realm
+### is repository's uuid.
+# realm = My First Repository
diff --git a/test/repositories/deep_svn/db/current b/test/repositories/deep_svn/db/current
new file mode 100644 (file)
index 0000000..270762e
--- /dev/null
@@ -0,0 +1 @@
+4 8 3
diff --git a/test/repositories/deep_svn/db/format b/test/repositories/deep_svn/db/format
new file mode 100644 (file)
index 0000000..0cfbf08
--- /dev/null
@@ -0,0 +1 @@
+2
diff --git a/test/repositories/deep_svn/db/fs-type b/test/repositories/deep_svn/db/fs-type
new file mode 100644 (file)
index 0000000..4fdd953
--- /dev/null
@@ -0,0 +1 @@
+fsfs
diff --git a/test/repositories/deep_svn/db/revprops/0 b/test/repositories/deep_svn/db/revprops/0
new file mode 100644 (file)
index 0000000..bd511d2
--- /dev/null
@@ -0,0 +1,5 @@
+K 8
+svn:date
+V 27
+2009-01-14T16:05:58.755928Z
+END
diff --git a/test/repositories/deep_svn/db/revprops/1 b/test/repositories/deep_svn/db/revprops/1
new file mode 100644 (file)
index 0000000..24e4f12
--- /dev/null
@@ -0,0 +1,14 @@
+K 10
+svn:author
+V 5
+robin
+K 8
+svn:date
+V 27
+2009-01-14T16:07:40.471018Z
+K 7
+svn:log
+V 17
+Initial Revision
+
+END
diff --git a/test/repositories/deep_svn/db/revprops/2 b/test/repositories/deep_svn/db/revprops/2
new file mode 100644 (file)
index 0000000..7e7349e
--- /dev/null
@@ -0,0 +1,14 @@
+K 10
+svn:author
+V 5
+robin
+K 8
+svn:date
+V 27
+2009-01-14T16:16:56.442589Z
+K 7
+svn:log
+V 21
+Delete the old trunk
+
+END
diff --git a/test/repositories/deep_svn/db/revprops/3 b/test/repositories/deep_svn/db/revprops/3
new file mode 100644 (file)
index 0000000..9299f2a
--- /dev/null
@@ -0,0 +1,14 @@
+K 10
+svn:author
+V 5
+robin
+K 8
+svn:date
+V 27
+2009-01-14T16:17:29.977940Z
+K 7
+svn:log
+V 32
+Replace the trunk with a branch
+
+END
diff --git a/test/repositories/deep_svn/db/revprops/4 b/test/repositories/deep_svn/db/revprops/4
new file mode 100644 (file)
index 0000000..be84872
--- /dev/null
@@ -0,0 +1,14 @@
+K 10
+svn:author
+V 5
+robin
+K 8
+svn:date
+V 27
+2009-01-14T16:18:25.528386Z
+K 7
+svn:log
+V 23
+Rename a subdirectory.
+
+END
diff --git a/test/repositories/deep_svn/db/revs/0 b/test/repositories/deep_svn/db/revs/0
new file mode 100644 (file)
index 0000000..10f5c45
--- /dev/null
@@ -0,0 +1,11 @@
+PLAIN
+END
+ENDREP
+id: 0.0.r0/17
+type: dir
+count: 0
+text: 0 0 4 4 2d2977d1c96f487abe4a1e202dd03b4e
+cpath: /
+
+
+17 107
diff --git a/test/repositories/deep_svn/db/revs/1 b/test/repositories/deep_svn/db/revs/1
new file mode 100644 (file)
index 0000000..0569294
--- /dev/null
@@ -0,0 +1,122 @@
+DELTA
+SVN\ 1ENDREP
+DELTA
+SVN\ 1ENDREP
+DELTA
+SVN\ 1ENDREP
+id: 7.0.r1/51
+type: file
+count: 0
+text: 1 0 4 0 d41d8cd98f00b204e9800998ecf8427e
+cpath: /trunk/old_file.rb
+copyroot: 0 /
+
+PLAIN
+K 11
+old_file.rb
+V 14
+file 7.0.r1/51
+END
+ENDREP
+id: 6.0.r1/227
+type: dir
+count: 0
+text: 1 173 41 41 63b852a950801663e65e081caea7602d
+cpath: /trunk
+copyroot: 0 /
+
+id: 5.0.r1/341
+type: file
+count: 0
+text: 1 17 4 0 d41d8cd98f00b204e9800998ecf8427e
+cpath: /branches/b/subdir/foo.rb
+copyroot: 0 /
+
+id: 4.0.r1/472
+type: file
+count: 0
+text: 1 34 4 0 d41d8cd98f00b204e9800998ecf8427e
+cpath: /branches/b/subdir/bar.rb
+copyroot: 0 /
+
+PLAIN
+K 6
+bar.rb
+V 15
+file 4.0.r1/472
+K 6
+foo.rb
+V 15
+file 5.0.r1/341
+END
+ENDREP
+id: 3.0.r1/684
+type: dir
+count: 0
+text: 1 603 68 68 6aaba93c23a2dabf6a6f95f54b0a7d5a
+cpath: /branches/b/subdir
+copyroot: 0 /
+
+PLAIN
+K 6
+subdir
+V 14
+dir 3.0.r1/684
+END
+ENDREP
+id: 2.0.r1/858
+type: dir
+count: 0
+text: 1 810 35 35 b12ae5d0f9a9744de382d115f90562e2
+cpath: /branches/b
+copyroot: 0 /
+
+PLAIN
+K 1
+b
+V 14
+dir 2.0.r1/858
+END
+ENDREP
+id: 1.0.r1/1020
+type: dir
+count: 0
+text: 1 977 30 30 447b66a3170c93068d9ab9cf65c63c55
+cpath: /branches
+copyroot: 0 /
+
+PLAIN
+K 8
+branches
+V 15
+dir 1.0.r1/1020
+K 5
+trunk
+V 14
+dir 6.0.r1/227
+END
+ENDREP
+id: 0.0.r1/1219
+type: dir
+pred: 0.0.r0/17
+count: 1
+text: 1 1138 68 68 fd28449b68d85ed021460dd72fa4c392
+cpath: /
+copyroot: 0 /
+
+_6.0.t0-1 add true false /trunk/old_file.rb
+
+_4.0.t0-1 add true false /branches/b/subdir/foo.rb
+
+_5.0.t0-1 add false false /trunk
+
+_3.0.t0-1 add true false /branches/b/subdir/bar.rb
+
+_0.0.t0-1 add false false /branches
+
+_1.0.t0-1 add false false /branches/b
+
+_2.0.t0-1 add false false /branches/b/subdir
+
+
+1219 1346
diff --git a/test/repositories/deep_svn/db/revs/2 b/test/repositories/deep_svn/db/revs/2
new file mode 100644 (file)
index 0000000..e7ca0f1
--- /dev/null
@@ -0,0 +1,19 @@
+PLAIN
+K 8
+branches
+V 15
+dir 1.0.r1/1020
+END
+ENDREP
+id: 0.0.r2/51
+type: dir
+pred: 0.0.r1/1219
+count: 2
+text: 2 0 38 38 c733c53690687023e18fd0245000d5bd
+cpath: /
+copyroot: 0 /
+
+6.0.r1/227 delete false false /trunk
+
+
+51 175
diff --git a/test/repositories/deep_svn/db/revs/3 b/test/repositories/deep_svn/db/revs/3
new file mode 100644 (file)
index 0000000..b531d59
--- /dev/null
@@ -0,0 +1,44 @@
+id: 2.1.r3/0
+type: dir
+pred: 2.0.r1/858
+count: 1
+text: 1 810 35 35 b12ae5d0f9a9744de382d115f90562e2
+cpath: /trunk
+copyfrom: 2 /branches/b
+
+PLAIN
+END
+ENDREP
+id: 1.0.r3/156
+type: dir
+pred: 1.0.r1/1020
+count: 1
+text: 3 139 4 4 2d2977d1c96f487abe4a1e202dd03b4e
+cpath: /branches
+copyroot: 0 /
+
+PLAIN
+K 8
+branches
+V 14
+dir 1.0.r3/156
+K 5
+trunk
+V 12
+dir 2.1.r3/0
+END
+ENDREP
+id: 0.0.r3/367
+type: dir
+pred: 0.0.r2/51
+count: 3
+text: 3 289 65 65 d5facf43cc213a293424d83b651f6306
+cpath: /
+copyroot: 0 /
+
+2._0.t2-1 add false false /trunk
+2 /branches/b
+2.0.r1/858 delete false false /branches/b
+
+
+367 492
diff --git a/test/repositories/deep_svn/db/revs/4 b/test/repositories/deep_svn/db/revs/4
new file mode 100644 (file)
index 0000000..485f33e
--- /dev/null
@@ -0,0 +1,48 @@
+id: 3.2.r4/0
+type: dir
+pred: 3.0.r1/684
+count: 1
+text: 1 603 68 68 6aaba93c23a2dabf6a6f95f54b0a7d5a
+cpath: /trunk/newdir
+copyfrom: 3 /trunk/subdir
+
+PLAIN
+K 6
+newdir
+V 12
+dir 3.2.r4/0
+END
+ENDREP
+id: 2.1.r4/194
+type: dir
+pred: 2.1.r3/0
+count: 2
+text: 4 148 33 33 7cde7a8425af32fbe5f76c5240f43c74
+cpath: /trunk
+copyroot: 3 /trunk
+
+PLAIN
+K 8
+branches
+V 14
+dir 1.0.r3/156
+K 5
+trunk
+V 14
+dir 2.1.r4/194
+END
+ENDREP
+id: 0.0.r4/408
+type: dir
+pred: 0.0.r3/367
+count: 4
+text: 4 328 67 67 3818dc120a6ec64e2453306ab5a827f3
+cpath: /
+copyroot: 0 /
+
+3._0.t3-1 add false false /trunk/newdir
+3 /trunk/subdir
+3.0.r1/684 delete false false /trunk/subdir
+
+
+408 534
diff --git a/test/repositories/deep_svn/db/uuid b/test/repositories/deep_svn/db/uuid
new file mode 100644 (file)
index 0000000..7ae337a
--- /dev/null
@@ -0,0 +1 @@
+8ab099c8-7f08-4752-91f3-1a221f1f3b2b
diff --git a/test/repositories/deep_svn/db/write-lock b/test/repositories/deep_svn/db/write-lock
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/repositories/deep_svn/format b/test/repositories/deep_svn/format
new file mode 100644 (file)
index 0000000..7ed6ff8
--- /dev/null
@@ -0,0 +1 @@
+5
diff --git a/test/repositories/deep_svn/hooks/post-commit.tmpl b/test/repositories/deep_svn/hooks/post-commit.tmpl
new file mode 100644 (file)
index 0000000..b8345c6
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+# POST-COMMIT HOOK
+#
+# The post-commit hook is invoked after a commit.  Subversion runs
+# this hook by invoking a program (script, executable, binary, etc.)
+# named 'post-commit' (for which this file is a template) with the 
+# following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] REV          (the number of the revision just committed)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# Because the commit has already completed and cannot be undone,
+# the exit code of the hook program is ignored.  The hook program
+# can use the 'svnlook' utility to help it examine the
+# newly-committed tree.
+#
+# On a Unix system, the normal procedure is to have 'post-commit'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'post-commit' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'post-commit.bat' or 'post-commit.exe',
+# but the basic idea is the same.
+# 
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+REV="$2"
+
+commit-email.pl "$REPOS" "$REV" commit-watchers@example.org
+log-commit.py --repository "$REPOS" --revision "$REV"
diff --git a/test/repositories/deep_svn/hooks/post-lock.tmpl b/test/repositories/deep_svn/hooks/post-lock.tmpl
new file mode 100644 (file)
index 0000000..c779f11
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# POST-LOCK HOOK
+#
+# The post-lock hook is run after a path is locked.  Subversion runs
+# this hook by invoking a program (script, executable, binary, etc.)
+# named 'post-lock' (for which this file is a template) with the 
+# following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] USER         (the user who created the lock)
+#
+# The paths that were just locked are passed to the hook via STDIN (as
+# of Subversion 1.2, only one path is passed per invocation, but the
+# plan is to pass all locked paths at once, so the hook program
+# should be written accordingly).
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# Because the lock has already been created and cannot be undone,
+# the exit code of the hook program is ignored.  The hook program
+# can use the 'svnlook' utility to help it examine the
+# newly-created lock.
+#
+# On a Unix system, the normal procedure is to have 'post-lock'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'post-lock' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'post-lock.bat' or 'post-lock.exe',
+# but the basic idea is the same.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter:
+
+REPOS="$1"
+USER="$2"
+
+# Send email to interested parties, let them know a lock was created:
+mailer.py lock "$REPOS" "$USER" /path/to/mailer.conf
diff --git a/test/repositories/deep_svn/hooks/post-revprop-change.tmpl b/test/repositories/deep_svn/hooks/post-revprop-change.tmpl
new file mode 100644 (file)
index 0000000..2ed8b9a
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# POST-REVPROP-CHANGE HOOK
+#
+# The post-revprop-change hook is invoked after a revision property
+# has been added, modified or deleted.  Subversion runs this hook by
+# invoking a program (script, executable, binary, etc.) named
+# 'post-revprop-change' (for which this file is a template), with the
+# following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] REV          (the revision that was tweaked)
+#   [3] USER         (the username of the person tweaking the property)
+#   [4] PROPNAME     (the property that was changed)
+#   [5] ACTION       (the property was 'A'dded, 'M'odified, or 'D'eleted)
+#
+#   [STDIN] PROPVAL  ** the old property value is passed via STDIN.
+#
+# Because the propchange has already completed and cannot be undone,
+# the exit code of the hook program is ignored.  The hook program
+# can use the 'svnlook' utility to help it examine the
+# new property value.
+#
+# On a Unix system, the normal procedure is to have 'post-revprop-change'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'post-revprop-change' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'post-revprop-change.bat' or 'post-revprop-change.exe',
+# but the basic idea is the same.
+# 
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+REV="$2"
+USER="$3"
+PROPNAME="$4"
+ACTION="$5"
+
+propchange-email.pl "$REPOS" "$REV" "$USER" "$PROPNAME" watchers@example.org
diff --git a/test/repositories/deep_svn/hooks/post-unlock.tmpl b/test/repositories/deep_svn/hooks/post-unlock.tmpl
new file mode 100644 (file)
index 0000000..ae95c4b
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+# POST-UNLOCK HOOK
+#
+# The post-unlock hook runs after a path is unlocked.  Subversion runs
+# this hook by invoking a program (script, executable, binary, etc.)
+# named 'post-unlock' (for which this file is a template) with the 
+# following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] USER         (the user who destroyed the lock)
+#
+# The paths that were just unlocked are passed to the hook via STDIN
+# (as of Subversion 1.2, only one path is passed per invocation, but
+# the plan is to pass all unlocked paths at once, so the hook program
+# should be written accordingly).
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# Because the lock has already been destroyed and cannot be undone,
+# the exit code of the hook program is ignored.
+#
+# On a Unix system, the normal procedure is to have 'post-unlock'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'post-unlock' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'post-unlock.bat' or 'post-unlock.exe',
+# but the basic idea is the same.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter:
+
+REPOS="$1"
+USER="$2"
+
+# Send email to interested parties, let them know a lock was removed:
+mailer.py unlock "$REPOS" "$USER" /path/to/mailer.conf
diff --git a/test/repositories/deep_svn/hooks/pre-commit.tmpl b/test/repositories/deep_svn/hooks/pre-commit.tmpl
new file mode 100644 (file)
index 0000000..7444c51
--- /dev/null
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+# PRE-COMMIT HOOK
+#
+# The pre-commit hook is invoked before a Subversion txn is
+# committed.  Subversion runs this hook by invoking a program
+# (script, executable, binary, etc.) named 'pre-commit' (for which
+# this file is a template), with the following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] TXN-NAME     (the name of the txn about to be committed)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# If the hook program exits with success, the txn is committed; but
+# if it exits with failure (non-zero), the txn is aborted, no commit
+# takes place, and STDERR is returned to the client.   The hook
+# program can use the 'svnlook' utility to help it examine the txn.
+#
+# On a Unix system, the normal procedure is to have 'pre-commit'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+#   ***  NOTE: THE HOOK PROGRAM MUST NOT MODIFY THE TXN, EXCEPT  ***
+#   ***  FOR REVISION PROPERTIES (like svn:log or svn:author).   ***
+#
+#   This is why we recommend using the read-only 'svnlook' utility.
+#   In the future, Subversion may enforce the rule that pre-commit
+#   hooks should not modify the versioned data in txns, or else come
+#   up with a mechanism to make it safe to do so (by informing the
+#   committing client of the changes).  However, right now neither
+#   mechanism is implemented, so hook writers just have to be careful.
+#
+# Note that 'pre-commit' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'pre-commit.bat' or 'pre-commit.exe',
+# but the basic idea is the same.
+#
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+TXN="$2"
+
+# Make sure that the log message contains some text.
+SVNLOOK=/opt/local/bin/svnlook
+$SVNLOOK log -t "$TXN" "$REPOS" | \
+   grep "[a-zA-Z0-9]" > /dev/null || exit 1
+
+# Check that the author of this commit has the rights to perform
+# the commit on the files and directories being modified.
+commit-access-control.pl "$REPOS" "$TXN" commit-access-control.cfg || exit 1
+
+# All checks passed, so allow the commit.
+exit 0
diff --git a/test/repositories/deep_svn/hooks/pre-lock.tmpl b/test/repositories/deep_svn/hooks/pre-lock.tmpl
new file mode 100644 (file)
index 0000000..020d172
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+# PRE-LOCK HOOK
+#
+# The pre-lock hook is invoked before an exclusive lock is
+# created.  Subversion runs this hook by invoking a program 
+# (script, executable, binary, etc.) named 'pre-lock' (for which
+# this file is a template), with the following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] PATH         (the path in the repository about to be locked)
+#   [3] USER         (the user creating the lock)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# If the hook program exits with success, the lock is created; but
+# if it exits with failure (non-zero), the lock action is aborted
+# and STDERR is returned to the client.
+
+# On a Unix system, the normal procedure is to have 'pre-lock'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'pre-lock' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'pre-lock.bat' or 'pre-lock.exe',
+# but the basic idea is the same.
+#
+# Here is an example hook script, for a Unix /bin/sh interpreter:
+
+REPOS="$1"
+PATH="$2"
+USER="$3"
+
+# If a lock exists and is owned by a different person, don't allow it
+# to be stolen (e.g., with 'svn lock --force ...').
+
+# (Maybe this script could send email to the lock owner?)
+SVNLOOK=/opt/local/bin/svnlook
+GREP=/bin/grep
+SED=/bin/sed
+
+LOCK_OWNER=`$SVNLOOK lock "$REPOS" "$PATH" | \
+            $GREP '^Owner: ' | $SED 's/Owner: //'`
+
+# If we get no result from svnlook, there's no lock, allow the lock to
+# happen:
+if [ "$LOCK_OWNER" = "" ]; then
+  exit 0
+fi
+
+# If the person locking matches the lock's owner, allow the lock to
+# happen:
+if [ "$LOCK_OWNER" = "$USER" ]; then
+  exit 0
+fi
+
+# Otherwise, we've got an owner mismatch, so return failure:
+echo "Error: $PATH already locked by ${LOCK_OWNER}." 1>&2
+exit 1
diff --git a/test/repositories/deep_svn/hooks/pre-revprop-change.tmpl b/test/repositories/deep_svn/hooks/pre-revprop-change.tmpl
new file mode 100644 (file)
index 0000000..2f2de98
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+# PRE-REVPROP-CHANGE HOOK
+#
+# The pre-revprop-change hook is invoked before a revision property
+# is added, modified or deleted.  Subversion runs this hook by invoking
+# a program (script, executable, binary, etc.) named 'pre-revprop-change'
+# (for which this file is a template), with the following ordered
+# arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] REVISION     (the revision being tweaked)
+#   [3] USER         (the username of the person tweaking the property)
+#   [4] PROPNAME     (the property being set on the revision)
+#   [5] ACTION       (the property is being 'A'dded, 'M'odified, or 'D'eleted)
+#
+#   [STDIN] PROPVAL  ** the new property value is passed via STDIN.
+#
+# If the hook program exits with success, the propchange happens; but
+# if it exits with failure (non-zero), the propchange doesn't happen.
+# The hook program can use the 'svnlook' utility to examine the 
+# existing value of the revision property.
+#
+# WARNING: unlike other hooks, this hook MUST exist for revision
+# properties to be changed.  If the hook does not exist, Subversion 
+# will behave as if the hook were present, but failed.  The reason
+# for this is that revision properties are UNVERSIONED, meaning that
+# a successful propchange is destructive;  the old value is gone
+# forever.  We recommend the hook back up the old value somewhere.
+#
+# On a Unix system, the normal procedure is to have 'pre-revprop-change'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'pre-revprop-change' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'pre-revprop-change.bat' or 'pre-revprop-change.exe',
+# but the basic idea is the same.
+#
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+REV="$2"
+USER="$3"
+PROPNAME="$4"
+ACTION="$5"
+
+if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
+
+echo "Changing revision properties other than svn:log is prohibited" >&2
+exit 1
diff --git a/test/repositories/deep_svn/hooks/pre-unlock.tmpl b/test/repositories/deep_svn/hooks/pre-unlock.tmpl
new file mode 100644 (file)
index 0000000..795f55f
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+# PRE-UNLOCK HOOK
+#
+# The pre-unlock hook is invoked before an exclusive lock is
+# destroyed.  Subversion runs this hook by invoking a program 
+# (script, executable, binary, etc.) named 'pre-unlock' (for which
+# this file is a template), with the following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] PATH         (the path in the repository about to be unlocked)
+#   [3] USER         (the user destroying the lock)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# If the hook program exits with success, the lock is destroyed; but
+# if it exits with failure (non-zero), the unlock action is aborted
+# and STDERR is returned to the client.
+
+# On a Unix system, the normal procedure is to have 'pre-unlock'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'pre-unlock' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'pre-unlock.bat' or 'pre-unlock.exe',
+# but the basic idea is the same.
+#
+# Here is an example hook script, for a Unix /bin/sh interpreter:
+
+REPOS="$1"
+PATH="$2"
+USER="$3"
+
+# If a lock is owned by a different person, don't allow it be broken.
+# (Maybe this script could send email to the lock owner?)
+
+SVNLOOK=/opt/local/bin/svnlook
+GREP=/bin/grep
+SED=/bin/sed
+
+LOCK_OWNER=`$SVNLOOK lock "$REPOS" "$PATH" | \
+            $GREP '^Owner: ' | $SED 's/Owner: //'`
+
+# If we get no result from svnlook, there's no lock, return success:
+if [ "$LOCK_OWNER" = "" ]; then
+  exit 0
+fi
+# If the person unlocking matches the lock's owner, return success:
+if [ "$LOCK_OWNER" = "$USER" ]; then
+  exit 0
+fi
+
+# Otherwise, we've got an owner mismatch, so return failure:
+echo "Error: $PATH locked by ${LOCK_OWNER}." 1>&2
+exit 1
diff --git a/test/repositories/deep_svn/hooks/start-commit.tmpl b/test/repositories/deep_svn/hooks/start-commit.tmpl
new file mode 100644 (file)
index 0000000..348d706
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# START-COMMIT HOOK
+#
+# The start-commit hook is invoked before a Subversion txn is created
+# in the process of doing a commit.  Subversion runs this hook
+# by invoking a program (script, executable, binary, etc.) named
+# 'start-commit' (for which this file is a template)
+# with the following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] USER         (the authenticated user attempting to commit)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# If the hook program exits with success, the commit continues; but
+# if it exits with failure (non-zero), the commit is stopped before
+# a Subversion txn is created, and STDERR is returned to the client.
+#
+# On a Unix system, the normal procedure is to have 'start-commit'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'start-commit' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'start-commit.bat' or 'start-commit.exe',
+# but the basic idea is the same.
+# 
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+USER="$2"
+
+commit-allower.pl --repository "$REPOS" --user "$USER" || exit 1
+special-auth-check.py --user "$USER" --auth-level 3 || exit 1
+
+# All checks passed, so allow the commit.
+exit 0
diff --git a/test/repositories/deep_svn/locks/db-logs.lock b/test/repositories/deep_svn/locks/db-logs.lock
new file mode 100644 (file)
index 0000000..20dd636
--- /dev/null
@@ -0,0 +1,3 @@
+This file is not used by Subversion 1.3.x or later.
+However, its existence is required for compatibility with
+Subversion 1.2.x or earlier.
diff --git a/test/repositories/deep_svn/locks/db.lock b/test/repositories/deep_svn/locks/db.lock
new file mode 100644 (file)
index 0000000..20dd636
--- /dev/null
@@ -0,0 +1,3 @@
+This file is not used by Subversion 1.3.x or later.
+However, its existence is required for compatibility with
+Subversion 1.2.x or earlier.
diff --git a/test/repositories/git.tgz b/test/repositories/git.tgz
new file mode 100644 (file)
index 0000000..5ff058f
Binary files /dev/null and b/test/repositories/git.tgz differ
diff --git a/test/repositories/svn/README.txt b/test/repositories/svn/README.txt
new file mode 100644 (file)
index 0000000..3bf5a57
--- /dev/null
@@ -0,0 +1,5 @@
+This is a Subversion repository; use the 'svnadmin' tool to examine
+it.  Do not add, delete, or modify files here unless you know how
+to avoid corrupting the repository.
+
+Visit http://subversion.tigris.org/ for more information.
diff --git a/test/repositories/svn/conf/authz b/test/repositories/svn/conf/authz
new file mode 100644 (file)
index 0000000..29272af
--- /dev/null
@@ -0,0 +1,21 @@
+### This file is an example authorization file for svnserve.
+### Its format is identical to that of mod_authz_svn authorization
+### files.
+### As shown below each section defines authorizations for the path and
+### (optional) repository specified by the section name.
+### The authorizations follow. An authorization line can refer to a
+### single user, to a group of users defined in a special [groups]
+### section, or to anyone using the '*' wildcard.  Each definition can
+### grant read ('r') access, read-write ('rw') access, or no access
+### ('').
+
+# [groups]
+# harry_and_sally = harry,sally
+
+# [/foo/bar]
+# harry = rw
+# * =
+
+# [repository:/baz/fuz]
+# @harry_and_sally = rw
+# * = r
diff --git a/test/repositories/svn/conf/passwd b/test/repositories/svn/conf/passwd
new file mode 100644 (file)
index 0000000..f18ac15
--- /dev/null
@@ -0,0 +1,8 @@
+### This file is an example password file for svnserve.
+### Its format is similar to that of svnserve.conf. As shown in the
+### example below it contains one section labelled [users].
+### The name and password for each user follow, one account per line.
+
+# [users]
+# harry = harryssecret
+# sally = sallyssecret
diff --git a/test/repositories/svn/conf/svnserve.conf b/test/repositories/svn/conf/svnserve.conf
new file mode 100644 (file)
index 0000000..41d9e94
--- /dev/null
@@ -0,0 +1,30 @@
+### This file controls the configuration of the svnserve daemon, if you
+### use it to allow access to this repository.  (If you only allow
+### access through http: and/or file: URLs, then this file is
+### irrelevant.)
+
+### Visit http://subversion.tigris.org/ for more information.
+
+# [general]
+### These options control access to the repository for unauthenticated
+### and authenticated users.  Valid values are "write", "read",
+### and "none".  The sample settings below are the defaults.
+# anon-access = read
+# auth-access = write
+### The password-db option controls the location of the password
+### database file.  Unless you specify a path starting with a /,
+### the file's location is relative to the conf directory.
+### Uncomment the line below to use the default password file.
+# password-db = passwd
+### The authz-db option controls the location of the authorization
+### rules for path-based access control.  Unless you specify a path
+### starting with a /, the file's location is relative to the conf
+### directory.  If you don't specify an authz-db, no path-based access
+### control is done.
+### Uncomment the line below to use the default authorization file.
+# authz-db = authz
+### This option specifies the authentication realm of the repository.
+### If two repositories have the same authentication realm, they should
+### have the same password database, and vice versa.  The default realm
+### is repository's uuid.
+# realm = My First Repository
diff --git a/test/repositories/svn/db/current b/test/repositories/svn/db/current
new file mode 100644 (file)
index 0000000..1331681
--- /dev/null
@@ -0,0 +1 @@
+5 8 2
diff --git a/test/repositories/svn/db/format b/test/repositories/svn/db/format
new file mode 100644 (file)
index 0000000..d00491f
--- /dev/null
@@ -0,0 +1 @@
+1
diff --git a/test/repositories/svn/db/fs-type b/test/repositories/svn/db/fs-type
new file mode 100644 (file)
index 0000000..4fdd953
--- /dev/null
@@ -0,0 +1 @@
+fsfs
diff --git a/test/repositories/svn/db/revprops/0 b/test/repositories/svn/db/revprops/0
new file mode 100644 (file)
index 0000000..853fc3c
--- /dev/null
@@ -0,0 +1,5 @@
+K 8
+svn:date
+V 27
+2006-06-11T18:24:23.808631Z
+END
diff --git a/test/repositories/svn/db/revprops/1 b/test/repositories/svn/db/revprops/1
new file mode 100644 (file)
index 0000000..dcbcbe1
--- /dev/null
@@ -0,0 +1,14 @@
+K 10
+svn:author
+V 5
+robin
+K 8
+svn:date
+V 27
+2006-06-11T18:28:00.288498Z
+K 7
+svn:log
+V 16
+Initial Checkin
+
+END
diff --git a/test/repositories/svn/db/revprops/2 b/test/repositories/svn/db/revprops/2
new file mode 100644 (file)
index 0000000..5446866
--- /dev/null
@@ -0,0 +1,13 @@
+K 10
+svn:author
+V 5
+robin
+K 8
+svn:date
+V 27
+2006-06-11T18:32:13.933350Z
+K 7
+svn:log
+V 14
+added makefile
+END
diff --git a/test/repositories/svn/db/revprops/3 b/test/repositories/svn/db/revprops/3
new file mode 100644 (file)
index 0000000..50cf1d8
--- /dev/null
@@ -0,0 +1,13 @@
+K 10
+svn:author
+V 5
+robin
+K 8
+svn:date
+V 27
+2006-06-11T18:34:17.555480Z
+K 7
+svn:log
+V 43
+added some documentation and licensing info
+END
diff --git a/test/repositories/svn/db/revprops/4 b/test/repositories/svn/db/revprops/4
new file mode 100644 (file)
index 0000000..1ba88ed
--- /dev/null
@@ -0,0 +1,13 @@
+K 10
+svn:author
+V 5
+jason
+K 8
+svn:date
+V 27
+2006-07-14T22:17:08.940982Z
+K 7
+svn:log
+V 41
+added bs COPYING to catch global licenses
+END
diff --git a/test/repositories/svn/db/revprops/5 b/test/repositories/svn/db/revprops/5
new file mode 100644 (file)
index 0000000..b9b64b3
--- /dev/null
@@ -0,0 +1,13 @@
+K 10
+svn:author
+V 5
+jason
+K 8
+svn:date
+V 27
+2006-07-14T23:07:15.813350Z
+K 7
+svn:log
+V 14
+moving COPYING
+END
diff --git a/test/repositories/svn/db/revs/0 b/test/repositories/svn/db/revs/0
new file mode 100644 (file)
index 0000000..10f5c45
--- /dev/null
@@ -0,0 +1,11 @@
+PLAIN
+END
+ENDREP
+id: 0.0.r0/17
+type: dir
+count: 0
+text: 0 0 4 4 2d2977d1c96f487abe4a1e202dd03b4e
+cpath: /
+
+
+17 107
diff --git a/test/repositories/svn/db/revs/1 b/test/repositories/svn/db/revs/1
new file mode 100644 (file)
index 0000000..c2aa3a8
Binary files /dev/null and b/test/repositories/svn/db/revs/1 differ
diff --git a/test/repositories/svn/db/revs/2 b/test/repositories/svn/db/revs/2
new file mode 100644 (file)
index 0000000..ff63af3
Binary files /dev/null and b/test/repositories/svn/db/revs/2 differ
diff --git a/test/repositories/svn/db/revs/3 b/test/repositories/svn/db/revs/3
new file mode 100644 (file)
index 0000000..b33e8a7
Binary files /dev/null and b/test/repositories/svn/db/revs/3 differ
diff --git a/test/repositories/svn/db/revs/4 b/test/repositories/svn/db/revs/4
new file mode 100644 (file)
index 0000000..4933dbf
Binary files /dev/null and b/test/repositories/svn/db/revs/4 differ
diff --git a/test/repositories/svn/db/revs/5 b/test/repositories/svn/db/revs/5
new file mode 100644 (file)
index 0000000..e7d1c73
--- /dev/null
@@ -0,0 +1,64 @@
+id: 7.1.r5/0
+type: file
+pred: 7.0.r4/10502
+count: 1
+text: 4 0 10489 18787 d1504d81b4282ee3ebcf3cdaf7f93238
+cpath: /trunk/COPYING
+copyfrom: 4 /COPYING
+
+PLAIN
+K 7
+COPYING
+V 13
+file 7.1.r5/0
+K 6
+README
+V 15
+file 6.0.r3/400
+K 12
+helloworld.c
+V 15
+file 2.0.r3/256
+K 8
+makefile
+V 14
+file 5.0.r2/59
+END
+ENDREP
+id: 1.0.r5/303
+type: dir
+pred: 1.0.r3/643
+count: 3
+text: 5 151 139 139 f64dae99765c4ad6dd4f923d5c122b03
+cpath: /trunk
+copyroot: 0 /
+
+PLAIN
+K 8
+branches
+V 14
+dir 3.0.r1/385
+K 4
+tags
+V 14
+dir 4.0.r1/451
+K 5
+trunk
+V 14
+dir 1.0.r5/303
+END
+ENDREP
+id: 0.0.r5/545
+type: dir
+pred: 0.0.r4/10769
+count: 5
+text: 5 436 96 96 2d909fa92b9b195db0fd14e3f7732c24
+cpath: /
+copyroot: 0 /
+
+7.0.r4/10502 delete false false /COPYING
+
+7._0.t4-1 add false false /trunk/COPYING
+4 /COPYING
+
+545 673
diff --git a/test/repositories/svn/db/uuid b/test/repositories/svn/db/uuid
new file mode 100644 (file)
index 0000000..5d2ddb4
--- /dev/null
@@ -0,0 +1 @@
+6a9cefd4-a008-4d2a-a89b-d77e99cd6eb1
diff --git a/test/repositories/svn/db/write-lock b/test/repositories/svn/db/write-lock
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/repositories/svn/format b/test/repositories/svn/format
new file mode 100644 (file)
index 0000000..00750ed
--- /dev/null
@@ -0,0 +1 @@
+3
diff --git a/test/repositories/svn/hooks/post-commit.tmpl b/test/repositories/svn/hooks/post-commit.tmpl
new file mode 100644 (file)
index 0000000..b8345c6
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+# POST-COMMIT HOOK
+#
+# The post-commit hook is invoked after a commit.  Subversion runs
+# this hook by invoking a program (script, executable, binary, etc.)
+# named 'post-commit' (for which this file is a template) with the 
+# following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] REV          (the number of the revision just committed)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# Because the commit has already completed and cannot be undone,
+# the exit code of the hook program is ignored.  The hook program
+# can use the 'svnlook' utility to help it examine the
+# newly-committed tree.
+#
+# On a Unix system, the normal procedure is to have 'post-commit'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'post-commit' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'post-commit.bat' or 'post-commit.exe',
+# but the basic idea is the same.
+# 
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+REV="$2"
+
+commit-email.pl "$REPOS" "$REV" commit-watchers@example.org
+log-commit.py --repository "$REPOS" --revision "$REV"
diff --git a/test/repositories/svn/hooks/post-lock.tmpl b/test/repositories/svn/hooks/post-lock.tmpl
new file mode 100644 (file)
index 0000000..c779f11
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# POST-LOCK HOOK
+#
+# The post-lock hook is run after a path is locked.  Subversion runs
+# this hook by invoking a program (script, executable, binary, etc.)
+# named 'post-lock' (for which this file is a template) with the 
+# following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] USER         (the user who created the lock)
+#
+# The paths that were just locked are passed to the hook via STDIN (as
+# of Subversion 1.2, only one path is passed per invocation, but the
+# plan is to pass all locked paths at once, so the hook program
+# should be written accordingly).
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# Because the lock has already been created and cannot be undone,
+# the exit code of the hook program is ignored.  The hook program
+# can use the 'svnlook' utility to help it examine the
+# newly-created lock.
+#
+# On a Unix system, the normal procedure is to have 'post-lock'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'post-lock' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'post-lock.bat' or 'post-lock.exe',
+# but the basic idea is the same.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter:
+
+REPOS="$1"
+USER="$2"
+
+# Send email to interested parties, let them know a lock was created:
+mailer.py lock "$REPOS" "$USER" /path/to/mailer.conf
diff --git a/test/repositories/svn/hooks/post-revprop-change.tmpl b/test/repositories/svn/hooks/post-revprop-change.tmpl
new file mode 100644 (file)
index 0000000..2ed8b9a
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# POST-REVPROP-CHANGE HOOK
+#
+# The post-revprop-change hook is invoked after a revision property
+# has been added, modified or deleted.  Subversion runs this hook by
+# invoking a program (script, executable, binary, etc.) named
+# 'post-revprop-change' (for which this file is a template), with the
+# following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] REV          (the revision that was tweaked)
+#   [3] USER         (the username of the person tweaking the property)
+#   [4] PROPNAME     (the property that was changed)
+#   [5] ACTION       (the property was 'A'dded, 'M'odified, or 'D'eleted)
+#
+#   [STDIN] PROPVAL  ** the old property value is passed via STDIN.
+#
+# Because the propchange has already completed and cannot be undone,
+# the exit code of the hook program is ignored.  The hook program
+# can use the 'svnlook' utility to help it examine the
+# new property value.
+#
+# On a Unix system, the normal procedure is to have 'post-revprop-change'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'post-revprop-change' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'post-revprop-change.bat' or 'post-revprop-change.exe',
+# but the basic idea is the same.
+# 
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+REV="$2"
+USER="$3"
+PROPNAME="$4"
+ACTION="$5"
+
+propchange-email.pl "$REPOS" "$REV" "$USER" "$PROPNAME" watchers@example.org
diff --git a/test/repositories/svn/hooks/post-unlock.tmpl b/test/repositories/svn/hooks/post-unlock.tmpl
new file mode 100644 (file)
index 0000000..ae95c4b
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+# POST-UNLOCK HOOK
+#
+# The post-unlock hook runs after a path is unlocked.  Subversion runs
+# this hook by invoking a program (script, executable, binary, etc.)
+# named 'post-unlock' (for which this file is a template) with the 
+# following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] USER         (the user who destroyed the lock)
+#
+# The paths that were just unlocked are passed to the hook via STDIN
+# (as of Subversion 1.2, only one path is passed per invocation, but
+# the plan is to pass all unlocked paths at once, so the hook program
+# should be written accordingly).
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# Because the lock has already been destroyed and cannot be undone,
+# the exit code of the hook program is ignored.
+#
+# On a Unix system, the normal procedure is to have 'post-unlock'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'post-unlock' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'post-unlock.bat' or 'post-unlock.exe',
+# but the basic idea is the same.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter:
+
+REPOS="$1"
+USER="$2"
+
+# Send email to interested parties, let them know a lock was removed:
+mailer.py unlock "$REPOS" "$USER" /path/to/mailer.conf
diff --git a/test/repositories/svn/hooks/pre-commit.tmpl b/test/repositories/svn/hooks/pre-commit.tmpl
new file mode 100644 (file)
index 0000000..7444c51
--- /dev/null
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+# PRE-COMMIT HOOK
+#
+# The pre-commit hook is invoked before a Subversion txn is
+# committed.  Subversion runs this hook by invoking a program
+# (script, executable, binary, etc.) named 'pre-commit' (for which
+# this file is a template), with the following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] TXN-NAME     (the name of the txn about to be committed)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# If the hook program exits with success, the txn is committed; but
+# if it exits with failure (non-zero), the txn is aborted, no commit
+# takes place, and STDERR is returned to the client.   The hook
+# program can use the 'svnlook' utility to help it examine the txn.
+#
+# On a Unix system, the normal procedure is to have 'pre-commit'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+#   ***  NOTE: THE HOOK PROGRAM MUST NOT MODIFY THE TXN, EXCEPT  ***
+#   ***  FOR REVISION PROPERTIES (like svn:log or svn:author).   ***
+#
+#   This is why we recommend using the read-only 'svnlook' utility.
+#   In the future, Subversion may enforce the rule that pre-commit
+#   hooks should not modify the versioned data in txns, or else come
+#   up with a mechanism to make it safe to do so (by informing the
+#   committing client of the changes).  However, right now neither
+#   mechanism is implemented, so hook writers just have to be careful.
+#
+# Note that 'pre-commit' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'pre-commit.bat' or 'pre-commit.exe',
+# but the basic idea is the same.
+#
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+TXN="$2"
+
+# Make sure that the log message contains some text.
+SVNLOOK=/opt/local/bin/svnlook
+$SVNLOOK log -t "$TXN" "$REPOS" | \
+   grep "[a-zA-Z0-9]" > /dev/null || exit 1
+
+# Check that the author of this commit has the rights to perform
+# the commit on the files and directories being modified.
+commit-access-control.pl "$REPOS" "$TXN" commit-access-control.cfg || exit 1
+
+# All checks passed, so allow the commit.
+exit 0
diff --git a/test/repositories/svn/hooks/pre-lock.tmpl b/test/repositories/svn/hooks/pre-lock.tmpl
new file mode 100644 (file)
index 0000000..2f86bc0
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+# PRE-LOCK HOOK
+#
+# The pre-lock hook is invoked before an exclusive lock is
+# created.  Subversion runs this hook by invoking a program 
+# (script, executable, binary, etc.) named 'pre-lock' (for which
+# this file is a template), with the following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] PATH         (the path in the repository about to be locked)
+#   [3] USER         (the user creating the lock)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# If the hook program exits with success, the lock is created; but
+# if it exits with failure (non-zero), the lock action is aborted
+# and STDERR is returned to the client.
+
+# On a Unix system, the normal procedure is to have 'pre-lock'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'pre-lock' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'pre-lock.bat' or 'pre-lock.exe',
+# but the basic idea is the same.
+#
+# Here is an example hook script, for a Unix /bin/sh interpreter:
+
+REPOS="$1"
+PATH="$2"
+USER="$3"
+
+# If a lock exists and is owned by a different person, don't allow it
+# to be stolen (e.g., with 'svn lock --force ...').
+
+# (Maybe this script could send email to the lock owner?)
+SVNLOOK=/opt/local/bin/svnlook
+GREP=/bin/grep
+SED=/bin/sed
+
+LOCK_OWNER=`$SVNLOOK lock "$REPOS" "$PATH" | \
+            $GREP '^Owner: ' | $SED 's/Owner: //'`
+
+# If we get no result from svnlook, there's no lock, allow the lock to
+# happen:
+if [ "$LOCK_OWNER" == "" ]; then
+  exit 0
+fi
+
+# If the person locking matches the lock's owner, allow the lock to
+# happen:
+if [ "$LOCK_OWNER" == "$USER" ]; then
+  exit 0
+fi
+
+# Otherwise, we've got an owner mismatch, so return failure:
+echo "Error: $PATH already locked by ${LOCK_OWNER}." 1>&2
+exit 1
diff --git a/test/repositories/svn/hooks/pre-revprop-change.tmpl b/test/repositories/svn/hooks/pre-revprop-change.tmpl
new file mode 100644 (file)
index 0000000..2f2de98
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+# PRE-REVPROP-CHANGE HOOK
+#
+# The pre-revprop-change hook is invoked before a revision property
+# is added, modified or deleted.  Subversion runs this hook by invoking
+# a program (script, executable, binary, etc.) named 'pre-revprop-change'
+# (for which this file is a template), with the following ordered
+# arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] REVISION     (the revision being tweaked)
+#   [3] USER         (the username of the person tweaking the property)
+#   [4] PROPNAME     (the property being set on the revision)
+#   [5] ACTION       (the property is being 'A'dded, 'M'odified, or 'D'eleted)
+#
+#   [STDIN] PROPVAL  ** the new property value is passed via STDIN.
+#
+# If the hook program exits with success, the propchange happens; but
+# if it exits with failure (non-zero), the propchange doesn't happen.
+# The hook program can use the 'svnlook' utility to examine the 
+# existing value of the revision property.
+#
+# WARNING: unlike other hooks, this hook MUST exist for revision
+# properties to be changed.  If the hook does not exist, Subversion 
+# will behave as if the hook were present, but failed.  The reason
+# for this is that revision properties are UNVERSIONED, meaning that
+# a successful propchange is destructive;  the old value is gone
+# forever.  We recommend the hook back up the old value somewhere.
+#
+# On a Unix system, the normal procedure is to have 'pre-revprop-change'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'pre-revprop-change' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'pre-revprop-change.bat' or 'pre-revprop-change.exe',
+# but the basic idea is the same.
+#
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+REV="$2"
+USER="$3"
+PROPNAME="$4"
+ACTION="$5"
+
+if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
+
+echo "Changing revision properties other than svn:log is prohibited" >&2
+exit 1
diff --git a/test/repositories/svn/hooks/pre-unlock.tmpl b/test/repositories/svn/hooks/pre-unlock.tmpl
new file mode 100644 (file)
index 0000000..209feff
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+# PRE-UNLOCK HOOK
+#
+# The pre-unlock hook is invoked before an exclusive lock is
+# destroyed.  Subversion runs this hook by invoking a program 
+# (script, executable, binary, etc.) named 'pre-unlock' (for which
+# this file is a template), with the following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] PATH         (the path in the repository about to be unlocked)
+#   [3] USER         (the user destroying the lock)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# If the hook program exits with success, the lock is destroyed; but
+# if it exits with failure (non-zero), the unlock action is aborted
+# and STDERR is returned to the client.
+
+# On a Unix system, the normal procedure is to have 'pre-unlock'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'pre-unlock' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'pre-unlock.bat' or 'pre-unlock.exe',
+# but the basic idea is the same.
+#
+# Here is an example hook script, for a Unix /bin/sh interpreter:
+
+REPOS="$1"
+PATH="$2"
+USER="$3"
+
+# If a lock is owned by a different person, don't allow it be broken.
+# (Maybe this script could send email to the lock owner?)
+
+SVNLOOK=/opt/local/bin/svnlook
+GREP=/bin/grep
+SED=/bin/sed
+
+LOCK_OWNER=`$SVNLOOK lock "$REPOS" "$PATH" | \
+            $GREP '^Owner: ' | $SED 's/Owner: //'`
+
+# If we get no result from svnlook, there's no lock, return success:
+if [ "$LOCK_OWNER" == "" ]; then
+  exit 0
+fi
+# If the person unlocking matches the lock's owner, return success:
+if [ "$LOCK_OWNER" == "$USER" ]; then
+  exit 0
+fi
+
+# Otherwise, we've got an owner mismatch, so return failure:
+echo "Error: $PATH locked by ${LOCK_OWNER}." 1>&2
+exit 1
diff --git a/test/repositories/svn/hooks/start-commit.tmpl b/test/repositories/svn/hooks/start-commit.tmpl
new file mode 100644 (file)
index 0000000..348d706
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# START-COMMIT HOOK
+#
+# The start-commit hook is invoked before a Subversion txn is created
+# in the process of doing a commit.  Subversion runs this hook
+# by invoking a program (script, executable, binary, etc.) named
+# 'start-commit' (for which this file is a template)
+# with the following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] USER         (the authenticated user attempting to commit)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# If the hook program exits with success, the commit continues; but
+# if it exits with failure (non-zero), the commit is stopped before
+# a Subversion txn is created, and STDERR is returned to the client.
+#
+# On a Unix system, the normal procedure is to have 'start-commit'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'start-commit' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'start-commit.bat' or 'start-commit.exe',
+# but the basic idea is the same.
+# 
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+USER="$2"
+
+commit-allower.pl --repository "$REPOS" --user "$USER" || exit 1
+special-auth-check.py --user "$USER" --auth-level 3 || exit 1
+
+# All checks passed, so allow the commit.
+exit 0
diff --git a/test/repositories/svn/locks/db-logs.lock b/test/repositories/svn/locks/db-logs.lock
new file mode 100644 (file)
index 0000000..20dd636
--- /dev/null
@@ -0,0 +1,3 @@
+This file is not used by Subversion 1.3.x or later.
+However, its existence is required for compatibility with
+Subversion 1.2.x or earlier.
diff --git a/test/repositories/svn/locks/db.lock b/test/repositories/svn/locks/db.lock
new file mode 100644 (file)
index 0000000..20dd636
--- /dev/null
@@ -0,0 +1,3 @@
+This file is not used by Subversion 1.3.x or later.
+However, its existence is required for compatibility with
+Subversion 1.2.x or earlier.
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644 (file)
index 0000000..485617b
--- /dev/null
@@ -0,0 +1,84 @@
+require 'test/unit'
+require 'fileutils'
+require 'find'
+
+unless defined?(TEST_DIR)
+       TEST_DIR = File.dirname(__FILE__)
+end
+require TEST_DIR + '/../lib/scm'
+
+Scm::Adapters::AbstractAdapter.logger = Logger.new(File.open('log/test.log','a'))
+
+unless defined?(REPO_DIR)
+       REPO_DIR = File.expand_path(File.join(TEST_DIR, 'repositories'))
+end
+
+unless defined?(DATA_DIR)
+       DATA_DIR = File.expand_path(File.join(TEST_DIR, 'data'))
+end
+
+class Scm::Test < Test::Unit::TestCase
+       # For reasons unknown, the base class defines a default_test method to throw a failure.
+       # We override it with a no-op to prevent this 'helpful' feature.
+       def default_test
+       end
+
+       def assert_convert(parser, log, expected)
+               result = ''
+               parser.parse File.new(log), :writer => Scm::Parsers::XmlWriter.new(result)
+               assert_buffers_equal File.read(expected), result
+       end
+
+       # assert_equal just dumps the massive strings to the console, which is not helpful.
+       # Instead we try to indentify the line of the first error.
+       def assert_buffers_equal(expected, actual)
+               return if expected == actual
+
+               expected_lines = expected.split("\n")
+               actual_lines = actual.split("\n")
+
+               expected_lines.each_with_index do |line, i|
+                       if line != actual_lines[i]
+                               assert_equal line, actual_lines[i], "at line #{i} of the reference buffer"
+                       end
+               end
+
+               # We couldnt' find the mismatch. Just bail.
+               assert_equal expected_lines, actual_lines
+       end
+
+       # Expands a tarballed git repository and yields a GitAdapter that points to it.
+       def with_git_repository(name)
+               archive = name + '.tgz'
+               if Dir.entries(REPO_DIR).include?(archive)
+                       Scm::ScratchDir.new do |dir|
+                               `tar xzf #{File.join(REPO_DIR, archive)} --directory #{dir}`
+                               yield Scm::Adapters::GitAdapter.new(:url => File.join(dir, name)).normalize
+                       end
+               else
+                       raise RuntimeError.new("Repository archive #{File.join(REPO_DIR, archive)} not found.")
+               end
+       end
+
+       def with_svn_repository(name)
+               if Dir.entries(REPO_DIR).include?(name)
+                       Scm::ScratchDir.new do |dir|
+                               `cp -R #{File.join(REPO_DIR, name)} #{dir}`
+                               yield Scm::Adapters::SvnAdapter.new(:url => File.join(dir, name)).normalize
+                       end
+               else
+                       raise RuntimeError.new("Repository archive #{File.join(REPO_DIR, name)} not found.")
+               end
+       end
+
+       def with_cvs_repository(name)
+               if Dir.entries(REPO_DIR).include?(name)
+                       Scm::ScratchDir.new do |dir|
+                               `cp -R #{File.join(REPO_DIR, name)} #{dir}`
+                               yield Scm::Adapters::CvsAdapter.new(:url => File.join(dir, name)).normalize
+                       end
+               else
+                       raise RuntimeError.new("Repository archive #{File.join(REPO_DIR, name)} not found.")
+               end
+       end
+end
diff --git a/test/unit/abstract_adapter_test.rb b/test/unit/abstract_adapter_test.rb
new file mode 100644 (file)
index 0000000..b637255
--- /dev/null
@@ -0,0 +1,72 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class AbstractAdapterTest < Scm::Test
+               def test_simple_validation
+                       scm = AbstractAdapter.new()
+                       assert !scm.valid?
+                       assert_equal [[:url, "The URL can't be blank."]], scm.errors
+
+                       scm.url = "http://www.test.org/test"
+                       assert scm.valid?
+                       assert scm.errors.empty?
+               end
+
+               def test_valid_urls
+                       ['http://www.ohloh.net'].each do |url|
+                               assert !AbstractAdapter.new(:url => url).validate_url
+                       end
+               end
+
+               def test_invalid_urls
+                       [nil, '', '*' * 121].each do |url|
+                               assert AbstractAdapter.new(:url => url).validate_url.any?
+                       end
+               end
+
+               def test_invalid_usernames
+                       ['no spaces allowed', '/', ':', 'a'*33].each do |username|
+                               assert AbstractAdapter.new(:username => username).validate_username.any?
+                       end
+               end
+
+               def test_valid_usernames
+                       [nil,'','joe_36','a'*32].each do |username|
+                               assert !AbstractAdapter.new(:username => username).validate_username
+                       end
+               end
+
+               def test_invalid_passwords
+                       ['no spaces allowed', 'a'*33].each do |password|
+                               assert AbstractAdapter.new(:password => password).validate_password.any?
+                       end
+               end
+
+               def test_valid_passwords
+                       [nil,'','abc','a'*32].each do |password|
+                               assert !AbstractAdapter.new(:password => password).validate_password
+                       end
+               end
+
+               def test_invalid_branch_names
+                       ['%','a'*51].each do |branch_name|
+                               assert AbstractAdapter.new(:branch_name => branch_name).validate_branch_name.any?
+                       end
+               end
+
+               def test_valid_branch_names
+                       [nil,'','/trunk','_','a'*50].each do |branch_name|
+                               assert !AbstractAdapter.new(:branch_name => branch_name).validate_branch_name
+                       end
+               end
+
+               def test_normalize
+                       scm = AbstractAdapter.new(:url => "   http://www.test.org/test   ", :username => "  joe  ", :password => "  abc  ", :branch_name => "   trunk  ")
+                       scm.normalize
+                       assert_equal "http://www.test.org/test", scm.url
+                       assert_equal "trunk", scm.branch_name
+                       assert_equal "joe", scm.username
+                       assert_equal "abc", scm.password
+               end
+       end
+end
diff --git a/test/unit/array_writer_test.rb b/test/unit/array_writer_test.rb
new file mode 100644 (file)
index 0000000..f8b1686
--- /dev/null
@@ -0,0 +1,33 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Parsers
+       class ArrayWriterTest < Scm::Test
+
+               def test_basic
+
+                       log = <<-LOG
+------------------------------------------------------------------------
+r3 | robin | 2006-06-11 11:34:17 -0700 (Sun, 11 Jun 2006) | 1 line
+Changed paths:
+   A /trunk/README
+   M /trunk/helloworld.c
+
+added some documentation and licensing info
+------------------------------------------------------------------------
+                       LOG
+
+                       # By default, the ArrayWriter is used, and an empty string is parsed
+                       assert_equal [], SvnParser.parse
+                       assert_equal [], SvnParser.parse('')
+                       assert_equal [], SvnParser.parse('', :writer => ArrayWriter.new)
+
+                       result = SvnParser.parse(log, :writer => ArrayWriter.new)
+                       assert_equal 1, result.size
+                       assert_equal 'robin', result.first.committer_name
+                       assert_equal 3, result.first.token
+                       assert_equal 2, result.first.diffs.size
+                       assert_equal '/trunk/README', result.first.diffs.first.path
+                       assert_equal 'A', result.first.diffs.first.action
+               end
+       end
+end
diff --git a/test/unit/cvs_branch_number_test.rb b/test/unit/cvs_branch_number_test.rb
new file mode 100644 (file)
index 0000000..272cc8d
--- /dev/null
@@ -0,0 +1,166 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Parsers
+       class CvsBranchNumberTest < Scm::Test
+               def test_basic
+                       assert_equal [1,1], BranchNumber.new('1.1').to_a
+                       assert_equal [1234,1234], BranchNumber.new('1234.1234').to_a
+                       assert_equal [1,2,3,4], BranchNumber.new('1.2.3.4').to_a
+               end
+
+               def test_simple_inherits_from
+                       b = BranchNumber.new('1.3')
+
+                       assert b.inherits_from?(BranchNumber.new('1.2'))
+                       assert b.inherits_from?(BranchNumber.new('1.1'))
+                       assert b.inherits_from?(BranchNumber.new('1.3'))
+
+                       assert !b.inherits_from?(BranchNumber.new('1.4'))
+                       assert !b.inherits_from?(BranchNumber.new('1.1.2.1'))
+                       assert !b.inherits_from?(BranchNumber.new('1.2.2.1'))
+                       assert !b.inherits_from?(BranchNumber.new('1.3.2.1'))
+               end
+
+               def test_complex_inherits_from
+                       b = BranchNumber.new('1.3.6.3.2.3')
+
+                       assert b.inherits_from?(BranchNumber.new('1.2'))
+                       assert b.inherits_from?(BranchNumber.new('1.1'))
+                       assert b.inherits_from?(BranchNumber.new('1.3'))
+                       assert b.inherits_from?(BranchNumber.new('1.3.6.1'))
+                       assert b.inherits_from?(BranchNumber.new('1.3.6.2'))
+                       assert b.inherits_from?(BranchNumber.new('1.3.6.3'))
+                       assert b.inherits_from?(BranchNumber.new('1.3.6.3.2.1'))
+                       assert b.inherits_from?(BranchNumber.new('1.3.6.3.2.2'))
+                       assert b.inherits_from?(BranchNumber.new('1.3.6.3.2.3'))
+
+                       assert !b.inherits_from?(BranchNumber.new('1.4'))
+                       assert !b.inherits_from?(BranchNumber.new('1.1.2.1'))
+                       assert !b.inherits_from?(BranchNumber.new('1.2.2.1'))
+                       assert !b.inherits_from?(BranchNumber.new('1.3.2.1'))
+                       assert !b.inherits_from?(BranchNumber.new('1.3.4.1'))
+                       assert !b.inherits_from?(BranchNumber.new('1.3.6.1.2.1'))
+                       assert !b.inherits_from?(BranchNumber.new('1.3.6.4'))
+                       assert !b.inherits_from?(BranchNumber.new('1.3.6.3.4.1'))
+                       assert !b.inherits_from?(BranchNumber.new('1.3.6.3.2.2.2.1'))
+                       assert !b.inherits_from?(BranchNumber.new('1.3.6.3.2.4'))
+               end
+
+               def test_primary_revision_number_change
+                       b = BranchNumber.new('2.3')
+
+                       assert b.inherits_from?(BranchNumber.new('2.2'))
+                       assert b.inherits_from?(BranchNumber.new('2.1'))
+                       assert b.inherits_from?(BranchNumber.new('1.1'))
+                       assert b.inherits_from?(BranchNumber.new('1.9999'))
+
+                       assert !b.inherits_from?(BranchNumber.new('2.4'))
+                       assert !b.inherits_from?(BranchNumber.new('3.1'))
+               end
+
+               def test_complex_primary_revision_number_change
+                       b = BranchNumber.new('2.3.2.1')
+
+                       assert b.inherits_from?(BranchNumber.new('2.3'))
+                       assert b.inherits_from?(BranchNumber.new('2.2'))
+                       assert b.inherits_from?(BranchNumber.new('1.1'))
+                       assert b.inherits_from?(BranchNumber.new('1.9999'))
+
+                       assert !b.inherits_from?(BranchNumber.new('3.1'))
+               end
+
+               # Crazy CVS inserts a zero before the last piece of a branch number
+               def test_magic_branch_numbers
+                       assert BranchNumber.new('1.1.2.1').inherits_from?(BranchNumber.new('1.1.0.2'))
+                       assert BranchNumber.new('1.1.2.1.2.1').inherits_from?(BranchNumber.new('1.1.0.2'))
+
+                       assert BranchNumber.new('1.1.0.2').inherits_from?(BranchNumber.new('1.1'))
+                       assert !BranchNumber.new('1.1.0.2').inherits_from?(BranchNumber.new('1.2'))
+                       assert !BranchNumber.new('1.1.0.2').inherits_from?(BranchNumber.new('1.1.2.1'))
+
+                       assert BranchNumber.new('1.1.0.4').inherits_from?(BranchNumber.new('1.1'))
+                       assert !BranchNumber.new('1.1.0.4').inherits_from?(BranchNumber.new('1.1.2.1'))
+                       assert !BranchNumber.new('1.1.0.4').inherits_from?(BranchNumber.new('1.1.0.2'))
+                       assert !BranchNumber.new('1.1.0.4').inherits_from?(BranchNumber.new('1.1.4.1'))
+                       assert !BranchNumber.new('1.1.0.4').inherits_from?(BranchNumber.new('1.1.0.6'))
+               end
+
+               def test_simple_on_same_line
+                       b = BranchNumber.new('1.3')
+
+                       assert b.on_same_line?(BranchNumber.new('1.2'))
+                       assert b.on_same_line?(BranchNumber.new('1.1'))
+                       assert b.on_same_line?(BranchNumber.new('1.3'))
+                       assert b.on_same_line?(BranchNumber.new('1.4'))
+
+                       assert !b.on_same_line?(BranchNumber.new('1.1.2.1'))
+                       assert !b.on_same_line?(BranchNumber.new('1.2.2.1'))
+                       assert !b.on_same_line?(BranchNumber.new('1.3.2.1'))
+               end
+
+               def test_complex_on_same_line
+                       b = BranchNumber.new('1.3.6.3.2.3')
+
+                       assert b.on_same_line?(BranchNumber.new('1.1'))
+                       assert b.on_same_line?(BranchNumber.new('1.2'))
+                       assert b.on_same_line?(BranchNumber.new('1.3'))
+                       assert b.on_same_line?(BranchNumber.new('1.3.6.1'))
+                       assert b.on_same_line?(BranchNumber.new('1.3.6.2'))
+                       assert b.on_same_line?(BranchNumber.new('1.3.6.3'))
+                       assert b.on_same_line?(BranchNumber.new('1.3.6.3.2.1'))
+                       assert b.on_same_line?(BranchNumber.new('1.3.6.3.2.2'))
+                       assert b.on_same_line?(BranchNumber.new('1.3.6.3.2.3'))
+                       assert b.on_same_line?(BranchNumber.new('1.3.6.3.2.4'))
+                       assert b.on_same_line?(BranchNumber.new('1.3.6.3.2.99'))
+
+                       assert !b.on_same_line?(BranchNumber.new('1.4'))
+                       assert !b.on_same_line?(BranchNumber.new('1.1.2.1'))
+                       assert !b.on_same_line?(BranchNumber.new('1.2.2.1'))
+                       assert !b.on_same_line?(BranchNumber.new('1.3.2.1'))
+                       assert !b.on_same_line?(BranchNumber.new('1.3.4.1'))
+                       assert !b.on_same_line?(BranchNumber.new('1.3.6.1.2.1'))
+                       assert !b.on_same_line?(BranchNumber.new('1.3.6.4'))
+                       assert !b.on_same_line?(BranchNumber.new('1.3.6.3.4.1'))
+                       assert !b.on_same_line?(BranchNumber.new('1.3.6.3.2.2.2.1'))
+                       assert !b.on_same_line?(BranchNumber.new('1.3.6.3.2.99.2.1'))
+               end
+
+               def test_primary_revision_number_change
+                       b = BranchNumber.new('2.3')
+
+                       assert b.on_same_line?(BranchNumber.new('2.2'))
+                       assert b.on_same_line?(BranchNumber.new('2.1'))
+                       assert b.on_same_line?(BranchNumber.new('1.1'))
+                       assert b.on_same_line?(BranchNumber.new('1.9999'))
+                       assert b.on_same_line?(BranchNumber.new('2.4'))
+                       assert b.on_same_line?(BranchNumber.new('3.1'))
+               end
+
+               def test_complex_primary_revision_number_change
+                       b = BranchNumber.new('2.3.2.1')
+
+                       assert b.on_same_line?(BranchNumber.new('2.3'))
+                       assert b.on_same_line?(BranchNumber.new('2.2'))
+                       assert b.on_same_line?(BranchNumber.new('1.1'))
+                       assert b.on_same_line?(BranchNumber.new('1.9999'))
+                       assert !b.on_same_line?(BranchNumber.new('2.4'))
+                       assert !b.on_same_line?(BranchNumber.new('3.1'))
+               end
+
+               # Crazy CVS inserts a zero before the last piece of a branch number
+               def test_magic_branch_numbers
+                       assert BranchNumber.new('1.1.2.1').on_same_line?(BranchNumber.new('1.1.0.2'))
+                       assert BranchNumber.new('1.1.2.1.2.1').on_same_line?(BranchNumber.new('1.1.0.2'))
+
+                       assert BranchNumber.new('1.1.0.2').on_same_line?(BranchNumber.new('1.1'))
+                       assert !BranchNumber.new('1.1.0.2').on_same_line?(BranchNumber.new('1.2'))
+                       assert BranchNumber.new('1.1.0.2').on_same_line?(BranchNumber.new('1.1.2.1'))
+
+                       assert BranchNumber.new('1.1.0.4').on_same_line?(BranchNumber.new('1.1'))
+                       assert !BranchNumber.new('1.1.0.4').on_same_line?(BranchNumber.new('1.1.2.1'))
+                       assert !BranchNumber.new('1.1.0.4').on_same_line?(BranchNumber.new('1.1.0.2'))
+                       assert BranchNumber.new('1.1.0.4').on_same_line?(BranchNumber.new('1.1.4.1'))
+                       assert !BranchNumber.new('1.1.0.4').on_same_line?(BranchNumber.new('1.1.0.6'))
+               end
+       end
+end
diff --git a/test/unit/cvs_commits_test.rb b/test/unit/cvs_commits_test.rb
new file mode 100644 (file)
index 0000000..83cb55f
--- /dev/null
@@ -0,0 +1,27 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class CvsCommitsTest < Scm::Test
+
+               def test_commits
+                       with_cvs_repository('cvs') do |cvs|
+
+                               assert_equal ['2006/06/29 16:19:58',
+                                                                                       '2006/06/29 16:21:07',
+                                                                                       '2006/06/29 18:14:47',
+                                                                                       '2006/06/29 18:45:29',
+                                                                                       '2006/06/29 18:48:54',
+                                                                                       '2006/06/29 18:52:23'], cvs.commits.collect { |c| c.token }
+
+                               assert_equal ['2006/06/29 18:48:54',
+                                                                                       '2006/06/29 18:52:23'], cvs.commits('2006/06/29 18:45:29').collect { |c| c.token }
+
+                               # Make sure we are date format agnostic (2008/01/01 is the same as 2008-01-01)
+                               assert_equal ['2006/06/29 18:48:54',
+                                                                                       '2006/06/29 18:52:23'], cvs.commits('2006-06-29 18:45:29').collect { |c| c.token }
+
+                               assert_equal [], cvs.commits('2006/06/29 18:52:23').collect { |c| c.token }
+                       end
+               end
+       end
+end
diff --git a/test/unit/cvs_convert_test.rb b/test/unit/cvs_convert_test.rb
new file mode 100644 (file)
index 0000000..9a3da40
--- /dev/null
@@ -0,0 +1,30 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class CvsConvertTest < Scm::Test
+
+               def test_basic_convert
+                       with_cvs_repository('cvs') do |src|
+                               Scm::ScratchDir.new do |dest_dir|
+                                       dest = GitAdapter.new(:url => dest_dir).normalize
+                                       assert !dest.exist?
+
+                                       dest.pull(src)
+                                       assert dest.exist?
+
+                                       dest_commits = dest.commits
+                                       src.commits.each_with_index do |c, i|
+                                               # Because CVS does not track authors (only committers),
+                                               # the CVS committer becomes the Git author.
+                                               assert_equal c.committer_date, dest_commits[i].author_date
+                                               assert_equal c.committer_name, dest_commits[i].author_name
+
+                                               # Depending upon version of Git used, we may or may not have a trailing \n.
+                                               # We don't really care, so just compare the stripped versions.
+                                               assert_equal c.message.strip, dest_commits[i].message.strip
+                                       end
+                               end
+                       end
+               end
+       end
+end
diff --git a/test/unit/cvs_misc_test.rb b/test/unit/cvs_misc_test.rb
new file mode 100644 (file)
index 0000000..59e7ece
--- /dev/null
@@ -0,0 +1,49 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class CvsMiscTest < Scm::Test
+               def test_local_directory_trim
+                       r = CvsAdapter.new(:url => "/Users/robin/cvs_repo/", :module_name => "simple")
+                       assert_equal "/Users/robin/cvs_repo/simple/foo.rb", r.trim_directory('/Users/robin/cvs_repo/simple/foo.rb')
+               end
+
+               def test_remote_directory_trim
+                       r = CvsAdapter.new(:url => ':pserver:anonymous:@moodle.cvs.sourceforge.net:/cvsroot/moodle', :module_name => "contrib")
+                       assert_equal "foo.rb", r.trim_directory('/cvsroot/moodle/contrib/foo.rb')
+               end
+
+               def test_remote_directory_trim_with_port_number
+                       r = CvsAdapter.new(:url => ':pserver:anoncvs:anoncvs@libvirt.org:2401/data/cvs', :module_name => "libvirt")
+                       assert_equal "docs/html/Attic", r.trim_directory('/data/cvs/libvirt/docs/html/Attic')
+               end
+
+               def test_ordered_directory_list
+                       r = CvsAdapter.new(:url => ':pserver:anonymous:@moodle.cvs.sourceforge.net:/cvsroot/moodle', :module_name => "contrib")
+
+                       l = r.build_ordered_directory_list(["/cvsroot/moodle/contrib/foo/bar".intern,
+                                                                                                                                                               "/cvsroot/moodle/contrib".intern,
+                                                                                                                                                               "/cvsroot/moodle/contrib/hello".intern,
+                                                                                                                                                               "/cvsroot/moodle/contrib/hello".intern])
+
+                       assert_equal 4,l.size
+                       assert_equal "", l[0]
+                       assert_equal "foo", l[1]
+                       assert_equal "hello", l[2]
+                       assert_equal "foo/bar", l[3]
+               end
+
+               def test_ordered_directory_list_ignores_Attic
+                       r = CvsAdapter.new(:url => ':pserver:anonymous:@moodle.cvs.sourceforge.net:/cvsroot/moodle', :module_name => 'contrib')
+
+                       l = r.build_ordered_directory_list(["/cvsroot/moodle/contrib/foo/bar".intern,
+                                                                                                                                                               "/cvsroot/moodle/contrib/Attic".intern,
+                                                                                                                                                               "/cvsroot/moodle/contrib/hello/Attic".intern])
+
+                       assert_equal 4,l.size
+                       assert_equal "", l[0]
+                       assert_equal "foo", l[1]
+                       assert_equal "hello", l[2]
+                       assert_equal "foo/bar", l[3]
+               end
+       end
+end
diff --git a/test/unit/cvs_parser_test.rb b/test/unit/cvs_parser_test.rb
new file mode 100644 (file)
index 0000000..ac3ae67
--- /dev/null
@@ -0,0 +1,94 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Parsers
+       class CvsParserTest < Scm::Test
+
+               def test_basic
+                       assert_convert(CvsParser, DATA_DIR + '/basic.rlog', DATA_DIR + '/basic.ohlog')
+               end
+
+               def test_empty_array
+                       assert_equal([], CvsParser.parse(''))
+               end
+
+               def test_empty_xml
+                       assert_equal("<?xml version=\"1.0\"?>\n<ohloh_log scm=\"cvs\">\n</ohloh_log>\n", CvsParser.parse('', :writer => XmlWriter.new))
+               end
+
+               def test_log_parser
+                       revisions = CvsParser.parse File.read(DATA_DIR + '/basic.rlog')
+
+                       assert_equal 2, revisions.size
+
+                       assert_equal '2005/07/25 17:09:59', revisions[0].token
+                       assert_equal 'pizzandre', revisions[0].committer_name
+                       assert_equal Time.utc(2005,07,25,17,9,59), revisions[0].committer_date
+                       assert_equal '*** empty log message ***', revisions[0].message
+
+                       assert_equal '2005/07/25 17:11:06', revisions[1].token
+                       assert_equal 'pizzandre', revisions[1].committer_name
+                       assert_equal Time.utc(2005,07,25,17,11,6), revisions[1].committer_date
+                       assert_equal 'Addin UNL file with using example-', revisions[1].message
+               end
+
+               # One file with several revisions
+               def test_multiple_revisions
+                       revisions = CvsParser.parse File.read(DATA_DIR + '/multiple_revisions.rlog')
+
+                       # There are 9 revisions in the rlog, but some of them are close together with the same message.
+                       # Therefore we bin them together into only 7 revisions.
+                       assert_equal 7, revisions.size
+
+                       assert_equal '2005/07/15 11:53:30', revisions[0].token
+                       assert_equal 'httpd', revisions[0].committer_name
+                       assert_equal 'Initial data for the intelliglue project', revisions[0].message
+
+                       assert_equal '2005/07/15 16:40:17', revisions[1].token
+                       assert_equal 'pizzandre', revisions[1].committer_name
+                       assert_equal '*** empty log message ***', revisions[1].message
+
+                       assert_equal '2005/07/26 20:35:13', revisions[5].token
+                       assert_equal 'pizzandre', revisions[5].committer_name
+                       assert_equal "Issue number:\nObtained from:\nSubmitted by:\nReviewed by:\nAdding current milestones-", revisions[5].message
+
+                       assert_equal '2005/07/26 20:39:16', revisions[6].token
+                       assert_equal 'pizzandre', revisions[6].committer_name
+                       assert_equal "Issue number:\nObtained from:\nSubmitted by:\nReviewed by:\nCompleting and fixing milestones texts", revisions[6].message
+               end
+
+               # A file is created and modified on the branch, then merged to the trunk, then deleted from the branch.
+               # From the trunk's point of view, we should see only the merge event.
+               def test_file_created_on_branch_as_seen_from_trunk
+                       revisions = CvsParser.parse File.read(DATA_DIR + '/file_created_on_branch.rlog'), :branch_name => 'HEAD'
+                       assert_equal 1, revisions.size
+                       assert_equal 'merged new_file.rb from branch onto the HEAD', revisions[0].message
+               end
+
+               # A file is created and modified on the branch, then merged to the trunk, then deleted from the branch.
+               # From the branch's point of view, we should see the add, modify, and delete only.
+               def test_file_created_on_branch_as_seen_from_branch
+                       revisions = CvsParser.parse File.read(DATA_DIR + '/file_created_on_branch.rlog'), :branch_name => 'my_branch'
+                       assert_equal 3, revisions.size
+                       assert_equal 'added new_file.rb on the branch', revisions[0].message
+                       assert_equal 'modifed new_file.rb on the branch only', revisions[1].message
+                       assert_equal 'removed new_file.rb from the branch only', revisions[2].message
+               end
+
+               # A file is created on the vender branch. This causes a simultaneous checkin on HEAD
+               # with a different message ('Initial revision') but same committer_name name and timestamp.
+               # We should only pick up one of these checkins.
+               def test_simultaneous_checkins
+                       revisions = CvsParser.parse File.read(DATA_DIR + '/simultaneous_checkins.rlog')
+                       assert_equal 1, revisions.size
+                       assert_equal 'Initial revision', revisions[0].message
+               end
+
+               # Two different authors check in with two different messages at the exact same moment.
+               # How this happens is a mystery, but I have seen it in rlogs.
+               # We arbitrarily choose the first one if so.
+               def test_simultaneous_checkins_2
+                       revisions = CvsParser.parse File.read(DATA_DIR + '/simultaneous_checkins_2.rlog')
+                       assert_equal 1, revisions.size
+               end
+       end
+end
diff --git a/test/unit/cvs_validation_test.rb b/test/unit/cvs_validation_test.rb
new file mode 100644 (file)
index 0000000..c3fe9bc
--- /dev/null
@@ -0,0 +1,140 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class CvsValidationTest < Scm::Test
+               def test_rejected_urls
+                       [       nil, "", "foo", "http:/", "http:://", "http://", "http://a",
+                               ":pserver", # that's not enough
+                               ":pserver:anonymous", #still not enough
+                               ":pserver:anonymous:@ipodder.cvs.sourceforge.net", # missing the path
+                               ":pserver:anonymous:::@ipodder.cvs.sourceforge.net:/cvsroot/ipodder", # too many colons
+                               ":pserver@ipodder.cvs.sourceforge.net:/cvsroot/ipodder", # not enough colons
+                               ":pserver:anonymous:@ipodder.cvs.sourceforge.net/cvsroot/ipodder", # hostname and path not separated by colon
+                               ":pserver:anonymous:@ipodder.cvs.source/forge.net:/cvsroot/ipodder", # slash in hostname
+                               ":pserver:anonymous:ipodder.cvs.sourceforge.net:/cvsroot/ipodder", # missing @
+                               ":pserver:anonymous:@ipodder.cvs.sourceforge.net:cvsroot/ipodder", # path does not begin at root
+                               ":pserver:anonymous:@ipodder.cvs.sourceforge.net:/cvsr%23oot/ipodder", # no encoded chars allowed
+                               ":pserver:anonymous:@ipodder.cvs.sourceforge.net:/cvsroot/ipodder;asdf", # no ; in url
+                               ":pserver:anonymous:@ipodder.cvs.sourceforge.net:/cvsroot/ipodder malicious code", # spaces not allowed
+                               "sourceforge.net/svn/project/trunk", # missing a protocol prefix
+                               "file:///home/robin/cvs", # file protocol is not allowed
+                               "http://svn.sourceforge.net", # http protocol is not allowed
+                               "git://kernel.org/whatever/linux.git" # git protocol is not allowed
+                       ].each do |url|
+                               # Rejected for both internal and public use
+                               [true, false].each do |p|
+                                       cvs = CvsAdapter.new(:url => url, :public_urls_only => p)
+                                       assert cvs.validate_url
+                               end
+                       end
+               end
+
+               def test_accepted_urls
+                       [       ":pserver:anonymous:@ipodder.cvs.sourceforge.net:/cvsroot/ipodder",
+                               ":pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot",
+                               ":pserver:anonymous:@cvs-mirror.mozilla.org:/cvsroot",
+                               ":pserver:guest:@cvs.dev.java.net:/shared/data/ccvs/repository",
+                               ":pserver:anoncvs:password@anoncvs.postgresql.org:/projects/cvsroot",
+                               ":pserver:anonymous:@rubyeclipse.cvs.sourceforge.net:/cvsroot/rubyeclipse",
+                               ":pserver:cvs:cvs@cvs.winehq.org:/home/wine",
+                               ":pserver:tcpdump:anoncvs@cvs.tcpdump.org:/tcpdump/master",
+                               ":pserver:anonymous:@user-mode-linux.cvs.sourceforge.net:/cvsroot/user-mode-linux",
+                               ":pserver:anonymous:@sc2.cvs.sourceforge.net:/cvsroot/sc2",
+                               ":pserver:cool-dev:@sc2.cvs.sourceforge.net:/cvsroot/sc2", # Hyphen should be OK in username
+                               ":pserver:cvs_anon:@cvs.scms.waikato.ac.nz:/usr/local/global-cvs/ml_cvs", # Underscores should be ok in path
+                               ":pserver:anonymous:freefem++@idared.ann.jussieu.fr:/Users/pubcvs/cvs" # Pluses should be OK
+                       ].each do |url|
+                               # Valid for both internal and public use
+                               [true, false].each do |p|
+                                       cvs = CvsAdapter.new(:url => url, :public_urls_only => p)
+                                       assert !cvs.validate_url
+                               end
+                       end
+               end
+
+               # Local files not accepted for public URLs
+               def test_local_file_url
+                       cvs = CvsAdapter.new(:url => "/root")
+                       assert !cvs.validate_url
+
+                       cvs = CvsAdapter.new(:url => "/root", :public_urls_only => true)
+                       assert cvs.validate_url
+               end
+
+               def test_rejected_module_names
+                       [nil,"","%",";","&","\n","\t"].each do |x|
+                               cvs = CvsAdapter.new(:url => ":pserver:cvs:cvs@cvs.test.org:/test", :module_name => x)
+                               assert !cvs.valid?
+                               assert cvs.errors.first[0] = :module_name
+                       end
+               end
+
+               def test_accepted_module_names
+                       ["myproject","my/project","my/project/2.0","my_project","0","My .Net Module", "my-module", "my-module++"].each do |x|
+                               cvs = CvsAdapter.new(:url => ":pserver:cvs:cvs@cvs.test.org:/test", :module_name => x)
+                               assert cvs.valid?
+                       end
+               end
+
+               def test_symlink_fixup
+                       cvs = CvsAdapter.new(:url => ":pserver:anoncvs:@cvs.netbeans.org:/cvs")
+                       assert_equal ":pserver:anoncvs:@cvs.netbeans.org:/shared/data/ccvs/repository", cvs.normalize.url
+
+                       cvs = CvsAdapter.new(:url => ":pserver:anoncvs:@cvs.netbeans.org:/cvs/")
+                       assert_equal ":pserver:anoncvs:@cvs.netbeans.org:/shared/data/ccvs/repository", cvs.normalize.url
+
+                       cvs = CvsAdapter.new(:url => ":pserver:anoncvs:@cvs.dev.java.net:/cvs")
+                       assert_equal ":pserver:anoncvs:@cvs.dev.java.net:/shared/data/ccvs/repository", cvs.normalize.url
+
+                       cvs = CvsAdapter.new(:url => ":PSERVER:ANONCVS:@CVS.DEV.JAVA.NET:/cvs")
+                       assert_equal ":PSERVER:ANONCVS:@CVS.DEV.JAVA.NET:/shared/data/ccvs/repository", cvs.normalize.url
+
+                       cvs = CvsAdapter.new(:url => ":pserver:anonymous:@cvs.gna.org:/cvs/eagleusb")
+                       assert_equal ":pserver:anonymous:@cvs.gna.org:/var/cvs/eagleusb", cvs.normalize.url
+               end
+
+               def test_sync_pserver_username_password
+                       # Pull username only from url
+                       cvs = CvsAdapter.new(:url => ":pserver:guest:@ohloh.net:/test")
+                       cvs.normalize
+                       assert_equal ':pserver:guest:@ohloh.net:/test', cvs.url
+                       assert_equal 'guest', cvs.username
+                       assert_equal '', cvs.password
+
+                       # Pull username and password from url
+                       cvs = CvsAdapter.new(:url => ":pserver:guest:secret@ohloh.net:/test")
+                       cvs.normalize
+                       assert_equal ':pserver:guest:secret@ohloh.net:/test', cvs.url
+                       assert_equal 'guest', cvs.username
+                       assert_equal 'secret', cvs.password
+
+                       # Apply username and password to url
+                       cvs = CvsAdapter.new(:url => ":pserver::@ohloh.net:/test", :username => "guest", :password => "secret")
+                       cvs.normalize
+                       assert_equal ':pserver:guest:secret@ohloh.net:/test', cvs.url
+                       assert_equal 'guest', cvs.username
+                       assert_equal 'secret', cvs.password
+
+                       # Passwords disagree, use :password attribute
+                       cvs = CvsAdapter.new(:url => ":pserver:guest:old@ohloh.net:/test", :username => "guest", :password => "new")
+                       cvs.normalize
+                       assert_equal ':pserver:guest:new@ohloh.net:/test', cvs.url
+                       assert_equal 'guest', cvs.username
+                       assert_equal 'new', cvs.password
+               end
+
+               def test_guess_forge
+                       cvs = CvsAdapter.new(:url => nil)
+                       assert_equal nil, cvs.guess_forge
+
+                       cvs = CvsAdapter.new(:url => "garbage_in_garbage_out")
+                       assert_equal nil, cvs.guess_forge
+
+                       cvs = CvsAdapter.new(:url => ':pserver:guest:@cvs.dev.java.net:/cvs')
+                       assert_equal 'java.net', cvs.guess_forge
+
+                       cvs = CvsAdapter.new(:url => ":PSERVER:ANONCVS:@CVS.DEV.JAVA.NET:/cvs")
+                       assert_equal 'java.net', cvs.guess_forge
+               end
+       end
+end
diff --git a/test/unit/git_cat_file_test.rb b/test/unit/git_cat_file_test.rb
new file mode 100644 (file)
index 0000000..f122e0c
--- /dev/null
@@ -0,0 +1,21 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class GitMiscTest < Scm::Test
+
+               def test_cat_file
+                       with_git_repository('git') do |git|
+expected = <<-EXPECTED
+/* Hello, World! */
+#include <stdio.h>
+main()
+{
+       printf("Hello, World!\\n");
+}
+EXPECTED
+                               assert_equal expected, git.cat_file(nil, Scm::Diff.new(:sha1 => '4c734ad53b272c9b3d719f214372ac497ff6c068'))
+                       end
+               end
+
+       end
+end
diff --git a/test/unit/git_commit_all_test.rb b/test/unit/git_commit_all_test.rb
new file mode 100644 (file)
index 0000000..0fdb111
--- /dev/null
@@ -0,0 +1,34 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class GitCommitAllTest < Scm::Test
+
+               def test_commit_all
+                       Scm::ScratchDir.new do |dir|
+                               git = GitAdapter.new(:url => dir).normalize
+
+                               git.init_db
+                               assert !git.anything_to_commit?
+
+                               File.open(File.join(dir, 'README'), 'w') {}
+                               assert git.anything_to_commit?
+
+                               c = Scm::Commit.new
+                               c.author_name = "John Q. Developer"
+                               c.message = "Initial checkin."
+                               git.commit_all(c)
+                               assert !git.anything_to_commit?
+
+                               assert_equal 1, git.commits.size
+
+                               assert_equal c.author_name, git.commits.first.author_name
+                               # Depending on version of Git used, we may or may not have trailing \n.
+                               # We don't really care, so just compare the stripped versions.
+                               assert_equal c.message.strip, git.commits.first.message.strip
+
+                               assert_equal ['.gitignore', 'README'], git.commits.first.diffs.collect { |d| d.path }.sort
+                       end
+               end
+
+       end
+end
diff --git a/test/unit/git_commits_test.rb b/test/unit/git_commits_test.rb
new file mode 100644 (file)
index 0000000..dc2c8ae
--- /dev/null
@@ -0,0 +1,35 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class GitCommitsTest < Scm::Test
+
+               def test_commit
+                       with_git_repository('git') do |git|
+                               assert_equal 4, git.commit_count
+                               assert_equal 2, git.commit_count('b6e9220c3cabe53a4ed7f32952aeaeb8a822603d')
+                               assert_equal 0, git.commit_count('1df547800dcd168e589bb9b26b4039bff3a7f7e4')
+
+                               assert_equal ['089c527c61235bd0793c49109b5bd34d439848c6',
+                                                                                       'b6e9220c3cabe53a4ed7f32952aeaeb8a822603d',
+                                                                                       '2e9366dd7a786fdb35f211fff1c8ea05c51968b1',
+                                                                                       '1df547800dcd168e589bb9b26b4039bff3a7f7e4'], git.commit_tokens
+
+                               assert_equal ['1df547800dcd168e589bb9b26b4039bff3a7f7e4'],
+                                       git.commit_tokens('2e9366dd7a786fdb35f211fff1c8ea05c51968b1')
+
+                               assert_equal [], git.commit_tokens('1df547800dcd168e589bb9b26b4039bff3a7f7e4')
+
+                               assert_equal ['089c527c61235bd0793c49109b5bd34d439848c6',
+                                                                                       'b6e9220c3cabe53a4ed7f32952aeaeb8a822603d',
+                                                                                       '2e9366dd7a786fdb35f211fff1c8ea05c51968b1',
+                                                                                       '1df547800dcd168e589bb9b26b4039bff3a7f7e4'], git.commits.collect { |c| c.token }
+
+                               assert_equal ['1df547800dcd168e589bb9b26b4039bff3a7f7e4'],
+                                       git.commits('2e9366dd7a786fdb35f211fff1c8ea05c51968b1').collect { |c| c.token }
+
+                               assert_equal [], git.commits('1df547800dcd168e589bb9b26b4039bff3a7f7e4')
+                       end
+               end
+
+       end
+end
diff --git a/test/unit/git_log_parser_test.rb b/test/unit/git_log_parser_test.rb
new file mode 100644 (file)
index 0000000..3712bc5
--- /dev/null
@@ -0,0 +1,213 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'date'
+
+module Scm::Adapters
+       class GitLogParserTest < Scm::Test
+
+               def test_basic
+                       commits = []
+
+                       helloworld = File.new(File.dirname(__FILE__) + '/../data/helloworld.log').read
+
+                       Git::LogParser.parse( helloworld ) do |commit|
+                               commits << commit
+                       end
+
+                       assert commits
+                       assert_equal 3, commits.size
+
+                       commits.each do |commit|
+                               # puts commit.inspect
+                               assert_equal 40, commit.token.length
+
+                               # 00000000.... is ok for parent_sha1 (if we have no parent), but not for us!
+                               assert_not_equal "0000000000000000000000000000000000000000", commit.token
+
+                               assert_equal "robin", commit.author_name
+
+                               commit.diffs.each do |d|
+                                       assert_equal 40, d.sha1.length
+                                       assert_equal 40, d.parent_sha1.length
+                                       # 00000000.... is ok for our parent's sha1 (if we have no parent), but not for us!
+                                       assert_not_equal "0000000000000000000000000000000000000000", d.sha1
+
+                                       assert d.path.length > 0
+                                       assert d.action =~ /[ACDMRTUXB]/
+                               end
+                       end
+
+                       assert_equal Time.gm(2006,6,11,11,28,0), commits[0].author_date
+                       assert_equal Time.gm(2006,6,11,18,32,13), commits[1].author_date
+                       assert_equal Time.gm(2006,6,11, 9,34,17), commits[2].author_date
+
+                       assert_equal "Initial Checkin", commits[0].message
+                       assert_equal "added makefile", commits[1].message
+                       assert_equal "added some documentation and licensing info", commits[2].message
+
+                       assert_equal '.gitignore', commits[0].diffs[0].path
+                       assert_equal 'A', commits[0].diffs[0].action
+                       assert_equal 'helloworld.c', commits[0].diffs[1].path
+                       assert_equal 'A', commits[0].diffs[1].action
+                       assert_equal 'ohloh_token', commits[0].diffs[2].path
+                       assert_equal 'A', commits[0].diffs[2].action
+
+                       assert_equal 'makefile', commits[1].diffs[0].path
+                       assert_equal 'A', commits[1].diffs[0].action
+                       assert_equal 'ohloh_token', commits[1].diffs[1].path
+                       assert_equal 'M', commits[1].diffs[1].action
+
+                       assert_equal 'README', commits[2].diffs[0].path
+                       assert_equal 'A', commits[2].diffs[0].action
+                       assert_equal 'helloworld.c', commits[2].diffs[1].path
+                       assert_equal 'M', commits[2].diffs[1].action
+                       assert_equal 'ohloh_token', commits[2].diffs[2].path
+                       assert_equal 'M', commits[2].diffs[2].action
+               end
+
+               # If the filename includes non-ASCII characters, the filename is in double quotes.
+               # The quotes must be stripped.
+               def test_filename_in_quotes
+                       log = <<-LOG
+__BEGIN_COMMIT__
+Commit: 0546fa73b6951be72956bf4c72c37255034d8bdc
+Author: e2jk
+Date: Tue, Mar 13 2007 17:08:49 -0700
+__BEGIN_COMMENT__
+Supprime le dossier des bibliotheques du projet
+<unknown>
+__END_COMMENT__
+:100644 100644 8ffcfcbb647ab353e7e885fb3fd897eef719d64f e4eaafd3ed351461cef016bf606f0ce6af057380 M     "Cin\303\251 Library/Cin\303\251 Library.nsi"
+               LOG
+
+               commits = []
+               Git::LogParser.parse( log ) do |commit|
+                       commits << commit
+               end
+               assert_equal "Cin\303\251 Library/Cin\303\251 Library.nsi", commits[0].diffs[0].path
+       end
+
+       # Not all commits include file diffs. Need to support that case.
+       def test_commit_without_diffs
+               log = <<-LOG
+__BEGIN_COMMIT__
+Commit: 9abc3b26e395ea5199362d6e19c705eb58842cd8
+Author: troth
+Date: Tue Feb 11 19:03:03 2003 +0000
+__BEGIN_COMMENT__
+Remove reference to avr-gcc in depend rule (cut & paste error).
+<unknown>
+__END_COMMENT__
+:100644 100644 a35924054b56a3dd308ac92505b811bdfecee777 f4f4738ae0f49a56d97ba61d7feb09aa35d9e69d M     Makefile
+__BEGIN_COMMIT__
+Commit: 10ed46d82c279d090b664c48a88a95e7ad76de2f
+Author: bdean
+Date: Sun Feb 9 13:36:47 2003 +0000
+__BEGIN_COMMENT__
+Test commit in new public repository.  Before this time this repo
+existed on a private system.  Commits made by 'bsd' on the old system
+were made by Brian Dean (bdean on the current system).
+__END_COMMENT__
+__BEGIN_COMMIT__
+Commit: 213c3220ff91eedda7323187fed0552e07069400
+Author: bsd
+Date: Sat Feb 8 04:20:39 2003 +0000
+__BEGIN_COMMENT__
+The last part of that last commit message should read:
+
+All others - modify program description.
+
+__END_COMMENT__
+               LOG
+
+               commits = []
+               Git::LogParser.parse( log ) do |commit|
+                       commits << commit
+               end
+
+               assert commits
+               assert_equal 3, commits.size
+
+               assert_equal "Remove reference to avr-gcc in depend rule (cut & paste error).", commits[0].message
+               assert_equal "Test commit in new public repository.  Before this time this repo\n"+
+                                                           "existed on a private system.  Commits made by 'bsd' on the old system\n"+
+                                                           "were made by Brian Dean (bdean on the current system).", commits[1].message
+
+               assert_equal "The last part of that last commit message should read:\n\nAll others - modify program description.\n", commits[2].message
+
+               assert_equal 1, commits[0].diffs.size
+               assert_equal 0, commits[1].diffs.size
+               assert_equal 0, commits[2].diffs.size
+       end
+
+       def test_ignore_submodules
+               log = <<-LOG
+__BEGIN_COMMIT__
+Commit: 9abc3b26e395ea5199362d6e19c705eb58842cd8
+Author: troth
+Date: Tue Feb 11 19:03:03 2003 +0000
+__BEGIN_COMMENT__
+Remove a submodule from the project
+__END_COMMENT__
+:160000 000000 f4f4738ae0f49a56d97ba61d7feb09aa35d9e69d 0000000000000000000000000000000000000000 D  submodule
+__BEGIN_COMMIT__
+Commit: 10ed46d82c279d090b664c48a88a95e7ad76de2f
+Author: bdean
+Date: Sun Feb 9 13:36:47 2003 +0000
+__BEGIN_COMMENT__
+Add a submodule to the project
+__END_COMMENT__
+:000000 160000 0000000000000000000000000000000000000000 f4f4738ae0f49a56d97ba61d7feb09aa35d9e69d A  submodule
+               LOG
+
+               commits = []
+               Git::LogParser.parse( log ) do |commit|
+                       commits << commit
+               end
+
+               assert commits
+               assert_equal 2, commits.size
+
+               commits.each do |commit|
+                       assert_equal 0, commit.diffs.size
+               end
+       end
+
+       def test_use_email_when_names_are_missing
+               log = <<-LOG
+__BEGIN_COMMIT__
+Commit: ea26f7280956f1112a8e68610cb9d6336a94585d
+Author: mickeyl
+AuthorEmail: mickeyl@openembedded.org
+Date: Wed, 11 Jun 2008 00:37:47 +0000
+__BEGIN_COMMENT__
+fso-image: remove openmoko-sound-system2 in favour of pulseaudio-meta
+<unknown>
+__END_COMMENT__
+:100644 100644 a5bd9a39acc1567586372b63f347fb4df4f20957 72e6bb0df6f21387b2a3e8e1519e4aefea6339a0 M      packages/images/fso-image.bb
+
+__BEGIN_COMMIT__
+Commit: fa3ee9d4cefc2db81adadf36da9cacbe92ce96f1
+Author: 
+AuthorEmail: mickeyl@openembedded.org
+Date: Wed, 11 Jun 2008 00:37:06 +0000
+__BEGIN_COMMENT__
+gst-plugins-good 0.10.7 add missing dependency to esound
+<unknown>
+__END_COMMENT__
+:100644 100644 e84c4801f1d7acb0606e37a3a5b8c681182b3659 fb551f5176419f07b7901fb76493c8bb75de20ff M      packages/gstreamer/gst-plugins-good_0.10
+
+                       LOG
+
+                       commits = []
+                       Git::LogParser.parse( log ) do |commit|
+                               commits << commit
+                       end
+
+                       assert commits
+                       assert_equal 2, commits.size
+
+                       assert_equal 'mickeyl', commits.first.author_name # Use name when present
+                       assert_equal 'mickeyl@openembedded.org', commits.last.author_name # Else use email
+               end
+       end
+end
diff --git a/test/unit/git_misc_test.rb b/test/unit/git_misc_test.rb
new file mode 100644 (file)
index 0000000..4d0c138
--- /dev/null
@@ -0,0 +1,25 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class GitMiscTest < Scm::Test
+
+               def test_export
+                       with_git_repository('git') do |git|
+                               Scm::ScratchDir.new do |dir|
+                                       git.export(dir)
+                                       assert_equal ['.','..','.gitignore','COPYING','README','helloworld.c','makefile','ohloh_token'], Dir.entries(dir).sort
+                               end
+                       end
+               end
+
+               def test_head
+                       with_git_repository('git') do |git|
+                               assert git.exist?
+                               assert_equal '1df547800dcd168e589bb9b26b4039bff3a7f7e4', git.head
+                               assert_equal ['master'], git.branches
+                               assert git.has_branch?('master')
+                       end
+               end
+
+       end
+end
diff --git a/test/unit/git_pull_test.rb b/test/unit/git_pull_test.rb
new file mode 100644 (file)
index 0000000..e39e210
--- /dev/null
@@ -0,0 +1,22 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class GitPullTest < Scm::Test
+
+               def test_basic_pull
+                       with_git_repository('git') do |src|
+                               Scm::ScratchDir.new do |dest_dir|
+
+                                       dest = GitAdapter.new(:url => dest_dir).normalize
+                                       assert !dest.exist?
+
+                                       dest.pull(src)
+                                       assert dest.exist?
+
+                                       assert_equal src.log, dest.log
+                               end
+                       end
+               end
+
+       end
+end
diff --git a/test/unit/git_push_test.rb b/test/unit/git_push_test.rb
new file mode 100644 (file)
index 0000000..209830c
--- /dev/null
@@ -0,0 +1,47 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class GitPushTest < Scm::Test
+
+               def test_hostname
+                       assert_equal "foo", GitAdapter.new(:url => 'foo:/bar').hostname
+                       assert_equal "/bar", GitAdapter.new(:url => 'foo:/bar').path
+
+                       assert !GitAdapter.new.hostname
+                       assert !GitAdapter.new(:url => '/bar').hostname
+                       assert_equal 'http', GitAdapter.new(:url => 'http://www.ohloh.net/bar').hostname
+               end
+
+               def test_local
+                       assert !GitAdapter.new(:url => "foo:/bar").local? # Assuming your machine is not named "foo" :-)
+                       assert !GitAdapter.new(:url => "http://www.ohloh.net/foo").local?
+                       assert GitAdapter.new(:url => "src").local?
+                       assert GitAdapter.new(:url => "/Users/robin/src").local?
+                       assert GitAdapter.new(:url => "#{`hostname`.strip}:src").local?
+                       assert GitAdapter.new(:url => "#{`hostname`.strip}:/Users/robin/src").local?
+               end
+
+               def test_basic_push
+                       with_git_repository('git') do |src|
+                               Scm::ScratchDir.new do |dest_dir|
+
+                                       dest = GitAdapter.new(:url => dest_dir).normalize
+                                       assert !dest.exist?
+
+                                       src.push(dest)
+                                       assert dest.exist?
+                                       assert_equal src.log, dest.log
+
+                                       # Now push again. This tests a different code path!
+                                       File.open(File.join(src.url, 'foo'), 'w') { }
+                                       src.commit_all(Scm::Commit.new)
+
+                                       src.push(dest)
+                                       assert dest.exist?
+                                       assert_equal src.log, dest.log
+
+                               end
+                       end
+               end
+       end
+end
diff --git a/test/unit/git_token_test.rb b/test/unit/git_token_test.rb
new file mode 100644 (file)
index 0000000..b237050
--- /dev/null
@@ -0,0 +1,37 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class GitTokenTest < Scm::Test
+
+               def test_no_token_returns_nil
+                       Scm::ScratchDir.new do |dir|
+                               git = GitAdapter.new(:url => dir).normalize
+                               assert !git.read_token
+                               git.init_db
+                               assert !git.read_token
+                       end
+               end
+
+               def test_write_and_read_token
+                       Scm::ScratchDir.new do |dir|
+                               git = GitAdapter.new(:url => dir).normalize
+                               git.init_db
+                               git.write_token("FOO")
+                               assert !git.read_token # Token not valid until committed
+                               git.commit_all(Scm::Commit.new)
+                               assert_equal "FOO", git.read_token
+                       end
+               end
+
+               def test_commit_all_includes_write_token
+                       Scm::ScratchDir.new do |dir|
+                               git = GitAdapter.new(:url => dir).normalize
+                               git.init_db
+                               c = Scm::Commit.new
+                               c.token = "BAR"
+                               git.commit_all(c)
+                               assert_equal c.token, git.read_token
+                       end
+               end
+       end
+end
diff --git a/test/unit/git_validation_test.rb b/test/unit/git_validation_test.rb
new file mode 100644 (file)
index 0000000..a404675
--- /dev/null
@@ -0,0 +1,41 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class GitValidationTest < Scm::Test
+               def test_rejected_urls
+                       [       nil, "", "foo", "http:/", "http:://", "http://", "http://a",
+                       "kernel.org/linux/linux.git", # missing a protocol prefix
+                       "http://kernel.org/linux/lin%32ux.git", # no encoded strings allowed
+                       "http://kernel.org/linux/linux.git malicious code", # no spaces allowed
+                       "svn://svn.mythtv.org/svn/trunk", # svn protocol is not allowed
+                       "/home/robin/cvs", # local file paths not allowed
+                       "file:///home/robin/cvs", # file protocol is not allowed
+                       ":pserver:anonymous:@juicereceiver.cvs.sourceforge.net:/cvsroot/juicereceiver" # pserver is just wrong
+                       ].each do |url|
+                               git = GitAdapter.new(:url => url)
+                               assert git.validate_url.any?
+                       end
+               end
+
+               def test_accepted_urls
+                       [ "http://kernel.org/pub/scm/git/git.git",
+                       "git://kernel.org/pub/scm/git/git.git",
+                       "https://kernel.org/pub/scm/git/git.git",
+                       "https://kernel.org:8080/pub/scm/git/git.git",
+                       "git://kernel.org/~foo/git.git",
+                       "http://git.onerussian.com/pub/deb/impose+.git"
+                       ].each do |url|
+                               git = GitAdapter.new(:url => url)
+                               assert !git.validate_url
+                       end
+               end
+
+               def test_guess_forge
+                       git = GitAdapter.new(:url => nil)
+                       assert_equal nil, git.guess_forge
+
+                       git = GitAdapter.new( :url => 'http://kernel.org/pub/scm/linux/kernel/git/stable/linux-2.6.17.y.git')
+                       assert_equal 'kernel.org', git.guess_forge
+               end
+       end
+end
diff --git a/test/unit/ohlog_command_line_test.rb b/test/unit/ohlog_command_line_test.rb
new file mode 100644 (file)
index 0000000..2619373
--- /dev/null
@@ -0,0 +1,33 @@
+module Scm::Parsers
+       class CommandLineTest < Scm::Test
+               def test_cvs_from_file
+                       result = `#{File.dirname(__FILE__) + '/../../bin/ohlog'} --xml --cvs #{DATA_DIR + '/basic.rlog'}`
+                       assert_equal 0, $?
+                       assert_buffers_equal File.read(DATA_DIR + '/basic.ohlog'), result
+               end
+
+               def test_cvs_from_pipe
+                       result = `cat #{DATA_DIR + '/basic.rlog'} | #{File.dirname(__FILE__) + '/../../bin/ohlog'} --xml --cvs`
+                       assert_equal 0, $?
+                       assert_buffers_equal File.read(DATA_DIR + '/basic.ohlog'), result
+               end
+
+               def test_svn_from_file
+                       result = `#{File.dirname(__FILE__) + '/../../bin/ohlog'} --xml --svn #{DATA_DIR + '/simple.svn_log'}`
+                       assert_equal 0, $?
+                       assert_buffers_equal File.read(DATA_DIR + '/simple.ohlog'), result
+               end
+
+               def test_svn_xml_from_file
+                       result = `#{File.dirname(__FILE__) + '/../../bin/ohlog'} --xml --svn-xml #{DATA_DIR + '/simple.svn_xml_log'}`
+                       assert_equal 0, $?
+                       assert_buffers_equal File.read(DATA_DIR + '/simple.ohlog'), result
+               end
+
+               def test_help
+                       result = `#{File.dirname(__FILE__) + '/../../bin/ohlog'} -?`
+                       assert_equal 0, $?
+                       assert result =~ /Examples:/
+               end
+       end
+end
diff --git a/test/unit/svn_cat_file_test.rb b/test/unit/svn_cat_file_test.rb
new file mode 100644 (file)
index 0000000..38f665e
--- /dev/null
@@ -0,0 +1,21 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class SvnCatFileTest < Scm::Test
+
+               def test_cat_file
+                       with_svn_repository('svn') do |svn|
+expected = <<-EXPECTED
+/* Hello, World! */
+#include <stdio.h>
+main()
+{
+       printf("Hello, World!\\n");
+}
+EXPECTED
+                               assert_equal expected, svn.cat_file(Scm::Commit.new(:token => "1"), Scm::Diff.new(:path => "trunk/helloworld.c"))
+                       end
+
+               end
+       end
+end
diff --git a/test/unit/svn_commits_test.rb b/test/unit/svn_commits_test.rb
new file mode 100644 (file)
index 0000000..5e9c503
--- /dev/null
@@ -0,0 +1,232 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class SvnCommitsTest < Scm::Test
+
+               def test_commits
+                       with_svn_repository('svn') do |svn|
+                               assert_equal 5, svn.commit_count
+                               assert_equal 3, svn.commit_count(2)
+                               assert_equal 0, svn.commit_count(1000)
+
+                               assert_equal [1,2,3,4,5], svn.commit_tokens
+                               assert_equal [3,4,5], svn.commit_tokens(2)
+                               assert_equal [], svn.commit_tokens(1000)
+
+                               assert_equal [1,2,3,4,5], svn.commits.collect { |c| c.token }
+                               assert_equal [3,4,5], svn.commits(2).collect { |c| c.token }
+                               assert_equal [], svn.commits(1000)
+                       end
+               end
+
+               # Confirms that the sha1 matches those created by git exactly
+               def test_sha1
+                       with_svn_repository('svn') do |svn|
+                               assert_equal '0000000000000000000000000000000000000000', svn.try_get_sha1('/trunk/file_not_found')
+                               assert_equal 'f6adcae4447809b651c787c078d255b2b4e963c5', svn.try_get_sha1('/trunk/helloworld.c')
+                       end
+               end
+
+               # Given a commit with diffs, fill in all of the SHA1 values.
+               def test_populate_sha1
+                       with_svn_repository('svn') do |svn|
+                               c = Scm::Commit.new(:token => 3)
+                               c.diffs = [Scm::Diff.new(:path => "/trunk/helloworld.c", :action => "M")]
+                               svn.populate_sha1s!(c)
+                               assert_equal 'f6adcae4447809b651c787c078d255b2b4e963c5', c.diffs.first.sha1
+                               assert_equal '4c734ad53b272c9b3d719f214372ac497ff6c068', c.diffs.first.parent_sha1
+                       end
+               end
+
+               def test_strip_commit_branch
+                       svn = SvnAdapter.new(:branch_name => "/trunk")
+                       commit = Scm::Commit.new
+
+                       # nil diffs before => nil diffs after
+                       assert !svn.strip_commit_branch(commit).diffs
+
+                       # [] diffs before => [] diffs after
+                       commit.diffs = []
+                       assert_equal [], svn.strip_commit_branch(commit).diffs
+
+                       commit.diffs = [
+                               Scm::Diff.new(:path => "/trunk"),
+                               Scm::Diff.new(:path => "/trunk/helloworld.c"),
+                               Scm::Diff.new(:path => "/branches/a")
+                       ]
+                       assert_equal ['', '/helloworld.c'], svn.strip_commit_branch(commit).diffs.collect { |d| d.path }.sort
+               end
+
+               def test_strip_diff_branch
+                       svn = SvnAdapter.new(:branch_name => "/trunk")
+                       assert !svn.strip_diff_branch(Scm::Diff.new)
+                       assert !svn.strip_diff_branch(Scm::Diff.new(:path => "/branches/b"))
+                       assert_equal '', svn.strip_diff_branch(Scm::Diff.new(:path => "/trunk")).path
+                       assert_equal '/helloworld.c', svn.strip_diff_branch(Scm::Diff.new(:path => "/trunk/helloworld.c")).path
+               end
+
+               def test_strip_path_branch
+                       # Returns nil for any path outside of SvnAdapter::branch_name
+                       assert !SvnAdapter.new.strip_path_branch(nil)
+                       assert !SvnAdapter.new(:branch_name => "/trunk").strip_path_branch("/branches/foo")
+                       assert !SvnAdapter.new(:branch_name => "/trunk").strip_path_branch("/t")
+
+                       # If branch_name is empty or root, returns path unchanged
+                       assert_equal '', SvnAdapter.new.strip_path_branch('')
+                       assert_equal '/trunk', SvnAdapter.new.strip_path_branch('/trunk')
+
+                       # If path is equal to or is a subdirectory of branch_name, returns subdirectory portion only.
+                       assert_equal '', SvnAdapter.new(:branch_name => "/trunk").strip_path_branch('/trunk')
+                       assert_equal '/foo', SvnAdapter.new(:branch_name => "/trunk").strip_path_branch('/trunk/foo')
+               end
+
+               def test_strip_path_branch_with_special_chars
+                       assert_equal '/foo', SvnAdapter.new(:branch_name => '/trunk/hamcrest-c++').strip_path_branch('/trunk/hamcrest-c++/foo')
+               end
+
+
+               def test_deep_commits
+                       with_svn_repository('deep_svn') do |svn|
+
+                               # The full repository contains 4 revisions...
+                               assert_equal 4, svn.commit_count
+
+                               # ...however, the current trunk contains only revisions 3 and 4.
+                               # That's because the branch was moved to replace the trunk at revision 3.
+                               #
+                               # 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 2, trunk.commit_count
+                               assert_equal [3,4], trunk.commit_tokens
+
+
+                               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
+                               #
+                               #   D /branches/b
+                               #   A /trunk (from /branches/b:2)
+                               #
+                               # 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.
+
+                               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
+                               #
+                               # 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, 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, deep_commits.last.diffs.size
+
+                               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', 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
+
+               # A mini-integration test.
+               # Check that SHA1 values are populated, directories are recursed, and outside branches are ignored.
+               def test_each_commit
+                       commits = []
+                       with_svn_repository('svn') do |svn|
+                               svn.each_commit do |e|
+                                       commits << e
+                                       assert e.token
+                                       assert e.committer_name
+                                       assert e.committer_date
+                                       assert e.message
+                                       assert e.diffs
+                                       e.diffs.each do |d|
+                                               assert d.action.length == 1
+                                               assert d.path.length > 0
+                                       end
+                               end
+                       end
+
+                       assert_equal [1, 2, 3, 4, 5], commits.collect { |c| c.token }
+                       assert_equal ['robin','robin','robin','jason','jason'], commits.collect { |c| c.committer_name }
+
+                       assert commits[0].committer_date - Time.utc(2006,6,11,18,28,0) < 1 # commits include milliseconds
+                       assert commits[1].committer_date - Time.utc(2006,6,11,18,32,13) < 1
+                       assert commits[2].committer_date - Time.utc(2006,6,11,18,34,17) < 1
+                       assert commits[3].committer_date - Time.utc(2006,7,14,22,17,8) < 1
+                       assert commits[4].committer_date - Time.utc(2006,7,14,23,7,15) < 1
+
+                       assert_equal "Initial Checkin\n", commits[0].message
+                       assert_equal "added makefile", commits[1].message
+                       assert_equal "added some documentation and licensing info", commits[2].message
+                       assert_equal "added bs COPYING to catch global licenses", commits[3].message
+                       assert_equal "moving COPYING", commits[4].message
+
+                       assert_equal 1, commits[0].diffs.size
+                       assert_equal 'A', commits[0].diffs[0].action
+                       assert_equal '/trunk/helloworld.c', commits[0].diffs[0].path
+                       assert_equal '4c734ad53b272c9b3d719f214372ac497ff6c068', commits[0].diffs[0].sha1
+                       assert_equal '0000000000000000000000000000000000000000', commits[0].diffs[0].parent_sha1
+
+                       assert_equal 1, commits[1].diffs.size
+                       assert_equal 'A', commits[1].diffs[0].action
+                       assert_equal '/trunk/makefile', commits[1].diffs[0].path
+                       assert_equal 'af2dfd5070b01a19b672861e595de98c101c49cc', commits[1].diffs[0].sha1
+                       assert_equal '0000000000000000000000000000000000000000', commits[1].diffs[0].parent_sha1
+
+                       assert_equal 2, commits[2].diffs.size
+                       assert_equal 'A', commits[2].diffs[0].action
+                       assert_equal '/trunk/README', commits[2].diffs[0].path
+                       assert_equal 'f0547ce063095e66be74618bc410989df226d2d2', commits[2].diffs[0].sha1
+                       assert_equal '0000000000000000000000000000000000000000', commits[2].diffs[0].parent_sha1
+                       assert_equal 'M', commits[2].diffs[1].action
+                       assert_equal '/trunk/helloworld.c', commits[2].diffs[1].path
+                       assert_equal 'f6adcae4447809b651c787c078d255b2b4e963c5', commits[2].diffs[1].sha1
+                       assert_equal '4c734ad53b272c9b3d719f214372ac497ff6c068', commits[2].diffs[1].parent_sha1
+
+                       assert_equal 1, commits[3].diffs.size
+                       assert_equal 'A', commits[3].diffs[0].action
+                       assert_equal '/COPYING', commits[3].diffs[0].path
+                       assert_equal '6ff87c4664981e4397625791c8ea3bbb5f2279a3', commits[3].diffs[0].sha1
+                       assert_equal '0000000000000000000000000000000000000000', commits[3].diffs[0].parent_sha1
+
+                       assert_equal 2, commits[4].diffs.size
+                       assert_equal 'D', commits[4].diffs[0].action
+                       assert_equal '/COPYING', commits[4].diffs[0].path
+                       assert_equal '0000000000000000000000000000000000000000', commits[4].diffs[0].sha1
+                       assert_equal '6ff87c4664981e4397625791c8ea3bbb5f2279a3', commits[4].diffs[0].parent_sha1
+                       assert_equal 'A', commits[4].diffs[1].action
+                       assert_equal '/trunk/COPYING', commits[4].diffs[1].path
+                       assert_equal '6ff87c4664981e4397625791c8ea3bbb5f2279a3', commits[4].diffs[1].sha1
+                       assert_equal '0000000000000000000000000000000000000000', commits[4].diffs[1].parent_sha1
+               end
+
+
+       end
+end
diff --git a/test/unit/svn_convert_test.rb b/test/unit/svn_convert_test.rb
new file mode 100644 (file)
index 0000000..b7c01ac
--- /dev/null
@@ -0,0 +1,28 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class SvnConvertTest < Scm::Test
+               def test_basic_convert
+                       with_svn_repository('svn') do |src|
+                               Scm::ScratchDir.new do |dest_dir|
+                                       dest = GitAdapter.new(:url => dest_dir).normalize
+                                       assert !dest.exist?
+
+                                       dest.pull(src)
+                                       assert dest.exist?
+
+                                       dest_commits = dest.commits
+                                       src.commits.each_with_index do |c, i|
+                                               # Because Subversion does not track authors (only committers),
+                                               # the Subversion committer becomes the Git author.
+                                               assert_equal c.committer_name, dest_commits[i].author_name
+                                               assert_equal c.committer_date, dest_commits[i].author_date
+
+                                               # The svn-to-git conversion process loses the trailing \n for single-line messages
+                                               assert_equal c.message.strip, dest_commits[i].message.strip
+                                       end
+                               end
+                       end
+               end
+       end
+end
diff --git a/test/unit/svn_misc_test.rb b/test/unit/svn_misc_test.rb
new file mode 100644 (file)
index 0000000..3fd5fe2
--- /dev/null
@@ -0,0 +1,59 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class SvnMiscTest < Scm::Test
+
+               def test_export
+                       with_svn_repository('svn') do |svn|
+                               Scm::ScratchDir.new do |dir|
+                                       svn.export(dir)
+                                       assert_equal ['.','..','branches','tags','trunk'], Dir.entries(dir).sort
+                               end
+                       end
+               end
+
+               def test_path
+                       assert !SvnAdapter.new(:url => "http://svn.collab.net/repos/svn/trunk").path
+                       assert !SvnAdapter.new(:url => "svn://svn.collab.net/repos/svn/trunk").path
+                       assert_equal "/foo/bar", SvnAdapter.new(:url => "file:///foo/bar").path
+                       assert_equal "foo/bar", SvnAdapter.new(:url => "file://foo/bar").path
+                       assert_equal "/foo/bar", SvnAdapter.new(:url => "svn+ssh://server/foo/bar").path
+               end
+
+               def test_hostname
+                       assert !SvnAdapter.new(:url => "http://svn.collab.net/repos/svn/trunk").hostname
+                       assert !SvnAdapter.new(:url => "svn://svn.collab.net/repos/svn/trunk").hostname
+                       assert !SvnAdapter.new(:url => "file:///foo/bar").hostname
+                       assert_equal "server", SvnAdapter.new(:url => "svn+ssh://server/foo/bar").hostname
+               end
+
+               def test_info
+                       with_svn_repository('svn') do |svn|
+                               assert_equal svn.url, svn.root
+                               assert_equal "6a9cefd4-a008-4d2a-a89b-d77e99cd6eb1", svn.uuid
+                               assert_equal 5, svn.max_revision
+                               assert_equal 'directory', svn.node_kind
+
+                               assert_equal 'file', svn.node_kind('trunk/helloworld.c',1)
+                       end
+               end
+
+               def test_ls
+                       with_svn_repository('svn') do |svn|
+                               assert_equal ['branches/', 'tags/', 'trunk/'], svn.ls
+                               assert_equal ['COPYING','README','helloworld.c','makefile'], svn.ls('trunk')
+                               assert_equal ['helloworld.c'], svn.ls('trunk', 1)
+
+                               assert_equal ['trunk/helloworld.c'], svn.recurse_files(nil, 1)
+                               assert_equal ['helloworld.c'], svn.recurse_files('/trunk', 1)
+                       end
+               end
+
+               def test_is_directory
+                       with_svn_repository('svn') do |svn|
+                               assert svn.is_directory?('trunk')
+                               assert !svn.is_directory?('trunk/helloworld.c')
+                       end
+               end
+       end
+end
diff --git a/test/unit/svn_parser_test.rb b/test/unit/svn_parser_test.rb
new file mode 100644 (file)
index 0000000..ffcaa0b
--- /dev/null
@@ -0,0 +1,137 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Parsers
+       class SvnParserTest < Scm::Test
+
+               def test_basic
+                       assert_convert(SvnParser, DATA_DIR + '/simple.svn_log', DATA_DIR + '/simple.ohlog')
+               end
+
+               def test_empty_array
+                       assert_equal([], SvnParser.parse(''))
+               end
+
+               def test_empty_xml
+                       assert_equal("<?xml version=\"1.0\"?>\n<ohloh_log scm=\"svn\">\n</ohloh_log>\n", SvnParser.parse('', :writer => XmlWriter.new))
+               end
+
+               def test_yield_instead_of_writer
+                       commits = []
+                       result = SvnParser.parse(File.read(DATA_DIR + '/simple.svn_log')) do |commit|
+                               commits << commit.token
+                       end
+                       assert_nil result
+                       assert_equal [5, 4, 3, 2, 1], commits
+               end
+
+               def test_log_parser
+                       sample_log = <<SAMPLE
+------------------------------------------------------------------------
+r1 | robin | 2006-06-11 11:28:00 -0700 (Sun, 11 Jun 2006) | 2 lines
+
+Initial Checkin
+
+------------------------------------------------------------------------
+r2 | jason | 2006-06-11 11:32:13 -0700 (Sun, 11 Jun 2006) | 1 line
+
+added makefile
+------------------------------------------------------------------------
+r3 | robin | 2006-06-11 11:34:17 -0700 (Sun, 11 Jun 2006) | 1 line
+
+added some documentation and licensing info
+------------------------------------------------------------------------
+SAMPLE
+
+                       revs = SvnParser.parse(sample_log)
+
+                       assert revs
+                       assert_equal 3, revs.size
+
+                       assert_equal 1, revs[0].token
+                       assert_equal 'robin', revs[0].committer_name
+                       assert_equal "Initial Checkin\n", revs[0].message # Note \n at end of comment
+                       assert_equal Time.utc(2006,6,11,18,28,00), revs[0].committer_date
+
+                       assert_equal 2, revs[1].token
+                       assert_equal 'jason', revs[1].committer_name
+                       assert_equal "added makefile", revs[1].message # Note no \n at end of comment
+                       assert_equal Time.utc(2006,6,11,18,32,13), revs[1].committer_date
+
+                       assert_equal 3, revs[2].token
+                       assert_equal 'robin', revs[2].committer_name
+                       assert_equal "added some documentation and licensing info", revs[2].message
+                       assert_equal Time.utc(2006,6,11,18,34,17), revs[2].committer_date
+               end
+
+               # This is an excerpt from the log for Wireshark. It includes Subversion log excerpts in
+               # its comments, which really screwed us up. This test confirms that I've fixed the
+               # parser to ignore log excerpts in the comments.
+               def test_log_embedded_in_comments
+                       log = <<LOG
+------------------------------------------------------------------------
+r21932 | jmayer | 2007-05-25 01:34:15 -0700 (Fri, 25 May 2007) | 22 lines
+
+Update from samba tree revision 23054 to 23135
+============================ Samba log start ============
+------------------------------------------------------------------------
+r23069 | metze | 2007-05-22 13:23:36 +0200 (Tue, 22 May 2007) | 3 lines
+Changed paths:
+   M /branches/SAMBA_4_0/source/pidl/tests/Util.pm
+
+print out the command, to find out the problem on host 'tridge'
+
+metze
+------------------------------------------------------------------------
+r23071 | metze | 2007-05-22 14:45:58 +0200 (Tue, 22 May 2007) | 3 lines
+Changed paths:
+   M /branches/SAMBA_4_0/source/pidl/tests/Util.pm
+
+print the command on failure only
+
+metze
+------------------------------------------------------------------------
+------------------------------------------------------------------------
+============================ Samba log end ==============
+
+------------------------------------------------------------------------
+r21931 | kukosa | 2007-05-24 23:54:39 -0700 (Thu, 24 May 2007) | 2 lines
+
+UMTS RRC updated to 3GPP TS 25.331 V7.4.0 (2007-03) and moved to one directory
+
+------------------------------------------------------------------------
+LOG
+                       revs = SvnParser.parse(log)
+
+                       assert revs
+                       assert_equal 2, revs.size
+
+                       assert_equal 21932, revs[0].token
+                       assert_equal 21931, revs[1].token
+
+                       comment = <<COMMENT
+Update from samba tree revision 23054 to 23135
+============================ Samba log start ============
+------------------------------------------------------------------------
+r23069 | metze | 2007-05-22 13:23:36 +0200 (Tue, 22 May 2007) | 3 lines
+Changed paths:
+   M /branches/SAMBA_4_0/source/pidl/tests/Util.pm
+
+print out the command, to find out the problem on host 'tridge'
+
+metze
+------------------------------------------------------------------------
+r23071 | metze | 2007-05-22 14:45:58 +0200 (Tue, 22 May 2007) | 3 lines
+Changed paths:
+   M /branches/SAMBA_4_0/source/pidl/tests/Util.pm
+
+print the command on failure only
+
+metze
+------------------------------------------------------------------------
+------------------------------------------------------------------------
+============================ Samba log end ==============
+COMMENT
+                       assert_equal comment, revs[0].message
+               end
+       end
+end
diff --git a/test/unit/svn_pull_test.rb b/test/unit/svn_pull_test.rb
new file mode 100644 (file)
index 0000000..61de105
--- /dev/null
@@ -0,0 +1,59 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'socket'
+
+module Scm::Adapters
+       class SvnPullTest < Scm::Test
+
+               def test_svnadmin_create
+                       Scm::ScratchDir.new do |dir|
+                               url = File.join(dir, "my_svn_repo")
+                               svn = SvnAdapter.new(:url => url).normalize
+
+                               assert !svn.exist?
+                               svn.svnadmin_create
+                               assert svn.exist?
+
+                               # Ensure that revision properties are settable
+                               svn.propset('foo','bar')
+                               assert_equal 'bar', svn.propget('foo')
+                       end
+               end
+
+               def test_basic_pull_using_svnsync
+                       with_svn_repository('svn') do |src|
+                               Scm::ScratchDir.new do |dest_dir|
+
+                                       dest = SvnAdapter.new(:url => dest_dir).normalize
+                                       assert !dest.exist?
+
+                                       dest.pull(src)
+                                       assert dest.exist?
+
+                                       assert_equal src.log, dest.log
+                               end
+                       end
+               end
+
+               def test_svnadmin_create_local
+                       Scm::ScratchDir.new do |dir|
+                               svn = SvnAdapter.new(:url => "file://#{dir}")
+                               svn.svnadmin_create_local
+                               assert svn.exist?
+                               assert FileTest.exist?(File.join(dir, 'hooks', 'pre-revprop-change'))
+                               assert FileTest.executable?(File.join(dir, 'hooks', 'pre-revprop-change'))
+                               svn.run File.join(dir, 'hooks', 'pre-revprop-change')
+                       end
+               end
+
+               def test_svnadmin_create_remote
+                       Scm::ScratchDir.new do |dir|
+                               svn = SvnAdapter.new(:url => "svn+ssh://#{Socket.gethostname}#{dir}")
+                               svn.svnadmin_create_remote
+                               assert svn.exist?
+                               assert FileTest.exist?(File.join(dir, 'hooks', 'pre-revprop-change'))
+                               assert FileTest.executable?(File.join(dir, 'hooks', 'pre-revprop-change'))
+                               svn.run File.join(dir, 'hooks', 'pre-revprop-change')
+                       end
+               end
+       end
+end
diff --git a/test/unit/svn_push_test.rb b/test/unit/svn_push_test.rb
new file mode 100644 (file)
index 0000000..e945e22
--- /dev/null
@@ -0,0 +1,40 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'socket'
+
+module Scm::Adapters
+       class SvnPushTest < Scm::Test
+
+               def test_basic_push_using_svnsync
+                       with_svn_repository('svn') do |src|
+                               Scm::ScratchDir.new do |dest_dir|
+
+                                       dest = SvnAdapter.new(:url => dest_dir).normalize
+                                       assert !dest.exist?
+
+                                       src.push(dest)
+                                       assert dest.exist?
+
+                                       assert_equal src.log, dest.log
+                               end
+                       end
+               end
+
+               # Triggers the "ssh" code path by using svn+ssh:// protocol instead of file:// protocol.
+               # Simulates pushing to another server in our cluster.
+               def test_ssh_push_using_svnsync
+                       with_svn_repository('svn') do |src|
+                               Scm::ScratchDir.new do |dest_dir|
+
+                                       dest = SvnAdapter.new(:url => "svn+ssh://#{Socket.gethostname}#{File.expand_path(dest_dir)}").normalize
+                                       assert !dest.exist?
+
+                                       src.push(dest)
+                                       assert dest.exist?
+
+                                       assert_equal src.log, dest.log
+                               end
+                       end
+               end
+
+       end
+end
diff --git a/test/unit/svn_validation_test.rb b/test/unit/svn_validation_test.rb
new file mode 100644 (file)
index 0000000..966bdad
--- /dev/null
@@ -0,0 +1,97 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Adapters
+       class SvnValidationTest < Scm::Test
+               def test_rejected_urls
+                       [       nil, "", "foo", "http:/", "http:://", "http://",
+                       "sourceforge.net/svn/project/trunk", # missing a protocol prefix
+                       "http://robin@svn.sourceforge.net/", # must not include a username with the url
+                       "http://svn.sourceforge.net/asdf/asdf/ malicious code", # no spaces allowed
+                       "/home/robin/cvs", # local file paths not allowed
+                       "git://kernel.org/whatever/linux.git", # git protocol is not allowed
+                       ":pserver:anonymous:@juicereceiver.cvs.sourceforge.net:/cvsroot/juicereceiver", # pserver is just wrong
+                       "svn://svn.gajim.org:/gajim/trunk", # invalid port number
+                       "svn://svn.gajim.org:abc/gajim/trunk", # invalid port number
+                       "svn log https://svn.sourceforge.net/svnroot/myserver/trunk"
+                       ].each do |url|
+                               # Rejected for both internal and public use
+                               [true, false].each do |p|
+                                       svn = SvnAdapter.new(:url => url, :public_urls_only => p)
+                                       assert svn.validate_url
+                               end
+                       end
+               end
+
+               def test_accepted_urls
+                       [       "https://svn.sourceforge.net/svnroot/opende/trunk", # https protocol OK
+                       "svn://svn.gajim.org/gajim/trunk", # svn protocol OK
+                       "http://svn.mythtv.org/svn/trunk/mythtv", # http protocol OK
+                       "https://svn.sourceforge.net/svnroot/vienna-rss/trunk/2.0.0", # periods, numbers and dashes OK
+                       "svn://svn.gajim.org:3690/gajim/trunk", # port number OK
+                       "http://svn.mythtv.org:80/svn/trunk/mythtv", # port number OK
+                       "http://svn.gnome.org/svn/gtk+/trunk", # + character OK
+                       "http://svn.gnome.org", # no path, no trailing /, just a domain name is OK
+                       "http://brlcad.svn.sourceforge.net/svnroot/brlcad/rt^3/trunk", # a caret ^ is allowed
+                       "http://www.thus.ch/~patrick/svn/pvalsecc" # ~ is allowed
+                       ].each do |url|
+                               # Accepted for both internal and public use
+                               [true, false].each do |p|
+                                       svn = SvnAdapter.new(:url => url, :public_urls_only => p)
+                                       assert !svn.validate_url
+                               end
+                       end
+               end
+
+               # These urls are not available to the public
+               def test_rejected_public_urls
+                       [ "file:///home/robin/svn"
+                       ].each do |url|
+                               svn = SvnAdapter.new(:url => url, :public_urls_only => true)
+                               assert svn.validate_url
+
+                               svn = SvnAdapter.new(:url => url)
+                               assert !svn.validate_url
+                       end
+               end
+
+               def test_guess_forge
+                       svn = SvnAdapter.new(:url => nil)
+                       assert_equal nil, svn.guess_forge
+
+                       svn = SvnAdapter.new(:url => 'garbage_in_garbage_out')
+                       assert_equal nil, svn.guess_forge
+
+                       svn = SvnAdapter.new(:url => 'svn://rubyforge.org//var/svn/rubyomf2097')
+                       assert_equal 'rubyforge.org', svn.guess_forge
+
+                       svn = SvnAdapter.new(:url => 'svn://rubyforge.org:3960//var/svn/rubyomf2097')
+                       assert_equal 'rubyforge.org', svn.guess_forge
+
+                       svn = SvnAdapter.new(:url => 'https://svn.sourceforge.net/svnroot/typo3/CoreDocs/trunk')
+                       assert_equal 'sourceforge.net', svn.guess_forge
+
+                       svn = SvnAdapter.new(:url => 'https://svn.sourceforge.net:80/svnroot/typo3/CoreDocs/trunk')
+                       assert_equal 'sourceforge.net', svn.guess_forge
+
+                       svn = SvnAdapter.new(:url => 'https://vegastrike.svn.sourceforge.net/svnroot/vegastrike/trunk')
+                       assert_equal 'sourceforge.net', svn.guess_forge
+
+                       svn = SvnAdapter.new(:url => 'https://appfuse.dev.java.net/svn/appfuse/trunk')
+                       assert_equal 'java.net', svn.guess_forge
+
+                       svn = SvnAdapter.new(:url => 'http://moulinette.googlecode.com/svn/trunk')
+                       assert_equal 'googlecode.com', svn.guess_forge
+               end
+
+               def test_sourceforge_requires_https
+                       assert_equal 'https://gallery.svn.sourceforge.net/svnroot/gallery/trunk/gallery2',
+                               SvnAdapter.new(:url => 'http://gallery.svn.sourceforge.net/svnroot/gallery/trunk/gallery2').normalize.url
+
+                       assert_equal 'https://gallery.svn.sourceforge.net/svnroot/gallery/trunk/gallery2',
+                               SvnAdapter.new(:url => 'https://gallery.svn.sourceforge.net/svnroot/gallery/trunk/gallery2').normalize.url
+
+                       assert_equal 'http://pianosa.googlecode.com/svn/trunk',
+                               SvnAdapter.new(:url => 'http://pianosa.googlecode.com/svn/trunk').normalize.url
+               end
+       end
+end
diff --git a/test/unit/svn_xml_parser_test.rb b/test/unit/svn_xml_parser_test.rb
new file mode 100644 (file)
index 0000000..de1f12d
--- /dev/null
@@ -0,0 +1,19 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+module Scm::Parsers
+       class SvnXmlParserTest < Scm::Test
+
+               def test_basic
+                       assert_convert(SvnXmlParser, DATA_DIR + '/simple.svn_xml_log', DATA_DIR + '/simple.ohlog')
+               end
+
+               def test_empty_array
+                       assert_equal([], SvnXmlParser.parse(''))
+               end
+
+               def test_empty_xml
+                       assert_equal("<?xml version=\"1.0\"?>\n<ohloh_log scm=\"svn\">\n</ohloh_log>\n", SvnXmlParser.parse('', :writer => XmlWriter.new))
+               end
+
+       end
+end