##// END OF EJS Templates
cvsps: recognize and eliminate CVS' synthetic "file added" revisions.
Greg Ward -
r7862:02981000 1.2.1 default
parent child Browse files
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 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)
@@ -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