rscm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +198 -0
- data/Rakefile +118 -0
- data/ext/rscm.jar +0 -0
- data/lib/rscm.rb +10 -0
- data/lib/rscm/abstract_log_parser.rb +49 -0
- data/lib/rscm/abstract_scm.rb +229 -0
- data/lib/rscm/changes.rb +271 -0
- data/lib/rscm/cvs/cvs.rb +363 -0
- data/lib/rscm/cvs/cvs_log_parser.rb +161 -0
- data/lib/rscm/darcs/darcs.rb +69 -0
- data/lib/rscm/line_editor.rb +46 -0
- data/lib/rscm/logging.rb +5 -0
- data/lib/rscm/monotone/monotone.rb +107 -0
- data/lib/rscm/mooky/mooky.rb +13 -0
- data/lib/rscm/parser.rb +39 -0
- data/lib/rscm/path_converter.rb +92 -0
- data/lib/rscm/perforce/perforce.rb +415 -0
- data/lib/rscm/starteam/starteam.rb +99 -0
- data/lib/rscm/svn/svn.rb +337 -0
- data/lib/rscm/svn/svn_log_parser.rb +134 -0
- data/lib/rscm/time_ext.rb +125 -0
- data/test/rscm/apply_label_scm_tests.rb +26 -0
- data/test/rscm/changes_fixture.rb +20 -0
- data/test/rscm/changes_test.rb +129 -0
- data/test/rscm/cvs/cvs_log_parser_test.rb +575 -0
- data/test/rscm/cvs/cvs_test.rb +22 -0
- data/test/rscm/darcs/darcs_test.rb +14 -0
- data/test/rscm/difftool_test.rb +40 -0
- data/test/rscm/file_ext.rb +12 -0
- data/test/rscm/generic_scm_tests.rb +282 -0
- data/test/rscm/line_editor_test.rb +76 -0
- data/test/rscm/mockit.rb +130 -0
- data/test/rscm/mockit_test.rb +117 -0
- data/test/rscm/monotone/monotone_test.rb +19 -0
- data/test/rscm/mooky/mooky_test.rb +14 -0
- data/test/rscm/parser_test.rb +47 -0
- data/test/rscm/path_converter_test.rb +52 -0
- data/test/rscm/perforce/perforce_test.rb +14 -0
- data/test/rscm/starteam/starteam_test.rb +36 -0
- data/test/rscm/svn/svn_log_parser_test.rb +111 -0
- data/test/rscm/svn/svn_test.rb +28 -0
- data/test/rscm/tempdir.rb +12 -0
- 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
|
data/lib/rscm/logging.rb
ADDED
@@ -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
|
data/lib/rscm/parser.rb
ADDED
@@ -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
|