comparison hgext/convert/cvsps.py @ 7862:02981000012e 1.2.1

cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
author Greg Ward <greg-hg@gerg.ca>
date Wed, 18 Mar 2009 09:15:38 -0400
parents 49355875c805
children 9bbcfa898cd3
comparison
equal deleted inserted replaced
7861:2bc14da14992 7862:02981000012e
31 .lines - a tuple (+lines, -lines) or None 31 .lines - a tuple (+lines, -lines) or None
32 .parent - Previous revision of this entry 32 .parent - Previous revision of this entry
33 .rcs - name of file as returned from CVS 33 .rcs - name of file as returned from CVS
34 .revision - revision number as tuple 34 .revision - revision number as tuple
35 .tags - list of tags on the file 35 .tags - list of tags on the file
36 .synthetic - is this a synthetic "file ... added on ..." revision?
36 ''' 37 '''
37 def __init__(self, **entries): 38 def __init__(self, **entries):
38 self.__dict__.update(entries) 39 self.__dict__.update(entries)
39 40
40 class logerror(Exception): 41 class logerror(Exception):
105 re_32 = re.compile('=============================================================================$') 106 re_32 = re.compile('=============================================================================$')
106 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$') 107 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
107 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?') 108 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?')
108 re_70 = re.compile('branches: (.+);$') 109 re_70 = re.compile('branches: (.+);$')
109 110
111 file_added_re = re.compile(r'file [^/]+ was (initially )?added on branch')
112
110 prefix = '' # leading path to strip of what we get from CVS 113 prefix = '' # leading path to strip of what we get from CVS
111 114
112 if directory is None: 115 if directory is None:
113 # Current working directory 116 # Current working directory
114 117
277 # as this state is re-entered for subsequent revisions of a file. 280 # as this state is re-entered for subsequent revisions of a file.
278 match = re_50.match(line) 281 match = re_50.match(line)
279 assert match, _('expected revision number') 282 assert match, _('expected revision number')
280 e = logentry(rcs=scache(rcs), file=scache(filename), 283 e = logentry(rcs=scache(rcs), file=scache(filename),
281 revision=tuple([int(x) for x in match.group(1).split('.')]), 284 revision=tuple([int(x) for x in match.group(1).split('.')]),
282 branches=[], parent=None) 285 branches=[], parent=None,
286 synthetic=False)
283 state = 6 287 state = 6
284 288
285 elif state == 6: 289 elif state == 6:
286 # expecting date, author, state, lines changed 290 # expecting date, author, state, lines changed
287 match = re_60.match(line) 291 match = re_60.match(line)
336 state = 0 340 state = 0
337 store = True 341 store = True
338 else: 342 else:
339 e.comment.append(line) 343 e.comment.append(line)
340 344
345 # When a file is added on a branch B1, CVS creates a synthetic
346 # dead trunk revision 1.1 so that the branch has a root.
347 # Likewise, if you merge such a file to a later branch B2 (one
348 # that already existed when the file was added on B1), CVS
349 # creates a synthetic dead revision 1.1.x.1 on B2. Don't drop
350 # these revisions now, but mark them synthetic so
351 # createchangeset() can take care of them.
352 if (store and
353 e.dead and
354 e.revision[-1] == 1 and # 1.1 or 1.1.x.1
355 len(e.comment) == 1 and
356 file_added_re.match(e.comment[0])):
357 ui.debug(_('found synthetic rev in %s: %r\n')
358 % (e.rcs, e.comment[0]))
359 e.synthetic = True
360
341 if store: 361 if store:
342 # clean up the results and save in the log. 362 # clean up the results and save in the log.
343 store = False 363 store = False
344 e.tags = util.sort([scache(x) for x in tags.get(e.revision, [])]) 364 e.tags = util.sort([scache(x) for x in tags.get(e.revision, [])])
345 e.comment = scache('\n'.join(e.comment)) 365 e.comment = scache('\n'.join(e.comment))
397 .comment - commit message 417 .comment - commit message
398 .date - the commit date as a (time,tz) tuple 418 .date - the commit date as a (time,tz) tuple
399 .entries - list of logentry objects in this changeset 419 .entries - list of logentry objects in this changeset
400 .parents - list of one or two parent changesets 420 .parents - list of one or two parent changesets
401 .tags - list of tags on this changeset 421 .tags - list of tags on this changeset
422 .synthetic - from synthetic revision "file ... added on branch ..."
402 ''' 423 '''
403 def __init__(self, **entries): 424 def __init__(self, **entries):
404 self.__dict__.update(entries) 425 self.__dict__.update(entries)
405 426
406 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None): 427 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
435 ui.status(util.ellipsis(t, 80) + '\n') 456 ui.status(util.ellipsis(t, 80) + '\n')
436 457
437 c.entries.append(e) 458 c.entries.append(e)
438 files[e.file] = True 459 files[e.file] = True
439 c.date = e.date # changeset date is date of latest commit in it 460 c.date = e.date # changeset date is date of latest commit in it
461
462 # Mark synthetic changesets
463
464 for c in changesets:
465 # Synthetic revisions always get their own changeset, because
466 # the log message includes the filename. E.g. if you add file3
467 # and file4 on a branch, you get four log entries and three
468 # changesets:
469 # "File file3 was added on branch ..." (synthetic, 1 entry)
470 # "File file4 was added on branch ..." (synthetic, 1 entry)
471 # "Add file3 and file4 to fix ..." (real, 2 entries)
472 # Hence the check for 1 entry here.
473 c.synthetic = (len(c.entries) == 1 and c.entries[0].synthetic)
440 474
441 # Sort files in each changeset 475 # Sort files in each changeset
442 476
443 for c in changesets: 477 for c in changesets:
444 def pathcompare(l, r): 478 def pathcompare(l, r):
544 for f in c.entries: 578 for f in c.entries:
545 p = max(p, versions.get((f.rcs, f.parent), None)) 579 p = max(p, versions.get((f.rcs, f.parent), None))
546 580
547 c.parents = [] 581 c.parents = []
548 if p is not None: 582 if p is not None:
549 c.parents.append(changesets[p]) 583 p = changesets[p]
584
585 # Ensure no changeset has a synthetic changeset as a parent.
586 while p.synthetic:
587 assert len(p.parents) <= 1, \
588 _('synthetic changeset cannot have multiple parents')
589 if p.parents:
590 p = p.parents[0]
591 else:
592 p = None
593 break
594
595 if p is not None:
596 c.parents.append(p)
550 597
551 if mergefrom: 598 if mergefrom:
552 m = mergefrom.search(c.comment) 599 m = mergefrom.search(c.comment)
553 if m: 600 if m:
554 m = m.group(1) 601 m = m.group(1)
580 continue 627 continue
581 628
582 branches[c.branch] = i 629 branches[c.branch] = i
583 i += 1 630 i += 1
584 631
632 # Drop synthetic changesets (safe now that we have ensured no other
633 # changesets can have them as parents).
634 i = 0
635 while i < len(changesets):
636 if changesets[i].synthetic:
637 del changesets[i]
638 else:
639 i += 1
640
585 # Number changesets 641 # Number changesets
586 642
587 for i, c in enumerate(changesets): 643 for i, c in enumerate(changesets):
588 c.id = i + 1 644 c.id = i + 1
589 645