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