rscm 0.2.1.1404 → 0.3.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 +34 -23
- data/Rakefile +24 -29
- data/bin/touch.exe +0 -0
- data/lib/rscm.rb +6 -3
- data/lib/rscm/annotations.rb +26 -7
- data/lib/rscm/{abstract_scm.rb → base.rb} +109 -71
- data/lib/rscm/better.rb +16 -0
- data/lib/rscm/logging.rb +11 -5
- data/lib/rscm/path_converter.rb +9 -16
- data/lib/rscm/revision.rb +201 -0
- data/lib/rscm/revision_file.rb +71 -0
- data/lib/rscm/scm/clearcase.rb +7 -7
- data/lib/rscm/scm/cvs.rb +69 -70
- data/lib/rscm/scm/cvs_log_parser.rb +29 -29
- data/lib/rscm/scm/darcs.rb +82 -34
- data/lib/rscm/scm/darcs_log_parser.rb +65 -0
- data/lib/rscm/scm/monotone.rb +249 -77
- data/lib/rscm/scm/monotone_log_parser.rb +57 -43
- data/lib/rscm/scm/mooky.rb +3 -3
- data/lib/rscm/scm/perforce.rb +196 -134
- data/lib/rscm/scm/star_team.rb +10 -10
- data/lib/rscm/scm/subversion.rb +106 -77
- data/lib/rscm/scm/subversion_log_parser.rb +76 -47
- data/lib/rscm/time_ext.rb +2 -116
- data/test/rscm/annotations_test.rb +15 -2
- data/test/rscm/{abstract_scm_test.rb → base_test.rb} +3 -3
- data/test/rscm/difftool_test.rb +9 -3
- data/test/rscm/generic_scm_tests.rb +195 -124
- data/test/rscm/revision_fixture.rb +20 -0
- data/test/rscm/revision_test.rb +129 -0
- data/test/rscm/{changesets.yaml → revisions.yaml} +10 -10
- data/test/rscm/scm/clearcase.log +608 -0
- data/test/rscm/scm/clearcase_test.rb +39 -0
- data/test/rscm/scm/cvs_log_parser_test.rb +73 -73
- data/test/rscm/scm/cvs_test.rb +1 -1
- data/test/rscm/scm/darcs_log_parser_test.rb +171 -0
- data/test/rscm/scm/monotone_log_parser_test.rb +49 -31
- data/test/rscm/scm/monotone_test.rb +3 -2
- data/test/rscm/scm/p4client_test.rb +33 -0
- data/test/rscm/scm/perforce_test.rb +25 -3
- data/test/rscm/scm/star_team.rb +9 -9
- data/test/rscm/scm/subversion_log_parser_test.rb +107 -47
- metadata +17 -13
- data/lib/multipart.rb +0 -95
- data/lib/rscm/RSS.txt +0 -41
- data/lib/rscm/changes.rb +0 -268
- data/lib/rscm/example.yaml +0 -21
- data/lib/rubyforge_file_publisher.rb +0 -176
- data/test/rscm/changes_fixture.rb +0 -20
- data/test/rscm/changes_test.rb +0 -129
data/lib/rscm/better.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module RSCM
|
2
|
+
class Better
|
3
|
+
@@logger = nil
|
4
|
+
@@logger = RSCM_DEFAULT_LOGGER
|
5
|
+
|
6
|
+
def self.popen(cmd, mode="r", expected_exit=0, &proc)
|
7
|
+
@@logger.info "Executing command: '#{cmd}'" if @@logger
|
8
|
+
ret = IO.popen(cmd, mode) do |io|
|
9
|
+
proc.call(io)
|
10
|
+
end
|
11
|
+
exit_code = $? >> 8
|
12
|
+
raise "Command\n'#{cmd}'\nfailed with code #{exit_code} in\n#{Dir.pwd}\nExpected exit code: #{expected_exit}" if exit_code != expected_exit
|
13
|
+
ret
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/rscm/logging.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
require_gem 'log4r'
|
1
|
+
require 'logger'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
begin
|
4
|
+
RSCM_DEFAULT_LOGGER = Logger.new("#{HOMEDIR}/.rscm.log")
|
5
|
+
rescue StandardError
|
6
|
+
RSCM_DEFAULT_LOGGER = Logger.new(STDERR)
|
7
|
+
RSCM_DEFAULT_LOGGER.level = Logger::WARN
|
8
|
+
RSCM_DEFAULT_LOGGER.warn(
|
9
|
+
"RSCM Error: Unable to access log file. Please ensure that #{HOMEDIR}/.rscm.log exists and is chmod 0666. " +
|
10
|
+
"The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
|
11
|
+
)
|
12
|
+
end
|
data/lib/rscm/path_converter.rb
CHANGED
@@ -1,20 +1,15 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require 'rscm/logging'
|
3
|
-
|
4
1
|
WIN32 = RUBY_PLATFORM == "i386-mswin32"
|
5
2
|
CYGWIN = RUBY_PLATFORM == "i386-cygwin"
|
6
3
|
WINDOWS = WIN32 || CYGWIN
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
Log.info "Executing command: '#{cmd}'"
|
12
|
-
ret = IO.popen(cmd, mode, &proc)
|
13
|
-
exit_code = $? >> 8
|
14
|
-
raise "#{cmd} failed with code #{exit_code} in #{Dir.pwd}. Expected exit code: #{expected_exit}" if exit_code != expected_exit
|
15
|
-
ret
|
4
|
+
if(WINDOWS)
|
5
|
+
HOMEDIR = RSCM::PathConverter.nativepath_to_filepath("#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}").gsub(/\\/, "/")
|
6
|
+
else
|
7
|
+
HOMEDIR = ENV['HOME']
|
16
8
|
end
|
17
9
|
|
10
|
+
require 'fileutils'
|
11
|
+
require 'rscm/logging'
|
12
|
+
|
18
13
|
def with_working_dir(dir)
|
19
14
|
# Can't use Dir.chdir{ block } - will fail with multithreaded code.
|
20
15
|
# http://www.ruby-doc.org/core/classes/Dir.html#M000790
|
@@ -22,10 +17,8 @@ def with_working_dir(dir)
|
|
22
17
|
prev = Dir.pwd
|
23
18
|
begin
|
24
19
|
dir = File.expand_path(dir)
|
25
|
-
Log.info "Making directory: '#{dir}'"
|
26
20
|
FileUtils.mkdir_p(dir)
|
27
21
|
Dir.chdir(dir)
|
28
|
-
Log.info "In directory: '#{dir}'"
|
29
22
|
yield
|
30
23
|
ensure
|
31
24
|
Dir.chdir(prev)
|
@@ -42,7 +35,7 @@ module RSCM
|
|
42
35
|
path.gsub(/\//, "\\")
|
43
36
|
elsif(CYGWIN)
|
44
37
|
cmd = "cygpath --windows #{path}"
|
45
|
-
|
38
|
+
Better.popen(cmd) do |io|
|
46
39
|
cygpath = io.read.chomp
|
47
40
|
escaped ? cygpath.gsub(/\\/, "\\\\\\\\") : cygpath
|
48
41
|
end
|
@@ -68,7 +61,7 @@ module RSCM
|
|
68
61
|
elsif(CYGWIN)
|
69
62
|
path = path.gsub(/\\/, "/")
|
70
63
|
cmd = "cygpath --unix #{path}"
|
71
|
-
|
64
|
+
Better.popen(cmd) do |io|
|
72
65
|
io.read.chomp
|
73
66
|
end
|
74
67
|
else
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'xmlrpc/utils'
|
2
|
+
require 'rscm/time_ext'
|
3
|
+
require 'rscm/revision_file'
|
4
|
+
|
5
|
+
module RSCM
|
6
|
+
|
7
|
+
# A collection of Revision.
|
8
|
+
class Revisions
|
9
|
+
include Enumerable
|
10
|
+
include XMLRPC::Marshallable
|
11
|
+
|
12
|
+
attr_accessor :revisions
|
13
|
+
|
14
|
+
def initialize(revisions=[])
|
15
|
+
@revisions = revisions
|
16
|
+
end
|
17
|
+
|
18
|
+
# Accepts a visitor that will receive callbacks while
|
19
|
+
# iterating over this instance's internal structure.
|
20
|
+
# The visitor should respond to the following methods:
|
21
|
+
#
|
22
|
+
# * visit_revisions(revisions)
|
23
|
+
# * visit_revision(revision)
|
24
|
+
# * visit_file(file)
|
25
|
+
#
|
26
|
+
def accept(visitor)
|
27
|
+
visitor.visit_revisions(self)
|
28
|
+
self.each{|revision| revision.accept(visitor)}
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](file)
|
32
|
+
@revisions[file]
|
33
|
+
end
|
34
|
+
|
35
|
+
def each(&block)
|
36
|
+
@revisions.each(&block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def reverse
|
40
|
+
r = clone
|
41
|
+
r.revisions = @revisions.dup.reverse
|
42
|
+
r
|
43
|
+
end
|
44
|
+
|
45
|
+
def length
|
46
|
+
@revisions.length
|
47
|
+
end
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
return false if !other.is_a?(self.class)
|
51
|
+
@revisions == other.revisions
|
52
|
+
end
|
53
|
+
|
54
|
+
def empty?
|
55
|
+
@revisions.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
# The set of developers that contributed to all of the contained Revision s.
|
59
|
+
def developers
|
60
|
+
result = []
|
61
|
+
each do |revision|
|
62
|
+
result << revision.developer unless result.index(revision.developer)
|
63
|
+
end
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
# The latest Revision (with the latest time)
|
68
|
+
# or nil if there are none.
|
69
|
+
def latest
|
70
|
+
result = nil
|
71
|
+
each do |revision|
|
72
|
+
result = revision if result.nil? || result.time < revision.time
|
73
|
+
end
|
74
|
+
result
|
75
|
+
end
|
76
|
+
|
77
|
+
# Adds a File or a Revision.
|
78
|
+
# If the argument is a File and no corresponding Revision exists,
|
79
|
+
# a new Revision is created, added, and the File is added to that Revision -
|
80
|
+
# and then finally the newly created Revision is returned.
|
81
|
+
# Otherwise nil is returned.
|
82
|
+
def add(file_or_revision)
|
83
|
+
if(file_or_revision.is_a?(Revision))
|
84
|
+
@revisions << file_or_revision
|
85
|
+
return file_or_revision
|
86
|
+
else
|
87
|
+
revision = @revisions.find { |a_revision| a_revision.can_contain?(file_or_revision) }
|
88
|
+
if(revision.nil?)
|
89
|
+
revision = Revision.new
|
90
|
+
@revisions << revision
|
91
|
+
revision << file_or_revision
|
92
|
+
return revision
|
93
|
+
end
|
94
|
+
revision << file_or_revision
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def push(*file_or_revisions)
|
100
|
+
file_or_revisions.each { |file_or_revision| self << (file_or_revision) }
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
# Sorts the revisions according to time
|
105
|
+
def sort!
|
106
|
+
@revisions.sort!
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
# Represents a collection of File that were committed at the same time.
|
113
|
+
# Non-transactional SCMs (such as CVS and StarTeam) emulate Revision
|
114
|
+
# by grouping File s that were committed by the same developer, with the
|
115
|
+
# same commit message, and within a "reasonably" small timespan.
|
116
|
+
class Revision
|
117
|
+
include Enumerable
|
118
|
+
include XMLRPC::Marshallable
|
119
|
+
|
120
|
+
attr_reader :files
|
121
|
+
attr_accessor :identifier
|
122
|
+
attr_accessor :developer
|
123
|
+
attr_accessor :message
|
124
|
+
attr_accessor :time
|
125
|
+
|
126
|
+
def initialize(files=[])
|
127
|
+
@files = files
|
128
|
+
end
|
129
|
+
|
130
|
+
def accept(visitor)
|
131
|
+
visitor.visit_revision(self)
|
132
|
+
@files.each{|file| file.accept(visitor)}
|
133
|
+
end
|
134
|
+
|
135
|
+
def << (file)
|
136
|
+
@files << file
|
137
|
+
if(self.time.nil? || self.time < file.time unless file.time.nil?)
|
138
|
+
self.time = file.time
|
139
|
+
self.identifier = self.time if(self.identifier.nil? || self.identifier.is_a?(Time))
|
140
|
+
end
|
141
|
+
self.developer = file.developer if file.developer
|
142
|
+
self.message = file.message if file.message
|
143
|
+
end
|
144
|
+
|
145
|
+
def [] (file)
|
146
|
+
@files[file]
|
147
|
+
end
|
148
|
+
|
149
|
+
def each(&block)
|
150
|
+
@files.each(&block)
|
151
|
+
end
|
152
|
+
|
153
|
+
def pop
|
154
|
+
@files.pop
|
155
|
+
end
|
156
|
+
|
157
|
+
def length
|
158
|
+
@files.length
|
159
|
+
end
|
160
|
+
|
161
|
+
def time=(t)
|
162
|
+
raise "time must be a Time object - it was a #{t.class.name} with the string value #{t}" unless t.is_a?(Time)
|
163
|
+
raise "can't set time to an inferiour value than the previous value" if @time && (t < @time)
|
164
|
+
@time = t
|
165
|
+
end
|
166
|
+
|
167
|
+
def ==(other)
|
168
|
+
return false if !other.is_a?(self.class)
|
169
|
+
@files == other.files
|
170
|
+
end
|
171
|
+
|
172
|
+
def <=>(other)
|
173
|
+
@time <=> other.time
|
174
|
+
end
|
175
|
+
|
176
|
+
# Whether this instance can contain a File. Used
|
177
|
+
# by non-transactional SCMs.
|
178
|
+
def can_contain?(file)
|
179
|
+
self.developer == file.developer &&
|
180
|
+
self.message == file.message &&
|
181
|
+
(self.time - file.time).abs < 60
|
182
|
+
end
|
183
|
+
|
184
|
+
# String representation that can be used for debugging.
|
185
|
+
def to_s
|
186
|
+
result = "#{identifier} | #{developer} | #{time} | #{message}\n"
|
187
|
+
self.each do |file|
|
188
|
+
result << " " << file.to_s << "\n"
|
189
|
+
end
|
190
|
+
result
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns the identifier of the revision. This is the revision
|
194
|
+
# (if defined) or an UTC time if it is not natively supported by the scm.
|
195
|
+
def identifierAA
|
196
|
+
@identifier || @time
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module RSCM
|
2
|
+
# Represents a file within a Revision, and also information about how this file
|
3
|
+
# was modified compared with the previous revision.
|
4
|
+
class RevisionFile
|
5
|
+
include XMLRPC::Marshallable
|
6
|
+
|
7
|
+
MODIFIED = "MODIFIED"
|
8
|
+
DELETED = "DELETED"
|
9
|
+
ADDED = "ADDED"
|
10
|
+
MOVED = "MOVED"
|
11
|
+
|
12
|
+
attr_accessor :status
|
13
|
+
attr_accessor :path
|
14
|
+
attr_accessor :previous_native_revision_identifier
|
15
|
+
# The native SCM's revision for this file. For non-transactional SCMs this is different from
|
16
|
+
# the parent Revision's
|
17
|
+
attr_accessor :native_revision_identifier
|
18
|
+
|
19
|
+
attr_accessor :developer
|
20
|
+
attr_accessor :message
|
21
|
+
# This is a UTC ruby time
|
22
|
+
attr_accessor :time
|
23
|
+
|
24
|
+
def initialize(path=nil, status=nil, developer=nil, message=nil, native_revision_identifier=nil, time=nil)
|
25
|
+
@path, @developer, @message, @native_revision_identifier, @time, @status = path, developer, message, native_revision_identifier, time, status
|
26
|
+
end
|
27
|
+
|
28
|
+
def accept(visitor)
|
29
|
+
visitor.visit_file(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"#{path} | #{native_revision_identifier}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def developer=(developer)
|
37
|
+
raise "can't be null" if developer.nil?
|
38
|
+
@developer = developer
|
39
|
+
end
|
40
|
+
|
41
|
+
def message=(message)
|
42
|
+
raise "can't be null" if message.nil?
|
43
|
+
@message = message
|
44
|
+
end
|
45
|
+
|
46
|
+
def path=(path)
|
47
|
+
raise "can't be null" if path.nil?
|
48
|
+
@path = path
|
49
|
+
end
|
50
|
+
|
51
|
+
def native_revision_identifier=(id)
|
52
|
+
raise "can't be null" if id.nil?
|
53
|
+
@native_revision_identifier = id
|
54
|
+
end
|
55
|
+
|
56
|
+
def time=(time)
|
57
|
+
raise "time must be a Time object" unless time.is_a?(Time)
|
58
|
+
@time = time
|
59
|
+
end
|
60
|
+
|
61
|
+
def ==(other)
|
62
|
+
return false if !other.is_a?(self.class)
|
63
|
+
self.path == other.path &&
|
64
|
+
self.developer == other.developer &&
|
65
|
+
self.message == other.message &&
|
66
|
+
self.native_revision_identifier == other.native_revision_identifier &&
|
67
|
+
self.time == other.time
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
data/lib/rscm/scm/clearcase.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
require 'rscm/
|
1
|
+
require 'rscm/base'
|
2
2
|
require 'rscm/path_converter'
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
5
|
module RSCM
|
6
|
-
class ClearCase <
|
6
|
+
class ClearCase < Base
|
7
7
|
|
8
8
|
LOG_FORMAT = "Developer:%u\\nTime:%Nd\\nExtendedName:%Xn\\nVersionId:%Vn\\nPreviousVersionId:%PVn\\nElementName:%En\\nOID:%On\\nO:%o\\nMessage:%Nc\\n------------------------------------------\\n"
|
9
9
|
|
@@ -11,13 +11,13 @@ module RSCM
|
|
11
11
|
"ClearCase"
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
result =
|
14
|
+
def revisions(checkout_dir, from_identifier, to_identifier=Time.infinity)
|
15
|
+
result = Revisions.new
|
16
16
|
with_working_dir(checkout_dir) do
|
17
17
|
since = from_identifier.strftime("%d-%b-%Y.%H:%M:%S")
|
18
18
|
cleartool("lshistory -recurse -nco -since #{since} -fmt #{LOG_FORMAT}") do |io|
|
19
19
|
io.each_line {|l| puts l}
|
20
|
-
|
20
|
+
revisions << Revision.new()
|
21
21
|
end
|
22
22
|
end
|
23
23
|
result
|
@@ -25,7 +25,7 @@ module RSCM
|
|
25
25
|
|
26
26
|
def diff(checkout_dir, change)
|
27
27
|
with_working_dir(checkout_dir) do
|
28
|
-
cleartool("diff -diff_format #{change.path}@@#{change.
|
28
|
+
cleartool("diff -diff_format #{change.path}@@#{change.previous_native_revision_identifier} #{change.path}@@#{change.revision}")
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -71,7 +71,7 @@ module RSCM
|
|
71
71
|
|
72
72
|
def cleartool(cleartool_cmd)
|
73
73
|
cmd = "cleartool #{cleartool_cmd}"
|
74
|
-
|
74
|
+
Better.popen(cmd, "r+") do |io|
|
75
75
|
if(block_given?)
|
76
76
|
return(yield(io))
|
77
77
|
else
|
data/lib/rscm/scm/cvs.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'rscm/
|
1
|
+
require 'rscm/base'
|
2
2
|
require 'rscm/path_converter'
|
3
3
|
require 'rscm/line_editor'
|
4
4
|
require 'rscm/scm/cvs_log_parser'
|
@@ -10,7 +10,7 @@ module RSCM
|
|
10
10
|
# You need a cvs executable on the PATH in order for it to work.
|
11
11
|
#
|
12
12
|
# NOTE: On Cygwin this has to be the win32 build of cvs and not the Cygwin one.
|
13
|
-
class Cvs <
|
13
|
+
class Cvs < Base
|
14
14
|
register self
|
15
15
|
|
16
16
|
ann :description => "CVSROOT"
|
@@ -33,22 +33,22 @@ module RSCM
|
|
33
33
|
"CVS"
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
36
|
+
def import_central(dir, message)
|
37
37
|
modname = File.basename(dir)
|
38
38
|
cvs(dir, "import -m \"#{message}\" #{modname} VENDOR START")
|
39
39
|
end
|
40
40
|
|
41
|
-
def add(
|
42
|
-
cvs(checkout_dir, "add #{relative_filename}")
|
41
|
+
def add(relative_filename)
|
42
|
+
cvs(@checkout_dir, "add #{relative_filename}")
|
43
43
|
end
|
44
44
|
|
45
45
|
# The extra simulate parameter is not in accordance with the AbstractSCM API,
|
46
46
|
# but it's optional and is only being used from within this class (uptodate? method).
|
47
|
-
def checkout(
|
47
|
+
def checkout(to_identifier=nil, simulate=false)
|
48
48
|
checked_out_files = []
|
49
|
-
if(checked_out?
|
49
|
+
if(checked_out?)
|
50
50
|
path_regex = /^[U|P] (.*)/
|
51
|
-
cvs(checkout_dir, update_command(to_identifier), simulate) do |line|
|
51
|
+
cvs(@checkout_dir, update_command(to_identifier), simulate) do |line|
|
52
52
|
if(line =~ path_regex)
|
53
53
|
path = $1.chomp
|
54
54
|
yield path if block_given?
|
@@ -56,12 +56,12 @@ module RSCM
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
else
|
59
|
-
prefix = File.basename(checkout_dir)
|
59
|
+
prefix = File.basename(@checkout_dir)
|
60
60
|
path_regex = /^[U|P] #{prefix}\/(.*)/
|
61
61
|
# This is a workaround for the fact that -d . doesn't work - must be an existing sub folder.
|
62
|
-
mkdir_p(checkout_dir) unless File.exist?(checkout_dir)
|
63
|
-
target_dir = File.basename(checkout_dir)
|
64
|
-
run_checkout_command_dir = File.dirname(checkout_dir)
|
62
|
+
mkdir_p(@checkout_dir) unless File.exist?(@checkout_dir)
|
63
|
+
target_dir = File.basename(@checkout_dir)
|
64
|
+
run_checkout_command_dir = File.dirname(@checkout_dir)
|
65
65
|
# -D is sticky, but subsequent updates will reset stickiness with -A
|
66
66
|
cvs(run_checkout_command_dir, checkout_command(target_dir, to_identifier), simulate) do |line|
|
67
67
|
if(line =~ path_regex)
|
@@ -74,65 +74,57 @@ module RSCM
|
|
74
74
|
checked_out_files
|
75
75
|
end
|
76
76
|
|
77
|
-
def
|
78
|
-
|
77
|
+
def commit(message, &proc)
|
78
|
+
cvs(@checkout_dir, commit_command(message), &proc)
|
79
79
|
end
|
80
80
|
|
81
|
-
def
|
82
|
-
|
83
|
-
end
|
84
|
-
|
85
|
-
def commit(checkout_dir, message, &proc)
|
86
|
-
cvs(checkout_dir, commit_command(message), &proc)
|
87
|
-
end
|
88
|
-
|
89
|
-
def uptodate?(checkout_dir, since)
|
90
|
-
if(!checked_out?(checkout_dir))
|
81
|
+
def uptodate?(identifier)
|
82
|
+
if(!checked_out?)
|
91
83
|
return false
|
92
84
|
end
|
93
85
|
|
94
86
|
# simulate a checkout
|
95
|
-
files = checkout(
|
87
|
+
files = checkout(identifier, true)
|
96
88
|
files.empty?
|
97
89
|
end
|
98
90
|
|
99
|
-
def
|
100
|
-
checkout(
|
101
|
-
parse_log(
|
91
|
+
def revisions(from_identifier, to_identifier=Time.infinity)
|
92
|
+
checkout(to_identifier) unless uptodate?(to_identifier) # must checkout to get revisions
|
93
|
+
parse_log(changes_command(from_identifier, to_identifier))
|
102
94
|
end
|
103
95
|
|
104
|
-
def diff(
|
105
|
-
with_working_dir(checkout_dir) do
|
96
|
+
def diff(change)
|
97
|
+
with_working_dir(@checkout_dir) do
|
106
98
|
opts = case change.status
|
107
|
-
when /#{
|
108
|
-
when /#{
|
109
|
-
when /#{
|
99
|
+
when /#{RevisionFile::MODIFIED}/; "#{revision_option(change.previous_native_revision_identifier)} #{revision_option(change.native_revision_identifier)}"
|
100
|
+
when /#{RevisionFile::DELETED}/; "#{revision_option(change.previous_native_revision_identifier)}"
|
101
|
+
when /#{RevisionFile::ADDED}/; "#{revision_option(Time.epoch)} #{revision_option(change.native_revision_identifier)}"
|
110
102
|
end
|
111
103
|
# IMPORTANT! CVS NT has a bug in the -N diff option
|
112
104
|
# http://www.cvsnt.org/pipermail/cvsnt-bugs/2004-November/000786.html
|
113
105
|
cmd = command_line("diff -Nu #{opts} #{change.path}")
|
114
|
-
|
106
|
+
Better.popen(cmd, "r", 1) do |io|
|
115
107
|
return(yield(io))
|
116
108
|
end
|
117
109
|
end
|
118
110
|
end
|
119
111
|
|
120
|
-
def apply_label(
|
121
|
-
cvs(checkout_dir, "tag -c #{label}")
|
112
|
+
def apply_label(label)
|
113
|
+
cvs(@checkout_dir, "tag -c #{label}")
|
122
114
|
end
|
123
115
|
|
124
116
|
def install_trigger(trigger_command, trigger_files_checkout_dir)
|
125
117
|
raise "mod can't be null or empty" if (mod.nil? || mod == "")
|
126
118
|
|
127
|
-
root_cvs = create_root_cvs
|
128
|
-
root_cvs.checkout
|
119
|
+
root_cvs = create_root_cvs(trigger_files_checkout_dir)
|
120
|
+
root_cvs.checkout
|
129
121
|
with_working_dir(trigger_files_checkout_dir) do
|
130
122
|
trigger_line = "#{mod} #{trigger_command}\n"
|
131
123
|
File.open("loginfo", File::WRONLY | File::APPEND) do |file|
|
132
124
|
file.puts(trigger_line)
|
133
125
|
end
|
134
126
|
begin
|
135
|
-
commit(
|
127
|
+
root_cvs.commit("Installed trigger for CVS module '#{mod}'")
|
136
128
|
rescue
|
137
129
|
raise "Couldn't commit the trigger back to CVS. Try to manually check out CVSROOT/loginfo, " +
|
138
130
|
"add the following line and commit it back:\n\n#{trigger_line}"
|
@@ -144,9 +136,9 @@ module RSCM
|
|
144
136
|
loginfo_line = "#{mod} #{trigger_command}"
|
145
137
|
regex = Regexp.new(Regexp.escape(loginfo_line))
|
146
138
|
|
147
|
-
root_cvs = create_root_cvs
|
139
|
+
root_cvs = create_root_cvs(trigger_files_checkout_dir)
|
148
140
|
begin
|
149
|
-
root_cvs.checkout
|
141
|
+
root_cvs.checkout
|
150
142
|
loginfo = File.join(trigger_files_checkout_dir, "loginfo")
|
151
143
|
return false if !File.exist?(loginfo)
|
152
144
|
|
@@ -168,23 +160,36 @@ module RSCM
|
|
168
160
|
loginfo_line = "#{mod} #{trigger_command}"
|
169
161
|
regex = Regexp.new(Regexp.escape(loginfo_line))
|
170
162
|
|
171
|
-
root_cvs = create_root_cvs
|
172
|
-
root_cvs.checkout
|
163
|
+
root_cvs = create_root_cvs(trigger_files_checkout_dir)
|
164
|
+
root_cvs.checkout
|
173
165
|
loginfo_path = File.join(trigger_files_checkout_dir, "loginfo")
|
174
166
|
File.comment_out(loginfo_path, regex, "# ")
|
175
167
|
with_working_dir(trigger_files_checkout_dir) do
|
176
|
-
commit(
|
168
|
+
root_cvs.commit("Uninstalled trigger for CVS mod '#{mod}'")
|
177
169
|
end
|
178
170
|
raise "Couldn't uninstall/commit trigger to loginfo" if trigger_installed?(trigger_command, trigger_files_checkout_dir)
|
179
171
|
end
|
180
172
|
|
181
|
-
def
|
182
|
-
raise "Can't create CVS repository for #{root}" unless
|
173
|
+
def create_central
|
174
|
+
raise "Can't create central CVS repository for #{root}" unless can_create_central?
|
183
175
|
File.mkpath(path)
|
184
176
|
cvs(path, "init")
|
185
177
|
end
|
178
|
+
|
179
|
+
def destroy_central
|
180
|
+
FileUtils.rm_rf(path)
|
181
|
+
end
|
186
182
|
|
187
|
-
def
|
183
|
+
def central_exists?
|
184
|
+
if(local?)
|
185
|
+
File.exists?("#{path}/CVSROOT/loginfo")
|
186
|
+
else
|
187
|
+
# don't know. assume yes.
|
188
|
+
true
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def can_create_central?
|
188
193
|
begin
|
189
194
|
local?
|
190
195
|
rescue
|
@@ -196,16 +201,7 @@ module RSCM
|
|
196
201
|
true
|
197
202
|
end
|
198
203
|
|
199
|
-
def
|
200
|
-
if(local?)
|
201
|
-
File.exists?("#{path}/CVSROOT/loginfo")
|
202
|
-
else
|
203
|
-
# don't know. assume yes.
|
204
|
-
true
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
def checked_out?(checkout_dir)
|
204
|
+
def checked_out?
|
209
205
|
rootcvs = File.expand_path("#{checkout_dir}/CVS/Root")
|
210
206
|
File.exists?(rootcvs)
|
211
207
|
end
|
@@ -217,7 +213,7 @@ module RSCM
|
|
217
213
|
dir = File.expand_path(dir)
|
218
214
|
execed_command_line = command_line(cmd, password, simulate)
|
219
215
|
with_working_dir(dir) do
|
220
|
-
|
216
|
+
Better.popen(execed_command_line) do |stdout|
|
221
217
|
stdout.each_line do |progress|
|
222
218
|
yield progress if block_given?
|
223
219
|
end
|
@@ -225,26 +221,26 @@ module RSCM
|
|
225
221
|
end
|
226
222
|
end
|
227
223
|
|
228
|
-
def parse_log(
|
224
|
+
def parse_log(cmd, &proc)
|
229
225
|
logged_command_line = command_line(cmd, hidden_password)
|
230
226
|
yield logged_command_line if block_given?
|
231
227
|
|
232
228
|
execed_command_line = command_line(cmd, password)
|
233
|
-
|
234
|
-
with_working_dir(checkout_dir) do
|
235
|
-
|
229
|
+
revisions = nil
|
230
|
+
with_working_dir(@checkout_dir) do
|
231
|
+
Better.popen(execed_command_line) do |stdout|
|
236
232
|
parser = CvsLogParser.new(stdout)
|
237
233
|
parser.cvspath = path
|
238
234
|
parser.cvsmodule = mod
|
239
|
-
|
235
|
+
revisions = parser.parse_revisions
|
240
236
|
end
|
241
237
|
end
|
242
|
-
|
238
|
+
revisions
|
243
239
|
end
|
244
240
|
|
245
241
|
def changes_command(from_identifier, to_identifier)
|
246
242
|
# https://www.cvshome.org/docs/manual/cvs-1.11.17/cvs_16.html#SEC144
|
247
|
-
# -N => Suppress the header if no
|
243
|
+
# -N => Suppress the header if no RevisionFiles are selected.
|
248
244
|
"log #{branch_option} -N #{period_option(from_identifier, to_identifier)}"
|
249
245
|
end
|
250
246
|
|
@@ -261,7 +257,7 @@ module RSCM
|
|
261
257
|
end
|
262
258
|
|
263
259
|
def checkout_command(target_dir, to_identifier)
|
264
|
-
"checkout #{branch_option} -d #{target_dir} #{
|
260
|
+
"checkout #{branch_option} -d #{target_dir} #{revision_option(to_identifier)} #{mod}"
|
265
261
|
end
|
266
262
|
|
267
263
|
def hidden_password
|
@@ -276,7 +272,7 @@ module RSCM
|
|
276
272
|
if(from_identifier.nil? && to_identifier.nil?)
|
277
273
|
""
|
278
274
|
else
|
279
|
-
"-d\"#{cvsdate(from_identifier)}
|
275
|
+
"-d\"#{cvsdate(from_identifier)}<#{cvsdate(to_identifier)}\" "
|
280
276
|
end
|
281
277
|
end
|
282
278
|
|
@@ -300,11 +296,14 @@ module RSCM
|
|
300
296
|
|
301
297
|
def command_line(cmd, password=nil, simulate=false)
|
302
298
|
cvs_options = simulate ? "-n" : ""
|
303
|
-
|
299
|
+
dev_null = WIN32 ? "nul" : "/dev/null"
|
300
|
+
"cvs -f \"-d#{root_with_password(password)}\" #{cvs_options} -q #{cmd} 2> #{dev_null}"
|
304
301
|
end
|
305
302
|
|
306
|
-
def create_root_cvs
|
307
|
-
Cvs.new(self.root, "CVSROOT", nil, self.password)
|
303
|
+
def create_root_cvs(checkout_dir)
|
304
|
+
cvs = Cvs.new(self.root, "CVSROOT", nil, self.password)
|
305
|
+
cvs.checkout_dir = checkout_dir
|
306
|
+
cvs
|
308
307
|
end
|
309
308
|
|
310
309
|
def revision_option(identifier)
|