##// END OF EJS Templates
convert: better support for CVS branchpoints (issue1447)...
Henrik Stuart -
r8756:6019e651 default
parent child Browse files
Show More
@@ -1,792 +1,836 b''
1 #
1 #
2 # Mercurial built-in replacement for cvsps.
2 # Mercurial built-in replacement for cvsps.
3 #
3 #
4 # Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk>
4 # Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 import os
9 import os
10 import re
10 import re
11 import cPickle as pickle
11 import cPickle as pickle
12 from mercurial import util
12 from mercurial import util
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14
14
15 def listsort(list, key):
15 def listsort(list, key):
16 "helper to sort by key in Python 2.3"
16 "helper to sort by key in Python 2.3"
17 try:
17 try:
18 list.sort(key=key)
18 list.sort(key=key)
19 except TypeError:
19 except TypeError:
20 list.sort(lambda l, r: cmp(key(l), key(r)))
20 list.sort(lambda l, r: cmp(key(l), key(r)))
21
21
22 class logentry(object):
22 class logentry(object):
23 '''Class logentry has the following attributes:
23 '''Class logentry has the following attributes:
24 .author - author name as CVS knows it
24 .author - author name as CVS knows it
25 .branch - name of branch this revision is on
25 .branch - name of branch this revision is on
26 .branches - revision tuple of branches starting at this revision
26 .branches - revision tuple of branches starting at this revision
27 .comment - commit message
27 .comment - commit message
28 .date - the commit date as a (time, tz) tuple
28 .date - the commit date as a (time, tz) tuple
29 .dead - true if file revision is dead
29 .dead - true if file revision is dead
30 .file - Name of file
30 .file - Name of file
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 .synthetic - is this a synthetic "file ... added on ..." revision?
37 .mergepoint- the branch that has been merged from
37 .mergepoint- the branch that has been merged from
38 (if present in rlog output)
38 (if present in rlog output)
39 .branchpoints- the branches that start at the current entry
39 '''
40 '''
40 def __init__(self, **entries):
41 def __init__(self, **entries):
41 self.__dict__.update(entries)
42 self.__dict__.update(entries)
42
43
43 def __repr__(self):
44 def __repr__(self):
44 return "<%s at 0x%x: %s %s>" % (self.__class__.__name__,
45 return "<%s at 0x%x: %s %s>" % (self.__class__.__name__,
45 id(self),
46 id(self),
46 self.file,
47 self.file,
47 ".".join(map(str, self.revision)))
48 ".".join(map(str, self.revision)))
48
49
49 class logerror(Exception):
50 class logerror(Exception):
50 pass
51 pass
51
52
52 def getrepopath(cvspath):
53 def getrepopath(cvspath):
53 """Return the repository path from a CVS path.
54 """Return the repository path from a CVS path.
54
55
55 >>> getrepopath('/foo/bar')
56 >>> getrepopath('/foo/bar')
56 '/foo/bar'
57 '/foo/bar'
57 >>> getrepopath('c:/foo/bar')
58 >>> getrepopath('c:/foo/bar')
58 'c:/foo/bar'
59 'c:/foo/bar'
59 >>> getrepopath(':pserver:10/foo/bar')
60 >>> getrepopath(':pserver:10/foo/bar')
60 '/foo/bar'
61 '/foo/bar'
61 >>> getrepopath(':pserver:10c:/foo/bar')
62 >>> getrepopath(':pserver:10c:/foo/bar')
62 '/foo/bar'
63 '/foo/bar'
63 >>> getrepopath(':pserver:/foo/bar')
64 >>> getrepopath(':pserver:/foo/bar')
64 '/foo/bar'
65 '/foo/bar'
65 >>> getrepopath(':pserver:c:/foo/bar')
66 >>> getrepopath(':pserver:c:/foo/bar')
66 'c:/foo/bar'
67 'c:/foo/bar'
67 >>> getrepopath(':pserver:truc@foo.bar:/foo/bar')
68 >>> getrepopath(':pserver:truc@foo.bar:/foo/bar')
68 '/foo/bar'
69 '/foo/bar'
69 >>> getrepopath(':pserver:truc@foo.bar:c:/foo/bar')
70 >>> getrepopath(':pserver:truc@foo.bar:c:/foo/bar')
70 'c:/foo/bar'
71 'c:/foo/bar'
71 """
72 """
72 # According to CVS manual, CVS paths are expressed like:
73 # According to CVS manual, CVS paths are expressed like:
73 # [:method:][[user][:password]@]hostname[:[port]]/path/to/repository
74 # [:method:][[user][:password]@]hostname[:[port]]/path/to/repository
74 #
75 #
75 # Unfortunately, Windows absolute paths start with a drive letter
76 # Unfortunately, Windows absolute paths start with a drive letter
76 # like 'c:' making it harder to parse. Here we assume that drive
77 # like 'c:' making it harder to parse. Here we assume that drive
77 # letters are only one character long and any CVS component before
78 # letters are only one character long and any CVS component before
78 # the repository path is at least 2 characters long, and use this
79 # the repository path is at least 2 characters long, and use this
79 # to disambiguate.
80 # to disambiguate.
80 parts = cvspath.split(':')
81 parts = cvspath.split(':')
81 if len(parts) == 1:
82 if len(parts) == 1:
82 return parts[0]
83 return parts[0]
83 # Here there is an ambiguous case if we have a port number
84 # Here there is an ambiguous case if we have a port number
84 # immediately followed by a Windows driver letter. We assume this
85 # immediately followed by a Windows driver letter. We assume this
85 # never happens and decide it must be CVS path component,
86 # never happens and decide it must be CVS path component,
86 # therefore ignoring it.
87 # therefore ignoring it.
87 if len(parts[-2]) > 1:
88 if len(parts[-2]) > 1:
88 return parts[-1].lstrip('0123456789')
89 return parts[-1].lstrip('0123456789')
89 return parts[-2] + ':' + parts[-1]
90 return parts[-2] + ':' + parts[-1]
90
91
91 def createlog(ui, directory=None, root="", rlog=True, cache=None):
92 def createlog(ui, directory=None, root="", rlog=True, cache=None):
92 '''Collect the CVS rlog'''
93 '''Collect the CVS rlog'''
93
94
94 # Because we store many duplicate commit log messages, reusing strings
95 # Because we store many duplicate commit log messages, reusing strings
95 # saves a lot of memory and pickle storage space.
96 # saves a lot of memory and pickle storage space.
96 _scache = {}
97 _scache = {}
97 def scache(s):
98 def scache(s):
98 "return a shared version of a string"
99 "return a shared version of a string"
99 return _scache.setdefault(s, s)
100 return _scache.setdefault(s, s)
100
101
101 ui.status(_('collecting CVS rlog\n'))
102 ui.status(_('collecting CVS rlog\n'))
102
103
103 log = [] # list of logentry objects containing the CVS state
104 log = [] # list of logentry objects containing the CVS state
104
105
105 # patterns to match in CVS (r)log output, by state of use
106 # patterns to match in CVS (r)log output, by state of use
106 re_00 = re.compile('RCS file: (.+)$')
107 re_00 = re.compile('RCS file: (.+)$')
107 re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
108 re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
108 re_02 = re.compile('cvs (r?log|server): (.+)\n$')
109 re_02 = re.compile('cvs (r?log|server): (.+)\n$')
109 re_03 = re.compile("(Cannot access.+CVSROOT)|"
110 re_03 = re.compile("(Cannot access.+CVSROOT)|"
110 "(can't create temporary directory.+)$")
111 "(can't create temporary directory.+)$")
111 re_10 = re.compile('Working file: (.+)$')
112 re_10 = re.compile('Working file: (.+)$')
112 re_20 = re.compile('symbolic names:')
113 re_20 = re.compile('symbolic names:')
113 re_30 = re.compile('\t(.+): ([\\d.]+)$')
114 re_30 = re.compile('\t(.+): ([\\d.]+)$')
114 re_31 = re.compile('----------------------------$')
115 re_31 = re.compile('----------------------------$')
115 re_32 = re.compile('======================================='
116 re_32 = re.compile('======================================='
116 '======================================$')
117 '======================================$')
117 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
118 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
118 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);'
119 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);'
119 r'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?'
120 r'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?'
120 r'(.*mergepoint:\s+([^;]+);)?')
121 r'(.*mergepoint:\s+([^;]+);)?')
121 re_70 = re.compile('branches: (.+);$')
122 re_70 = re.compile('branches: (.+);$')
122
123
123 file_added_re = re.compile(r'file [^/]+ was (initially )?added on branch')
124 file_added_re = re.compile(r'file [^/]+ was (initially )?added on branch')
124
125
125 prefix = '' # leading path to strip of what we get from CVS
126 prefix = '' # leading path to strip of what we get from CVS
126
127
127 if directory is None:
128 if directory is None:
128 # Current working directory
129 # Current working directory
129
130
130 # Get the real directory in the repository
131 # Get the real directory in the repository
131 try:
132 try:
132 prefix = file(os.path.join('CVS','Repository')).read().strip()
133 prefix = file(os.path.join('CVS','Repository')).read().strip()
133 if prefix == ".":
134 if prefix == ".":
134 prefix = ""
135 prefix = ""
135 directory = prefix
136 directory = prefix
136 except IOError:
137 except IOError:
137 raise logerror('Not a CVS sandbox')
138 raise logerror('Not a CVS sandbox')
138
139
139 if prefix and not prefix.endswith(os.sep):
140 if prefix and not prefix.endswith(os.sep):
140 prefix += os.sep
141 prefix += os.sep
141
142
142 # Use the Root file in the sandbox, if it exists
143 # Use the Root file in the sandbox, if it exists
143 try:
144 try:
144 root = file(os.path.join('CVS','Root')).read().strip()
145 root = file(os.path.join('CVS','Root')).read().strip()
145 except IOError:
146 except IOError:
146 pass
147 pass
147
148
148 if not root:
149 if not root:
149 root = os.environ.get('CVSROOT', '')
150 root = os.environ.get('CVSROOT', '')
150
151
151 # read log cache if one exists
152 # read log cache if one exists
152 oldlog = []
153 oldlog = []
153 date = None
154 date = None
154
155
155 if cache:
156 if cache:
156 cachedir = os.path.expanduser('~/.hg.cvsps')
157 cachedir = os.path.expanduser('~/.hg.cvsps')
157 if not os.path.exists(cachedir):
158 if not os.path.exists(cachedir):
158 os.mkdir(cachedir)
159 os.mkdir(cachedir)
159
160
160 # The cvsps cache pickle needs a uniquified name, based on the
161 # The cvsps cache pickle needs a uniquified name, based on the
161 # repository location. The address may have all sort of nasties
162 # repository location. The address may have all sort of nasties
162 # in it, slashes, colons and such. So here we take just the
163 # in it, slashes, colons and such. So here we take just the
163 # alphanumerics, concatenated in a way that does not mix up the
164 # alphanumerics, concatenated in a way that does not mix up the
164 # various components, so that
165 # various components, so that
165 # :pserver:user@server:/path
166 # :pserver:user@server:/path
166 # and
167 # and
167 # /pserver/user/server/path
168 # /pserver/user/server/path
168 # are mapped to different cache file names.
169 # are mapped to different cache file names.
169 cachefile = root.split(":") + [directory, "cache"]
170 cachefile = root.split(":") + [directory, "cache"]
170 cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s]
171 cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s]
171 cachefile = os.path.join(cachedir,
172 cachefile = os.path.join(cachedir,
172 '.'.join([s for s in cachefile if s]))
173 '.'.join([s for s in cachefile if s]))
173
174
174 if cache == 'update':
175 if cache == 'update':
175 try:
176 try:
176 ui.note(_('reading cvs log cache %s\n') % cachefile)
177 ui.note(_('reading cvs log cache %s\n') % cachefile)
177 oldlog = pickle.load(file(cachefile))
178 oldlog = pickle.load(file(cachefile))
178 ui.note(_('cache has %d log entries\n') % len(oldlog))
179 ui.note(_('cache has %d log entries\n') % len(oldlog))
179 except Exception, e:
180 except Exception, e:
180 ui.note(_('error reading cache: %r\n') % e)
181 ui.note(_('error reading cache: %r\n') % e)
181
182
182 if oldlog:
183 if oldlog:
183 date = oldlog[-1].date # last commit date as a (time,tz) tuple
184 date = oldlog[-1].date # last commit date as a (time,tz) tuple
184 date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
185 date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
185
186
186 # build the CVS commandline
187 # build the CVS commandline
187 cmd = ['cvs', '-q']
188 cmd = ['cvs', '-q']
188 if root:
189 if root:
189 cmd.append('-d%s' % root)
190 cmd.append('-d%s' % root)
190 p = util.normpath(getrepopath(root))
191 p = util.normpath(getrepopath(root))
191 if not p.endswith('/'):
192 if not p.endswith('/'):
192 p += '/'
193 p += '/'
193 prefix = p + util.normpath(prefix)
194 prefix = p + util.normpath(prefix)
194 cmd.append(['log', 'rlog'][rlog])
195 cmd.append(['log', 'rlog'][rlog])
195 if date:
196 if date:
196 # no space between option and date string
197 # no space between option and date string
197 cmd.append('-d>%s' % date)
198 cmd.append('-d>%s' % date)
198 cmd.append(directory)
199 cmd.append(directory)
199
200
200 # state machine begins here
201 # state machine begins here
201 tags = {} # dictionary of revisions on current file with their tags
202 tags = {} # dictionary of revisions on current file with their tags
202 branchmap = {} # mapping between branch names and revision numbers
203 branchmap = {} # mapping between branch names and revision numbers
203 state = 0
204 state = 0
204 store = False # set when a new record can be appended
205 store = False # set when a new record can be appended
205
206
206 cmd = [util.shellquote(arg) for arg in cmd]
207 cmd = [util.shellquote(arg) for arg in cmd]
207 ui.note(_("running %s\n") % (' '.join(cmd)))
208 ui.note(_("running %s\n") % (' '.join(cmd)))
208 ui.debug(_("prefix=%r directory=%r root=%r\n") % (prefix, directory, root))
209 ui.debug(_("prefix=%r directory=%r root=%r\n") % (prefix, directory, root))
209
210
210 pfp = util.popen(' '.join(cmd))
211 pfp = util.popen(' '.join(cmd))
211 peek = pfp.readline()
212 peek = pfp.readline()
212 while True:
213 while True:
213 line = peek
214 line = peek
214 if line == '':
215 if line == '':
215 break
216 break
216 peek = pfp.readline()
217 peek = pfp.readline()
217 if line.endswith('\n'):
218 if line.endswith('\n'):
218 line = line[:-1]
219 line = line[:-1]
219 #ui.debug('state=%d line=%r\n' % (state, line))
220 #ui.debug('state=%d line=%r\n' % (state, line))
220
221
221 if state == 0:
222 if state == 0:
222 # initial state, consume input until we see 'RCS file'
223 # initial state, consume input until we see 'RCS file'
223 match = re_00.match(line)
224 match = re_00.match(line)
224 if match:
225 if match:
225 rcs = match.group(1)
226 rcs = match.group(1)
226 tags = {}
227 tags = {}
227 if rlog:
228 if rlog:
228 filename = util.normpath(rcs[:-2])
229 filename = util.normpath(rcs[:-2])
229 if filename.startswith(prefix):
230 if filename.startswith(prefix):
230 filename = filename[len(prefix):]
231 filename = filename[len(prefix):]
231 if filename.startswith('/'):
232 if filename.startswith('/'):
232 filename = filename[1:]
233 filename = filename[1:]
233 if filename.startswith('Attic/'):
234 if filename.startswith('Attic/'):
234 filename = filename[6:]
235 filename = filename[6:]
235 else:
236 else:
236 filename = filename.replace('/Attic/', '/')
237 filename = filename.replace('/Attic/', '/')
237 state = 2
238 state = 2
238 continue
239 continue
239 state = 1
240 state = 1
240 continue
241 continue
241 match = re_01.match(line)
242 match = re_01.match(line)
242 if match:
243 if match:
243 raise Exception(match.group(1))
244 raise Exception(match.group(1))
244 match = re_02.match(line)
245 match = re_02.match(line)
245 if match:
246 if match:
246 raise Exception(match.group(2))
247 raise Exception(match.group(2))
247 if re_03.match(line):
248 if re_03.match(line):
248 raise Exception(line)
249 raise Exception(line)
249
250
250 elif state == 1:
251 elif state == 1:
251 # expect 'Working file' (only when using log instead of rlog)
252 # expect 'Working file' (only when using log instead of rlog)
252 match = re_10.match(line)
253 match = re_10.match(line)
253 assert match, _('RCS file must be followed by working file')
254 assert match, _('RCS file must be followed by working file')
254 filename = util.normpath(match.group(1))
255 filename = util.normpath(match.group(1))
255 state = 2
256 state = 2
256
257
257 elif state == 2:
258 elif state == 2:
258 # expect 'symbolic names'
259 # expect 'symbolic names'
259 if re_20.match(line):
260 if re_20.match(line):
260 branchmap = {}
261 branchmap = {}
261 state = 3
262 state = 3
262
263
263 elif state == 3:
264 elif state == 3:
264 # read the symbolic names and store as tags
265 # read the symbolic names and store as tags
265 match = re_30.match(line)
266 match = re_30.match(line)
266 if match:
267 if match:
267 rev = [int(x) for x in match.group(2).split('.')]
268 rev = [int(x) for x in match.group(2).split('.')]
268
269
269 # Convert magic branch number to an odd-numbered one
270 # Convert magic branch number to an odd-numbered one
270 revn = len(rev)
271 revn = len(rev)
271 if revn > 3 and (revn % 2) == 0 and rev[-2] == 0:
272 if revn > 3 and (revn % 2) == 0 and rev[-2] == 0:
272 rev = rev[:-2] + rev[-1:]
273 rev = rev[:-2] + rev[-1:]
273 rev = tuple(rev)
274 rev = tuple(rev)
274
275
275 if rev not in tags:
276 if rev not in tags:
276 tags[rev] = []
277 tags[rev] = []
277 tags[rev].append(match.group(1))
278 tags[rev].append(match.group(1))
278 branchmap[match.group(1)] = match.group(2)
279 branchmap[match.group(1)] = match.group(2)
279
280
280 elif re_31.match(line):
281 elif re_31.match(line):
281 state = 5
282 state = 5
282 elif re_32.match(line):
283 elif re_32.match(line):
283 state = 0
284 state = 0
284
285
285 elif state == 4:
286 elif state == 4:
286 # expecting '------' separator before first revision
287 # expecting '------' separator before first revision
287 if re_31.match(line):
288 if re_31.match(line):
288 state = 5
289 state = 5
289 else:
290 else:
290 assert not re_32.match(line), _('must have at least '
291 assert not re_32.match(line), _('must have at least '
291 'some revisions')
292 'some revisions')
292
293
293 elif state == 5:
294 elif state == 5:
294 # expecting revision number and possibly (ignored) lock indication
295 # expecting revision number and possibly (ignored) lock indication
295 # we create the logentry here from values stored in states 0 to 4,
296 # we create the logentry here from values stored in states 0 to 4,
296 # as this state is re-entered for subsequent revisions of a file.
297 # as this state is re-entered for subsequent revisions of a file.
297 match = re_50.match(line)
298 match = re_50.match(line)
298 assert match, _('expected revision number')
299 assert match, _('expected revision number')
299 e = logentry(rcs=scache(rcs), file=scache(filename),
300 e = logentry(rcs=scache(rcs), file=scache(filename),
300 revision=tuple([int(x) for x in match.group(1).split('.')]),
301 revision=tuple([int(x) for x in match.group(1).split('.')]),
301 branches=[], parent=None,
302 branches=[], parent=None,
302 synthetic=False)
303 synthetic=False)
303 state = 6
304 state = 6
304
305
305 elif state == 6:
306 elif state == 6:
306 # expecting date, author, state, lines changed
307 # expecting date, author, state, lines changed
307 match = re_60.match(line)
308 match = re_60.match(line)
308 assert match, _('revision must be followed by date line')
309 assert match, _('revision must be followed by date line')
309 d = match.group(1)
310 d = match.group(1)
310 if d[2] == '/':
311 if d[2] == '/':
311 # Y2K
312 # Y2K
312 d = '19' + d
313 d = '19' + d
313
314
314 if len(d.split()) != 3:
315 if len(d.split()) != 3:
315 # cvs log dates always in GMT
316 # cvs log dates always in GMT
316 d = d + ' UTC'
317 d = d + ' UTC'
317 e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S',
318 e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S',
318 '%Y/%m/%d %H:%M:%S',
319 '%Y/%m/%d %H:%M:%S',
319 '%Y-%m-%d %H:%M:%S'])
320 '%Y-%m-%d %H:%M:%S'])
320 e.author = scache(match.group(2))
321 e.author = scache(match.group(2))
321 e.dead = match.group(3).lower() == 'dead'
322 e.dead = match.group(3).lower() == 'dead'
322
323
323 if match.group(5):
324 if match.group(5):
324 if match.group(6):
325 if match.group(6):
325 e.lines = (int(match.group(5)), int(match.group(6)))
326 e.lines = (int(match.group(5)), int(match.group(6)))
326 else:
327 else:
327 e.lines = (int(match.group(5)), 0)
328 e.lines = (int(match.group(5)), 0)
328 elif match.group(6):
329 elif match.group(6):
329 e.lines = (0, int(match.group(6)))
330 e.lines = (0, int(match.group(6)))
330 else:
331 else:
331 e.lines = None
332 e.lines = None
332
333
333 if match.group(7): # cvsnt mergepoint
334 if match.group(7): # cvsnt mergepoint
334 myrev = match.group(8).split('.')
335 myrev = match.group(8).split('.')
335 if len(myrev) == 2: # head
336 if len(myrev) == 2: # head
336 e.mergepoint = 'HEAD'
337 e.mergepoint = 'HEAD'
337 else:
338 else:
338 myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]])
339 myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]])
339 branches = [b for b in branchmap if branchmap[b] == myrev]
340 branches = [b for b in branchmap if branchmap[b] == myrev]
340 assert len(branches) == 1, 'unknown branch: %s' % e.mergepoint
341 assert len(branches) == 1, 'unknown branch: %s' % e.mergepoint
341 e.mergepoint = branches[0]
342 e.mergepoint = branches[0]
342 else:
343 else:
343 e.mergepoint = None
344 e.mergepoint = None
344 e.comment = []
345 e.comment = []
345 state = 7
346 state = 7
346
347
347 elif state == 7:
348 elif state == 7:
348 # read the revision numbers of branches that start at this revision
349 # read the revision numbers of branches that start at this revision
349 # or store the commit log message otherwise
350 # or store the commit log message otherwise
350 m = re_70.match(line)
351 m = re_70.match(line)
351 if m:
352 if m:
352 e.branches = [tuple([int(y) for y in x.strip().split('.')])
353 e.branches = [tuple([int(y) for y in x.strip().split('.')])
353 for x in m.group(1).split(';')]
354 for x in m.group(1).split(';')]
354 state = 8
355 state = 8
355 elif re_31.match(line) and re_50.match(peek):
356 elif re_31.match(line) and re_50.match(peek):
356 state = 5
357 state = 5
357 store = True
358 store = True
358 elif re_32.match(line):
359 elif re_32.match(line):
359 state = 0
360 state = 0
360 store = True
361 store = True
361 else:
362 else:
362 e.comment.append(line)
363 e.comment.append(line)
363
364
364 elif state == 8:
365 elif state == 8:
365 # store commit log message
366 # store commit log message
366 if re_31.match(line):
367 if re_31.match(line):
367 state = 5
368 state = 5
368 store = True
369 store = True
369 elif re_32.match(line):
370 elif re_32.match(line):
370 state = 0
371 state = 0
371 store = True
372 store = True
372 else:
373 else:
373 e.comment.append(line)
374 e.comment.append(line)
374
375
375 # When a file is added on a branch B1, CVS creates a synthetic
376 # When a file is added on a branch B1, CVS creates a synthetic
376 # dead trunk revision 1.1 so that the branch has a root.
377 # dead trunk revision 1.1 so that the branch has a root.
377 # Likewise, if you merge such a file to a later branch B2 (one
378 # Likewise, if you merge such a file to a later branch B2 (one
378 # that already existed when the file was added on B1), CVS
379 # that already existed when the file was added on B1), CVS
379 # creates a synthetic dead revision 1.1.x.1 on B2. Don't drop
380 # creates a synthetic dead revision 1.1.x.1 on B2. Don't drop
380 # these revisions now, but mark them synthetic so
381 # these revisions now, but mark them synthetic so
381 # createchangeset() can take care of them.
382 # createchangeset() can take care of them.
382 if (store and
383 if (store and
383 e.dead and
384 e.dead and
384 e.revision[-1] == 1 and # 1.1 or 1.1.x.1
385 e.revision[-1] == 1 and # 1.1 or 1.1.x.1
385 len(e.comment) == 1 and
386 len(e.comment) == 1 and
386 file_added_re.match(e.comment[0])):
387 file_added_re.match(e.comment[0])):
387 ui.debug(_('found synthetic revision in %s: %r\n')
388 ui.debug(_('found synthetic revision in %s: %r\n')
388 % (e.rcs, e.comment[0]))
389 % (e.rcs, e.comment[0]))
389 e.synthetic = True
390 e.synthetic = True
390
391
391 if store:
392 if store:
392 # clean up the results and save in the log.
393 # clean up the results and save in the log.
393 store = False
394 store = False
394 e.tags = sorted([scache(x) for x in tags.get(e.revision, [])])
395 e.tags = sorted([scache(x) for x in tags.get(e.revision, [])])
395 e.comment = scache('\n'.join(e.comment))
396 e.comment = scache('\n'.join(e.comment))
396
397
397 revn = len(e.revision)
398 revn = len(e.revision)
398 if revn > 3 and (revn % 2) == 0:
399 if revn > 3 and (revn % 2) == 0:
399 e.branch = tags.get(e.revision[:-1], [None])[0]
400 e.branch = tags.get(e.revision[:-1], [None])[0]
400 else:
401 else:
401 e.branch = None
402 e.branch = None
402
403
404 # find the branches starting from this revision
405 branchpoints = set()
406 for branch, revision in branchmap.iteritems():
407 revparts = tuple([int(i) for i in revision.split('.')])
408 if revparts[-2] == 0 and revparts[-1] % 2 == 0:
409 # normal branch
410 if revparts[:-2] == e.revision:
411 branchpoints.add(branch)
412 elif revparts == (1,1,1): # vendor branch
413 if revparts in e.branches:
414 branchpoints.add(branch)
415 e.branchpoints = branchpoints
416
403 log.append(e)
417 log.append(e)
404
418
405 if len(log) % 100 == 0:
419 if len(log) % 100 == 0:
406 ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n')
420 ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n')
407
421
408 listsort(log, key=lambda x:(x.rcs, x.revision))
422 listsort(log, key=lambda x:(x.rcs, x.revision))
409
423
410 # find parent revisions of individual files
424 # find parent revisions of individual files
411 versions = {}
425 versions = {}
412 for e in log:
426 for e in log:
413 branch = e.revision[:-1]
427 branch = e.revision[:-1]
414 p = versions.get((e.rcs, branch), None)
428 p = versions.get((e.rcs, branch), None)
415 if p is None:
429 if p is None:
416 p = e.revision[:-2]
430 p = e.revision[:-2]
417 e.parent = p
431 e.parent = p
418 versions[(e.rcs, branch)] = e.revision
432 versions[(e.rcs, branch)] = e.revision
419
433
420 # update the log cache
434 # update the log cache
421 if cache:
435 if cache:
422 if log:
436 if log:
423 # join up the old and new logs
437 # join up the old and new logs
424 listsort(log, key=lambda x:x.date)
438 listsort(log, key=lambda x:x.date)
425
439
426 if oldlog and oldlog[-1].date >= log[0].date:
440 if oldlog and oldlog[-1].date >= log[0].date:
427 raise logerror('Log cache overlaps with new log entries,'
441 raise logerror('Log cache overlaps with new log entries,'
428 ' re-run without cache.')
442 ' re-run without cache.')
429
443
430 log = oldlog + log
444 log = oldlog + log
431
445
432 # write the new cachefile
446 # write the new cachefile
433 ui.note(_('writing cvs log cache %s\n') % cachefile)
447 ui.note(_('writing cvs log cache %s\n') % cachefile)
434 pickle.dump(log, file(cachefile, 'w'))
448 pickle.dump(log, file(cachefile, 'w'))
435 else:
449 else:
436 log = oldlog
450 log = oldlog
437
451
438 ui.status(_('%d log entries\n') % len(log))
452 ui.status(_('%d log entries\n') % len(log))
439
453
440 return log
454 return log
441
455
442
456
443 class changeset(object):
457 class changeset(object):
444 '''Class changeset has the following attributes:
458 '''Class changeset has the following attributes:
445 .id - integer identifying this changeset (list index)
459 .id - integer identifying this changeset (list index)
446 .author - author name as CVS knows it
460 .author - author name as CVS knows it
447 .branch - name of branch this changeset is on, or None
461 .branch - name of branch this changeset is on, or None
448 .comment - commit message
462 .comment - commit message
449 .date - the commit date as a (time,tz) tuple
463 .date - the commit date as a (time,tz) tuple
450 .entries - list of logentry objects in this changeset
464 .entries - list of logentry objects in this changeset
451 .parents - list of one or two parent changesets
465 .parents - list of one or two parent changesets
452 .tags - list of tags on this changeset
466 .tags - list of tags on this changeset
453 .synthetic - from synthetic revision "file ... added on branch ..."
467 .synthetic - from synthetic revision "file ... added on branch ..."
454 .mergepoint- the branch that has been merged from
468 .mergepoint- the branch that has been merged from
455 (if present in rlog output)
469 (if present in rlog output)
470 .branchpoints- the branches that start at the current entry
456 '''
471 '''
457 def __init__(self, **entries):
472 def __init__(self, **entries):
458 self.__dict__.update(entries)
473 self.__dict__.update(entries)
459
474
460 def __repr__(self):
475 def __repr__(self):
461 return "<%s at 0x%x: %s>" % (self.__class__.__name__,
476 return "<%s at 0x%x: %s>" % (self.__class__.__name__,
462 id(self),
477 id(self),
463 getattr(self, 'id', "(no id)"))
478 getattr(self, 'id', "(no id)"))
464
479
465 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
480 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
466 '''Convert log into changesets.'''
481 '''Convert log into changesets.'''
467
482
468 ui.status(_('creating changesets\n'))
483 ui.status(_('creating changesets\n'))
469
484
470 # Merge changesets
485 # Merge changesets
471
486
472 listsort(log, key=lambda x:(x.comment, x.author, x.branch, x.date))
487 listsort(log, key=lambda x:(x.comment, x.author, x.branch, x.date))
473
488
474 changesets = []
489 changesets = []
475 files = set()
490 files = set()
476 c = None
491 c = None
477 for i, e in enumerate(log):
492 for i, e in enumerate(log):
478
493
479 # Check if log entry belongs to the current changeset or not.
494 # Check if log entry belongs to the current changeset or not.
495
496 # Since CVS is file centric, two different file revisions with
497 # different branchpoints should be treated as belonging to two
498 # different changesets (and the ordering is important and not
499 # honoured by cvsps at this point).
500 #
501 # Consider the following case:
502 # foo 1.1 branchpoints: [MYBRANCH]
503 # bar 1.1 branchpoints: [MYBRANCH, MYBRANCH2]
504 #
505 # Here foo is part only of MYBRANCH, but not MYBRANCH2, e.g. a
506 # later version of foo may be in MYBRANCH2, so foo should be the
507 # first changeset and bar the next and MYBRANCH and MYBRANCH2
508 # should both start off of the bar changeset. No provisions are
509 # made to ensure that this is, in fact, what happens.
480 if not (c and
510 if not (c and
481 e.comment == c.comment and
511 e.comment == c.comment and
482 e.author == c.author and
512 e.author == c.author and
483 e.branch == c.branch and
513 e.branch == c.branch and
514 e.branchpoints == c.branchpoints and
484 ((c.date[0] + c.date[1]) <=
515 ((c.date[0] + c.date[1]) <=
485 (e.date[0] + e.date[1]) <=
516 (e.date[0] + e.date[1]) <=
486 (c.date[0] + c.date[1]) + fuzz) and
517 (c.date[0] + c.date[1]) + fuzz) and
487 e.file not in files):
518 e.file not in files):
488 c = changeset(comment=e.comment, author=e.author,
519 c = changeset(comment=e.comment, author=e.author,
489 branch=e.branch, date=e.date, entries=[],
520 branch=e.branch, date=e.date, entries=[],
490 mergepoint=getattr(e, 'mergepoint', None))
521 mergepoint=getattr(e, 'mergepoint', None),
522 branchpoints=getattr(e, 'branchpoints', set()))
491 changesets.append(c)
523 changesets.append(c)
492 files = set()
524 files = set()
493 if len(changesets) % 100 == 0:
525 if len(changesets) % 100 == 0:
494 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
526 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
495 ui.status(util.ellipsis(t, 80) + '\n')
527 ui.status(util.ellipsis(t, 80) + '\n')
496
528
497 c.entries.append(e)
529 c.entries.append(e)
498 files.add(e.file)
530 files.add(e.file)
499 c.date = e.date # changeset date is date of latest commit in it
531 c.date = e.date # changeset date is date of latest commit in it
500
532
501 # Mark synthetic changesets
533 # Mark synthetic changesets
502
534
503 for c in changesets:
535 for c in changesets:
504 # Synthetic revisions always get their own changeset, because
536 # Synthetic revisions always get their own changeset, because
505 # the log message includes the filename. E.g. if you add file3
537 # the log message includes the filename. E.g. if you add file3
506 # and file4 on a branch, you get four log entries and three
538 # and file4 on a branch, you get four log entries and three
507 # changesets:
539 # changesets:
508 # "File file3 was added on branch ..." (synthetic, 1 entry)
540 # "File file3 was added on branch ..." (synthetic, 1 entry)
509 # "File file4 was added on branch ..." (synthetic, 1 entry)
541 # "File file4 was added on branch ..." (synthetic, 1 entry)
510 # "Add file3 and file4 to fix ..." (real, 2 entries)
542 # "Add file3 and file4 to fix ..." (real, 2 entries)
511 # Hence the check for 1 entry here.
543 # Hence the check for 1 entry here.
512 synth = getattr(c.entries[0], 'synthetic', None)
544 synth = getattr(c.entries[0], 'synthetic', None)
513 c.synthetic = (len(c.entries) == 1 and synth)
545 c.synthetic = (len(c.entries) == 1 and synth)
514
546
515 # Sort files in each changeset
547 # Sort files in each changeset
516
548
517 for c in changesets:
549 for c in changesets:
518 def pathcompare(l, r):
550 def pathcompare(l, r):
519 'Mimic cvsps sorting order'
551 'Mimic cvsps sorting order'
520 l = l.split('/')
552 l = l.split('/')
521 r = r.split('/')
553 r = r.split('/')
522 nl = len(l)
554 nl = len(l)
523 nr = len(r)
555 nr = len(r)
524 n = min(nl, nr)
556 n = min(nl, nr)
525 for i in range(n):
557 for i in range(n):
526 if i + 1 == nl and nl < nr:
558 if i + 1 == nl and nl < nr:
527 return -1
559 return -1
528 elif i + 1 == nr and nl > nr:
560 elif i + 1 == nr and nl > nr:
529 return +1
561 return +1
530 elif l[i] < r[i]:
562 elif l[i] < r[i]:
531 return -1
563 return -1
532 elif l[i] > r[i]:
564 elif l[i] > r[i]:
533 return +1
565 return +1
534 return 0
566 return 0
535 def entitycompare(l, r):
567 def entitycompare(l, r):
536 return pathcompare(l.file, r.file)
568 return pathcompare(l.file, r.file)
537
569
538 c.entries.sort(entitycompare)
570 c.entries.sort(entitycompare)
539
571
540 # Sort changesets by date
572 # Sort changesets by date
541
573
542 def cscmp(l, r):
574 def cscmp(l, r):
543 d = sum(l.date) - sum(r.date)
575 d = sum(l.date) - sum(r.date)
544 if d:
576 if d:
545 return d
577 return d
546
578
547 # detect vendor branches and initial commits on a branch
579 # detect vendor branches and initial commits on a branch
548 le = {}
580 le = {}
549 for e in l.entries:
581 for e in l.entries:
550 le[e.rcs] = e.revision
582 le[e.rcs] = e.revision
551 re = {}
583 re = {}
552 for e in r.entries:
584 for e in r.entries:
553 re[e.rcs] = e.revision
585 re[e.rcs] = e.revision
554
586
555 d = 0
587 d = 0
556 for e in l.entries:
588 for e in l.entries:
557 if re.get(e.rcs, None) == e.parent:
589 if re.get(e.rcs, None) == e.parent:
558 assert not d
590 assert not d
559 d = 1
591 d = 1
560 break
592 break
561
593
562 for e in r.entries:
594 for e in r.entries:
563 if le.get(e.rcs, None) == e.parent:
595 if le.get(e.rcs, None) == e.parent:
564 assert not d
596 assert not d
565 d = -1
597 d = -1
566 break
598 break
567
599
568 return d
600 return d
569
601
570 changesets.sort(cscmp)
602 changesets.sort(cscmp)
571
603
572 # Collect tags
604 # Collect tags
573
605
574 globaltags = {}
606 globaltags = {}
575 for c in changesets:
607 for c in changesets:
576 for e in c.entries:
608 for e in c.entries:
577 for tag in e.tags:
609 for tag in e.tags:
578 # remember which is the latest changeset to have this tag
610 # remember which is the latest changeset to have this tag
579 globaltags[tag] = c
611 globaltags[tag] = c
580
612
581 for c in changesets:
613 for c in changesets:
582 tags = set()
614 tags = set()
583 for e in c.entries:
615 for e in c.entries:
584 tags.update(e.tags)
616 tags.update(e.tags)
585 # remember tags only if this is the latest changeset to have it
617 # remember tags only if this is the latest changeset to have it
586 c.tags = sorted(tag for tag in tags if globaltags[tag] is c)
618 c.tags = sorted(tag for tag in tags if globaltags[tag] is c)
587
619
588 # Find parent changesets, handle {{mergetobranch BRANCHNAME}}
620 # Find parent changesets, handle {{mergetobranch BRANCHNAME}}
589 # by inserting dummy changesets with two parents, and handle
621 # by inserting dummy changesets with two parents, and handle
590 # {{mergefrombranch BRANCHNAME}} by setting two parents.
622 # {{mergefrombranch BRANCHNAME}} by setting two parents.
591
623
592 if mergeto is None:
624 if mergeto is None:
593 mergeto = r'{{mergetobranch ([-\w]+)}}'
625 mergeto = r'{{mergetobranch ([-\w]+)}}'
594 if mergeto:
626 if mergeto:
595 mergeto = re.compile(mergeto)
627 mergeto = re.compile(mergeto)
596
628
597 if mergefrom is None:
629 if mergefrom is None:
598 mergefrom = r'{{mergefrombranch ([-\w]+)}}'
630 mergefrom = r'{{mergefrombranch ([-\w]+)}}'
599 if mergefrom:
631 if mergefrom:
600 mergefrom = re.compile(mergefrom)
632 mergefrom = re.compile(mergefrom)
601
633
602 versions = {} # changeset index where we saw any particular file version
634 versions = {} # changeset index where we saw any particular file version
603 branches = {} # changeset index where we saw a branch
635 branches = {} # changeset index where we saw a branch
604 n = len(changesets)
636 n = len(changesets)
605 i = 0
637 i = 0
606 while i<n:
638 while i<n:
607 c = changesets[i]
639 c = changesets[i]
608
640
609 for f in c.entries:
641 for f in c.entries:
610 versions[(f.rcs, f.revision)] = i
642 versions[(f.rcs, f.revision)] = i
611
643
612 p = None
644 p = None
613 if c.branch in branches:
645 if c.branch in branches:
614 p = branches[c.branch]
646 p = branches[c.branch]
615 else:
647 else:
616 for f in c.entries:
648 # first changeset on a new branch
617 p = max(p, versions.get((f.rcs, f.parent), None))
649 # the parent is a changeset with the branch in its
650 # branchpoints such that it is the latest possible
651 # commit without any intervening, unrelated commits.
652
653 for candidate in xrange(i):
654 if c.branch not in changesets[candidate].branchpoints:
655 if p is not None:
656 break
657 continue
658 p = candidate
618
659
619 c.parents = []
660 c.parents = []
620 if p is not None:
661 if p is not None:
621 p = changesets[p]
662 p = changesets[p]
622
663
623 # Ensure no changeset has a synthetic changeset as a parent.
664 # Ensure no changeset has a synthetic changeset as a parent.
624 while p.synthetic:
665 while p.synthetic:
625 assert len(p.parents) <= 1, \
666 assert len(p.parents) <= 1, \
626 _('synthetic changeset cannot have multiple parents')
667 _('synthetic changeset cannot have multiple parents')
627 if p.parents:
668 if p.parents:
628 p = p.parents[0]
669 p = p.parents[0]
629 else:
670 else:
630 p = None
671 p = None
631 break
672 break
632
673
633 if p is not None:
674 if p is not None:
634 c.parents.append(p)
675 c.parents.append(p)
635
676
636 if c.mergepoint:
677 if c.mergepoint:
637 if c.mergepoint == 'HEAD':
678 if c.mergepoint == 'HEAD':
638 c.mergepoint = None
679 c.mergepoint = None
639 c.parents.append(changesets[branches[c.mergepoint]])
680 c.parents.append(changesets[branches[c.mergepoint]])
640
681
641 if mergefrom:
682 if mergefrom:
642 m = mergefrom.search(c.comment)
683 m = mergefrom.search(c.comment)
643 if m:
684 if m:
644 m = m.group(1)
685 m = m.group(1)
645 if m == 'HEAD':
686 if m == 'HEAD':
646 m = None
687 m = None
647 try:
688 try:
648 candidate = changesets[branches[m]]
689 candidate = changesets[branches[m]]
649 except KeyError:
690 except KeyError:
650 ui.warn(_("warning: CVS commit message references "
691 ui.warn(_("warning: CVS commit message references "
651 "non-existent branch %r:\n%s\n")
692 "non-existent branch %r:\n%s\n")
652 % (m, c.comment))
693 % (m, c.comment))
653 if m in branches and c.branch != m and not candidate.synthetic:
694 if m in branches and c.branch != m and not candidate.synthetic:
654 c.parents.append(candidate)
695 c.parents.append(candidate)
655
696
656 if mergeto:
697 if mergeto:
657 m = mergeto.search(c.comment)
698 m = mergeto.search(c.comment)
658 if m:
699 if m:
659 try:
700 try:
660 m = m.group(1)
701 m = m.group(1)
661 if m == 'HEAD':
702 if m == 'HEAD':
662 m = None
703 m = None
663 except:
704 except:
664 m = None # if no group found then merge to HEAD
705 m = None # if no group found then merge to HEAD
665 if m in branches and c.branch != m:
706 if m in branches and c.branch != m:
666 # insert empty changeset for merge
707 # insert empty changeset for merge
667 cc = changeset(author=c.author, branch=m, date=c.date,
708 cc = changeset(author=c.author, branch=m, date=c.date,
668 comment='convert-repo: CVS merge from branch %s' % c.branch,
709 comment='convert-repo: CVS merge from branch %s' % c.branch,
669 entries=[], tags=[], parents=[changesets[branches[m]], c])
710 entries=[], tags=[], parents=[changesets[branches[m]], c])
670 changesets.insert(i + 1, cc)
711 changesets.insert(i + 1, cc)
671 branches[m] = i + 1
712 branches[m] = i + 1
672
713
673 # adjust our loop counters now we have inserted a new entry
714 # adjust our loop counters now we have inserted a new entry
674 n += 1
715 n += 1
675 i += 2
716 i += 2
676 continue
717 continue
677
718
678 branches[c.branch] = i
719 branches[c.branch] = i
679 i += 1
720 i += 1
680
721
681 # Drop synthetic changesets (safe now that we have ensured no other
722 # Drop synthetic changesets (safe now that we have ensured no other
682 # changesets can have them as parents).
723 # changesets can have them as parents).
683 i = 0
724 i = 0
684 while i < len(changesets):
725 while i < len(changesets):
685 if changesets[i].synthetic:
726 if changesets[i].synthetic:
686 del changesets[i]
727 del changesets[i]
687 else:
728 else:
688 i += 1
729 i += 1
689
730
690 # Number changesets
731 # Number changesets
691
732
692 for i, c in enumerate(changesets):
733 for i, c in enumerate(changesets):
693 c.id = i + 1
734 c.id = i + 1
694
735
695 ui.status(_('%d changeset entries\n') % len(changesets))
736 ui.status(_('%d changeset entries\n') % len(changesets))
696
737
697 return changesets
738 return changesets
698
739
699
740
700 def debugcvsps(ui, *args, **opts):
741 def debugcvsps(ui, *args, **opts):
701 '''Read CVS rlog for current directory or named path in
742 '''Read CVS rlog for current directory or named path in
702 repository, and convert the log to changesets based on matching
743 repository, and convert the log to changesets based on matching
703 commit log entries and dates.
744 commit log entries and dates.
704 '''
745 '''
705 if opts["new_cache"]:
746 if opts["new_cache"]:
706 cache = "write"
747 cache = "write"
707 elif opts["update_cache"]:
748 elif opts["update_cache"]:
708 cache = "update"
749 cache = "update"
709 else:
750 else:
710 cache = None
751 cache = None
711
752
712 revisions = opts["revisions"]
753 revisions = opts["revisions"]
713
754
714 try:
755 try:
715 if args:
756 if args:
716 log = []
757 log = []
717 for d in args:
758 for d in args:
718 log += createlog(ui, d, root=opts["root"], cache=cache)
759 log += createlog(ui, d, root=opts["root"], cache=cache)
719 else:
760 else:
720 log = createlog(ui, root=opts["root"], cache=cache)
761 log = createlog(ui, root=opts["root"], cache=cache)
721 except logerror, e:
762 except logerror, e:
722 ui.write("%r\n"%e)
763 ui.write("%r\n"%e)
723 return
764 return
724
765
725 changesets = createchangeset(ui, log, opts["fuzz"])
766 changesets = createchangeset(ui, log, opts["fuzz"])
726 del log
767 del log
727
768
728 # Print changesets (optionally filtered)
769 # Print changesets (optionally filtered)
729
770
730 off = len(revisions)
771 off = len(revisions)
731 branches = {} # latest version number in each branch
772 branches = {} # latest version number in each branch
732 ancestors = {} # parent branch
773 ancestors = {} # parent branch
733 for cs in changesets:
774 for cs in changesets:
734
775
735 if opts["ancestors"]:
776 if opts["ancestors"]:
736 if cs.branch not in branches and cs.parents and cs.parents[0].id:
777 if cs.branch not in branches and cs.parents and cs.parents[0].id:
737 ancestors[cs.branch] = (changesets[cs.parents[0].id-1].branch,
778 ancestors[cs.branch] = (changesets[cs.parents[0].id-1].branch,
738 cs.parents[0].id)
779 cs.parents[0].id)
739 branches[cs.branch] = cs.id
780 branches[cs.branch] = cs.id
740
781
741 # limit by branches
782 # limit by branches
742 if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]:
783 if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]:
743 continue
784 continue
744
785
745 if not off:
786 if not off:
746 # Note: trailing spaces on several lines here are needed to have
787 # Note: trailing spaces on several lines here are needed to have
747 # bug-for-bug compatibility with cvsps.
788 # bug-for-bug compatibility with cvsps.
748 ui.write('---------------------\n')
789 ui.write('---------------------\n')
749 ui.write('PatchSet %d \n' % cs.id)
790 ui.write('PatchSet %d \n' % cs.id)
750 ui.write('Date: %s\n' % util.datestr(cs.date,
791 ui.write('Date: %s\n' % util.datestr(cs.date,
751 '%Y/%m/%d %H:%M:%S %1%2'))
792 '%Y/%m/%d %H:%M:%S %1%2'))
752 ui.write('Author: %s\n' % cs.author)
793 ui.write('Author: %s\n' % cs.author)
753 ui.write('Branch: %s\n' % (cs.branch or 'HEAD'))
794 ui.write('Branch: %s\n' % (cs.branch or 'HEAD'))
754 ui.write('Tag%s: %s \n' % (['', 's'][len(cs.tags)>1],
795 ui.write('Tag%s: %s \n' % (['', 's'][len(cs.tags)>1],
755 ','.join(cs.tags) or '(none)'))
796 ','.join(cs.tags) or '(none)'))
797 branchpoints = getattr(cs, 'branchpoints', None)
798 if branchpoints:
799 ui.write('Branchpoints: %s \n' % ', '.join(branchpoints))
756 if opts["parents"] and cs.parents:
800 if opts["parents"] and cs.parents:
757 if len(cs.parents)>1:
801 if len(cs.parents)>1:
758 ui.write('Parents: %s\n' % (','.join([str(p.id) for p in cs.parents])))
802 ui.write('Parents: %s\n' % (','.join([str(p.id) for p in cs.parents])))
759 else:
803 else:
760 ui.write('Parent: %d\n' % cs.parents[0].id)
804 ui.write('Parent: %d\n' % cs.parents[0].id)
761
805
762 if opts["ancestors"]:
806 if opts["ancestors"]:
763 b = cs.branch
807 b = cs.branch
764 r = []
808 r = []
765 while b:
809 while b:
766 b, c = ancestors[b]
810 b, c = ancestors[b]
767 r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
811 r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
768 if r:
812 if r:
769 ui.write('Ancestors: %s\n' % (','.join(r)))
813 ui.write('Ancestors: %s\n' % (','.join(r)))
770
814
771 ui.write('Log:\n')
815 ui.write('Log:\n')
772 ui.write('%s\n\n' % cs.comment)
816 ui.write('%s\n\n' % cs.comment)
773 ui.write('Members: \n')
817 ui.write('Members: \n')
774 for f in cs.entries:
818 for f in cs.entries:
775 fn = f.file
819 fn = f.file
776 if fn.startswith(opts["prefix"]):
820 if fn.startswith(opts["prefix"]):
777 fn = fn[len(opts["prefix"]):]
821 fn = fn[len(opts["prefix"]):]
778 ui.write('\t%s:%s->%s%s \n' % (fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
822 ui.write('\t%s:%s->%s%s \n' % (fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
779 '.'.join([str(x) for x in f.revision]), ['', '(DEAD)'][f.dead]))
823 '.'.join([str(x) for x in f.revision]), ['', '(DEAD)'][f.dead]))
780 ui.write('\n')
824 ui.write('\n')
781
825
782 # have we seen the start tag?
826 # have we seen the start tag?
783 if revisions and off:
827 if revisions and off:
784 if revisions[0] == str(cs.id) or \
828 if revisions[0] == str(cs.id) or \
785 revisions[0] in cs.tags:
829 revisions[0] in cs.tags:
786 off = False
830 off = False
787
831
788 # see if we reached the end tag
832 # see if we reached the end tag
789 if len(revisions)>1 and not off:
833 if len(revisions)>1 and not off:
790 if revisions[1] == str(cs.id) or \
834 if revisions[1] == str(cs.id) or \
791 revisions[1] in cs.tags:
835 revisions[1] in cs.tags:
792 break
836 break
@@ -1,64 +1,112 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 # This is http://www.selenic.com/mercurial/bts/issue1148
3 # This is http://www.selenic.com/mercurial/bts/issue1148
4 # and http://www.selenic.com/mercurial/bts/issue1447
4
5
5 "$TESTDIR/hghave" cvs || exit 80
6 "$TESTDIR/hghave" cvs || exit 80
6
7
7 cvscall()
8 cvscall()
8 {
9 {
9 cvs -f "$@"
10 cvs -f "$@"
10 }
11 }
11
12
12 echo "[extensions]" >> $HGRCPATH
13 echo "[extensions]" >> $HGRCPATH
13 echo "convert = " >> $HGRCPATH
14 echo "convert = " >> $HGRCPATH
14 echo "graphlog = " >> $HGRCPATH
15 echo "graphlog = " >> $HGRCPATH
15 echo "[convert]" >> $HGRCPATH
16 echo "[convert]" >> $HGRCPATH
16 echo "cvsps=builtin" >> $HGRCPATH
17 echo "cvsps=builtin" >> $HGRCPATH
17 echo "cvsps.cache=0" >> $HGRCPATH
18 echo "cvsps.cache=0" >> $HGRCPATH
18
19
19 echo % create cvs repository
20 echo % create cvs repository
20 mkdir cvsrepo
21 mkdir cvsrepo
21 cd cvsrepo
22 cd cvsrepo
22 CVSROOT=`pwd`
23 CVSROOT=`pwd`
23 export CVSROOT
24 export CVSROOT
24 CVS_OPTIONS=-f
25 CVS_OPTIONS=-f
25 export CVS_OPTIONS
26 export CVS_OPTIONS
26 cd ..
27 cd ..
27
28
28 cvscall -q -d "$CVSROOT" init
29 cvscall -q -d "$CVSROOT" init
29
30
30 echo % Create a new project
31 echo % Create a new project
31
32
32 mkdir src
33 mkdir src
33 cd src
34 cd src
34 echo "1" > a
35 echo "1" > a
35 echo "1" > b
36 echo "1" > b
36 cvscall import -m "init" src v0 r0 | sort
37 cvscall import -m "init" src v0 r0 | sort
37 cd ..
38 cd ..
38 cvscall co src
39 cvscall co src
39 cd src
40 cd src
40
41
41 echo % Branch the project
42 echo % Branch the project
42
43
43 cvscall tag -b BRANCH
44 cvscall tag -b BRANCH
44 cvscall up -r BRANCH > /dev/null
45 cvscall up -r BRANCH > /dev/null
45
46
46 echo % Modify file a, then b, then a
47 echo % Modify file a, then b, then a
47
48
48 echo "2" > a
49 echo "2" > a
49 cvscall ci -m "mod a" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
50 cvscall ci -m "mod a" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
50
51
51 echo "2" > b
52 echo "2" > b
52 cvscall ci -m "mod b" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
53 cvscall ci -m "mod b" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
53
54
54 echo "3" > a
55 echo "3" > a
55 cvscall ci -m "mod a again" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
56 cvscall ci -m "mod a again" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
56
57
57 echo % Convert
58 echo % Convert
58
59
59 cd ..
60 cd ..
60 hg convert src | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
61 hg convert src | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
61
62
62 echo % Check the result
63 echo % Check the result
63
64
64 hg -R src-hg glog --template '{rev} ({branches}) {desc} files: {files}\n'
65 hg -R src-hg glog --template '{rev} ({branches}) {desc} files: {files}\n'
66
67 echo ""
68
69 echo % issue 1447
70 cvscall()
71 {
72 echo cvs -f "$@"
73 cvs -f "$@"
74 sleep 1
75 }
76
77 cvsci()
78 {
79 echo cvs -f ci "$@"
80 cvs -f ci "$@" >/dev/null 2>&1
81 sleep 1
82 }
83
84 cvscall -Q -d `pwd`/cvsmaster2 init >/dev/null 2>&1
85 cd cvsmaster2
86 export CVSROOT=`pwd`
87 mkdir foo
88 cd ..
89 cvscall -Q co -d cvswork2 foo
90
91 cd cvswork2
92 echo foo > a.txt
93 echo bar > b.txt
94 cvscall -Q add a.txt b.txt
95 cvsci -m "Initial commit"
96
97 echo foo > b.txt
98 cvsci -m "Fix b on HEAD"
99
100 echo bar > a.txt
101 cvsci -m "Small fix in a on HEAD"
102
103 cvscall -Q tag -b BRANCH
104 cvscall -Q up -P -rBRANCH
105
106 echo baz > b.txt
107 cvsci -m "Change on BRANCH in b"
108
109 hg debugcvsps -x --parents foo | sed -e 's/Author:.*/Author:/' -e 's/Date:.*/Date:/'
110
111 cd ..
112
@@ -1,53 +1,121 b''
1 % create cvs repository
1 % create cvs repository
2 % Create a new project
2 % Create a new project
3
3
4
4
5 N src/a
5 N src/a
6 N src/b
6 N src/b
7 No conflicts created by this import
7 No conflicts created by this import
8 cvs checkout: Updating src
8 cvs checkout: Updating src
9 U src/a
9 U src/a
10 U src/b
10 U src/b
11 % Branch the project
11 % Branch the project
12 cvs tag: Tagging .
12 cvs tag: Tagging .
13 T a
13 T a
14 T b
14 T b
15 cvs update: Updating .
15 cvs update: Updating .
16 % Modify file a, then b, then a
16 % Modify file a, then b, then a
17 cvs commit: Examining .
17 cvs commit: Examining .
18 checking in src/a,v
18 checking in src/a,v
19 cvs commit: Examining .
19 cvs commit: Examining .
20 checking in src/b,v
20 checking in src/b,v
21 cvs commit: Examining .
21 cvs commit: Examining .
22 checking in src/a,v
22 checking in src/a,v
23 % Convert
23 % Convert
24 assuming destination src-hg
24 assuming destination src-hg
25 initializing destination src-hg repository
25 initializing destination src-hg repository
26 connecting to cvsrepo
26 connecting to cvsrepo
27 scanning source...
27 scanning source...
28 using builtin cvsps
28 using builtin cvsps
29 collecting CVS rlog
29 collecting CVS rlog
30 7 log entries
30 7 log entries
31 creating changesets
31 creating changesets
32 5 changeset entries
32 5 changeset entries
33 sorting...
33 sorting...
34 converting...
34 converting...
35 4 Initial revision
35 4 Initial revision
36 3 init
36 3 init
37 2 mod a
37 2 mod a
38 1 mod b
38 1 mod b
39 0 mod a again
39 0 mod a again
40 updating tags
40 updating tags
41 % Check the result
41 % Check the result
42 o 5 () update tags files: .hgtags
42 o 5 () update tags files: .hgtags
43 |
43 |
44 | o 4 (BRANCH) mod a again files: a
44 | o 4 (BRANCH) mod a again files: a
45 | |
45 | |
46 | o 3 (BRANCH) mod b files: b
46 | o 3 (BRANCH) mod b files: b
47 | |
47 | |
48 | o 2 (BRANCH) mod a files: a
48 | o 2 (BRANCH) mod a files: a
49 | |
49 | |
50 | o 1 (v0) init files:
50 | o 1 (v0) init files:
51 |/
51 |/
52 o 0 () Initial revision files: a b
52 o 0 () Initial revision files: a b
53
53
54
55 % issue 1447
56 cvs -f -Q co -d cvswork2 foo
57 cvs -f -Q add a.txt b.txt
58 cvs -f ci -m Initial commit
59 cvs -f ci -m Fix b on HEAD
60 cvs -f ci -m Small fix in a on HEAD
61 cvs -f -Q tag -b BRANCH
62 cvs -f -Q up -P -rBRANCH
63 cvs -f ci -m Change on BRANCH in b
64 collecting CVS rlog
65 5 log entries
66 creating changesets
67 4 changeset entries
68 ---------------------
69 PatchSet 1
70 Date:
71 Author:
72 Branch: HEAD
73 Tag: (none)
74 Log:
75 Initial commit
76
77 Members:
78 a.txt:INITIAL->1.1
79 b.txt:INITIAL->1.1
80
81 ---------------------
82 PatchSet 2
83 Date:
84 Author:
85 Branch: HEAD
86 Tag: (none)
87 Branchpoints: BRANCH
88 Parent: 1
89 Log:
90 Fix b on HEAD
91
92 Members:
93 b.txt:1.1->1.2
94
95 ---------------------
96 PatchSet 3
97 Date:
98 Author:
99 Branch: HEAD
100 Tag: (none)
101 Branchpoints: BRANCH
102 Parent: 2
103 Log:
104 Small fix in a on HEAD
105
106 Members:
107 a.txt:1.1->1.2
108
109 ---------------------
110 PatchSet 4
111 Date:
112 Author:
113 Branch: BRANCH
114 Tag: (none)
115 Parent: 3
116 Log:
117 Change on BRANCH in b
118
119 Members:
120 b.txt:1.2->1.2.2.1
121
@@ -1,123 +1,122 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" cvs || exit 80
3 "$TESTDIR/hghave" cvs || exit 80
4
4
5 cvscall()
5 cvscall()
6 {
6 {
7 cvs -f "$@"
7 cvs -f "$@"
8 }
8 }
9
9
10 hgcat()
10 hgcat()
11 {
11 {
12 hg --cwd src-hg cat -r tip "$1"
12 hg --cwd src-hg cat -r tip "$1"
13 }
13 }
14
14
15 echo "[extensions]" >> $HGRCPATH
15 echo "[extensions]" >> $HGRCPATH
16 echo "convert = " >> $HGRCPATH
16 echo "convert = " >> $HGRCPATH
17 echo "graphlog = " >> $HGRCPATH
17 echo "graphlog = " >> $HGRCPATH
18 echo "[convert]" >> $HGRCPATH
18 echo "[convert]" >> $HGRCPATH
19 echo "cvsps=builtin" >> $HGRCPATH
19 echo "cvsps=builtin" >> $HGRCPATH
20
20
21 echo % create cvs repository
21 echo % create cvs repository
22 mkdir cvsrepo
22 mkdir cvsrepo
23 cd cvsrepo
23 cd cvsrepo
24 CVSROOT=`pwd`
24 CVSROOT=`pwd`
25 export CVSROOT
25 export CVSROOT
26 CVS_OPTIONS=-f
26 CVS_OPTIONS=-f
27 export CVS_OPTIONS
27 export CVS_OPTIONS
28 cd ..
28 cd ..
29
29
30 cvscall -q -d "$CVSROOT" init
30 cvscall -q -d "$CVSROOT" init
31
31
32 echo % create source directory
32 echo % create source directory
33 mkdir src-temp
33 mkdir src-temp
34 cd src-temp
34 cd src-temp
35 echo a > a
35 echo a > a
36 mkdir b
36 mkdir b
37 cd b
37 cd b
38 echo c > c
38 echo c > c
39 cd ..
39 cd ..
40
40
41 echo % import source directory
41 echo % import source directory
42 cvscall -q import -m import src INITIAL start
42 cvscall -q import -m import src INITIAL start
43 cd ..
43 cd ..
44
44
45 echo % checkout source directory
45 echo % checkout source directory
46 cvscall -q checkout src
46 cvscall -q checkout src
47
47
48 echo % commit a new revision changing b/c
48 echo % commit a new revision changing b/c
49 cd src
49 cd src
50 sleep 1
50 sleep 1
51 echo c >> b/c
51 echo c >> b/c
52 cvscall -q commit -mci0 . | grep '<--' |\
52 cvscall -q commit -mci0 . | grep '<--' |\
53 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
53 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
54 cd ..
54 cd ..
55
55
56 echo % convert fresh repo
56 echo % convert fresh repo
57 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
57 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
58 hgcat a
58 hgcat a
59 hgcat b/c
59 hgcat b/c
60
60
61 echo % convert fresh repo with --filemap
61 echo % convert fresh repo with --filemap
62 echo include b/c > filemap
62 echo include b/c > filemap
63 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
63 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
64 hgcat b/c
64 hgcat b/c
65 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
65 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
66
66
67 echo % commit new file revisions
67 echo % commit new file revisions
68 cd src
68 cd src
69 echo a >> a
69 echo a >> a
70 echo c >> b/c
70 echo c >> b/c
71 cvscall -q commit -mci1 . | grep '<--' |\
71 cvscall -q commit -mci1 . | grep '<--' |\
72 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
72 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
73 cd ..
73 cd ..
74
74
75 echo % convert again
75 echo % convert again
76 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
76 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
77 hgcat a
77 hgcat a
78 hgcat b/c
78 hgcat b/c
79
79
80 echo % convert again with --filemap
80 echo % convert again with --filemap
81 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
81 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
82 hgcat b/c
82 hgcat b/c
83 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
83 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
84
84
85 echo % commit branch
85 echo % commit branch
86 cd src
86 cd src
87 cvs -q update -r1.1 b/c
87 cvs -q update -r1.1 b/c
88 cvs -q tag -b branch
88 cvs -q tag -b branch
89 cvs -q update -r branch > /dev/null
89 cvs -q update -r branch > /dev/null
90 echo d >> b/c
90 echo d >> b/c
91 cvs -q commit -mci2 . | grep '<--' |\
91 cvs -q commit -mci2 . | grep '<--' |\
92 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
92 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
93 cd ..
93 cd ..
94
94
95 echo % convert again
95 echo % convert again
96 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
96 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
97 hgcat a
98 hgcat b/c
97 hgcat b/c
99
98
100 echo % convert again with --filemap
99 echo % convert again with --filemap
101 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
100 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
102 hgcat b/c
101 hgcat b/c
103 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
102 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
104
103
105 echo % commit a new revision with funny log message
104 echo % commit a new revision with funny log message
106 cd src
105 cd src
107 sleep 1
106 sleep 1
108 echo e >> a
107 echo e >> a
109 cvscall -q commit -m'funny
108 cvscall -q commit -m'funny
110 ----------------------------
109 ----------------------------
111 log message' . | grep '<--' |\
110 log message' . | grep '<--' |\
112 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
111 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
113 cd ..
112 cd ..
114
113
115 echo % convert again
114 echo % convert again
116 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
115 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
117
116
118 echo "graphlog = " >> $HGRCPATH
117 echo "graphlog = " >> $HGRCPATH
119 hg -R src-hg glog --template '{rev} ({branches}) {desc} files: {files}\n'
118 hg -R src-hg glog --template '{rev} ({branches}) {desc} files: {files}\n'
120
119
121 echo % testing debugcvsps
120 echo % testing debugcvsps
122 cd src
121 cd src
123 hg debugcvsps | sed -e 's/Author:.*/Author:/' -e 's/Date:.*/Date:/'
122 hg debugcvsps | sed -e 's/Author:.*/Author:/' -e 's/Date:.*/Date:/'
@@ -1,237 +1,261 b''
1 % create cvs repository
1 % create cvs repository
2 % create source directory
2 % create source directory
3 % import source directory
3 % import source directory
4 N src/a
4 N src/a
5 N src/b/c
5 N src/b/c
6
6
7 No conflicts created by this import
7 No conflicts created by this import
8
8
9 % checkout source directory
9 % checkout source directory
10 U src/a
10 U src/a
11 U src/b/c
11 U src/b/c
12 % commit a new revision changing b/c
12 % commit a new revision changing b/c
13 checking in src/b/c,v
13 checking in src/b/c,v
14 % convert fresh repo
14 % convert fresh repo
15 initializing destination src-hg repository
15 initializing destination src-hg repository
16 connecting to cvsrepo
16 connecting to cvsrepo
17 scanning source...
17 scanning source...
18 using builtin cvsps
18 using builtin cvsps
19 collecting CVS rlog
19 collecting CVS rlog
20 5 log entries
20 5 log entries
21 creating changesets
21 creating changesets
22 3 changeset entries
22 3 changeset entries
23 sorting...
23 sorting...
24 converting...
24 converting...
25 2 Initial revision
25 2 Initial revision
26 1 import
26 1 import
27 0 ci0
27 0 ci0
28 updating tags
28 updating tags
29 a
29 a
30 c
30 c
31 c
31 c
32 % convert fresh repo with --filemap
32 % convert fresh repo with --filemap
33 initializing destination src-filemap repository
33 initializing destination src-filemap repository
34 connecting to cvsrepo
34 connecting to cvsrepo
35 scanning source...
35 scanning source...
36 using builtin cvsps
36 using builtin cvsps
37 collecting CVS rlog
37 collecting CVS rlog
38 5 log entries
38 5 log entries
39 creating changesets
39 creating changesets
40 3 changeset entries
40 3 changeset entries
41 sorting...
41 sorting...
42 converting...
42 converting...
43 2 Initial revision
43 2 Initial revision
44 1 import
44 1 import
45 filtering out empty revision
45 filtering out empty revision
46 rolling back last transaction
46 rolling back last transaction
47 0 ci0
47 0 ci0
48 updating tags
48 updating tags
49 c
49 c
50 c
50 c
51 2 update tags files: .hgtags
51 2 update tags files: .hgtags
52 1 ci0 files: b/c
52 1 ci0 files: b/c
53 0 Initial revision files: b/c
53 0 Initial revision files: b/c
54 % commit new file revisions
54 % commit new file revisions
55 checking in src/a,v
55 checking in src/a,v
56 checking in src/b/c,v
56 checking in src/b/c,v
57 % convert again
57 % convert again
58 connecting to cvsrepo
58 connecting to cvsrepo
59 scanning source...
59 scanning source...
60 using builtin cvsps
60 using builtin cvsps
61 collecting CVS rlog
61 collecting CVS rlog
62 7 log entries
62 7 log entries
63 creating changesets
63 creating changesets
64 4 changeset entries
64 4 changeset entries
65 sorting...
65 sorting...
66 converting...
66 converting...
67 0 ci1
67 0 ci1
68 a
68 a
69 a
69 a
70 c
70 c
71 c
71 c
72 c
72 c
73 % convert again with --filemap
73 % convert again with --filemap
74 connecting to cvsrepo
74 connecting to cvsrepo
75 scanning source...
75 scanning source...
76 using builtin cvsps
76 using builtin cvsps
77 collecting CVS rlog
77 collecting CVS rlog
78 7 log entries
78 7 log entries
79 creating changesets
79 creating changesets
80 4 changeset entries
80 4 changeset entries
81 sorting...
81 sorting...
82 converting...
82 converting...
83 0 ci1
83 0 ci1
84 c
84 c
85 c
85 c
86 c
86 c
87 3 ci1 files: b/c
87 3 ci1 files: b/c
88 2 update tags files: .hgtags
88 2 update tags files: .hgtags
89 1 ci0 files: b/c
89 1 ci0 files: b/c
90 0 Initial revision files: b/c
90 0 Initial revision files: b/c
91 % commit branch
91 % commit branch
92 U b/c
92 U b/c
93 T a
93 T a
94 T b/c
94 T b/c
95 checking in src/b/c,v
95 checking in src/b/c,v
96 % convert again
96 % convert again
97 connecting to cvsrepo
97 connecting to cvsrepo
98 scanning source...
98 scanning source...
99 using builtin cvsps
99 using builtin cvsps
100 collecting CVS rlog
100 collecting CVS rlog
101 8 log entries
101 8 log entries
102 creating changesets
102 creating changesets
103 5 changeset entries
103 5 changeset entries
104 sorting...
104 sorting...
105 converting...
105 converting...
106 0 ci2
106 0 ci2
107 a
108 c
107 c
109 d
108 d
110 % convert again with --filemap
109 % convert again with --filemap
111 connecting to cvsrepo
110 connecting to cvsrepo
112 scanning source...
111 scanning source...
113 using builtin cvsps
112 using builtin cvsps
114 collecting CVS rlog
113 collecting CVS rlog
115 8 log entries
114 8 log entries
116 creating changesets
115 creating changesets
117 5 changeset entries
116 5 changeset entries
118 sorting...
117 sorting...
119 converting...
118 converting...
120 0 ci2
119 0 ci2
121 c
120 c
122 d
121 d
123 4 ci2 files: b/c
122 4 ci2 files: b/c
124 3 ci1 files: b/c
123 3 ci1 files: b/c
125 2 update tags files: .hgtags
124 2 update tags files: .hgtags
126 1 ci0 files: b/c
125 1 ci0 files: b/c
127 0 Initial revision files: b/c
126 0 Initial revision files: b/c
128 % commit a new revision with funny log message
127 % commit a new revision with funny log message
129 checking in src/a,v
128 checking in src/a,v
130 % convert again
129 % convert again
131 connecting to cvsrepo
130 connecting to cvsrepo
132 scanning source...
131 scanning source...
133 using builtin cvsps
132 using builtin cvsps
134 collecting CVS rlog
133 collecting CVS rlog
135 9 log entries
134 9 log entries
136 creating changesets
135 creating changesets
137 6 changeset entries
136 6 changeset entries
138 sorting...
137 sorting...
139 converting...
138 converting...
140 0 funny
139 0 funny
141 o 6 (branch) funny
140 o 6 (branch) funny
142 | ----------------------------
141 | ----------------------------
143 | log message files: a
142 | log message files: a
144 o 5 (branch) ci2 files: b/c
143 o 5 (branch) ci2 files: b/c
144
145 o 4 () ci1 files: a b/c
145 |
146 |
146 | o 4 () ci1 files: a b/c
147 o 3 () update tags files: .hgtags
147 | |
148 |
148 | o 3 () update tags files: .hgtags
149 o 2 () ci0 files: b/c
149 | |
150 |
150 | o 2 () ci0 files: b/c
151 |/
152 | o 1 (INITIAL) import files:
151 | o 1 (INITIAL) import files:
153 |/
152 |/
154 o 0 () Initial revision files: a b/c
153 o 0 () Initial revision files: a b/c
155
154
156 % testing debugcvsps
155 % testing debugcvsps
157 collecting CVS rlog
156 collecting CVS rlog
158 9 log entries
157 9 log entries
159 creating changesets
158 creating changesets
160 6 changeset entries
159 8 changeset entries
161 ---------------------
160 ---------------------
162 PatchSet 1
161 PatchSet 1
163 Date:
162 Date:
164 Author:
163 Author:
165 Branch: HEAD
164 Branch: HEAD
166 Tag: (none)
165 Tag: (none)
166 Branchpoints: INITIAL
167 Log:
167 Log:
168 Initial revision
168 Initial revision
169
169
170 Members:
170 Members:
171 a:INITIAL->1.1
171 a:INITIAL->1.1
172
173 ---------------------
174 PatchSet 2
175 Date:
176 Author:
177 Branch: HEAD
178 Tag: (none)
179 Branchpoints: INITIAL, branch
180 Log:
181 Initial revision
182
183 Members:
172 b/c:INITIAL->1.1
184 b/c:INITIAL->1.1
173
185
174 ---------------------
186 ---------------------
175 PatchSet 2
187 PatchSet 3
176 Date:
188 Date:
177 Author:
189 Author:
178 Branch: INITIAL
190 Branch: INITIAL
179 Tag: start
191 Tag: start
180 Log:
192 Log:
181 import
193 import
182
194
183 Members:
195 Members:
184 a:1.1->1.1.1.1
196 a:1.1->1.1.1.1
185 b/c:1.1->1.1.1.1
197 b/c:1.1->1.1.1.1
186
198
187 ---------------------
199 ---------------------
188 PatchSet 3
200 PatchSet 4
189 Date:
201 Date:
190 Author:
202 Author:
191 Branch: HEAD
203 Branch: HEAD
192 Tag: (none)
204 Tag: (none)
193 Log:
205 Log:
194 ci0
206 ci0
195
207
196 Members:
208 Members:
197 b/c:1.1->1.2
209 b/c:1.1->1.2
198
210
199 ---------------------
211 ---------------------
200 PatchSet 4
212 PatchSet 5
213 Date:
214 Author:
215 Branch: HEAD
216 Tag: (none)
217 Branchpoints: branch
218 Log:
219 ci1
220
221 Members:
222 a:1.1->1.2
223
224 ---------------------
225 PatchSet 6
201 Date:
226 Date:
202 Author:
227 Author:
203 Branch: HEAD
228 Branch: HEAD
204 Tag: (none)
229 Tag: (none)
205 Log:
230 Log:
206 ci1
231 ci1
207
232
208 Members:
233 Members:
209 a:1.1->1.2
210 b/c:1.2->1.3
234 b/c:1.2->1.3
211
235
212 ---------------------
236 ---------------------
213 PatchSet 5
237 PatchSet 7
214 Date:
238 Date:
215 Author:
239 Author:
216 Branch: branch
240 Branch: branch
217 Tag: (none)
241 Tag: (none)
218 Log:
242 Log:
219 ci2
243 ci2
220
244
221 Members:
245 Members:
222 b/c:1.1->1.1.2.1
246 b/c:1.1->1.1.2.1
223
247
224 ---------------------
248 ---------------------
225 PatchSet 6
249 PatchSet 8
226 Date:
250 Date:
227 Author:
251 Author:
228 Branch: branch
252 Branch: branch
229 Tag: (none)
253 Tag: (none)
230 Log:
254 Log:
231 funny
255 funny
232 ----------------------------
256 ----------------------------
233 log message
257 log message
234
258
235 Members:
259 Members:
236 a:1.2->1.2.2.1
260 a:1.2->1.2.2.1
237
261
General Comments 0
You need to be logged in to leave comments. Login now