rscm 0.1.0.1338 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/README +13 -28
  2. data/Rakefile +25 -24
  3. data/lib/rscm.rb +1 -6
  4. data/lib/rscm/abstract_scm.rb +36 -10
  5. data/lib/rscm/annotations.rb +50 -0
  6. data/lib/rscm/changes.rb +2 -5
  7. data/lib/rscm/logging.rb +1 -0
  8. data/lib/rscm/path_converter.rb +3 -2
  9. data/lib/rscm/{cvs → scm}/cvs.rb +16 -9
  10. data/lib/rscm/{cvs → scm}/cvs_log_parser.rb +4 -4
  11. data/lib/rscm/{darcs → scm}/darcs.rb +6 -3
  12. data/lib/rscm/scm/monotone.rb +162 -0
  13. data/lib/rscm/scm/monotone_log_parser.rb +95 -0
  14. data/lib/rscm/scm/mooky.rb +21 -0
  15. data/lib/rscm/{perforce → scm}/perforce.rb +7 -4
  16. data/lib/rscm/{starteam/starteam.rb → scm/star_team.rb} +23 -3
  17. data/lib/rscm/{svn/svn.rb → scm/subversion.rb} +17 -10
  18. data/lib/rscm/{svn/svn_log_parser.rb → scm/subversion_log_parser.rb} +8 -7
  19. data/test/rscm/abstract_scm_test.rb +21 -0
  20. data/test/rscm/annotations_test.rb +57 -0
  21. data/test/rscm/changes_fixture.rb +7 -7
  22. data/test/rscm/changes_test.rb +3 -3
  23. data/test/rscm/generic_scm_tests.rb +2 -2
  24. data/test/rscm/{cvs → scm}/cvs-dataforge.log +0 -0
  25. data/test/rscm/{cvs → scm}/cvs-test.log +0 -0
  26. data/test/rscm/{cvs → scm}/cvs_log_parser_test.rb +12 -13
  27. data/test/rscm/{cvs → scm}/cvs_test.rb +7 -7
  28. data/test/rscm/{darcs → scm}/darcs_test.rb +1 -1
  29. data/test/rscm/{monotone → scm}/keys +0 -0
  30. data/test/rscm/scm/monotone_log_parser_test.rb +109 -0
  31. data/test/rscm/{monotone → scm}/monotone_test.rb +1 -1
  32. data/test/rscm/{mooky → scm}/mooky_test.rb +1 -1
  33. data/test/rscm/{perforce → scm}/perforce_test.rb +1 -1
  34. data/test/rscm/{starteam/starteam_test.rb → scm/star_team.rb} +1 -1
  35. data/test/rscm/{svn/svn_log_parser_test.rb → scm/subversion_log_parser_test.rb} +25 -10
  36. data/test/rscm/{svn/svn_test.rb → scm/subversion_test.rb} +4 -5
  37. data/test/rscm/{svn/cargo-svn.log → scm/svn-cargo.log} +0 -0
  38. data/test/rscm/scm/svn-growl.log +875 -0
  39. data/test/rscm/scm/svn-growl2.log +30 -0
  40. data/test/rscm/{svn/proxytoys-svn.log → scm/svn-proxytoys.log} +0 -0
  41. metadata +35 -44
  42. data/lib/rscm/attr_attr.rb +0 -36
  43. data/lib/rscm/monotone/monotone.rb +0 -107
  44. data/lib/rscm/mooky/mooky.rb +0 -13
  45. data/test/actual +0 -3
  46. data/test/expected +0 -3
  47. 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
- def initialize(dir=nil)
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 = nil)
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 = nil, files = nil)
59
- client(checkout_dir).changesets(from_identifier, to_identifier, files)
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, files)
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
- attr_accessor :user_name, :password, :server_name, :server_port, :project_name, :view_name, :folder_name
23
+ ann :description => "User name"
24
+ attr_accessor :user_name
23
25
 
24
- def initialize(user_name=nil, password=nil, server_name=nil, server_port=nil, project_name=nil, view_name=nil, folder_name=nil)
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, files=nil, &proc)
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/svn/svn_log_parser'
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 SVN < AbstractSCM
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=nil, path="")
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 = SVNLogParser.new(stdout, path, checkout_dir)
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, files=nil)
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, files)}"
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 = SVNLogParser.new(stdout, path, checkout_dir)
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, files)
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("\"{%Y-%m-%d %H:%M:%S\"}")
322
+ time.utc.strftime("{\"%Y-%m-%d %H:%M:%S\"}")
316
323
  end
317
324
 
318
325
  def commit_command(message)