Mercurial > hg
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 |