rscm 0.1.0.1338 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +13 -28
- data/Rakefile +25 -24
- data/lib/rscm.rb +1 -6
- data/lib/rscm/abstract_scm.rb +36 -10
- data/lib/rscm/annotations.rb +50 -0
- data/lib/rscm/changes.rb +2 -5
- data/lib/rscm/logging.rb +1 -0
- data/lib/rscm/path_converter.rb +3 -2
- data/lib/rscm/{cvs → scm}/cvs.rb +16 -9
- data/lib/rscm/{cvs → scm}/cvs_log_parser.rb +4 -4
- data/lib/rscm/{darcs → scm}/darcs.rb +6 -3
- data/lib/rscm/scm/monotone.rb +162 -0
- data/lib/rscm/scm/monotone_log_parser.rb +95 -0
- data/lib/rscm/scm/mooky.rb +21 -0
- data/lib/rscm/{perforce → scm}/perforce.rb +7 -4
- data/lib/rscm/{starteam/starteam.rb → scm/star_team.rb} +23 -3
- data/lib/rscm/{svn/svn.rb → scm/subversion.rb} +17 -10
- data/lib/rscm/{svn/svn_log_parser.rb → scm/subversion_log_parser.rb} +8 -7
- data/test/rscm/abstract_scm_test.rb +21 -0
- data/test/rscm/annotations_test.rb +57 -0
- data/test/rscm/changes_fixture.rb +7 -7
- data/test/rscm/changes_test.rb +3 -3
- data/test/rscm/generic_scm_tests.rb +2 -2
- data/test/rscm/{cvs → scm}/cvs-dataforge.log +0 -0
- data/test/rscm/{cvs → scm}/cvs-test.log +0 -0
- data/test/rscm/{cvs → scm}/cvs_log_parser_test.rb +12 -13
- data/test/rscm/{cvs → scm}/cvs_test.rb +7 -7
- data/test/rscm/{darcs → scm}/darcs_test.rb +1 -1
- data/test/rscm/{monotone → scm}/keys +0 -0
- data/test/rscm/scm/monotone_log_parser_test.rb +109 -0
- data/test/rscm/{monotone → scm}/monotone_test.rb +1 -1
- data/test/rscm/{mooky → scm}/mooky_test.rb +1 -1
- data/test/rscm/{perforce → scm}/perforce_test.rb +1 -1
- data/test/rscm/{starteam/starteam_test.rb → scm/star_team.rb} +1 -1
- data/test/rscm/{svn/svn_log_parser_test.rb → scm/subversion_log_parser_test.rb} +25 -10
- data/test/rscm/{svn/svn_test.rb → scm/subversion_test.rb} +4 -5
- data/test/rscm/{svn/cargo-svn.log → scm/svn-cargo.log} +0 -0
- data/test/rscm/scm/svn-growl.log +875 -0
- data/test/rscm/scm/svn-growl2.log +30 -0
- data/test/rscm/{svn/proxytoys-svn.log → scm/svn-proxytoys.log} +0 -0
- metadata +35 -44
- data/lib/rscm/attr_attr.rb +0 -36
- data/lib/rscm/monotone/monotone.rb +0 -107
- data/lib/rscm/mooky/mooky.rb +0 -13
- data/test/actual +0 -3
- data/test/expected +0 -3
- data/test/rscm/attr_attr_test.rb +0 -32
| @@ -1,12 +1,15 @@ | |
| 1 | 
            -
            require 'rscm/abstract_scm'
         | 
| 2 1 | 
             
            require 'tempfile'
         | 
| 3 | 
            -
            require 'rscm/path_converter'
         | 
| 4 2 | 
             
            require 'fileutils'
         | 
| 3 | 
            +
            require 'rscm'
         | 
| 5 4 |  | 
| 6 5 | 
             
            module RSCM
         | 
| 7 6 | 
             
              class Darcs < AbstractSCM
         | 
| 7 | 
            +
                register self
         | 
| 8 8 |  | 
| 9 | 
            -
                 | 
| 9 | 
            +
                ann :description => "Directory"
         | 
| 10 | 
            +
                attr_accessor :dir
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def initialize(dir=".")
         | 
