Show More
@@ -0,0 +1,65 b'' | |||||
|
1 | #!/bin/sh | |||
|
2 | ||||
|
3 | # This feature requires use of builtin cvsps! | |||
|
4 | "$TESTDIR/hghave" cvs || exit 80 | |||
|
5 | ||||
|
6 | # XXX lots of duplication with other test-convert-cvs* scripts | |||
|
7 | ||||
|
8 | set -e | |||
|
9 | ||||
|
10 | echo "[extensions]" >> $HGRCPATH | |||
|
11 | echo "convert = " >> $HGRCPATH | |||
|
12 | echo "[convert]" >> $HGRCPATH | |||
|
13 | echo "cvsps=builtin" >> $HGRCPATH | |||
|
14 | ||||
|
15 | echo % create cvs repository with one project | |||
|
16 | mkdir cvsrepo | |||
|
17 | cd cvsrepo | |||
|
18 | export CVSROOT=`pwd` | |||
|
19 | export CVS_OPTIONS=-f | |||
|
20 | cd .. | |||
|
21 | ||||
|
22 | filter='sed "s:$CVSROOT:*REPO*:g"' | |||
|
23 | cvscall() | |||
|
24 | { | |||
|
25 | cvs -f "$@" | eval $filter | |||
|
26 | } | |||
|
27 | ||||
|
28 | cvscall -q -d "$CVSROOT" init | |||
|
29 | mkdir cvsrepo/proj | |||
|
30 | ||||
|
31 | cvscall co proj | |||
|
32 | ||||
|
33 | echo % create file1 on the trunk | |||
|
34 | cd proj | |||
|
35 | touch file1 | |||
|
36 | cvscall add file1 | |||
|
37 | cvscall ci -m"add file1 on trunk" file1 | |||
|
38 | ||||
|
39 | echo % create two branches | |||
|
40 | cvscall tag -b v1_0 | |||
|
41 | cvscall tag -b v1_1 | |||
|
42 | ||||
|
43 | echo % create file2 on branch v1_0 | |||
|
44 | cvs up -rv1_0 | |||
|
45 | touch file2 | |||
|
46 | cvscall add file2 | |||
|
47 | cvscall ci -m"add file2 on branch v1_0" file2 | |||
|
48 | ||||
|
49 | echo % create file3, file4 on branch v1_1 | |||
|
50 | cvs up -rv1_1 | |||
|
51 | touch file3 | |||
|
52 | touch file4 | |||
|
53 | cvscall add file3 file4 | |||
|
54 | cvscall ci -m"add file3, file4 on branch v1_1" file3 file4 | |||
|
55 | ||||
|
56 | echo % merge file2 from v1_0 to v1_1 | |||
|
57 | cvscall up -jv1_0 | |||
|
58 | cvscall ci -m"merge file2 from v1_0 to v1_1" | |||
|
59 | ||||
|
60 | echo % convert to hg | |||
|
61 | cd .. | |||
|
62 | hg convert proj proj.hg | eval $filter | |||
|
63 | ||||
|
64 | echo % hg log output | |||
|
65 | hg -R proj.hg log --template "{rev} {desc}\n" |
@@ -0,0 +1,72 b'' | |||||
|
1 | % create cvs repository with one project | |||
|
2 | cvs checkout: Updating proj | |||
|
3 | % create file1 on the trunk | |||
|
4 | cvs add: scheduling file `file1' for addition | |||
|
5 | cvs add: use 'cvs commit' to add this file permanently | |||
|
6 | RCS file: *REPO*/proj/file1,v | |||
|
7 | done | |||
|
8 | Checking in file1; | |||
|
9 | *REPO*/proj/file1,v <-- file1 | |||
|
10 | initial revision: 1.1 | |||
|
11 | done | |||
|
12 | % create two branches | |||
|
13 | cvs tag: Tagging . | |||
|
14 | T file1 | |||
|
15 | cvs tag: Tagging . | |||
|
16 | T file1 | |||
|
17 | % create file2 on branch v1_0 | |||
|
18 | cvs update: Updating . | |||
|
19 | cvs add: scheduling file `file2' for addition on branch `v1_0' | |||
|
20 | cvs add: use 'cvs commit' to add this file permanently | |||
|
21 | RCS file: *REPO*/proj/Attic/file2,v | |||
|
22 | done | |||
|
23 | Checking in file2; | |||
|
24 | *REPO*/proj/Attic/file2,v <-- file2 | |||
|
25 | new revision: 1.1.2.1; previous revision: 1.1 | |||
|
26 | done | |||
|
27 | % create file3, file4 on branch v1_1 | |||
|
28 | cvs update: Updating . | |||
|
29 | cvs update: file2 is no longer in the repository | |||
|
30 | cvs add: scheduling file `file3' for addition on branch `v1_1' | |||
|
31 | cvs add: scheduling file `file4' for addition on branch `v1_1' | |||
|
32 | cvs add: use 'cvs commit' to add these files permanently | |||
|
33 | RCS file: *REPO*/proj/Attic/file3,v | |||
|
34 | done | |||
|
35 | Checking in file3; | |||
|
36 | *REPO*/proj/Attic/file3,v <-- file3 | |||
|
37 | new revision: 1.1.2.1; previous revision: 1.1 | |||
|
38 | done | |||
|
39 | RCS file: *REPO*/proj/Attic/file4,v | |||
|
40 | done | |||
|
41 | Checking in file4; | |||
|
42 | *REPO*/proj/Attic/file4,v <-- file4 | |||
|
43 | new revision: 1.1.2.1; previous revision: 1.1 | |||
|
44 | done | |||
|
45 | % merge file2 from v1_0 to v1_1 | |||
|
46 | cvs update: Updating . | |||
|
47 | U file2 | |||
|
48 | cvs commit: Examining . | |||
|
49 | Checking in file2; | |||
|
50 | *REPO*/proj/Attic/file2,v <-- file2 | |||
|
51 | new revision: 1.1.4.2; previous revision: 1.1.4.1 | |||
|
52 | done | |||
|
53 | % convert to hg | |||
|
54 | initializing destination proj.hg repository | |||
|
55 | using builtin cvsps | |||
|
56 | collecting CVS rlog | |||
|
57 | 9 log entries | |||
|
58 | creating changesets | |||
|
59 | 4 changeset entries | |||
|
60 | connecting to *REPO* | |||
|
61 | scanning source... | |||
|
62 | sorting... | |||
|
63 | converting... | |||
|
64 | 3 add file1 on trunk | |||
|
65 | 2 add file2 on branch v1_0 | |||
|
66 | 1 add file3, file4 on branch v1_1 | |||
|
67 | 0 merge file2 from v1_0 to v1_1 | |||
|
68 | % hg log output | |||
|
69 | 3 merge file2 from v1_0 to v1_1 | |||
|
70 | 2 add file3, file4 on branch v1_1 | |||
|
71 | 1 add file2 on branch v1_0 | |||
|
72 | 0 add file1 on trunk |
@@ -33,6 +33,7 b' class logentry(object):' | |||||
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) | |
@@ -107,6 +108,8 b' def createlog(ui, directory=None, root="' | |||||
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: | |
@@ -279,7 +282,8 b' def createlog(ui, directory=None, root="' | |||||
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: | |
@@ -338,6 +342,22 b' def createlog(ui, directory=None, root="' | |||||
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 | |
@@ -399,6 +419,7 b' class changeset(object):' | |||||
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) | |
@@ -438,6 +459,19 b' def createchangeset(ui, log, fuzz=60, me' | |||||
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 | |
440 |
|
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) | |||
|
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: | |
@@ -546,7 +580,20 b' def createchangeset(ui, log, fuzz=60, me' | |||||
546 |
|
580 | |||
547 | c.parents = [] |
|
581 | c.parents = [] | |
548 | if p is not None: |
|
582 | if p is not None: | |
549 |
|
|
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) | |
@@ -582,6 +629,15 b' def createchangeset(ui, log, fuzz=60, me' | |||||
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): |
General Comments 0
You need to be logged in to leave comments.
Login now