rscm 0.1.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.
Files changed (43) hide show
  1. data/README +198 -0
  2. data/Rakefile +118 -0
  3. data/ext/rscm.jar +0 -0
  4. data/lib/rscm.rb +10 -0
  5. data/lib/rscm/abstract_log_parser.rb +49 -0
  6. data/lib/rscm/abstract_scm.rb +229 -0
  7. data/lib/rscm/changes.rb +271 -0
  8. data/lib/rscm/cvs/cvs.rb +363 -0
  9. data/lib/rscm/cvs/cvs_log_parser.rb +161 -0
  10. data/lib/rscm/darcs/darcs.rb +69 -0
  11. data/lib/rscm/line_editor.rb +46 -0
  12. data/lib/rscm/logging.rb +5 -0
  13. data/lib/rscm/monotone/monotone.rb +107 -0
  14. data/lib/rscm/mooky/mooky.rb +13 -0
  15. data/lib/rscm/parser.rb +39 -0
  16. data/lib/rscm/path_converter.rb +92 -0
  17. data/lib/rscm/perforce/perforce.rb +415 -0
  18. data/lib/rscm/starteam/starteam.rb +99 -0
  19. data/lib/rscm/svn/svn.rb +337 -0
  20. data/lib/rscm/svn/svn_log_parser.rb +134 -0
  21. data/lib/rscm/time_ext.rb +125 -0
  22. data/test/rscm/apply_label_scm_tests.rb +26 -0
  23. data/test/rscm/changes_fixture.rb +20 -0
  24. data/test/rscm/changes_test.rb +129 -0
  25. data/test/rscm/cvs/cvs_log_parser_test.rb +575 -0
  26. data/test/rscm/cvs/cvs_test.rb +22 -0
  27. data/test/rscm/darcs/darcs_test.rb +14 -0
  28. data/test/rscm/difftool_test.rb +40 -0
  29. data/test/rscm/file_ext.rb +12 -0
  30. data/test/rscm/generic_scm_tests.rb +282 -0
  31. data/test/rscm/line_editor_test.rb +76 -0
  32. data/test/rscm/mockit.rb +130 -0
  33. data/test/rscm/mockit_test.rb +117 -0
  34. data/test/rscm/monotone/monotone_test.rb +19 -0
  35. data/test/rscm/mooky/mooky_test.rb +14 -0
  36. data/test/rscm/parser_test.rb +47 -0
  37. data/test/rscm/path_converter_test.rb +52 -0
  38. data/test/rscm/perforce/perforce_test.rb +14 -0
  39. data/test/rscm/starteam/starteam_test.rb +36 -0
  40. data/test/rscm/svn/svn_log_parser_test.rb +111 -0
  41. data/test/rscm/svn/svn_test.rb +28 -0
  42. data/test/rscm/tempdir.rb +12 -0
  43. metadata +81 -0