| 10 13 | 
             
                  @dir = File.expand_path(dir)
         | 
| 11 14 | 
             
                end
         | 
| 12 15 |  | 
| @@ -0,0 +1,162 @@ | |
| 1 | 
            +
            require 'fileutils'
         | 
| 2 | 
            +
            require 'rscm'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module RSCM
         | 
| 5 | 
            +
              class Monotone < AbstractSCM
         | 
| 6 | 
            +
                register self
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                ann :description => "Database file"
         | 
| 9 | 
            +
                attr_accessor :db_file
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                ann :description => "Branch"
         | 
| 12 | 
            +
                attr_accessor :branch
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                ann :description => "Key"
         | 
| 15 | 
            +
                attr_accessor :key
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                ann :description => "Passphrase"
         | 
| 18 | 
            +
                attr_accessor :passphrase
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                ann :description => "Keys file"
         | 
| 21 | 
            +
                attr_accessor :keys_file
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def initialize(db_file="MT.db", branch="", key="", passphrase="", keys_file="")
         | 
| 24 | 
            +
                  @db_file = File.expand_path(db_file)
         | 
| 25 | 
            +
                  @branch = branch
         | 
| 26 | 
            +
                  @key = key
         | 
| 27 | 
            +
                  @passphrase = passphrase
         | 
| 28 | 
            +
                  @keys_file = keys_file
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def name
         | 
| 32 | 
            +
                  "Monotone"
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def create
         | 
| 36 | 
            +
                  FileUtils.mkdir_p(File.dirname(@db_file))
         | 
| 37 | 
            +
                  monotone("db init")
         | 
| 38 | 
            +
                  monotone("read") do |io|
         | 
| 39 | 
            +
                    io.write(File.open(@keys_file).read)
         | 
| 40 | 
            +
                    io.close_write
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def transactional?
         | 
| 45 | 
            +
                  true
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def import(dir, message)
         | 
| 49 | 
            +
                  dir = File.expand_path(dir)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  # post 0.17, this can be "cd dir && cmd add ."
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  files = Dir["#{dir}/*"]
         | 
| 54 | 
            +
                  relative_paths_to_add = to_relative(dir, files)
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  with_working_dir(dir) do
         | 
| 57 | 
            +
                    monotone("add #{relative_paths_to_add.join(' ')}")
         | 
| 58 | 
            +
                    monotone("commit '#{message}'", @branch, @key) do |io|
         | 
| 59 | 
            +
                      io.puts(@passphrase)
         | 
| 60 | 
            +
                      io.close_write
         | 
| 61 | 
            +
                      io.read
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def checked_out?(checkout_dir)
         | 
| 67 | 
            +
                  File.exists?("#{checkout_dir}/MT")
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def uptodate?(checkout_dir, from_identifier)
         | 
| 71 | 
            +
                  if (!checked_out?(checkout_dir))
         | 
| 72 | 
            +
                    false
         | 
| 73 | 
            +
                  else
         | 
| 74 | 
            +
                    lr = local_revision(checkout_dir)
         | 
| 75 | 
            +
                    hr = head_revision(checkout_dir)
         | 
| 76 | 
            +
                    lr == hr
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def local_revision(checkout_dir)
         | 
| 81 | 
            +
                  local_revision = nil
         | 
| 82 | 
            +
                  rev_file = File.expand_path("#{checkout_dir}/MT/revision")
         | 
| 83 | 
            +
                  local_revision = File.open(rev_file).read.strip
         | 
| 84 | 
            +
                  local_revision
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
                
         | 
| 87 | 
            +
                def head_revision(checkout_dir)
         | 
| 88 | 
            +
                  # FIXME: this will grab last head if heads are not merged.
         | 
| 89 | 
            +
                  head_revision = nil
         | 
| 90 | 
            +
                  monotone("heads", @branch) do |stdout|
         | 
| 91 | 
            +
                    stdout.each_line do |line|
         | 
| 92 | 
            +
                      next if (line =~ /^monotone:/)
         | 
| 93 | 
            +
                      head_revision = line.split(" ")[0]
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                  head_revision
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def changesets(checkout_dir, from_identifier, to_identifier=Time.infinity)
         | 
