##// END OF EJS Templates
clean up remaining generic exceptions
Matt Mackall -
r11122:2114e44b default
parent child Browse files
Show More
@@ -1,847 +1,847 b''
1 1 # Mercurial built-in replacement for cvsps.
2 2 #
3 3 # Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import os
9 9 import re
10 10 import cPickle as pickle
11 11 from mercurial import util
12 12 from mercurial.i18n import _
13 13 from mercurial import hook
14 14
15 15 class logentry(object):
16 16 '''Class logentry has the following attributes:
17 17 .author - author name as CVS knows it
18 18 .branch - name of branch this revision is on
19 19 .branches - revision tuple of branches starting at this revision
20 20 .comment - commit message
21 21 .date - the commit date as a (time, tz) tuple
22 22 .dead - true if file revision is dead
23 23 .file - Name of file
24 24 .lines - a tuple (+lines, -lines) or None
25 25 .parent - Previous revision of this entry
26 26 .rcs - name of file as returned from CVS
27 27 .revision - revision number as tuple
28 28 .tags - list of tags on the file
29 29 .synthetic - is this a synthetic "file ... added on ..." revision?
30 30 .mergepoint- the branch that has been merged from
31 31 (if present in rlog output)
32 32 .branchpoints- the branches that start at the current entry
33 33 '''
34 34 def __init__(self, **entries):
35 35 self.synthetic = False
36 36 self.__dict__.update(entries)
37 37
38 38 def __repr__(self):
39 39 return "<%s at 0x%x: %s %s>" % (self.__class__.__name__,
40 40 id(self),
41 41 self.file,
42 42 ".".join(map(str, self.revision)))
43 43
44 44 class logerror(Exception):
45 45 pass
46 46
47 47 def getrepopath(cvspath):
48 48 """Return the repository path from a CVS path.
49 49
50 50 >>> getrepopath('/foo/bar')
51 51 '/foo/bar'
52 52 >>> getrepopath('c:/foo/bar')
53 53 'c:/foo/bar'
54 54 >>> getrepopath(':pserver:10/foo/bar')
55 55 '/foo/bar'
56 56 >>> getrepopath(':pserver:10c:/foo/bar')
57 57 '/foo/bar'
58 58 >>> getrepopath(':pserver:/foo/bar')
59 59 '/foo/bar'
60 60 >>> getrepopath(':pserver:c:/foo/bar')
61 61 'c:/foo/bar'
62 62 >>> getrepopath(':pserver:truc@foo.bar:/foo/bar')
63 63 '/foo/bar'
64 64 >>> getrepopath(':pserver:truc@foo.bar:c:/foo/bar')
65 65 'c:/foo/bar'
66 66 """
67 67 # According to CVS manual, CVS paths are expressed like:
68 68 # [:method:][[user][:password]@]hostname[:[port]]/path/to/repository
69 69 #
70 70 # Unfortunately, Windows absolute paths start with a drive letter
71 71 # like 'c:' making it harder to parse. Here we assume that drive
72 72 # letters are only one character long and any CVS component before
73 73 # the repository path is at least 2 characters long, and use this
74 74 # to disambiguate.
75 75 parts = cvspath.split(':')
76 76 if len(parts) == 1:
77 77 return parts[0]
78 78 # Here there is an ambiguous case if we have a port number
79 79 # immediately followed by a Windows driver letter. We assume this
80 80 # never happens and decide it must be CVS path component,
81 81 # therefore ignoring it.
82 82 if len(parts[-2]) > 1:
83 83 return parts[-1].lstrip('0123456789')
84 84 return parts[-2] + ':' + parts[-1]
85 85
86 86 def createlog(ui, directory=None, root="", rlog=True, cache=None):
87 87 '''Collect the CVS rlog'''
88 88
89 89 # Because we store many duplicate commit log messages, reusing strings
90 90 # saves a lot of memory and pickle storage space.
91 91 _scache = {}
92 92 def scache(s):
93 93 "return a shared version of a string"
94 94 return _scache.setdefault(s, s)
95 95
96 96 ui.status(_('collecting CVS rlog\n'))
97 97
98 98 log = [] # list of logentry objects containing the CVS state
99 99
100 100 # patterns to match in CVS (r)log output, by state of use
101 101 re_00 = re.compile('RCS file: (.+)$')
102 102 re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
103 103 re_02 = re.compile('cvs (r?log|server): (.+)\n$')
104 104 re_03 = re.compile("(Cannot access.+CVSROOT)|"
105 105 "(can't create temporary directory.+)$")
106 106 re_10 = re.compile('Working file: (.+)$')
107 107 re_20 = re.compile('symbolic names:')
108 108 re_30 = re.compile('\t(.+): ([\\d.]+)$')
109 109 re_31 = re.compile('----------------------------$')
110 110 re_32 = re.compile('======================================='
111 111 '======================================$')
112 112 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
113 113 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);'
114 114 r'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?'
115 115 r'(.*mergepoint:\s+([^;]+);)?')
116 116 re_70 = re.compile('branches: (.+);$')
117 117
118 118 file_added_re = re.compile(r'file [^/]+ was (initially )?added on branch')
119 119
120 120 prefix = '' # leading path to strip of what we get from CVS
121 121
122 122 if directory is None:
123 123 # Current working directory
124 124
125 125 # Get the real directory in the repository
126 126 try:
127 127 prefix = open(os.path.join('CVS','Repository')).read().strip()
128 128 directory = prefix
129 129 if prefix == ".":
130 130 prefix = ""
131 131 except IOError:
132 132 raise logerror(_('not a CVS sandbox'))
133 133
134 134 if prefix and not prefix.endswith(os.sep):
135 135 prefix += os.sep
136 136
137 137 # Use the Root file in the sandbox, if it exists
138 138 try:
139 139 root = open(os.path.join('CVS','Root')).read().strip()
140 140 except IOError:
141 141 pass
142 142
143 143 if not root:
144 144 root = os.environ.get('CVSROOT', '')
145 145
146 146 # read log cache if one exists
147 147 oldlog = []
148 148 date = None
149 149
150 150 if cache:
151 151 cachedir = os.path.expanduser('~/.hg.cvsps')
152 152 if not os.path.exists(cachedir):
153 153 os.mkdir(cachedir)
154 154
155 155 # The cvsps cache pickle needs a uniquified name, based on the
156 156 # repository location. The address may have all sort of nasties
157 157 # in it, slashes, colons and such. So here we take just the
158 158 # alphanumerics, concatenated in a way that does not mix up the
159 159 # various components, so that
160 160 # :pserver:user@server:/path
161 161 # and
162 162 # /pserver/user/server/path
163 163 # are mapped to different cache file names.
164 164 cachefile = root.split(":") + [directory, "cache"]
165 165 cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s]
166 166 cachefile = os.path.join(cachedir,
167 167 '.'.join([s for s in cachefile if s]))
168 168
169 169 if cache == 'update':
170 170 try:
171 171 ui.note(_('reading cvs log cache %s\n') % cachefile)
172 172 oldlog = pickle.load(open(cachefile))
173 173 ui.note(_('cache has %d log entries\n') % len(oldlog))
174 174 except Exception, e:
175 175 ui.note(_('error reading cache: %r\n') % e)
176 176
177 177 if oldlog:
178 178 date = oldlog[-1].date # last commit date as a (time,tz) tuple
179 179 date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
180 180
181 181 # build the CVS commandline
182 182 cmd = ['cvs', '-q']
183 183 if root:
184 184 cmd.append('-d%s' % root)
185 185 p = util.normpath(getrepopath(root))
186 186 if not p.endswith('/'):
187 187 p += '/'
188 188 if prefix:
189 189 # looks like normpath replaces "" by "."
190 190 prefix = p + util.normpath(prefix)
191 191 else:
192 192 prefix = p
193 193 cmd.append(['log', 'rlog'][rlog])
194 194 if date:
195 195 # no space between option and date string
196 196 cmd.append('-d>%s' % date)
197 197 cmd.append(directory)
198 198
199 199 # state machine begins here
200 200 tags = {} # dictionary of revisions on current file with their tags
201 201 branchmap = {} # mapping between branch names and revision numbers
202 202 state = 0
203 203 store = False # set when a new record can be appended
204 204
205 205 cmd = [util.shellquote(arg) for arg in cmd]
206 206 ui.note(_("running %s\n") % (' '.join(cmd)))
207 207 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
208 208
209 209 pfp = util.popen(' '.join(cmd))
210 210 peek = pfp.readline()
211 211 while True:
212 212 line = peek
213 213 if line == '':
214 214 break
215 215 peek = pfp.readline()
216 216 if line.endswith('\n'):
217 217 line = line[:-1]
218 218 #ui.debug('state=%d line=%r\n' % (state, line))
219 219
220 220 if state == 0:
221 221 # initial state, consume input until we see 'RCS file'
222 222 match = re_00.match(line)
223 223 if match:
224 224 rcs = match.group(1)
225 225 tags = {}
226 226 if rlog:
227 227 filename = util.normpath(rcs[:-2])
228 228 if filename.startswith(prefix):
229 229 filename = filename[len(prefix):]
230 230 if filename.startswith('/'):
231 231 filename = filename[1:]
232 232 if filename.startswith('Attic/'):
233 233 filename = filename[6:]
234 234 else:
235 235 filename = filename.replace('/Attic/', '/')
236 236 state = 2
237 237 continue
238 238 state = 1
239 239 continue
240 240 match = re_01.match(line)
241 241 if match:
242 raise Exception(match.group(1))
242 raise logerror(match.group(1))
243 243 match = re_02.match(line)
244 244 if match:
245 raise Exception(match.group(2))
245 raise logerror(match.group(2))
246 246 if re_03.match(line):
247 raise Exception(line)
247 raise logerror(line)
248 248
249 249 elif state == 1:
250 250 # expect 'Working file' (only when using log instead of rlog)
251 251 match = re_10.match(line)
252 252 assert match, _('RCS file must be followed by working file')
253 253 filename = util.normpath(match.group(1))
254 254 state = 2
255 255
256 256 elif state == 2:
257 257 # expect 'symbolic names'
258 258 if re_20.match(line):
259 259 branchmap = {}
260 260 state = 3
261 261
262 262 elif state == 3:
263 263 # read the symbolic names and store as tags
264 264 match = re_30.match(line)
265 265 if match:
266 266 rev = [int(x) for x in match.group(2).split('.')]
267 267
268 268 # Convert magic branch number to an odd-numbered one
269 269 revn = len(rev)
270 270 if revn > 3 and (revn % 2) == 0 and rev[-2] == 0:
271 271 rev = rev[:-2] + rev[-1:]
272 272 rev = tuple(rev)
273 273
274 274 if rev not in tags:
275 275 tags[rev] = []
276 276 tags[rev].append(match.group(1))
277 277 branchmap[match.group(1)] = match.group(2)
278 278
279 279 elif re_31.match(line):
280 280 state = 5
281 281 elif re_32.match(line):
282 282 state = 0
283 283
284 284 elif state == 4:
285 285 # expecting '------' separator before first revision
286 286 if re_31.match(line):
287 287 state = 5
288 288 else:
289 289 assert not re_32.match(line), _('must have at least '
290 290 'some revisions')
291 291
292 292 elif state == 5:
293 293 # expecting revision number and possibly (ignored) lock indication
294 294 # we create the logentry here from values stored in states 0 to 4,
295 295 # as this state is re-entered for subsequent revisions of a file.
296 296 match = re_50.match(line)
297 297 assert match, _('expected revision number')
298 298 e = logentry(rcs=scache(rcs), file=scache(filename),
299 299 revision=tuple([int(x) for x in match.group(1).split('.')]),
300 300 branches=[], parent=None)
301 301 state = 6
302 302
303 303 elif state == 6:
304 304 # expecting date, author, state, lines changed
305 305 match = re_60.match(line)
306 306 assert match, _('revision must be followed by date line')
307 307 d = match.group(1)
308 308 if d[2] == '/':
309 309 # Y2K
310 310 d = '19' + d
311 311
312 312 if len(d.split()) != 3:
313 313 # cvs log dates always in GMT
314 314 d = d + ' UTC'
315 315 e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S',
316 316 '%Y/%m/%d %H:%M:%S',
317 317 '%Y-%m-%d %H:%M:%S'])
318 318 e.author = scache(match.group(2))
319 319 e.dead = match.group(3).lower() == 'dead'
320 320
321 321 if match.group(5):
322 322 if match.group(6):
323 323 e.lines = (int(match.group(5)), int(match.group(6)))
324 324 else:
325 325 e.lines = (int(match.group(5)), 0)
326 326 elif match.group(6):
327 327 e.lines = (0, int(match.group(6)))
328 328 else:
329 329 e.lines = None
330 330
331 331 if match.group(7): # cvsnt mergepoint
332 332 myrev = match.group(8).split('.')
333 333 if len(myrev) == 2: # head
334 334 e.mergepoint = 'HEAD'
335 335 else:
336 336 myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]])
337 337 branches = [b for b in branchmap if branchmap[b] == myrev]
338 338 assert len(branches) == 1, 'unknown branch: %s' % e.mergepoint
339 339 e.mergepoint = branches[0]
340 340 else:
341 341 e.mergepoint = None
342 342 e.comment = []
343 343 state = 7
344 344
345 345 elif state == 7:
346 346 # read the revision numbers of branches that start at this revision
347 347 # or store the commit log message otherwise
348 348 m = re_70.match(line)
349 349 if m:
350 350 e.branches = [tuple([int(y) for y in x.strip().split('.')])
351 351 for x in m.group(1).split(';')]
352 352 state = 8
353 353 elif re_31.match(line) and re_50.match(peek):
354 354 state = 5
355 355 store = True
356 356 elif re_32.match(line):
357 357 state = 0
358 358 store = True
359 359 else:
360 360 e.comment.append(line)
361 361
362 362 elif state == 8:
363 363 # store commit log message
364 364 if re_31.match(line):
365 365 state = 5
366 366 store = True
367 367 elif re_32.match(line):
368 368 state = 0
369 369 store = True
370 370 else:
371 371 e.comment.append(line)
372 372
373 373 # When a file is added on a branch B1, CVS creates a synthetic
374 374 # dead trunk revision 1.1 so that the branch has a root.
375 375 # Likewise, if you merge such a file to a later branch B2 (one
376 376 # that already existed when the file was added on B1), CVS
377 377 # creates a synthetic dead revision 1.1.x.1 on B2. Don't drop
378 378 # these revisions now, but mark them synthetic so
379 379 # createchangeset() can take care of them.
380 380 if (store and
381 381 e.dead and
382 382 e.revision[-1] == 1 and # 1.1 or 1.1.x.1
383 383 len(e.comment) == 1 and
384 384 file_added_re.match(e.comment[0])):
385 385 ui.debug('found synthetic revision in %s: %r\n'
386 386 % (e.rcs, e.comment[0]))
387 387 e.synthetic = True
388 388
389 389 if store:
390 390 # clean up the results and save in the log.
391 391 store = False
392 392 e.tags = sorted([scache(x) for x in tags.get(e.revision, [])])
393 393 e.comment = scache('\n'.join(e.comment))
394 394
395 395 revn = len(e.revision)
396 396 if revn > 3 and (revn % 2) == 0:
397 397 e.branch = tags.get(e.revision[:-1], [None])[0]
398 398 else:
399 399 e.branch = None
400 400
401 401 # find the branches starting from this revision
402 402 branchpoints = set()
403 403 for branch, revision in branchmap.iteritems():
404 404 revparts = tuple([int(i) for i in revision.split('.')])
405 405 if len(revparts) < 2: # bad tags
406 406 continue
407 407 if revparts[-2] == 0 and revparts[-1] % 2 == 0:
408 408 # normal branch
409 409 if revparts[:-2] == e.revision:
410 410 branchpoints.add(branch)
411 411 elif revparts == (1, 1, 1): # vendor branch
412 412 if revparts in e.branches:
413 413 branchpoints.add(branch)
414 414 e.branchpoints = branchpoints
415 415
416 416 log.append(e)
417 417
418 418 if len(log) % 100 == 0:
419 419 ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n')
420 420
421 421 log.sort(key=lambda x: (x.rcs, x.revision))
422 422
423 423 # find parent revisions of individual files
424 424 versions = {}
425 425 for e in log:
426 426 branch = e.revision[:-1]
427 427 p = versions.get((e.rcs, branch), None)
428 428 if p is None:
429 429 p = e.revision[:-2]
430 430 e.parent = p
431 431 versions[(e.rcs, branch)] = e.revision
432 432
433 433 # update the log cache
434 434 if cache:
435 435 if log:
436 436 # join up the old and new logs
437 437 log.sort(key=lambda x: x.date)
438 438
439 439 if oldlog and oldlog[-1].date >= log[0].date:
440 440 raise logerror(_('log cache overlaps with new log entries,'
441 441 ' re-run without cache.'))
442 442
443 443 log = oldlog + log
444 444
445 445 # write the new cachefile
446 446 ui.note(_('writing cvs log cache %s\n') % cachefile)
447 447 pickle.dump(log, open(cachefile, 'w'))
448 448 else:
449 449 log = oldlog
450 450
451 451 ui.status(_('%d log entries\n') % len(log))
452 452
453 453 hook.hook(ui, None, "cvslog", True, log=log)
454 454
455 455 return log
456 456
457 457
458 458 class changeset(object):
459 459 '''Class changeset has the following attributes:
460 460 .id - integer identifying this changeset (list index)
461 461 .author - author name as CVS knows it
462 462 .branch - name of branch this changeset is on, or None
463 463 .comment - commit message
464 464 .date - the commit date as a (time,tz) tuple
465 465 .entries - list of logentry objects in this changeset
466 466 .parents - list of one or two parent changesets
467 467 .tags - list of tags on this changeset
468 468 .synthetic - from synthetic revision "file ... added on branch ..."
469 469 .mergepoint- the branch that has been merged from
470 470 (if present in rlog output)
471 471 .branchpoints- the branches that start at the current entry
472 472 '''
473 473 def __init__(self, **entries):
474 474 self.synthetic = False
475 475 self.__dict__.update(entries)
476 476
477 477 def __repr__(self):
478 478 return "<%s at 0x%x: %s>" % (self.__class__.__name__,
479 479 id(self),
480 480 getattr(self, 'id', "(no id)"))
481 481
482 482 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
483 483 '''Convert log into changesets.'''
484 484
485 485 ui.status(_('creating changesets\n'))
486 486
487 487 # Merge changesets
488 488
489 489 log.sort(key=lambda x: (x.comment, x.author, x.branch, x.date))
490 490
491 491 changesets = []
492 492 files = set()
493 493 c = None
494 494 for i, e in enumerate(log):
495 495
496 496 # Check if log entry belongs to the current changeset or not.
497 497
498 498 # Since CVS is file centric, two different file revisions with
499 499 # different branchpoints should be treated as belonging to two
500 500 # different changesets (and the ordering is important and not
501 501 # honoured by cvsps at this point).
502 502 #
503 503 # Consider the following case:
504 504 # foo 1.1 branchpoints: [MYBRANCH]
505 505 # bar 1.1 branchpoints: [MYBRANCH, MYBRANCH2]
506 506 #
507 507 # Here foo is part only of MYBRANCH, but not MYBRANCH2, e.g. a
508 508 # later version of foo may be in MYBRANCH2, so foo should be the
509 509 # first changeset and bar the next and MYBRANCH and MYBRANCH2
510 510 # should both start off of the bar changeset. No provisions are
511 511 # made to ensure that this is, in fact, what happens.
512 512 if not (c and
513 513 e.comment == c.comment and
514 514 e.author == c.author and
515 515 e.branch == c.branch and
516 516 (not hasattr(e, 'branchpoints') or
517 517 not hasattr (c, 'branchpoints') or
518 518 e.branchpoints == c.branchpoints) and
519 519 ((c.date[0] + c.date[1]) <=
520 520 (e.date[0] + e.date[1]) <=
521 521 (c.date[0] + c.date[1]) + fuzz) and
522 522 e.file not in files):
523 523 c = changeset(comment=e.comment, author=e.author,
524 524 branch=e.branch, date=e.date, entries=[],
525 525 mergepoint=getattr(e, 'mergepoint', None),
526 526 branchpoints=getattr(e, 'branchpoints', set()))
527 527 changesets.append(c)
528 528 files = set()
529 529 if len(changesets) % 100 == 0:
530 530 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
531 531 ui.status(util.ellipsis(t, 80) + '\n')
532 532
533 533 c.entries.append(e)
534 534 files.add(e.file)
535 535 c.date = e.date # changeset date is date of latest commit in it
536 536
537 537 # Mark synthetic changesets
538 538
539 539 for c in changesets:
540 540 # Synthetic revisions always get their own changeset, because
541 541 # the log message includes the filename. E.g. if you add file3
542 542 # and file4 on a branch, you get four log entries and three
543 543 # changesets:
544 544 # "File file3 was added on branch ..." (synthetic, 1 entry)
545 545 # "File file4 was added on branch ..." (synthetic, 1 entry)
546 546 # "Add file3 and file4 to fix ..." (real, 2 entries)
547 547 # Hence the check for 1 entry here.
548 548 c.synthetic = len(c.entries) == 1 and c.entries[0].synthetic
549 549
550 550 # Sort files in each changeset
551 551
552 552 for c in changesets:
553 553 def pathcompare(l, r):
554 554 'Mimic cvsps sorting order'
555 555 l = l.split('/')
556 556 r = r.split('/')
557 557 nl = len(l)
558 558 nr = len(r)
559 559 n = min(nl, nr)
560 560 for i in range(n):
561 561 if i + 1 == nl and nl < nr:
562 562 return -1
563 563 elif i + 1 == nr and nl > nr:
564 564 return +1
565 565 elif l[i] < r[i]:
566 566 return -1
567 567 elif l[i] > r[i]:
568 568 return +1
569 569 return 0
570 570 def entitycompare(l, r):
571 571 return pathcompare(l.file, r.file)
572 572
573 573 c.entries.sort(entitycompare)
574 574
575 575 # Sort changesets by date
576 576
577 577 def cscmp(l, r):
578 578 d = sum(l.date) - sum(r.date)
579 579 if d:
580 580 return d
581 581
582 582 # detect vendor branches and initial commits on a branch
583 583 le = {}
584 584 for e in l.entries:
585 585 le[e.rcs] = e.revision
586 586 re = {}
587 587 for e in r.entries:
588 588 re[e.rcs] = e.revision
589 589
590 590 d = 0
591 591 for e in l.entries:
592 592 if re.get(e.rcs, None) == e.parent:
593 593 assert not d
594 594 d = 1
595 595 break
596 596
597 597 for e in r.entries:
598 598 if le.get(e.rcs, None) == e.parent:
599 599 assert not d
600 600 d = -1
601 601 break
602 602
603 603 return d
604 604
605 605 changesets.sort(cscmp)
606 606
607 607 # Collect tags
608 608
609 609 globaltags = {}
610 610 for c in changesets:
611 611 for e in c.entries:
612 612 for tag in e.tags:
613 613 # remember which is the latest changeset to have this tag
614 614 globaltags[tag] = c
615 615
616 616 for c in changesets:
617 617 tags = set()
618 618 for e in c.entries:
619 619 tags.update(e.tags)
620 620 # remember tags only if this is the latest changeset to have it
621 621 c.tags = sorted(tag for tag in tags if globaltags[tag] is c)
622 622
623 623 # Find parent changesets, handle {{mergetobranch BRANCHNAME}}
624 624 # by inserting dummy changesets with two parents, and handle
625 625 # {{mergefrombranch BRANCHNAME}} by setting two parents.
626 626
627 627 if mergeto is None:
628 628 mergeto = r'{{mergetobranch ([-\w]+)}}'
629 629 if mergeto:
630 630 mergeto = re.compile(mergeto)
631 631
632 632 if mergefrom is None:
633 633 mergefrom = r'{{mergefrombranch ([-\w]+)}}'
634 634 if mergefrom:
635 635 mergefrom = re.compile(mergefrom)
636 636
637 637 versions = {} # changeset index where we saw any particular file version
638 638 branches = {} # changeset index where we saw a branch
639 639 n = len(changesets)
640 640 i = 0
641 641 while i < n:
642 642 c = changesets[i]
643 643
644 644 for f in c.entries:
645 645 versions[(f.rcs, f.revision)] = i
646 646
647 647 p = None
648 648 if c.branch in branches:
649 649 p = branches[c.branch]
650 650 else:
651 651 # first changeset on a new branch
652 652 # the parent is a changeset with the branch in its
653 653 # branchpoints such that it is the latest possible
654 654 # commit without any intervening, unrelated commits.
655 655
656 656 for candidate in xrange(i):
657 657 if c.branch not in changesets[candidate].branchpoints:
658 658 if p is not None:
659 659 break
660 660 continue
661 661 p = candidate
662 662
663 663 c.parents = []
664 664 if p is not None:
665 665 p = changesets[p]
666 666
667 667 # Ensure no changeset has a synthetic changeset as a parent.
668 668 while p.synthetic:
669 669 assert len(p.parents) <= 1, \
670 670 _('synthetic changeset cannot have multiple parents')
671 671 if p.parents:
672 672 p = p.parents[0]
673 673 else:
674 674 p = None
675 675 break
676 676
677 677 if p is not None:
678 678 c.parents.append(p)
679 679
680 680 if c.mergepoint:
681 681 if c.mergepoint == 'HEAD':
682 682 c.mergepoint = None
683 683 c.parents.append(changesets[branches[c.mergepoint]])
684 684
685 685 if mergefrom:
686 686 m = mergefrom.search(c.comment)
687 687 if m:
688 688 m = m.group(1)
689 689 if m == 'HEAD':
690 690 m = None
691 691 try:
692 692 candidate = changesets[branches[m]]
693 693 except KeyError:
694 694 ui.warn(_("warning: CVS commit message references "
695 695 "non-existent branch %r:\n%s\n")
696 696 % (m, c.comment))
697 697 if m in branches and c.branch != m and not candidate.synthetic:
698 698 c.parents.append(candidate)
699 699
700 700 if mergeto:
701 701 m = mergeto.search(c.comment)
702 702 if m:
703 703 try:
704 704 m = m.group(1)
705 705 if m == 'HEAD':
706 706 m = None
707 707 except:
708 708 m = None # if no group found then merge to HEAD
709 709 if m in branches and c.branch != m:
710 710 # insert empty changeset for merge
711 711 cc = changeset(
712 712 author=c.author, branch=m, date=c.date,
713 713 comment='convert-repo: CVS merge from branch %s'
714 714 % c.branch,
715 715 entries=[], tags=[],
716 716 parents=[changesets[branches[m]], c])
717 717 changesets.insert(i + 1, cc)
718 718 branches[m] = i + 1
719 719
720 720 # adjust our loop counters now we have inserted a new entry
721 721 n += 1
722 722 i += 2
723 723 continue
724 724
725 725 branches[c.branch] = i
726 726 i += 1
727 727
728 728 # Drop synthetic changesets (safe now that we have ensured no other
729 729 # changesets can have them as parents).
730 730 i = 0
731 731 while i < len(changesets):
732 732 if changesets[i].synthetic:
733 733 del changesets[i]
734 734 else:
735 735 i += 1
736 736
737 737 # Number changesets
738 738
739 739 for i, c in enumerate(changesets):
740 740 c.id = i + 1
741 741
742 742 ui.status(_('%d changeset entries\n') % len(changesets))
743 743
744 744 hook.hook(ui, None, "cvschangesets", True, changesets=changesets)
745 745
746 746 return changesets
747 747
748 748
749 749 def debugcvsps(ui, *args, **opts):
750 750 '''Read CVS rlog for current directory or named path in
751 751 repository, and convert the log to changesets based on matching
752 752 commit log entries and dates.
753 753 '''
754 754 if opts["new_cache"]:
755 755 cache = "write"
756 756 elif opts["update_cache"]:
757 757 cache = "update"
758 758 else:
759 759 cache = None
760 760
761 761 revisions = opts["revisions"]
762 762
763 763 try:
764 764 if args:
765 765 log = []
766 766 for d in args:
767 767 log += createlog(ui, d, root=opts["root"], cache=cache)
768 768 else:
769 769 log = createlog(ui, root=opts["root"], cache=cache)
770 770 except logerror, e:
771 771 ui.write("%r\n"%e)
772 772 return
773 773
774 774 changesets = createchangeset(ui, log, opts["fuzz"])
775 775 del log
776 776
777 777 # Print changesets (optionally filtered)
778 778
779 779 off = len(revisions)
780 780 branches = {} # latest version number in each branch
781 781 ancestors = {} # parent branch
782 782 for cs in changesets:
783 783
784 784 if opts["ancestors"]:
785 785 if cs.branch not in branches and cs.parents and cs.parents[0].id:
786 786 ancestors[cs.branch] = (changesets[cs.parents[0].id - 1].branch,
787 787 cs.parents[0].id)
788 788 branches[cs.branch] = cs.id
789 789
790 790 # limit by branches
791 791 if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]:
792 792 continue
793 793
794 794 if not off:
795 795 # Note: trailing spaces on several lines here are needed to have
796 796 # bug-for-bug compatibility with cvsps.
797 797 ui.write('---------------------\n')
798 798 ui.write('PatchSet %d \n' % cs.id)
799 799 ui.write('Date: %s\n' % util.datestr(cs.date,
800 800 '%Y/%m/%d %H:%M:%S %1%2'))
801 801 ui.write('Author: %s\n' % cs.author)
802 802 ui.write('Branch: %s\n' % (cs.branch or 'HEAD'))
803 803 ui.write('Tag%s: %s \n' % (['', 's'][len(cs.tags) > 1],
804 804 ','.join(cs.tags) or '(none)'))
805 805 branchpoints = getattr(cs, 'branchpoints', None)
806 806 if branchpoints:
807 807 ui.write('Branchpoints: %s \n' % ', '.join(branchpoints))
808 808 if opts["parents"] and cs.parents:
809 809 if len(cs.parents) > 1:
810 810 ui.write('Parents: %s\n' %
811 811 (','.join([str(p.id) for p in cs.parents])))
812 812 else:
813 813 ui.write('Parent: %d\n' % cs.parents[0].id)
814 814
815 815 if opts["ancestors"]:
816 816 b = cs.branch
817 817 r = []
818 818 while b:
819 819 b, c = ancestors[b]
820 820 r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
821 821 if r:
822 822 ui.write('Ancestors: %s\n' % (','.join(r)))
823 823
824 824 ui.write('Log:\n')
825 825 ui.write('%s\n\n' % cs.comment)
826 826 ui.write('Members: \n')
827 827 for f in cs.entries:
828 828 fn = f.file
829 829 if fn.startswith(opts["prefix"]):
830 830 fn = fn[len(opts["prefix"]):]
831 831 ui.write('\t%s:%s->%s%s \n' % (
832 832 fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
833 833 '.'.join([str(x) for x in f.revision]),
834 834 ['', '(DEAD)'][f.dead]))
835 835 ui.write('\n')
836 836
837 837 # have we seen the start tag?
838 838 if revisions and off:
839 839 if revisions[0] == str(cs.id) or \
840 840 revisions[0] in cs.tags:
841 841 off = False
842 842
843 843 # see if we reached the end tag
844 844 if len(revisions) > 1 and not off:
845 845 if revisions[1] == str(cs.id) or \
846 846 revisions[1] in cs.tags:
847 847 break
@@ -1,118 +1,118 b''
1 1 # mpatch.py - Python implementation of mpatch.c
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import struct
9 9 try:
10 10 from cStringIO import StringIO
11 11 except ImportError:
12 12 from StringIO import StringIO
13 13
14 14 # This attempts to apply a series of patches in time proportional to
15 15 # the total size of the patches, rather than patches * len(text). This
16 16 # means rather than shuffling strings around, we shuffle around
17 17 # pointers to fragments with fragment lists.
18 18 #
19 19 # When the fragment lists get too long, we collapse them. To do this
20 20 # efficiently, we do all our operations inside a buffer created by
21 21 # mmap and simply use memmove. This avoids creating a bunch of large
22 22 # temporary string buffers.
23 23
24 24 def patches(a, bins):
25 25 if not bins:
26 26 return a
27 27
28 28 plens = [len(x) for x in bins]
29 29 pl = sum(plens)
30 30 bl = len(a) + pl
31 31 tl = bl + bl + pl # enough for the patches and two working texts
32 32 b1, b2 = 0, bl
33 33
34 34 if not tl:
35 35 return a
36 36
37 37 m = StringIO()
38 38 def move(dest, src, count):
39 39 """move count bytes from src to dest
40 40
41 41 The file pointer is left at the end of dest.
42 42 """
43 43 m.seek(src)
44 44 buf = m.read(count)
45 45 m.seek(dest)
46 46 m.write(buf)
47 47
48 48 # load our original text
49 49 m.write(a)
50 50 frags = [(len(a), b1)]
51 51
52 52 # copy all the patches into our segment so we can memmove from them
53 53 pos = b2 + bl
54 54 m.seek(pos)
55 55 for p in bins: m.write(p)
56 56
57 57 def pull(dst, src, l): # pull l bytes from src
58 58 while l:
59 59 f = src.pop(0)
60 60 if f[0] > l: # do we need to split?
61 61 src.insert(0, (f[0] - l, f[1] + l))
62 62 dst.append((l, f[1]))
63 63 return
64 64 dst.append(f)
65 65 l -= f[0]
66 66
67 67 def collect(buf, list):
68 68 start = buf
69 69 for l, p in list:
70 70 move(buf, p, l)
71 71 buf += l
72 72 return (buf - start, start)
73 73
74 74 for plen in plens:
75 75 # if our list gets too long, execute it
76 76 if len(frags) > 128:
77 77 b2, b1 = b1, b2
78 78 frags = [collect(b1, frags)]
79 79
80 80 new = []
81 81 end = pos + plen
82 82 last = 0
83 83 while pos < end:
84 84 m.seek(pos)
85 85 p1, p2, l = struct.unpack(">lll", m.read(12))
86 86 pull(new, frags, p1 - last) # what didn't change
87 87 pull([], frags, p2 - p1) # what got deleted
88 88 new.append((l, pos + 12)) # what got added
89 89 pos += l + 12
90 90 last = p2
91 91 frags = new + frags # what was left at the end
92 92
93 93 t = collect(b2, frags)
94 94
95 95 m.seek(t[1])
96 96 return m.read(t[0])
97 97
98 98 def patchedsize(orig, delta):
99 99 outlen, last, bin = 0, 0, 0
100 100 binend = len(delta)
101 101 data = 12
102 102
103 103 while data <= binend:
104 104 decode = delta[bin:bin + 12]
105 105 start, end, length = struct.unpack(">lll", decode)
106 106 if start > end:
107 107 break
108 108 bin = data + length
109 109 data = bin + 12
110 110 outlen += start - last
111 111 last = end
112 112 outlen += length
113 113
114 114 if bin != binend:
115 raise Exception("patch cannot be decoded")
115 raise ValueError("patch cannot be decoded")
116 116
117 117 outlen += orig - last
118 118 return outlen
General Comments 0
You need to be logged in to leave comments. Login now