##// END OF EJS Templates
CVS: add support for modules names with spaces (#1434)....
Jean-Philippe Lang -
r1513:40a9bbe94684
parent child
Show More
@@ -1,354 +1,354
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class CvsAdapter < AbstractAdapter
24 24
25 25 # CVS executable name
26 26 CVS_BIN = "cvs"
27 27
28 28 # Guidelines for the input:
29 29 # url -> the project-path, relative to the cvsroot (eg. module name)
30 30 # root_url -> the good old, sometimes damned, CVSROOT
31 31 # login -> unnecessary
32 32 # password -> unnecessary too
33 33 def initialize(url, root_url=nil, login=nil, password=nil)
34 34 @url = url
35 35 @login = login if login && !login.empty?
36 36 @password = (password || "") if @login
37 37 #TODO: better Exception here (IllegalArgumentException)
38 38 raise CommandFailed if root_url.blank?
39 39 @root_url = root_url
40 40 end
41 41
42 42 def root_url
43 43 @root_url
44 44 end
45 45
46 46 def url
47 47 @url
48 48 end
49 49
50 50 def info
51 51 logger.debug "<cvs> info"
52 52 Info.new({:root_url => @root_url, :lastrev => nil})
53 53 end
54 54
55 55 def get_previous_revision(revision)
56 56 CvsRevisionHelper.new(revision).prevRev
57 57 end
58 58
59 59 # Returns an Entries collection
60 60 # or nil if the given path doesn't exist in the repository
61 61 # this method is used by the repository-browser (aka LIST)
62 62 def entries(path=nil, identifier=nil)
63 63 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
64 64 path_with_project="#{url}#{with_leading_slash(path)}"
65 65 entries = Entries.new
66 66 cmd = "#{CVS_BIN} -d #{root_url} rls -ed"
67 67 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
68 cmd << " #{path_with_project}"
68 cmd << " #{shell_quote path_with_project}"
69 69 shellout(cmd) do |io|
70 70 io.each_line(){|line|
71 71 fields=line.chop.split('/',-1)
72 72 logger.debug(">>InspectLine #{fields.inspect}")
73 73
74 74 if fields[0]!="D"
75 75 entries << Entry.new({:name => fields[-5],
76 76 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
77 77 :path => "#{path}/#{fields[-5]}",
78 78 :kind => 'file',
79 79 :size => nil,
80 80 :lastrev => Revision.new({
81 81 :revision => fields[-4],
82 82 :name => fields[-4],
83 83 :time => Time.parse(fields[-3]),
84 84 :author => ''
85 85 })
86 86 })
87 87 else
88 88 entries << Entry.new({:name => fields[1],
89 89 :path => "#{path}/#{fields[1]}",
90 90 :kind => 'dir',
91 91 :size => nil,
92 92 :lastrev => nil
93 93 })
94 94 end
95 95 }
96 96 end
97 97 return nil if $? && $?.exitstatus != 0
98 98 entries.sort_by_name
99 99 end
100 100
101 101 STARTLOG="----------------------------"
102 102 ENDLOG ="============================================================================="
103 103
104 104 # Returns all revisions found between identifier_from and identifier_to
105 105 # in the repository. both identifier have to be dates or nil.
106 106 # these method returns nothing but yield every result in block
107 107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
108 108 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
109 109
110 110 path_with_project="#{url}#{with_leading_slash(path)}"
111 111 cmd = "#{CVS_BIN} -d #{root_url} rlog"
112 112 cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from
113 cmd << " #{path_with_project}"
113 cmd << " #{shell_quote path_with_project}"
114 114 shellout(cmd) do |io|
115 115 state="entry_start"
116 116
117 117 commit_log=String.new
118 118 revision=nil
119 119 date=nil
120 120 author=nil
121 121 entry_path=nil
122 122 entry_name=nil
123 123 file_state=nil
124 124 branch_map=nil
125 125
126 126 io.each_line() do |line|
127 127
128 128 if state!="revision" && /^#{ENDLOG}/ =~ line
129 129 commit_log=String.new
130 130 revision=nil
131 131 state="entry_start"
132 132 end
133 133
134 134 if state=="entry_start"
135 135 branch_map=Hash.new
136 136 # gsub(/^:.*@[^:]+:\d*/, '') is here to remove :pserver:anonymous@foo.bar: string if present in the url
137 137 if /^RCS file: #{Regexp.escape(root_url.gsub(/^:.*@[^:]+:\d*/, ''))}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
138 138 entry_path = normalize_cvs_path($1)
139 139 entry_name = normalize_path(File.basename($1))
140 140 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
141 141 elsif /^head: (.+)$/ =~ line
142 142 entry_headRev = $1 #unless entry.nil?
143 143 elsif /^symbolic names:/ =~ line
144 144 state="symbolic" #unless entry.nil?
145 145 elsif /^#{STARTLOG}/ =~ line
146 146 commit_log=String.new
147 147 state="revision"
148 148 end
149 149 next
150 150 elsif state=="symbolic"
151 151 if /^(.*):\s(.*)/ =~ (line.strip)
152 152 branch_map[$1]=$2
153 153 else
154 154 state="tags"
155 155 next
156 156 end
157 157 elsif state=="tags"
158 158 if /^#{STARTLOG}/ =~ line
159 159 commit_log = ""
160 160 state="revision"
161 161 elsif /^#{ENDLOG}/ =~ line
162 162 state="head"
163 163 end
164 164 next
165 165 elsif state=="revision"
166 166 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
167 167 if revision
168 168
169 169 revHelper=CvsRevisionHelper.new(revision)
170 170 revBranch="HEAD"
171 171
172 172 branch_map.each() do |branch_name,branch_point|
173 173 if revHelper.is_in_branch_with_symbol(branch_point)
174 174 revBranch=branch_name
175 175 end
176 176 end
177 177
178 178 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
179 179
180 180 yield Revision.new({
181 181 :time => date,
182 182 :author => author,
183 183 :message=>commit_log.chomp,
184 184 :paths => [{
185 185 :revision => revision,
186 186 :branch=> revBranch,
187 187 :path=>entry_path,
188 188 :name=>entry_name,
189 189 :kind=>'file',
190 190 :action=>file_state
191 191 }]
192 192 })
193 193 end
194 194
195 195 commit_log=String.new
196 196 revision=nil
197 197
198 198 if /^#{ENDLOG}/ =~ line
199 199 state="entry_start"
200 200 end
201 201 next
202 202 end
203 203
204 204 if /^branches: (.+)$/ =~ line
205 205 #TODO: version.branch = $1
206 206 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
207 207 revision = $1
208 208 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
209 209 date = Time.parse($1)
210 210 author = /author: ([^;]+)/.match(line)[1]
211 211 file_state = /state: ([^;]+)/.match(line)[1]
212 212 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
213 213 # useful for stats or something else
214 214 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
215 215 # unless linechanges.nil?
216 216 # version.line_plus = linechanges[1]
217 217 # version.line_minus = linechanges[2]
218 218 # else
219 219 # version.line_plus = 0
220 220 # version.line_minus = 0
221 221 # end
222 222 else
223 223 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
224 224 end
225 225 end
226 226 end
227 227 end
228 228 end
229 229
230 230 def diff(path, identifier_from, identifier_to=nil)
231 231 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
232 232 path_with_project="#{url}#{with_leading_slash(path)}"
233 cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{path_with_project}"
233 cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
234 234 diff = []
235 235 shellout(cmd) do |io|
236 236 io.each_line do |line|
237 237 diff << line
238 238 end
239 239 end
240 240 return nil if $? && $?.exitstatus != 0
241 241 diff
242 242 end
243 243
244 244 def cat(path, identifier=nil)
245 245 identifier = (identifier) ? identifier : "HEAD"
246 246 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
247 247 path_with_project="#{url}#{with_leading_slash(path)}"
248 cmd = "#{CVS_BIN} -d #{root_url} co -r#{identifier} -p #{path_with_project}"
248 cmd = "#{CVS_BIN} -d #{root_url} co -r#{identifier} -p #{shell_quote path_with_project}"
249 249 cat = nil
250 250 shellout(cmd) do |io|
251 251 cat = io.read
252 252 end
253 253 return nil if $? && $?.exitstatus != 0
254 254 cat
255 255 end
256 256
257 257 def annotate(path, identifier=nil)
258 258 identifier = (identifier) ? identifier : "HEAD"
259 259 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
260 260 path_with_project="#{url}#{with_leading_slash(path)}"
261 cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{path_with_project}"
261 cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
262 262 blame = Annotate.new
263 263 shellout(cmd) do |io|
264 264 io.each_line do |line|
265 265 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
266 266 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
267 267 end
268 268 end
269 269 return nil if $? && $?.exitstatus != 0
270 270 blame
271 271 end
272 272
273 273 private
274 274
275 275 # convert a date/time into the CVS-format
276 276 def time_to_cvstime(time)
277 277 return nil if time.nil?
278 278 unless time.kind_of? Time
279 279 time = Time.parse(time)
280 280 end
281 281 return time.strftime("%Y-%m-%d %H:%M:%S")
282 282 end
283 283
284 284 def normalize_cvs_path(path)
285 285 normalize_path(path.gsub(/Attic\//,''))
286 286 end
287 287
288 288 def normalize_path(path)
289 289 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
290 290 end
291 291 end
292 292
293 293 class CvsRevisionHelper
294 294 attr_accessor :complete_rev, :revision, :base, :branchid
295 295
296 296 def initialize(complete_rev)
297 297 @complete_rev = complete_rev
298 298 parseRevision()
299 299 end
300 300
301 301 def branchPoint
302 302 return @base
303 303 end
304 304
305 305 def branchVersion
306 306 if isBranchRevision
307 307 return @base+"."+@branchid
308 308 end
309 309 return @base
310 310 end
311 311
312 312 def isBranchRevision
313 313 !@branchid.nil?
314 314 end
315 315
316 316 def prevRev
317 317 unless @revision==0
318 318 return buildRevision(@revision-1)
319 319 end
320 320 return buildRevision(@revision)
321 321 end
322 322
323 323 def is_in_branch_with_symbol(branch_symbol)
324 324 bpieces=branch_symbol.split(".")
325 325 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
326 326 return (branchVersion==branch_start)
327 327 end
328 328
329 329 private
330 330 def buildRevision(rev)
331 331 if rev== 0
332 332 @base
333 333 elsif @branchid.nil?
334 334 @base+"."+rev.to_s
335 335 else
336 336 @base+"."+@branchid+"."+rev.to_s
337 337 end
338 338 end
339 339
340 340 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
341 341 def parseRevision()
342 342 pieces=@complete_rev.split(".")
343 343 @revision=pieces.last.to_i
344 344 baseSize=1
345 345 baseSize+=(pieces.size/2)
346 346 @base=pieces[0..-baseSize].join(".")
347 347 if baseSize > 2
348 348 @branchid=pieces[-2]
349 349 end
350 350 end
351 351 end
352 352 end
353 353 end
354 354 end
General Comments 0
You need to be logged in to leave comments. Login now