| 100 | 
            +
                  from_identifier = Time.epoch if from_identifier.nil?
         | 
| 101 | 
            +
                  to_identifier = Time.infinity if to_identifier.nil?
         | 
| 102 | 
            +
                  with_working_dir(checkout_dir) do
         | 
| 103 | 
            +
                    monotone("log", @branch, @key) do |stdout|
         | 
| 104 | 
            +
                      MonotoneLogParser.new.parse_changesets(stdout, from_identifier, to_identifier)
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                def commit(checkout_dir, message)
         | 
| 110 | 
            +
                  with_working_dir(checkout_dir) do
         | 
| 111 | 
            +
                    monotone("commit '#{message}'", @branch, @key) do |io|
         | 
| 112 | 
            +
                      io.puts(@passphrase)
         | 
| 113 | 
            +
                      io.close_write
         | 
| 114 | 
            +
                      io.read
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
              protected
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                # Checks out silently. Called by superclass' checkout.
         | 
| 122 | 
            +
                def checkout_silent(checkout_dir, to_identifier)
         | 
| 123 | 
            +
                  checkout_dir = PathConverter.filepath_to_nativepath(checkout_dir, false)
         | 
| 124 | 
            +
                  if checked_out?(checkout_dir)
         | 
| 125 | 
            +
                    with_working_dir(checkout_dir) do
         | 
| 126 | 
            +
                      monotone("update")
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
                  else
         | 
| 129 | 
            +
                    monotone("checkout #{checkout_dir}", @branch, @key) do |stdout|
         | 
| 130 | 
            +
                      stdout.each_line do |line|
         | 
| 131 | 
            +
                        # TODO: checkout prints nothing to stdout - may be fixed in a future monotone.
         | 
| 132 | 
            +
                        # When/if it happens we may want to do a kosher implementation of checkout
         | 
| 133 | 
            +
                        # to get yields as checkouts happen.
         | 
| 134 | 
            +
                        yield line if block_given?
         | 
| 135 | 
            +
                      end
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                # Administrative files that should be ignored when counting files.
         | 
| 141 | 
            +
                def ignore_paths
         | 
| 142 | 
            +
                  return [/MT/, /\.mt-attrs/]
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
              private
         | 
| 146 | 
            +
              
         | 
| 147 | 
            +
                def monotone(monotone_cmd, branch=nil, key=nil)
         | 
| 148 | 
            +
                  branch_opt = branch ? "--branch=\"#{branch}\"" : ""
         | 
| 149 | 
            +
                  key_opt = key ? "--key=\"#{key}\"" : ""
         | 
| 150 | 
            +
                  cmd = "monotone --db=\"#{@db_file}\" #{branch_opt} #{key_opt} #{monotone_cmd}"
         | 
| 151 | 
            +
                  safer_popen(cmd, "r+") do |io|
         | 
| 152 | 
            +
                    if(block_given?)
         | 
| 153 | 
            +
                      return(yield(io))
         | 
| 154 | 
            +
                    else
         | 
| 155 | 
            +
                      # just read stdout so we can exit
         | 
| 156 | 
            +
                      io.read
         | 
| 157 | 
            +
                    end
         | 
| 158 | 
            +
                  end
         | 
| 159 | 
            +
                end
         | 
| 160 | 
            +
              
         | 
| 161 | 
            +
              end
         | 
| 162 | 
            +
            end
         | 
| @@ -0,0 +1,95 @@ | |
| 1 | 
            +
            require 'rscm'
         | 
| 2 | 
            +
            require 'time'
         | 
| 3 | 
            +
            require 'stringio'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module RSCM
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              class MonotoneLogParser
         | 
| 8 | 
            +
              
         | 
| 9 | 
            +
                def parse_changesets(io, from_identifier=Time.epoch, to_identifier=Time.infinity)
         | 
| 10 | 
            +
                  # skip first separator
         | 
| 11 | 
            +
                  io.readline
         | 
| 12 | 
            +
                  
         | 
| 13 | 
            +
                  changesets = ChangeSets.new
         | 
| 14 | 
            +
                  changeset_string = ""
         | 
| 15 | 
            +
                  
         | 
