##// END OF EJS Templates
Fixed (CVS adapter): changes not recorded when using :pserver string....
Jean-Philippe Lang -
r894:1469a4c8df57
parent child
Show More
@@ -1,352 +1,353
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 the entry identified by path and revision identifier
60 60 # or nil if entry doesn't exist in the repository
61 61 # this method returns all revisions from one single SCM-Entry
62 62 def entry(path=nil, identifier="HEAD")
63 63 e = entries(path, identifier)
64 64 logger.debug("<cvs-result> #{e.first.inspect}") if e
65 65 e ? e.first : nil
66 66 end
67 67
68 68 # Returns an Entries collection
69 69 # or nil if the given path doesn't exist in the repository
70 70 # this method is used by the repository-browser (aka LIST)
71 71 def entries(path=nil, identifier=nil)
72 72 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
73 73 path_with_project="#{url}#{with_leading_slash(path)}"
74 74 entries = Entries.new
75 75 cmd = "#{CVS_BIN} -d #{root_url} rls -ed #{path_with_project}"
76 76 shellout(cmd) do |io|
77 77 io.each_line(){|line|
78 78 fields=line.chop.split('/',-1)
79 79 logger.debug(">>InspectLine #{fields.inspect}")
80 80
81 81 if fields[0]!="D"
82 82 entries << Entry.new({:name => fields[-5],
83 83 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
84 84 :path => "#{path}/#{fields[-5]}",
85 85 :kind => 'file',
86 86 :size => nil,
87 87 :lastrev => Revision.new({
88 88 :revision => fields[-4],
89 89 :name => fields[-4],
90 90 :time => Time.parse(fields[-3]),
91 91 :author => ''
92 92 })
93 93 })
94 94 else
95 95 entries << Entry.new({:name => fields[1],
96 96 :path => "#{path}/#{fields[1]}",
97 97 :kind => 'dir',
98 98 :size => nil,
99 99 :lastrev => nil
100 100 })
101 101 end
102 102 }
103 103 end
104 104 return nil if $? && $?.exitstatus != 0
105 105 entries.sort_by_name
106 106 rescue Errno::ENOENT => e
107 107 raise CommandFailed
108 108 end
109 109
110 110 STARTLOG="----------------------------"
111 111 ENDLOG ="============================================================================="
112 112
113 113 # Returns all revisions found between identifier_from and identifier_to
114 114 # in the repository. both identifier have to be dates or nil.
115 115 # these method returns nothing but yield every result in block
116 116 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
117 117 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
118 118
119 119 path_with_project="#{url}#{with_leading_slash(path)}"
120 120 cmd = "#{CVS_BIN} -d #{root_url} rlog"
121 121 cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from
122 122 cmd << " #{path_with_project}"
123 123 shellout(cmd) do |io|
124 124 state="entry_start"
125 125
126 126 commit_log=String.new
127 127 revision=nil
128 128 date=nil
129 129 author=nil
130 130 entry_path=nil
131 131 entry_name=nil
132 132 file_state=nil
133 133 branch_map=nil
134 134
135 135 io.each_line() do |line|
136 136
137 137 if state!="revision" && /^#{ENDLOG}/ =~ line
138 138 commit_log=String.new
139 139 revision=nil
140 140 state="entry_start"
141 141 end
142 142
143 143 if state=="entry_start"
144 144 branch_map=Hash.new
145 if /^RCS file: #{Regexp.escape(root_url)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
145 # gsub(/^:.*@[^:]+:/, '') is here to remove :pserver:anonymous@foo.bar: string if present in the url
146 if /^RCS file: #{Regexp.escape(root_url.gsub(/^:.*@[^:]+:/, ''))}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
146 147 entry_path = normalize_cvs_path($1)
147 148 entry_name = normalize_path(File.basename($1))
148 149 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
149 150 elsif /^head: (.+)$/ =~ line
150 151 entry_headRev = $1 #unless entry.nil?
151 152 elsif /^symbolic names:/ =~ line
152 153 state="symbolic" #unless entry.nil?
153 154 elsif /^#{STARTLOG}/ =~ line
154 155 commit_log=String.new
155 156 state="revision"
156 157 end
157 158 next
158 159 elsif state=="symbolic"
159 160 if /^(.*):\s(.*)/ =~ (line.strip)
160 161 branch_map[$1]=$2
161 162 else
162 163 state="tags"
163 164 next
164 165 end
165 166 elsif state=="tags"
166 167 if /^#{STARTLOG}/ =~ line
167 168 commit_log = ""
168 169 state="revision"
169 170 elsif /^#{ENDLOG}/ =~ line
170 171 state="head"
171 172 end
172 173 next
173 174 elsif state=="revision"
174 175 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
175 176 if revision
176 177
177 178 revHelper=CvsRevisionHelper.new(revision)
178 179 revBranch="HEAD"
179 180
180 181 branch_map.each() do |branch_name,branch_point|
181 182 if revHelper.is_in_branch_with_symbol(branch_point)
182 183 revBranch=branch_name
183 184 end
184 185 end
185 186
186 187 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
187 188
188 189 yield Revision.new({
189 190 :time => date,
190 191 :author => author,
191 192 :message=>commit_log.chomp,
192 193 :paths => [{
193 194 :revision => revision,
194 195 :branch=> revBranch,
195 196 :path=>entry_path,
196 197 :name=>entry_name,
197 198 :kind=>'file',
198 199 :action=>file_state
199 200 }]
200 201 })
201 202 end
202 203
203 204 commit_log=String.new
204 205 revision=nil
205 206
206 207 if /^#{ENDLOG}/ =~ line
207 208 state="entry_start"
208 209 end
209 210 next
210 211 end
211 212
212 213 if /^branches: (.+)$/ =~ line
213 214 #TODO: version.branch = $1
214 215 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
215 216 revision = $1
216 217 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
217 218 date = Time.parse($1)
218 219 author = /author: ([^;]+)/.match(line)[1]
219 220 file_state = /state: ([^;]+)/.match(line)[1]
220 221 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
221 222 # useful for stats or something else
222 223 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
223 224 # unless linechanges.nil?
224 225 # version.line_plus = linechanges[1]
225 226 # version.line_minus = linechanges[2]
226 227 # else
227 228 # version.line_plus = 0
228 229 # version.line_minus = 0
229 230 # end
230 231 else
231 232 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
232 233 end
233 234 end
234 235 end
235 236 end
236 237 rescue Errno::ENOENT => e
237 238 raise CommandFailed
238 239 end
239 240
240 241 def diff(path, identifier_from, identifier_to=nil, type="inline")
241 242 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
242 243 path_with_project="#{url}#{with_leading_slash(path)}"
243 244 cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{path_with_project}"
244 245 diff = []
245 246 shellout(cmd) do |io|
246 247 io.each_line do |line|
247 248 diff << line
248 249 end
249 250 end
250 251 return nil if $? && $?.exitstatus != 0
251 252 DiffTableList.new diff, type
252 253 rescue Errno::ENOENT => e
253 254 raise CommandFailed
254 255 end
255 256
256 257 def cat(path, identifier=nil)
257 258 identifier = (identifier) ? identifier : "HEAD"
258 259 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
259 260 path_with_project="#{url}#{with_leading_slash(path)}"
260 261 cmd = "#{CVS_BIN} -d #{root_url} co -r#{identifier} -p #{path_with_project}"
261 262 cat = nil
262 263 shellout(cmd) do |io|
263 264 cat = io.read
264 265 end
265 266 return nil if $? && $?.exitstatus != 0
266 267 cat
267 268 rescue Errno::ENOENT => e
268 269 raise CommandFailed
269 270 end
270 271
271 272 private
272 273
273 274 # convert a date/time into the CVS-format
274 275 def time_to_cvstime(time)
275 276 return nil if time.nil?
276 277 unless time.kind_of? Time
277 278 time = Time.parse(time)
278 279 end
279 280 return time.strftime("%Y-%m-%d %H:%M:%S")
280 281 end
281 282
282 283 def normalize_cvs_path(path)
283 284 normalize_path(path.gsub(/Attic\//,''))
284 285 end
285 286
286 287 def normalize_path(path)
287 288 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
288 289 end
289 290 end
290 291
291 292 class CvsRevisionHelper
292 293 attr_accessor :complete_rev, :revision, :base, :branchid
293 294
294 295 def initialize(complete_rev)
295 296 @complete_rev = complete_rev
296 297 parseRevision()
297 298 end
298 299
299 300 def branchPoint
300 301 return @base
301 302 end
302 303
303 304 def branchVersion
304 305 if isBranchRevision
305 306 return @base+"."+@branchid
306 307 end
307 308 return @base
308 309 end
309 310
310 311 def isBranchRevision
311 312 !@branchid.nil?
312 313 end
313 314
314 315 def prevRev
315 316 unless @revision==0
316 317 return buildRevision(@revision-1)
317 318 end
318 319 return buildRevision(@revision)
319 320 end
320 321
321 322 def is_in_branch_with_symbol(branch_symbol)
322 323 bpieces=branch_symbol.split(".")
323 324 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
324 325 return (branchVersion==branch_start)
325 326 end
326 327
327 328 private
328 329 def buildRevision(rev)
329 330 if rev== 0
330 331 @base
331 332 elsif @branchid.nil?
332 333 @base+"."+rev.to_s
333 334 else
334 335 @base+"."+@branchid+"."+rev.to_s
335 336 end
336 337 end
337 338
338 339 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
339 340 def parseRevision()
340 341 pieces=@complete_rev.split(".")
341 342 @revision=pieces.last.to_i
342 343 baseSize=1
343 344 baseSize+=(pieces.size/2)
344 345 @base=pieces[0..-baseSize].join(".")
345 346 if baseSize > 2
346 347 @branchid=pieces[-2]
347 348 end
348 349 end
349 350 end
350 351 end
351 352 end
352 353 end
@@ -1,127 +1,127
1 1 // ** I18N
2 2
3 3 // Calendar RU language
4 4 // Translation: Sly Golovanov, http://golovanov.net, <sly@golovanov.net>
5 5 // Encoding: any
6 6 // Distributed under the same terms as the calendar itself.
7 7
8 8 // For translators: please use UTF-8 if possible. We strongly believe that
9 9 // Unicode is the answer to a real internationalized world. Also please
10 10 // include your contact information in the header, as can be seen above.
11 11
12 12 // full day names
13 13 Calendar._DN = new Array
14 14 ("воскресенье",
15 15 "понедельник",
16 16 "вторник",
17 17 "среда",
18 18 "четверг",
19 19 "пятница",
20 20 "суббота",
21 21 "воскресенье");
22 22
23 23 // Please note that the following array of short day names (and the same goes
24 24 // for short month names, _SMN) isn't absolutely necessary. We give it here
25 25 // for exemplification on how one can customize the short day names, but if
26 26 // they are simply the first N letters of the full name you can simply say:
27 27 //
28 28 // Calendar._SDN_len = N; // short day name length
29 29 // Calendar._SMN_len = N; // short month name length
30 30 //
31 31 // If N = 3 then this is not needed either since we assume a value of 3 if not
32 32 // present, to be compatible with translation files that were written before
33 33 // this feature.
34 34
35 35 // short day names
36 36 Calendar._SDN = new Array
37 37 ("вск",
38 38 "пон",
39 39 "втр",
40 40 "срд",
41 41 "чет",
42 42 "пят",
43 43 "суб",
44 44 "вск");
45 45
46 46 // First day of the week. "0" means display Sunday first, "1" means display
47 47 // Monday first, etc.
48 Calendar._FD = 0;
48 Calendar._FD = 1;
49 49
50 50 // full month names
51 51 Calendar._MN = new Array
52 52 ("январь",
53 53 "февраль",
54 54 "март",
55 55 "апрель",
56 56 "май",
57 57 "июнь",
58 58 "июль",
59 59 "август",
60 60 "сентябрь",
61 61 "октябрь",
62 62 "ноябрь",
63 63 "декабрь");
64 64
65 65 // short month names
66 66 Calendar._SMN = new Array
67 67 ("янв",
68 68 "фев",
69 69 "мар",
70 70 "апр",
71 71 "май",
72 72 "июн",
73 73 "июл",
74 74 "авг",
75 75 "сен",
76 76 "окт",
77 77 "ноя",
78 78 "дек");
79 79
80 80 // tooltips
81 81 Calendar._TT = {};
82 82 Calendar._TT["INFO"] = "О календаре...";
83 83
84 84 Calendar._TT["ABOUT"] =
85 85 "DHTML Date/Time Selector\n" +
86 86 "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
87 87 "For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
88 88 "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
89 89 "\n\n" +
90 90 "Как выбрать дату:\n" +
91 91 "- При помощи кнопок \xab, \xbb можно выбрать год\n" +
92 92 "- При помощи кнопок " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " можно выбрать месяц\n" +
93 93 "- Подержите эти кнопки нажатыми, чтобы появилось меню быстрого выбора.";
94 94 Calendar._TT["ABOUT_TIME"] = "\n\n" +
95 95 "Как выбрать время:\n" +
96 96 "- При клике на часах или минутах они увеличиваются\n" +
97 97 "- при клике с нажатой клавишей Shift они уменьшаются\n" +
98 98 "- если нажать и двигать мышкой влево/вправо, они будут меняться быстрее.";
99 99
100 100 Calendar._TT["PREV_YEAR"] = "На год назад (удерживать для меню)";
101 101 Calendar._TT["PREV_MONTH"] = "На месяц назад (удерживать для меню)";
102 102 Calendar._TT["GO_TODAY"] = "Сегодня";
103 103 Calendar._TT["NEXT_MONTH"] = "На месяц вперед (удерживать для меню)";
104 104 Calendar._TT["NEXT_YEAR"] = "На год вперед (удерживать для меню)";
105 105 Calendar._TT["SEL_DATE"] = "Выберите дату";
106 106 Calendar._TT["DRAG_TO_MOVE"] = "Перетаскивайте мышкой";
107 107 Calendar._TT["PART_TODAY"] = " (сегодня)";
108 108
109 109 // the following is to inform that "%s" is to be the first day of week
110 110 // %s will be replaced with the day name.
111 111 Calendar._TT["DAY_FIRST"] = "Первый день недели будет %s";
112 112
113 113 // This may be locale-dependent. It specifies the week-end days, as an array
114 114 // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
115 115 // means Monday, etc.
116 116 Calendar._TT["WEEKEND"] = "0,6";
117 117
118 118 Calendar._TT["CLOSE"] = "Закрыть";
119 119 Calendar._TT["TODAY"] = "Сегодня";
120 120 Calendar._TT["TIME_PART"] = "(Shift-)клик или нажать и двигать";
121 121
122 122 // date formats
123 123 Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
124 124 Calendar._TT["TT_DATE_FORMAT"] = "%e %b, %a";
125 125
126 126 Calendar._TT["WK"] = "нед";
127 127 Calendar._TT["TIME"] = "Время:";
General Comments 0
You need to be logged in to leave comments. Login now