rscm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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