##// END OF EJS Templates
cvsps.py: remove unused Changeset member of logentry
Frank Kingswood -
r6699:386561a5 default
parent child Browse files
Show More
@@ -1,552 +1,551 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
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os
9 import os
10 import re
10 import re
11 import sys
11 import sys
12 import cPickle as pickle
12 import cPickle as pickle
13 from mercurial import util
13 from mercurial import util
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 def listsort(list, key):
16 def listsort(list, key):
17 "helper to sort by key in Python 2.3"
17 "helper to sort by key in Python 2.3"
18 try:
18 try:
19 list.sort(key=key)
19 list.sort(key=key)
20 except TypeError:
20 except TypeError:
21 list.sort(lambda l, r: cmp(key(l), key(r)))
21 list.sort(lambda l, r: cmp(key(l), key(r)))
22
22
23 class logentry(object):
23 class logentry(object):
24 '''Class logentry has the following attributes:
24 '''Class logentry has the following attributes:
25 .author - author name as CVS knows it
25 .author - author name as CVS knows it
26 .branch - name of branch this revision is on
26 .branch - name of branch this revision is on
27 .branches - revision tuple of branches starting at this revision
27 .branches - revision tuple of branches starting at this revision
28 .comment - commit message
28 .comment - commit message
29 .date - the commit date as a (time, tz) tuple
29 .date - the commit date as a (time, tz) tuple
30 .dead - true if file revision is dead
30 .dead - true if file revision is dead
31 .file - Name of file
31 .file - Name of file
32 .lines - a tuple (+lines, -lines) or None
32 .lines - a tuple (+lines, -lines) or None
33 .parent - Previous revision of this entry
33 .parent - Previous revision of this entry
34 .rcs - name of file as returned from CVS
34 .rcs - name of file as returned from CVS
35 .revision - revision number as tuple
35 .revision - revision number as tuple
36 .tags - list of tags on the file
36 .tags - list of tags on the file
37 '''
37 '''
38 def __init__(self, **entries):
38 def __init__(self, **entries):
39 self.__dict__.update(entries)
39 self.__dict__.update(entries)
40
40
41 class logerror(Exception):
41 class logerror(Exception):
42 pass
42 pass
43
43
44 def createlog(ui, directory=None, root="", rlog=True, cache=None):
44 def createlog(ui, directory=None, root="", rlog=True, cache=None):
45 '''Collect the CVS rlog'''
45 '''Collect the CVS rlog'''
46
46
47 # Because we store many duplicate commit log messages, reusing strings
47 # Because we store many duplicate commit log messages, reusing strings
48 # saves a lot of memory and pickle storage space.
48 # saves a lot of memory and pickle storage space.
49 _scache = {}
49 _scache = {}
50 def scache(s):
50 def scache(s):
51 "return a shared version of a string"
51 "return a shared version of a string"
52 return _scache.setdefault(s, s)
52 return _scache.setdefault(s, s)
53
53
54 ui.status(_('collecting CVS rlog\n'))
54 ui.status(_('collecting CVS rlog\n'))
55
55
56 log = [] # list of logentry objects containing the CVS state
56 log = [] # list of logentry objects containing the CVS state
57
57
58 # patterns to match in CVS (r)log output, by state of use
58 # patterns to match in CVS (r)log output, by state of use
59 re_00 = re.compile('RCS file: (.+)$')
59 re_00 = re.compile('RCS file: (.+)$')
60 re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
60 re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
61 re_02 = re.compile('cvs (r?log|server): (.+)\n$')
61 re_02 = re.compile('cvs (r?log|server): (.+)\n$')
62 re_03 = re.compile("(Cannot access.+CVSROOT)|(can't create temporary directory.+)$")
62 re_03 = re.compile("(Cannot access.+CVSROOT)|(can't create temporary directory.+)$")
63 re_10 = re.compile('Working file: (.+)$')
63 re_10 = re.compile('Working file: (.+)$')
64 re_20 = re.compile('symbolic names:')
64 re_20 = re.compile('symbolic names:')
65 re_30 = re.compile('\t(.+): ([\\d.]+)$')
65 re_30 = re.compile('\t(.+): ([\\d.]+)$')
66 re_31 = re.compile('----------------------------$')
66 re_31 = re.compile('----------------------------$')
67 re_32 = re.compile('=============================================================================$')
67 re_32 = re.compile('=============================================================================$')
68 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
68 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
69 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?')
69 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?')
70 re_70 = re.compile('branches: (.+);$')
70 re_70 = re.compile('branches: (.+);$')
71
71
72 prefix = '' # leading path to strip of what we get from CVS
72 prefix = '' # leading path to strip of what we get from CVS
73
73
74 if directory is None:
74 if directory is None:
75 # Current working directory
75 # Current working directory
76
76
77 # Get the real directory in the repository
77 # Get the real directory in the repository
78 try:
78 try:
79 prefix = file(os.path.join('CVS','Repository')).read().strip()
79 prefix = file(os.path.join('CVS','Repository')).read().strip()
80 if prefix == ".":
80 if prefix == ".":
81 prefix = ""
81 prefix = ""
82 directory = prefix
82 directory = prefix
83 except IOError:
83 except IOError:
84 raise logerror('Not a CVS sandbox')
84 raise logerror('Not a CVS sandbox')
85
85
86 if prefix and not prefix.endswith('/'):
86 if prefix and not prefix.endswith('/'):
87 prefix += '/'
87 prefix += '/'
88
88
89 # Use the Root file in the sandbox, if it exists
89 # Use the Root file in the sandbox, if it exists
90 try:
90 try:
91 root = file(os.path.join('CVS','Root')).read().strip()
91 root = file(os.path.join('CVS','Root')).read().strip()
92 except IOError:
92 except IOError:
93 pass
93 pass
94
94
95 if not root:
95 if not root:
96 root = os.environ.get('CVSROOT', '')
96 root = os.environ.get('CVSROOT', '')
97
97
98 # read log cache if one exists
98 # read log cache if one exists
99 oldlog = []
99 oldlog = []
100 date = None
100 date = None
101
101
102 if cache:
102 if cache:
103 cachedir = os.path.expanduser('~/.hg.cvsps')
103 cachedir = os.path.expanduser('~/.hg.cvsps')
104 if not os.path.exists(cachedir):
104 if not os.path.exists(cachedir):
105 os.mkdir(cachedir)
105 os.mkdir(cachedir)
106
106
107 # The cvsps cache pickle needs a uniquified name, based on the
107 # The cvsps cache pickle needs a uniquified name, based on the
108 # repository location. The address may have all sort of nasties
108 # repository location. The address may have all sort of nasties
109 # in it, slashes, colons and such. So here we take just the
109 # in it, slashes, colons and such. So here we take just the
110 # alphanumerics, concatenated in a way that does not mix up the
110 # alphanumerics, concatenated in a way that does not mix up the
111 # various components, so that
111 # various components, so that
112 # :pserver:user@server:/path
112 # :pserver:user@server:/path
113 # and
113 # and
114 # /pserver/user/server/path
114 # /pserver/user/server/path
115 # are mapped to different cache file names.
115 # are mapped to different cache file names.
116 cachefile = root.split(":") + [directory, "cache"]
116 cachefile = root.split(":") + [directory, "cache"]
117 cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s]
117 cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s]
118 cachefile = os.path.join(cachedir,
118 cachefile = os.path.join(cachedir,
119 '.'.join([s for s in cachefile if s]))
119 '.'.join([s for s in cachefile if s]))
120
120
121 if cache == 'update':
121 if cache == 'update':
122 try:
122 try:
123 ui.note(_('reading cvs log cache %s\n') % cachefile)
123 ui.note(_('reading cvs log cache %s\n') % cachefile)
124 oldlog = pickle.load(file(cachefile))
124 oldlog = pickle.load(file(cachefile))
125 ui.note(_('cache has %d log entries\n') % len(oldlog))
125 ui.note(_('cache has %d log entries\n') % len(oldlog))
126 except Exception, e:
126 except Exception, e:
127 ui.note(_('error reading cache: %r\n') % e)
127 ui.note(_('error reading cache: %r\n') % e)
128
128
129 if oldlog:
129 if oldlog:
130 date = oldlog[-1].date # last commit date as a (time,tz) tuple
130 date = oldlog[-1].date # last commit date as a (time,tz) tuple
131 date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
131 date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
132
132
133 # build the CVS commandline
133 # build the CVS commandline
134 cmd = ['cvs', '-q']
134 cmd = ['cvs', '-q']
135 if root:
135 if root:
136 cmd.append('-d%s' % root)
136 cmd.append('-d%s' % root)
137 p = root.split(':')[-1]
137 p = root.split(':')[-1]
138 if not p.endswith('/'):
138 if not p.endswith('/'):
139 p += '/'
139 p += '/'
140 prefix = p + prefix
140 prefix = p + prefix
141 cmd.append(['log', 'rlog'][rlog])
141 cmd.append(['log', 'rlog'][rlog])
142 if date:
142 if date:
143 # no space between option and date string
143 # no space between option and date string
144 cmd.append('-d>%s' % date)
144 cmd.append('-d>%s' % date)
145 cmd.append(directory)
145 cmd.append(directory)
146
146
147 # state machine begins here
147 # state machine begins here
148 tags = {} # dictionary of revisions on current file with their tags
148 tags = {} # dictionary of revisions on current file with their tags
149 state = 0
149 state = 0
150 store = False # set when a new record can be appended
150 store = False # set when a new record can be appended
151
151
152 cmd = [util.shellquote(arg) for arg in cmd]
152 cmd = [util.shellquote(arg) for arg in cmd]
153 ui.note("running %s\n" % (' '.join(cmd)))
153 ui.note("running %s\n" % (' '.join(cmd)))
154 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
154 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
155
155
156 for line in util.popen(' '.join(cmd)):
156 for line in util.popen(' '.join(cmd)):
157 if line.endswith('\n'):
157 if line.endswith('\n'):
158 line = line[:-1]
158 line = line[:-1]
159 #ui.debug('state=%d line=%r\n' % (state, line))
159 #ui.debug('state=%d line=%r\n' % (state, line))
160
160
161 if state == 0:
161 if state == 0:
162 # initial state, consume input until we see 'RCS file'
162 # initial state, consume input until we see 'RCS file'
163 match = re_00.match(line)
163 match = re_00.match(line)
164 if match:
164 if match:
165 rcs = match.group(1)
165 rcs = match.group(1)
166 tags = {}
166 tags = {}
167 if rlog:
167 if rlog:
168 filename = rcs[:-2]
168 filename = rcs[:-2]
169 if filename.startswith(prefix):
169 if filename.startswith(prefix):
170 filename = filename[len(prefix):]
170 filename = filename[len(prefix):]
171 if filename.startswith('/'):
171 if filename.startswith('/'):
172 filename = filename[1:]
172 filename = filename[1:]
173 if filename.startswith('Attic/'):
173 if filename.startswith('Attic/'):
174 filename = filename[6:]
174 filename = filename[6:]
175 else:
175 else:
176 filename = filename.replace('/Attic/', '/')
176 filename = filename.replace('/Attic/', '/')
177 state = 2
177 state = 2
178 continue
178 continue
179 state = 1
179 state = 1
180 continue
180 continue
181 match = re_01.match(line)
181 match = re_01.match(line)
182 if match:
182 if match:
183 raise Exception(match.group(1))
183 raise Exception(match.group(1))
184 match = re_02.match(line)
184 match = re_02.match(line)
185 if match:
185 if match:
186 raise Exception(match.group(2))
186 raise Exception(match.group(2))
187 if re_03.match(line):
187 if re_03.match(line):
188 raise Exception(line)
188 raise Exception(line)
189
189
190 elif state == 1:
190 elif state == 1:
191 # expect 'Working file' (only when using log instead of rlog)
191 # expect 'Working file' (only when using log instead of rlog)
192 match = re_10.match(line)
192 match = re_10.match(line)
193 assert match, _('RCS file must be followed by working file')
193 assert match, _('RCS file must be followed by working file')
194 filename = match.group(1)
194 filename = match.group(1)
195 state = 2
195 state = 2
196
196
197 elif state == 2:
197 elif state == 2:
198 # expect 'symbolic names'
198 # expect 'symbolic names'
199 if re_20.match(line):
199 if re_20.match(line):
200 state = 3
200 state = 3
201
201
202 elif state == 3:
202 elif state == 3:
203 # read the symbolic names and store as tags
203 # read the symbolic names and store as tags
204 match = re_30.match(line)
204 match = re_30.match(line)
205 if match:
205 if match:
206 rev = [int(x) for x in match.group(2).split('.')]
206 rev = [int(x) for x in match.group(2).split('.')]
207
207
208 # Convert magic branch number to an odd-numbered one
208 # Convert magic branch number to an odd-numbered one
209 revn = len(rev)
209 revn = len(rev)
210 if revn > 3 and (revn % 2) == 0 and rev[-2] == 0:
210 if revn > 3 and (revn % 2) == 0 and rev[-2] == 0:
211 rev = rev[:-2] + rev[-1:]
211 rev = rev[:-2] + rev[-1:]
212 rev = tuple(rev)
212 rev = tuple(rev)
213
213
214 if rev not in tags:
214 if rev not in tags:
215 tags[rev] = []
215 tags[rev] = []
216 tags[rev].append(match.group(1))
216 tags[rev].append(match.group(1))
217
217
218 elif re_31.match(line):
218 elif re_31.match(line):
219 state = 5
219 state = 5
220 elif re_32.match(line):
220 elif re_32.match(line):
221 state = 0
221 state = 0
222
222
223 elif state == 4:
223 elif state == 4:
224 # expecting '------' separator before first revision
224 # expecting '------' separator before first revision
225 if re_31.match(line):
225 if re_31.match(line):
226 state = 5
226 state = 5
227 else:
227 else:
228 assert not re_32.match(line), _('Must have at least some revisions')
228 assert not re_32.match(line), _('Must have at least some revisions')
229
229
230 elif state == 5:
230 elif state == 5:
231 # expecting revision number and possibly (ignored) lock indication
231 # expecting revision number and possibly (ignored) lock indication
232 # we create the logentry here from values stored in states 0 to 4,
232 # we create the logentry here from values stored in states 0 to 4,
233 # as this state is re-entered for subsequent revisions of a file.
233 # as this state is re-entered for subsequent revisions of a file.
234 match = re_50.match(line)
234 match = re_50.match(line)
235 assert match, _('expected revision number')
235 assert match, _('expected revision number')
236 e = logentry(rcs=scache(rcs), file=scache(filename),
236 e = logentry(rcs=scache(rcs), file=scache(filename),
237 revision=tuple([int(x) for x in match.group(1).split('.')]),
237 revision=tuple([int(x) for x in match.group(1).split('.')]),
238 branches=[], parent=None)
238 branches=[], parent=None)
239 state = 6
239 state = 6
240
240
241 elif state == 6:
241 elif state == 6:
242 # expecting date, author, state, lines changed
242 # expecting date, author, state, lines changed
243 match = re_60.match(line)
243 match = re_60.match(line)
244 assert match, _('revision must be followed by date line')
244 assert match, _('revision must be followed by date line')
245 d = match.group(1)
245 d = match.group(1)
246 if d[2] == '/':
246 if d[2] == '/':
247 # Y2K
247 # Y2K
248 d = '19' + d
248 d = '19' + d
249
249
250 if len(d.split()) != 3:
250 if len(d.split()) != 3:
251 # cvs log dates always in GMT
251 # cvs log dates always in GMT
252 d = d + ' UTC'
252 d = d + ' UTC'
253 e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y-%m-%d %H:%M:%S'])
253 e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y-%m-%d %H:%M:%S'])
254 e.author = scache(match.group(2))
254 e.author = scache(match.group(2))
255 e.dead = match.group(3).lower() == 'dead'
255 e.dead = match.group(3).lower() == 'dead'
256
256
257 if match.group(5):
257 if match.group(5):
258 if match.group(6):
258 if match.group(6):
259 e.lines = (int(match.group(5)), int(match.group(6)))
259 e.lines = (int(match.group(5)), int(match.group(6)))
260 else:
260 else:
261 e.lines = (int(match.group(5)), 0)
261 e.lines = (int(match.group(5)), 0)
262 elif match.group(6):
262 elif match.group(6):
263 e.lines = (0, int(match.group(6)))
263 e.lines = (0, int(match.group(6)))
264 else:
264 else:
265 e.lines = None
265 e.lines = None
266 e.comment = []
266 e.comment = []
267 state = 7
267 state = 7
268
268
269 elif state == 7:
269 elif state == 7:
270 # read the revision numbers of branches that start at this revision
270 # read the revision numbers of branches that start at this revision
271 # or store the commit log message otherwise
271 # or store the commit log message otherwise
272 m = re_70.match(line)
272 m = re_70.match(line)
273 if m:
273 if m:
274 e.branches = [tuple([int(y) for y in x.strip().split('.')])
274 e.branches = [tuple([int(y) for y in x.strip().split('.')])
275 for x in m.group(1).split(';')]
275 for x in m.group(1).split(';')]
276 state = 8
276 state = 8
277 elif re_31.match(line):
277 elif re_31.match(line):
278 state = 5
278 state = 5
279 store = True
279 store = True
280 elif re_32.match(line):
280 elif re_32.match(line):
281 state = 0
281 state = 0
282 store = True
282 store = True
283 else:
283 else:
284 e.comment.append(line)
284 e.comment.append(line)
285
285
286 elif state == 8:
286 elif state == 8:
287 # store commit log message
287 # store commit log message
288 if re_31.match(line):
288 if re_31.match(line):
289 state = 5
289 state = 5
290 store = True
290 store = True
291 elif re_32.match(line):
291 elif re_32.match(line):
292 state = 0
292 state = 0
293 store = True
293 store = True
294 else:
294 else:
295 e.comment.append(line)
295 e.comment.append(line)
296
296
297 if store:
297 if store:
298 # clean up the results and save in the log.
298 # clean up the results and save in the log.
299 store = False
299 store = False
300 e.tags = [scache(x) for x in tags.get(e.revision, [])]
300 e.tags = [scache(x) for x in tags.get(e.revision, [])]
301 e.tags.sort()
301 e.tags.sort()
302 e.comment = scache('\n'.join(e.comment))
302 e.comment = scache('\n'.join(e.comment))
303
303
304 revn = len(e.revision)
304 revn = len(e.revision)
305 if revn > 3 and (revn % 2) == 0:
305 if revn > 3 and (revn % 2) == 0:
306 e.branch = tags.get(e.revision[:-1], [None])[0]
306 e.branch = tags.get(e.revision[:-1], [None])[0]
307 else:
307 else:
308 e.branch = None
308 e.branch = None
309
309
310 log.append(e)
310 log.append(e)
311
311
312 if len(log) % 100 == 0:
312 if len(log) % 100 == 0:
313 ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n')
313 ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n')
314
314
315 listsort(log, key=lambda x:(x.rcs, x.revision))
315 listsort(log, key=lambda x:(x.rcs, x.revision))
316
316
317 # find parent revisions of individual files
317 # find parent revisions of individual files
318 versions = {}
318 versions = {}
319 for e in log:
319 for e in log:
320 branch = e.revision[:-1]
320 branch = e.revision[:-1]
321 p = versions.get((e.rcs, branch), None)
321 p = versions.get((e.rcs, branch), None)
322 if p is None:
322 if p is None:
323 p = e.revision[:-2]
323 p = e.revision[:-2]
324 e.parent = p
324 e.parent = p
325 versions[(e.rcs, branch)] = e.revision
325 versions[(e.rcs, branch)] = e.revision
326
326
327 # update the log cache
327 # update the log cache
328 if cache:
328 if cache:
329 if log:
329 if log:
330 # join up the old and new logs
330 # join up the old and new logs
331 listsort(log, key=lambda x:x.date)
331 listsort(log, key=lambda x:x.date)
332
332
333 if oldlog and oldlog[-1].date >= log[0].date:
333 if oldlog and oldlog[-1].date >= log[0].date:
334 raise logerror('Log cache overlaps with new log entries,'
334 raise logerror('Log cache overlaps with new log entries,'
335 ' re-run without cache.')
335 ' re-run without cache.')
336
336
337 log = oldlog + log
337 log = oldlog + log
338
338
339 # write the new cachefile
339 # write the new cachefile
340 ui.note(_('writing cvs log cache %s\n') % cachefile)
340 ui.note(_('writing cvs log cache %s\n') % cachefile)
341 pickle.dump(log, file(cachefile, 'w'))
341 pickle.dump(log, file(cachefile, 'w'))
342 else:
342 else:
343 log = oldlog
343 log = oldlog
344
344
345 ui.status(_('%d log entries\n') % len(log))
345 ui.status(_('%d log entries\n') % len(log))
346
346
347 return log
347 return log
348
348
349
349
350 class changeset(object):
350 class changeset(object):
351 '''Class changeset has the following attributes:
351 '''Class changeset has the following attributes:
352 .author - author name as CVS knows it
352 .author - author name as CVS knows it
353 .branch - name of branch this changeset is on, or None
353 .branch - name of branch this changeset is on, or None
354 .comment - commit message
354 .comment - commit message
355 .date - the commit date as a (time,tz) tuple
355 .date - the commit date as a (time,tz) tuple
356 .entries - list of logentry objects in this changeset
356 .entries - list of logentry objects in this changeset
357 .parents - list of one or two parent changesets
357 .parents - list of one or two parent changesets
358 .tags - list of tags on this changeset
358 .tags - list of tags on this changeset
359 '''
359 '''
360 def __init__(self, **entries):
360 def __init__(self, **entries):
361 self.__dict__.update(entries)
361 self.__dict__.update(entries)
362
362
363 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
363 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
364 '''Convert log into changesets.'''
364 '''Convert log into changesets.'''
365
365
366 ui.status(_('creating changesets\n'))
366 ui.status(_('creating changesets\n'))
367
367
368 # Merge changesets
368 # Merge changesets
369
369
370 listsort(log, key=lambda x:(x.comment, x.author, x.branch, x.date))
370 listsort(log, key=lambda x:(x.comment, x.author, x.branch, x.date))
371
371
372 changesets = []
372 changesets = []
373 files = {}
373 files = {}
374 c = None
374 c = None
375 for i, e in enumerate(log):
375 for i, e in enumerate(log):
376
376
377 # Check if log entry belongs to the current changeset or not.
377 # Check if log entry belongs to the current changeset or not.
378 if not (c and
378 if not (c and
379 e.comment == c.comment and
379 e.comment == c.comment and
380 e.author == c.author and
380 e.author == c.author and
381 e.branch == c.branch and
381 e.branch == c.branch and
382 ((c.date[0] + c.date[1]) <=
382 ((c.date[0] + c.date[1]) <=
383 (e.date[0] + e.date[1]) <=
383 (e.date[0] + e.date[1]) <=
384 (c.date[0] + c.date[1]) + fuzz) and
384 (c.date[0] + c.date[1]) + fuzz) and
385 e.file not in files):
385 e.file not in files):
386 c = changeset(comment=e.comment, author=e.author,
386 c = changeset(comment=e.comment, author=e.author,
387 branch=e.branch, date=e.date, entries=[])
387 branch=e.branch, date=e.date, entries=[])
388 changesets.append(c)
388 changesets.append(c)
389 files = {}
389 files = {}
390 if len(changesets) % 100 == 0:
390 if len(changesets) % 100 == 0:
391 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
391 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
392 ui.status(util.ellipsis(t, 80) + '\n')
392 ui.status(util.ellipsis(t, 80) + '\n')
393
393
394 e.Changeset = c
395 c.entries.append(e)
394 c.entries.append(e)
396 files[e.file] = True
395 files[e.file] = True
397 c.date = e.date # changeset date is date of latest commit in it
396 c.date = e.date # changeset date is date of latest commit in it
398
397
399 # Sort files in each changeset
398 # Sort files in each changeset
400
399
401 for c in changesets:
400 for c in changesets:
402 def pathcompare(l, r):
401 def pathcompare(l, r):
403 'Mimic cvsps sorting order'
402 'Mimic cvsps sorting order'
404 l = l.split('/')
403 l = l.split('/')
405 r = r.split('/')
404 r = r.split('/')
406 nl = len(l)
405 nl = len(l)
407 nr = len(r)
406 nr = len(r)
408 n = min(nl, nr)
407 n = min(nl, nr)
409 for i in range(n):
408 for i in range(n):
410 if i + 1 == nl and nl < nr:
409 if i + 1 == nl and nl < nr:
411 return -1
410 return -1
412 elif i + 1 == nr and nl > nr:
411 elif i + 1 == nr and nl > nr:
413 return +1
412 return +1
414 elif l[i] < r[i]:
413 elif l[i] < r[i]:
415 return -1
414 return -1
416 elif l[i] > r[i]:
415 elif l[i] > r[i]:
417 return +1
416 return +1
418 return 0
417 return 0
419 def entitycompare(l, r):
418 def entitycompare(l, r):
420 return pathcompare(l.file, r.file)
419 return pathcompare(l.file, r.file)
421
420
422 c.entries.sort(entitycompare)
421 c.entries.sort(entitycompare)
423
422
424 # Sort changesets by date
423 # Sort changesets by date
425
424
426 def cscmp(l, r):
425 def cscmp(l, r):
427 d = sum(l.date) - sum(r.date)
426 d = sum(l.date) - sum(r.date)
428 if d:
427 if d:
429 return d
428 return d
430
429
431 # detect vendor branches and initial commits on a branch
430 # detect vendor branches and initial commits on a branch
432 le = {}
431 le = {}
433 for e in l.entries:
432 for e in l.entries:
434 le[e.rcs] = e.revision
433 le[e.rcs] = e.revision
435 re = {}
434 re = {}
436 for e in r.entries:
435 for e in r.entries:
437 re[e.rcs] = e.revision
436 re[e.rcs] = e.revision
438
437
439 d = 0
438 d = 0
440 for e in l.entries:
439 for e in l.entries:
441 if re.get(e.rcs, None) == e.parent:
440 if re.get(e.rcs, None) == e.parent:
442 assert not d
441 assert not d
443 d = 1
442 d = 1
444 break
443 break
445
444
446 for e in r.entries:
445 for e in r.entries:
447 if le.get(e.rcs, None) == e.parent:
446 if le.get(e.rcs, None) == e.parent:
448 assert not d
447 assert not d
449 d = -1
448 d = -1
450 break
449 break
451
450
452 return d
451 return d
453
452
454 changesets.sort(cscmp)
453 changesets.sort(cscmp)
455
454
456 # Collect tags
455 # Collect tags
457
456
458 globaltags = {}
457 globaltags = {}
459 for c in changesets:
458 for c in changesets:
460 tags = {}
459 tags = {}
461 for e in c.entries:
460 for e in c.entries:
462 for tag in e.tags:
461 for tag in e.tags:
463 # remember which is the latest changeset to have this tag
462 # remember which is the latest changeset to have this tag
464 globaltags[tag] = c
463 globaltags[tag] = c
465
464
466 for c in changesets:
465 for c in changesets:
467 tags = {}
466 tags = {}
468 for e in c.entries:
467 for e in c.entries:
469 for tag in e.tags:
468 for tag in e.tags:
470 tags[tag] = True
469 tags[tag] = True
471 # remember tags only if this is the latest changeset to have it
470 # remember tags only if this is the latest changeset to have it
472 tagnames = [tag for tag in tags if globaltags[tag] is c]
471 tagnames = [tag for tag in tags if globaltags[tag] is c]
473 tagnames.sort()
472 tagnames.sort()
474 c.tags = tagnames
473 c.tags = tagnames
475
474
476 # Find parent changesets, handle {{mergetobranch BRANCHNAME}}
475 # Find parent changesets, handle {{mergetobranch BRANCHNAME}}
477 # by inserting dummy changesets with two parents, and handle
476 # by inserting dummy changesets with two parents, and handle
478 # {{mergefrombranch BRANCHNAME}} by setting two parents.
477 # {{mergefrombranch BRANCHNAME}} by setting two parents.
479
478
480 if mergeto is None:
479 if mergeto is None:
481 mergeto = r'{{mergetobranch ([-\w]+)}}'
480 mergeto = r'{{mergetobranch ([-\w]+)}}'
482 if mergeto:
481 if mergeto:
483 mergeto = re.compile(mergeto)
482 mergeto = re.compile(mergeto)
484
483
485 if mergefrom is None:
484 if mergefrom is None:
486 mergefrom = r'{{mergefrombranch ([-\w]+)}}'
485 mergefrom = r'{{mergefrombranch ([-\w]+)}}'
487 if mergefrom:
486 if mergefrom:
488 mergefrom = re.compile(mergefrom)
487 mergefrom = re.compile(mergefrom)
489
488
490 versions = {} # changeset index where we saw any particular file version
489 versions = {} # changeset index where we saw any particular file version
491 branches = {} # changeset index where we saw a branch
490 branches = {} # changeset index where we saw a branch
492 n = len(changesets)
491 n = len(changesets)
493 i = 0
492 i = 0
494 while i<n:
493 while i<n:
495 c = changesets[i]
494 c = changesets[i]
496
495
497 for f in c.entries:
496 for f in c.entries:
498 versions[(f.rcs, f.revision)] = i
497 versions[(f.rcs, f.revision)] = i
499
498
500 p = None
499 p = None
501 if c.branch in branches:
500 if c.branch in branches:
502 p = branches[c.branch]
501 p = branches[c.branch]
503 else:
502 else:
504 for f in c.entries:
503 for f in c.entries:
505 p = max(p, versions.get((f.rcs, f.parent), None))
504 p = max(p, versions.get((f.rcs, f.parent), None))
506
505
507 c.parents = []
506 c.parents = []
508 if p is not None:
507 if p is not None:
509 c.parents.append(changesets[p])
508 c.parents.append(changesets[p])
510
509
511 if mergefrom:
510 if mergefrom:
512 m = mergefrom.search(c.comment)
511 m = mergefrom.search(c.comment)
513 if m:
512 if m:
514 m = m.group(1)
513 m = m.group(1)
515 if m == 'HEAD':
514 if m == 'HEAD':
516 m = None
515 m = None
517 if m in branches and c.branch != m:
516 if m in branches and c.branch != m:
518 c.parents.append(changesets[branches[m]])
517 c.parents.append(changesets[branches[m]])
519
518
520 if mergeto:
519 if mergeto:
521 m = mergeto.search(c.comment)
520 m = mergeto.search(c.comment)
522 if m:
521 if m:
523 try:
522 try:
524 m = m.group(1)
523 m = m.group(1)
525 if m == 'HEAD':
524 if m == 'HEAD':
526 m = None
525 m = None
527 except:
526 except:
528 m = None # if no group found then merge to HEAD
527 m = None # if no group found then merge to HEAD
529 if m in branches and c.branch != m:
528 if m in branches and c.branch != m:
530 # insert empty changeset for merge
529 # insert empty changeset for merge
531 cc = changeset(author=c.author, branch=m, date=c.date,
530 cc = changeset(author=c.author, branch=m, date=c.date,
532 comment='convert-repo: CVS merge from branch %s' % c.branch,
531 comment='convert-repo: CVS merge from branch %s' % c.branch,
533 entries=[], tags=[], parents=[changesets[branches[m]], c])
532 entries=[], tags=[], parents=[changesets[branches[m]], c])
534 changesets.insert(i + 1, cc)
533 changesets.insert(i + 1, cc)
535 branches[m] = i + 1
534 branches[m] = i + 1
536
535
537 # adjust our loop counters now we have inserted a new entry
536 # adjust our loop counters now we have inserted a new entry
538 n += 1
537 n += 1
539 i += 2
538 i += 2
540 continue
539 continue
541
540
542 branches[c.branch] = i
541 branches[c.branch] = i
543 i += 1
542 i += 1
544
543
545 # Number changesets
544 # Number changesets
546
545
547 for i, c in enumerate(changesets):
546 for i, c in enumerate(changesets):
548 c.id = i + 1
547 c.id = i + 1
549
548
550 ui.status(_('%d changeset entries\n') % len(changesets))
549 ui.status(_('%d changeset entries\n') % len(changesets))
551
550
552 return changesets
551 return changesets
General Comments 0
You need to be logged in to leave comments. Login now