| 16 | 
            +
                  # hash of path => [array of revisions]
         | 
| 17 | 
            +
                  path_revisions = {}
         | 
| 18 | 
            +
                  io.each_line do |line|
         | 
| 19 | 
            +
                    if(line =~ /-----------------------------------------------------------------/)
         | 
| 20 | 
            +
                      changeset = parse_changeset(StringIO.new(changeset_string), path_revisions)
         | 
| 21 | 
            +
                      changesets.add(changeset)
         | 
| 22 | 
            +
                      changeset_string = ""
         | 
| 23 | 
            +
                    else
         | 
| 24 | 
            +
                      changeset_string << line
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                  changeset = parse_changeset(StringIO.new(changeset_string), path_revisions)
         | 
| 28 | 
            +
                  if((from_identifier <= changeset.time) && (changeset.time <= to_identifier))
         | 
| 29 | 
            +
                    changesets.add(changeset)
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  # set the previous revisions. most recent is at index 0.
         | 
| 33 | 
            +
                  changesets.each do |changeset|
         | 
| 34 | 
            +
                    changeset.each do |change|
         | 
| 35 | 
            +
                      current_index = path_revisions[change.path].index(change.revision)
         | 
| 36 | 
            +
                      change.previous_revision = path_revisions[change.path][current_index + 1]
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                  changesets
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def parse_changeset(changeset_io, path_revisions)
         | 
| 43 | 
            +
                  changeset = ChangeSet.new
         | 
| 44 | 
            +
                  state = nil
         | 
| 45 | 
            +
                  changeset_io.each_line do |line|
         | 
| 46 | 
            +
                    if(line =~ /^Revision: (.*)$/ && changeset.revision.nil?)
         | 
| 47 | 
            +
                      changeset.revision = $1
         | 
| 48 | 
            +
                    elsif(line =~ /^Author: (.*)$/ && changeset.developer.nil?)
         | 
| 49 | 
            +
                      changeset.developer = $1
         | 
| 50 | 
            +
                    elsif(line =~ /^Date: (.*)$/ && changeset.time.nil?)
         | 
| 51 | 
            +
                      changeset.time = Time.utc(
         | 
| 52 | 
            +
                        $1[0..3].to_i,
         | 
| 53 | 
            +
                        $1[5..6].to_i,
         | 
| 54 | 
            +
                        $1[8..9].to_i,
         | 
| 55 | 
            +
                        $1[11..12].to_i,
         | 
| 56 | 
            +
                        $1[14..15].to_i,
         | 
| 57 | 
            +
                        $1[17..18].to_i
         | 
| 58 | 
            +
                      )
         | 
| 59 | 
            +
                    elsif(line =~ /^ChangeLog:$/ && changeset.message.nil?)
         | 
| 60 | 
            +
                      state = :message
         | 
| 61 | 
            +
                    elsif(state == :message && changeset.message.nil?)
         | 
| 62 | 
            +
                      changeset.message = ""
         | 
| 63 | 
            +
                    elsif(state == :message && changeset.message)
         | 
| 64 | 
            +
                      changeset.message << line
         | 
| 65 | 
            +
                    elsif(line =~ /^Added files:$/)
         | 
| 66 | 
            +
                      state = :added
         | 
| 67 | 
            +
                    elsif(state == :added)
         | 
| 68 | 
            +
                      add_changes(changeset, line, Change::ADDED, path_revisions)
         | 
| 69 | 
            +
                    elsif(line =~ /^Modified files:$/)
         | 
| 70 | 
            +
                      state = :modified
         | 
| 71 | 
            +
                    elsif(state == :modified)
         | 
| 72 | 
            +
                      add_changes(changeset, line, Change::MODIFIED, path_revisions)
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                  changeset.message.chomp!
         | 
| 76 | 
            +
                  changeset
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
                
         | 
| 79 | 
            +
              private
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def add_changes(changeset, line, state, path_revisions)
         | 
| 82 | 
            +
                  paths = line.split(" ")
         | 
| 83 | 
            +
                  paths.each do |path|
         | 
| 84 | 
            +
                    changeset << Change.new(path, state, changeset.developer, nil, changeset.revision, changeset.time)
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    # now record path revisions so we can keep track of previous rev for each path
         | 
