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