equal
deleted
inserted
replaced
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 |