| 87 | 
            +
                    # doesn't work for moved files, and have no idea how to make it work either.
         | 
| 88 | 
            +
                    path_revisions[path] ||= [] 
         | 
| 89 | 
            +
                    path_revisions[path] << changeset.revision
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
                  
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            require 'rscm/abstract_scm'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RSCM
         | 
| 4 | 
            +
              class Mooky < AbstractSCM
         | 
| 5 | 
            +
                register self
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                ann :description => "The Foo", :tip => "Foo is nonsense"
         | 
| 8 | 
            +
                attr_accessor :foo
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                ann :description => "Le Bar", :tip => "Bar toi!"
         | 
| 11 | 
            +
                attr_accessor :bar
         | 
| 12 | 
            +
              
         | 
| 13 | 
            +
                def initialize(foo="", bar="chocolate bar")
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              
         | 
| 16 | 
            +
                def name
         | 
| 17 | 
            +
                  "Mooky"
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -15,11 +15,14 @@ module RSCM | |
| 15 15 | 
             
              # You need the p4/p4d executable on the PATH in order for it to work.
         | 
| 16 16 | 
             
              #
         | 
| 17 17 | 
             
              class Perforce < AbstractSCM
         | 
| 18 | 
            +
                register self
         | 
| 19 | 
            +
             | 
| 18 20 | 
             
                include FileUtils
         | 
| 19 21 |  | 
| 22 | 
            +
                ann :description => "Depot path", :tip => "The path to the Perforce depot"
         | 
| 20 23 | 
             
                attr_accessor :depotpath
         | 