@@ -0,0 +1,161 @@
1
+ require 'rscm/changes'
2
+ require 'rscm/abstract_log_parser'
3
+
4
+ require 'ftools'
5
+
6
+ module RSCM
7
+
8
+ class CVSLogParser < AbstractLogParser
9
+ REVISION_SEPARATOR = /^----------------------------$/
10
+ ENTRY_SEPARATOR = /^=============================================================================$/
11
+
12
+ attr_accessor :cvspath
13
+ attr_accessor :cvsmodule
14
+
15
+ def initialize(io)
16
+ super(io)
17
+ @log = ""
18
+ end
19
+
20
+ def parse_changesets
21
+ changesets = ChangeSets.new
22
+ while(log_entry = next_log_entry)
23
+ @log<<log_entry
24
+ @log<<""
25
+ begin
26
+ parse_changes(log_entry, changesets)
27
+ rescue Exception => e
28
+ $stderr.puts("could not parse log entry: #{log_entry}\ndue to: #{e.message}\n\t")
29
+ $stderr.puts(e.backtrace.join("\n\t"))
30
+ end
31
+ end
32
+ changesets.sort!
33
+ end
34
+
35
+ def next_log_entry
36
+ read_until_matching_line(ENTRY_SEPARATOR)
37
+ end
38
+
39
+ def split_entries(log_entry)
40
+ entries = [""]
41
+ log_entry.each_line do |line|
42
+ if line=~REVISION_SEPARATOR
43
+ entries << ""
44
+ else
45
+ entries[entries.length-1] << line
46
+ end
47
+ end
48
+ entries
49
+ end
50
+
51
+ def parse_changes(log_entry, changesets)
52
+ entries = split_entries(log_entry)
53
+
54
+ entries[1..entries.length].each do |entry|
55
+ change = parse_change(entry)
56
+ next if change.nil?
57
+ change.path = parse_path(entries[0])
58
+
59
+ change.status = Change::ADDED if change.revision =~ /1\.1$/
60
+
61
+ changeset = changesets.add(change)
62
+ # CVS doesn't have revision for changesets, use
63
+ # Fisheye-style revision
64
+ # changeset.revision = "MAIN:#{change.developer}:#{change.time.utc.strftime('%Y%m%d%H%M%S')}" if changeset
65
+ end
66
+ nil
67
+ end
68
+
69
+ def parse_head_revision(first_entry)
70
+ head_revision = extract_match(first_entry, /^head: (.*?)$/m)
71
+ end
72
+
73
+ def parse_path(first_entry)
74
+ working_file = extract_match(first_entry, /^Working file: (.*?)$/m)
75
+ return convert_all_slashes_to_forward_slashes(working_file) unless working_file.nil? || working_file == ""
76
+ make_relative_to_module(extract_required_match(first_entry, /^RCS file: (.*?)(,v|$)/m))
77
+ end
78
+
79
+ def make_relative_to_module(file)
80
+ return file if cvspath.nil? || cvsmodule.nil? || file.nil?
81
+ cvspath = convert_all_slashes_to_forward_slashes(self.cvspath)
82
+ convert_all_slashes_to_forward_slashes(file).gsub(/^#{cvspath}\/#{cvsmodule}\//, "")
83
+ end
84
+
85
+ def parse_change(change_entry)
86
+ raise "can't parse: #{change_entry}" if change_entry =~ REVISION_SEPARATOR
87
+
88
+ change_entry_lines = change_entry.split(/\r?\n/)
89
+ change = Change.new
90
+
91
+ change.revision = extract_match(change_entry_lines[0], /revision (.*)$/)
92
+
93
+ change.previous_revision = determine_previous_revision(change.revision)
94
+ change.time = parse_cvs_time(extract_required_match(change_entry_lines[1], /date: (.*?)(;|$)/))
95
+ change.developer = extract_match(change_entry_lines[1], /author: (.*?);/)
96
+
97
+ state = extract_match(change_entry_lines[1], /state: (.*?);/)
98
+ change.status = STATES[state]
99
+
100
+ message_start = 2
101
+ branches = nil
102
+ if(change_entry_lines[2] =~ /^branches:\s+(.*);/)
103
+ message_start = 3
104
+ branches = $1
105
+ end
106
+
107
+ change.message = change_entry_lines[message_start..-1].join("\n")
108
+
109
+ # Ignore the initial revision from import - we will have two of them
110
+ if(change.message == "Initial revision" && branches == "1.1.1")
111
+ return nil
112
+ end
113
+
114
+ change
115
+ end
116
+
117
+ def determine_previous_revision(revision)
118
+ if revision =~ /(.*)\.(.*)/
119
+ big_version_number = $1
120
+ small_version_number = $2.to_i
121
+ if small_version_number == 1
122
+ nil
123
+ else
124
+ "#{big_version_number}.#{small_version_number - 1}"
125
+ end
126
+ else
127
+ nil
128
+ end
129
+ end
130
+
131
+ def parse_cvs_time(time)
132
+ # 2003/11/09 15:39:25
133
+ Time.utc(time[0..3], time[5..6], time[8..9], time[11..12], time[14..15], time[17..18])
134
+ end
135
+
136
+ def extract_required_match(string, regexp)
137
+ if string=~regexp
138
+ return($1)
139
+ else
140
+ $stderr.puts("can't parse: '#{string}'\nexpected to match regexp: #{regexp.to_s}")
141
+ end
142
+ end
143
+
144
+ def extract_match(string, regexp)
145
+ if string=~regexp
146
+ return($1)
147
+ else
148
+ ""
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ # The state field is "Exp" both for added and modified files. retards!
155
+ # We need some additional logic to figure out whether it is added or not.
156
+ # Maybe look at the revision. (1.1 means new I think. - deal with it later)
157
+ STATES = {"dead" => Change::DELETED, "Exp" => Change::MODIFIED}
158
+
159
+ end
160
+
161
+ end
@@ -0,0 +1,69 @@
1
+ require 'rscm/abstract_scm'
2
+ require 'tempfile'
3
+ require 'rscm/path_converter'
4
+ require 'fileutils'
5
+
6
+ module RSCM
7
+ class Darcs < AbstractSCM
8
+
9
+ def initialize(dir=nil)
10
+ @dir = File.expand_path(dir)
11
+ end
12
+
13
+ def name
14
+ "Darcs"
15
+ end
16
+
17
+ def create
18
+ with_working_dir(@dir) do
19
+ IO.popen("darcs initialize") do |stdout|
20
+ stdout.each_line do |line|
21
+ yield line if block_given?
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def import(dir, message)
28
+ ENV["EMAIL"] = "dcontrol@codehaus.org"
29
+ FileUtils.cp_r(Dir.glob("#{dir}/*"), @dir)
30
+ with_working_dir(@dir) do
31
+ puts "IN::::: #{@dir}"
32
+ cmd = "darcs add --recursive ."
33
+ puts cmd
34
+ IO.popen(cmd) do |stdout|
35
+ stdout.each_line do |line|
36
+ yield line if block_given?
37
+ end
38
+ end
39
+ puts $?
40
+ logfile = Tempfile.new("darcs_logfile")
41
+ logfile.print(message)
42
+ logfile.close
43
+
44
+ cmd = "darcs record --all --patch-name \"something nice\" --logfile #{PathConverter.filepath_to_nativepath(logfile.path, false)}"
45
+ puts cmd
46
+ IO.popen(cmd) do |stdout|
47
+ stdout.each_line do |line|
48
+ yield line if block_given?
49
+ end
50
+ end
51
+ puts $?
52
+ end
53
+ end
54
+
55
+ def checkout(checkout_dir) # :yield: file
56
+ with_working_dir(File.dirname(checkout_dir)) do
57
+ cmd = "darcs get --verbose --repo-name #{File.basename(checkout_dir)} #{@dir}"
58
+ puts cmd
59
+ IO.popen(cmd) do |stdout|
60
+ stdout.each_line do |line|
61
+ puts line
62
+ yield line if block_given?
63
+ end
64
+ end
65
+ end
66
+ puts $?
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,46 @@
1
+ require 'tempfile'
2
+ require 'ftools'
3
+
4
+ module RSCM
5
+ module LineEditor
6
+ # Comments out line by line if they match the line_regex.
7
+ # Does not comment out already commented out lines.
8
+ # If comment_template is nil, the matching lines will be deleted
9
+ # Returns true if at least one line is commented out or changed
10
+ def comment_out(original, line_regex, comment_template, output)
11
+ did_comment_out = false
12
+ already_commented_exp = /^[#{comment_template}]/ unless comment_template.nil?
13
+ original.each_line do |line|
14
+ out_line = nil
15
+ if(line_regex =~ line)
16
+ if(already_commented_exp && already_commented_exp =~ line)
17
+ out_line = line
18
+ else
19
+ did_comment_out = true
20
+ out_line = "#{comment_template}#{line}" unless comment_template.nil?
21
+ end
22
+ else
23
+ out_line = line
24
+ end
25
+ output << out_line unless out_line.nil?
26
+ end
27
+ did_comment_out
28
+ end
29
+ module_function :comment_out
30
+ end
31
+ end
32
+
33
+ class File
34
+
35
+ def File.comment_out(path, line_regex, comment_template)
36
+ temp_file = Tempfile.new(File.basename(path))
37
+ temp_file_path = temp_file.path
38
+ original = File.new(path)
39
+ RSCM::LineEditor.comment_out(original, line_regex, comment_template, temp_file)
40
+
41
+ temp_file.close
42
+ original.close
43
+
44
+ File.copy(temp_file_path, path)
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require_gem 'log4r'
3
+
4
+ Log = Log4r::Logger.new("rscm")
5
+ Log.add Log4r::Outputter.stderr
@@ -0,0 +1,107 @@
1
+ require 'rscm/abstract_scm'
2
+ require 'fileutils'
3
+
4
+ module RSCM
5
+ class Monotone < AbstractSCM
6
+ def initialize(db_file=nil, branch=nil, key=nil, passphrase=nil, keys_file=nil)
7
+ @db_file = File.expand_path(db_file) if db_file
8
+ @branch = branch
9
+ @key = key
10
+ @passphrase = passphrase
11
+ @keys_file = keys_file
12
+ end
13
+
14
+ def name
15
+ "Monotone"
16
+ end
17
+
18
+ def create
19
+ FileUtils.mkdir_p(File.dirname(@db_file))
20
+ monotone("db init")
21
+ monotone("read") do |io|
22
+ io.write(File.open(@keys_file).read)
23
+ io.close_write
24
+ end
25
+ end
26
+
27
+ def transactional?
28
+ true
29
+ end
30
+
31
+ def import(dir, message)
32
+ dir = File.expand_path(dir)
33
+
34
+ # post 0.17, this can be "cd dir && cmd add ."
35
+
36
+ files = Dir["#{dir}/*"]
37
+ relative_paths_to_add = to_relative(dir, files)
38
+
39
+ with_working_dir(dir) do
40
+ monotone("add #{relative_paths_to_add.join(' ')}")
41
+ monotone("commit '#{message}'", @branch, @key) do |io|
42
+ io.puts(@passphrase)
43
+ io.close_write
44
+ io.read
45
+ end
46
+ end
47
+ end
48
+
49
+ def checked_out?(checkout_dir)
50
+ File.exists?("#{checkout_dir}/MT")
51
+ end
52
+
53
+ def uptodate?(checkout_dir, from_identifier)
54
+ if (!checked_out?(checkout_dir))
55
+ false
56
+ else
57
+ with_working_dir(checkout_dir) do
58
+ monotone("heads") do |stdout|
59
+ stdout.each_line do |line|
60
+ next if (line =~ /^monotone:/)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def commit(checkout_dir, message)
68
+
69
+ end
70
+
71
+ protected
72
+
73
+ # Checks out silently. Called by superclass' checkout.
74
+ def checkout_silent(checkout_dir, to_identifier)
75
+ monotone("checkout #{checkout_dir}", @branch, @key) do |stdout|
76
+ stdout.each_line do |line|
77
+ # TODO: checkout prints nothing to stdout - may be fixed in a future monotone.
78
+ # When/if it happens we may want to do a kosher implementation of checkout
79
+ # to get yields as checkouts happen.
80
+ yield line if block_given?
81
+ end
82
+ end
83
+ end
84
+
85
+ # Administrative files that should be ignored when counting files.
86
+ def ignore_paths
87
+ return [/MT/, /\.mt-attrs/]
88
+ end
89
+
90
+ private
91
+
92
+ def monotone(monotone_cmd, branch=nil, key=nil)
93
+ branch_opt = branch ? "--branch=\"#{branch}\"" : ""
94
+ key_opt = key ? "--key=\"#{key}\"" : ""
95
+ cmd = "monotone --db=\"#{@db_file}\" #{branch_opt} #{key_opt} #{monotone_cmd}"
96
+ safer_popen(cmd, "r+") do |io|
97
+ if(block_given?)
98
+ return(yield(io))
99
+ else
100
+ # just read stdout so we can exit
101
+ io.read
102
+ end
103
+ end
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,13 @@
1
+ require 'rscm/abstract_scm'
2
+
3
+ module RSCM
4
+ class Mooky < AbstractSCM
5
+ attr_accessor :foo
6
+ attr_accessor :bar
7
+
8
+ def name
9
+ "Mooky"
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ module RSCM
2
+ class Parser
3
+
4
+ def initialize(break_regexp)
5
+ @break_regexp = break_regexp
6
+ end
7
+
8
+ def parse(io, skip_line_parsing=false, &line_proc)
9
+ parse_until_regexp_matches(io, skip_line_parsing, &line_proc)
10
+ if(skip_line_parsing)
11
+ nil
12
+ else
13
+ next_result
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ def parse_line(line)
20
+ raise "Must override parse_line(line)"
21
+ end
22
+
23
+ def next_result
24
+ raise "Must override next_result(line)"
25
+ end
26
+
27
+ private
28
+
29
+ def parse_until_regexp_matches(io, skip_line_parsing, &line_proc)
30
+ io.each_line { |line|
31
+ yield line if block_given?
32
+ if line =~ @break_regexp
33
+ return
34
+ end
35
+ parse_line(line) unless skip_line_parsing
36
+ }
37
+ end
38
+ end
39
+ end