| 21 24 |  | 
| 22 | 
            -
                def initialize(repository_root_dir =  | 
| 25 | 
            +
                def initialize(repository_root_dir = "")
         | 
| 23 26 | 
             
                  @clients = {}
         | 
| 24 27 | 
             
                  @depotpath = repository_root_dir
         | 
| 25 28 | 
             
                end
         | 
| @@ -55,8 +58,8 @@ module RSCM | |
| 55 58 | 
             
                  client(checkout_dir).submit(message, &proc)
         | 
| 56 59 | 
             
                end
         | 
| 57 60 |  | 
| 58 | 
            -
                def changesets(checkout_dir, from_identifier, to_identifier | 
| 59 | 
            -
                  client(checkout_dir).changesets(from_identifier, to_identifier | 
| 61 | 
            +
                def changesets(checkout_dir, from_identifier, to_identifier=Time.infinity)
         | 
| 62 | 
            +
                  client(checkout_dir).changesets(from_identifier, to_identifier)
         | 
| 60 63 | 
             
                end
         | 
| 61 64 |  | 
| 62 65 | 
             
                def uptodate?(checkout_dir, from_identifier)
         | 
| @@ -251,7 +254,7 @@ module RSCM | |
| 251 254 | 
             
                  p4("sync -n").empty?
         | 
| 252 255 | 
             
                end
         | 
| 253 256 |  | 
| 254 | 
            -
                def changesets(from_identifier, to_identifier | 
| 257 | 
            +
                def changesets(from_identifier, to_identifier)
         | 
| 255 258 | 
             
                  changesets = changelists(from_identifier, to_identifier).collect {|changelist| to_changeset(changelist)}
         | 
| 256 259 | 
             
                  ChangeSets.new(changesets)
         | 
| 257 260 | 
             
                end
         | 
| @@ -18,10 +18,30 @@ module RSCM | |
| 18 18 | 
             
              # * Apache Ant (http://ant.apache.org/)
         | 
| 19 19 | 
             
              #
         | 
| 20 20 | 
             
              class StarTeam < AbstractSCM
         | 
| 21 | 
            +
                register self
         | 
| 21 22 |  | 
| 22 | 
            -
                 | 
| 23 | 
            +
                ann :description => "User name"
         | 
| 24 | 
            +
                attr_accessor :user_name
         | 
| 23 25 |  | 
| 24 | 
            -
                 | 
| 26 | 
            +
                ann :description => "Password"
         | 
| 27 | 
            +
                attr_accessor :password
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                ann :description => "Server name"
         | 
| 30 | 
            +
                attr_accessor :server_name
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                ann :description => "Server port"
         | 
| 33 | 
            +
                attr_accessor :server_port
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                ann :description => "Project name"
         | 
| 36 | 
            +
                attr_accessor :project_name
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                ann :description => "View name"
         | 
| 39 | 
            +
                attr_accessor :view_name
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                ann :description => "Folder name"
         | 
| 42 | 
            +
                attr_accessor :folder_name
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def initialize(user_name="", password="", server_name="", server_port="", project_name="", view_name="", folder_name="")
         | 
| 25 45 | 
             
                  @user_name, @password, @server_name, @server_port, @project_name, @view_name, @folder_name = user_name, password, server_name, server_port, project_name, view_name, folder_name
         | 
| 26 46 | 
             
                end
         | 
| 27 47 |  | 
| @@ -29,7 +49,7 @@ module RSCM | |
| 29 49 | 
             
                  "StarTeam"
         | 
| 30 50 | 
             
                end
         | 
| 31 51 |  | 
| 32 | 
            -
                def changesets(checkout_dir, from_identifier=Time.epoch, to_identifier=Time.infinity,  | 
| 52 | 
            +
                def changesets(checkout_dir, from_identifier=Time.epoch, to_identifier=Time.infinity, &proc)
         | 
| 33 53 | 
             
                  # just assuming it is a Time for now, may support labels later.
         | 
| 34 54 | 
             
                  # the java class really wants rfc822 and not rfc2822, but this works ok anyway.
         | 
| 35 55 | 
             
                  from = from_identifier.to_rfc2822
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            require 'rscm/abstract_scm'
         | 
| 2 2 | 
             
            require 'rscm/path_converter'
         | 
| 3 3 | 
             
            require 'rscm/line_editor'
         | 
| 4 | 
            -
            require 'rscm/ | 
| 4 | 
            +
            require 'rscm/scm/subversion_log_parser'
         | 
| 5 5 |  | 
| 6 6 | 
             
            module RSCM
         | 
| 7 7 |  | 
| @@ -10,14 +10,21 @@ module RSCM | |
| 10 10 | 
             
              # You need the svn/svnadmin executable on the PATH in order for it to work.
         | 
| 11 11 | 
             
              #
         | 
| 12 12 | 
             
              # NOTE: On Cygwin these have to be the win32 builds of svn/svnadmin and not the Cygwin ones.
         | 
| 13 | 
            -
              class  | 
| 13 | 
            +
              class Subversion < AbstractSCM
         | 
| 14 | 
            +
                register self
         | 
| 15 | 
            +
             | 
| 14 16 | 
             
                include FileUtils
         | 
| 15 17 | 
             
                include PathConverter
         | 
| 16 18 |  | 
| 19 | 
            +
                ann :description => "Repository URL"
         | 
| 20 | 
            +
                ann :tip => "If you specify a local URL (starting with file://) DamageControl can create the repository for you after you save (unless the repository already exists).<br>Using a file:// URL will also give you the option to have DamageControl install a trigger in Subversion, so that you don't have to use polling to detect changes.<br>On Windows, file URLs must look like file:///C:/jupiter/mars"
         | 
| 17 21 | 
             
                attr_accessor :url
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                ann :description => "Path"
         | 
| 24 | 
            +
                ann :tip => "This is the relative path from the start of the repository <br>to the end of the URL. For example, if your URL is <br>svn://your.server/path/to/repository/path/within/repository <br>then this value should be path/within/repository."
         | 
| 18 25 | 
             
                attr_accessor :path
         | 
| 19 26 |  | 
| 20 | 
            -
                def initialize(url= | 
| 27 | 
            +
                def initialize(url="", path="trunk")
         | 
| 21 28 | 
             
                  @url, @path = url, path
         | 
| 22 29 | 
             
                end
         | 
| 23 30 |  | 
| @@ -98,7 +105,7 @@ module RSCM | |
| 98 105 | 
             
                  cmd = "svn log #{repourl} -r HEAD"
         | 
| 99 106 | 
             
                  with_working_dir(checkout_dir) do
         | 
| 100 107 | 
             
                    safer_popen(cmd) do |stdout|
         | 
| 101 | 
            -
                      parser =  | 
| 108 | 
            +
                      parser = SubversionLogParser.new(stdout, path, checkout_dir)
         | 
| 102 109 | 
             
                      changesets = parser.parse_changesets
         | 
| 103 110 | 
             
                      changesets[0].revision.to_i
         | 
| 104 111 | 
             
                    end
         | 
| @@ -117,7 +124,7 @@ module RSCM | |
| 117 124 |  | 
| 118 125 | 
             
                def diff(checkout_dir, change, &block)
         | 
| 119 126 | 
             
                  with_working_dir(checkout_dir) do
         | 
| 120 | 
            -
                    cmd = "svn diff -r #{change.previous_revision}:#{change.revision} #{url}/#{change.path}"
         | 
| 127 | 
            +
                    cmd = "svn diff -r #{change.previous_revision}:#{change.revision} \"#{url}/#{change.path}\""
         | 
| 121 128 | 
             
                    safer_popen(cmd) do |io|
         | 
| 122 129 | 
             
                      return(yield(io))
         | 
| 123 130 | 
             
                    end
         | 
| @@ -179,18 +186,18 @@ module RSCM | |
| 179 186 | 
             
                  svn(dir, import_cmd)
         | 
| 180 187 | 
             
                end
         | 
| 181 188 |  | 
| 182 | 
            -
                def changesets(checkout_dir, from_identifier, to_identifier=Time.infinity | 
| 189 | 
            +
                def changesets(checkout_dir, from_identifier, to_identifier=Time.infinity)
         | 
| 183 190 | 
             
                  # Return empty changeset if the requested revision doesn't exist yet.
         | 
| 184 191 | 
             
                  return ChangeSets.new if(from_identifier.is_a?(Integer) && head_revision(checkout_dir) < from_identifier)
         | 
| 185 192 |  | 
| 186 193 | 
             
                  checkout_dir = PathConverter.filepath_to_nativepath(checkout_dir, false)
         | 
| 187 194 | 
             
                  changesets = nil
         | 
| 188 | 
            -
                  command = "svn #{changes_command(from_identifier, to_identifier | 
| 195 | 
            +
                  command = "svn #{changes_command(from_identifier, to_identifier)}"
         | 
| 189 196 | 
             
                  yield command if block_given?
         | 
| 190 197 |  | 
| 191 198 | 
             
                  with_working_dir(checkout_dir) do
         | 
| 192 199 | 
             
                    safer_popen(command) do |stdout|
         | 
| 193 | 
            -
                      parser =  | 
| 200 | 
            +
                      parser = SubversionLogParser.new(stdout, path, checkout_dir)
         | 
| 194 201 | 
             
                      changesets = parser.parse_changesets
         | 
| 195 202 | 
             
                    end
         | 
| 196 203 | 
             
                  end
         | 
| @@ -274,7 +281,7 @@ module RSCM | |
| 274 281 | 
             
                  "update #{revision_option(nil,to_identifier)}"
         | 
| 275 282 | 
             
                end
         | 
| 276 283 |  | 
| 277 | 
            -
                def changes_command(from_identifier, to_identifier | 
| 284 | 
            +
                def changes_command(from_identifier, to_identifier)
         | 
| 278 285 | 
             
                  # http://svnbook.red-bean.com/svnbook-1.1/svn-book.html#svn-ch-3-sect-3.3
         | 
| 279 286 | 
             
                  # file_list = files.join('\n')
         | 
| 280 287 | 
             
            # WEIRD cygwin bug garbles this!?!?!?!
         | 
| @@ -312,7 +319,7 @@ module RSCM | |
| 312 319 |  | 
| 313 320 | 
             
                def svndate(time)
         | 
| 314 321 | 
             
                  return nil unless time
         | 
| 315 | 
            -
                  time.utc.strftime("\" | 
| 322 | 
            +
                  time.utc.strftime("{\"%Y-%m-%d %H:%M:%S\"}")
         | 
| 316 323 | 
             
                end
         | 
| 317 324 |  | 
| 318 325 | 
             
                def commit_command(message)
         |