Mercurial > hg
comparison hgext/convert/cvsps.py @ 43076:2372284d9457
formatting: blacken the codebase
This is using my patch to black
(https://github.com/psf/black/pull/826) so we don't un-wrap collection
literals.
Done with:
hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S
# skip-blame mass-reformatting only
# no-check-commit reformats foo_bar functions
Differential Revision: https://phab.mercurial-scm.org/D6971
author | Augie Fackler <augie@google.com> |
---|---|
date | Sun, 06 Oct 2019 09:45:02 -0400 |
parents | 3b8a4587a456 |
children | 687b865b95ad |
comparison
equal
deleted
inserted
replaced
43075:57875cf423c9 | 43076:2372284d9457 |
---|---|
23 procutil, | 23 procutil, |
24 stringutil, | 24 stringutil, |
25 ) | 25 ) |
26 | 26 |
27 pickle = util.pickle | 27 pickle = util.pickle |
28 | |
28 | 29 |
29 class logentry(object): | 30 class logentry(object): |
30 '''Class logentry has the following attributes: | 31 '''Class logentry has the following attributes: |
31 .author - author name as CVS knows it | 32 .author - author name as CVS knows it |
32 .branch - name of branch this revision is on | 33 .branch - name of branch this revision is on |
44 .synthetic - is this a synthetic "file ... added on ..." revision? | 45 .synthetic - is this a synthetic "file ... added on ..." revision? |
45 .mergepoint - the branch that has been merged from (if present in | 46 .mergepoint - the branch that has been merged from (if present in |
46 rlog output) or None | 47 rlog output) or None |
47 .branchpoints - the branches that start at the current entry or empty | 48 .branchpoints - the branches that start at the current entry or empty |
48 ''' | 49 ''' |
50 | |
49 def __init__(self, **entries): | 51 def __init__(self, **entries): |
50 self.synthetic = False | 52 self.synthetic = False |
51 self.__dict__.update(entries) | 53 self.__dict__.update(entries) |
52 | 54 |
53 def __repr__(self): | 55 def __repr__(self): |
54 items = (r"%s=%r"%(k, self.__dict__[k]) for k in sorted(self.__dict__)) | 56 items = ( |
55 return r"%s(%s)"%(type(self).__name__, r", ".join(items)) | 57 r"%s=%r" % (k, self.__dict__[k]) for k in sorted(self.__dict__) |
58 ) | |
59 return r"%s(%s)" % (type(self).__name__, r", ".join(items)) | |
60 | |
56 | 61 |
57 class logerror(Exception): | 62 class logerror(Exception): |
58 pass | 63 pass |
64 | |
59 | 65 |
60 def getrepopath(cvspath): | 66 def getrepopath(cvspath): |
61 """Return the repository path from a CVS path. | 67 """Return the repository path from a CVS path. |
62 | 68 |
63 >>> getrepopath(b'/foo/bar') | 69 >>> getrepopath(b'/foo/bar') |
91 start = 0 | 97 start = 0 |
92 | 98 |
93 if atposition != -1: | 99 if atposition != -1: |
94 start = atposition | 100 start = atposition |
95 | 101 |
96 repopath = parts[-1][parts[-1].find('/', start):] | 102 repopath = parts[-1][parts[-1].find('/', start) :] |
97 return repopath | 103 return repopath |
104 | |
98 | 105 |
99 def createlog(ui, directory=None, root="", rlog=True, cache=None): | 106 def createlog(ui, directory=None, root="", rlog=True, cache=None): |
100 '''Collect the CVS rlog''' | 107 '''Collect the CVS rlog''' |
101 | 108 |
102 # Because we store many duplicate commit log messages, reusing strings | 109 # Because we store many duplicate commit log messages, reusing strings |
103 # saves a lot of memory and pickle storage space. | 110 # saves a lot of memory and pickle storage space. |
104 _scache = {} | 111 _scache = {} |
112 | |
105 def scache(s): | 113 def scache(s): |
106 "return a shared version of a string" | 114 "return a shared version of a string" |
107 return _scache.setdefault(s, s) | 115 return _scache.setdefault(s, s) |
108 | 116 |
109 ui.status(_('collecting CVS rlog\n')) | 117 ui.status(_('collecting CVS rlog\n')) |
110 | 118 |
111 log = [] # list of logentry objects containing the CVS state | 119 log = [] # list of logentry objects containing the CVS state |
112 | 120 |
113 # patterns to match in CVS (r)log output, by state of use | 121 # patterns to match in CVS (r)log output, by state of use |
114 re_00 = re.compile(b'RCS file: (.+)$') | 122 re_00 = re.compile(b'RCS file: (.+)$') |
115 re_01 = re.compile(b'cvs \\[r?log aborted\\]: (.+)$') | 123 re_01 = re.compile(b'cvs \\[r?log aborted\\]: (.+)$') |
116 re_02 = re.compile(b'cvs (r?log|server): (.+)\n$') | 124 re_02 = re.compile(b'cvs (r?log|server): (.+)\n$') |
117 re_03 = re.compile(b"(Cannot access.+CVSROOT)|" | 125 re_03 = re.compile( |
118 b"(can't create temporary directory.+)$") | 126 b"(Cannot access.+CVSROOT)|" b"(can't create temporary directory.+)$" |
127 ) | |
119 re_10 = re.compile(b'Working file: (.+)$') | 128 re_10 = re.compile(b'Working file: (.+)$') |
120 re_20 = re.compile(b'symbolic names:') | 129 re_20 = re.compile(b'symbolic names:') |
121 re_30 = re.compile(b'\t(.+): ([\\d.]+)$') | 130 re_30 = re.compile(b'\t(.+): ([\\d.]+)$') |
122 re_31 = re.compile(b'----------------------------$') | 131 re_31 = re.compile(b'----------------------------$') |
123 re_32 = re.compile(b'=======================================' | 132 re_32 = re.compile( |
124 b'======================================$') | 133 b'=======================================' |
134 b'======================================$' | |
135 ) | |
125 re_50 = re.compile(br'revision ([\d.]+)(\s+locked by:\s+.+;)?$') | 136 re_50 = re.compile(br'revision ([\d.]+)(\s+locked by:\s+.+;)?$') |
126 re_60 = re.compile(br'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);' | 137 re_60 = re.compile( |
127 br'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?' | 138 br'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);' |
128 br'(\s+commitid:\s+([^;]+);)?' | 139 br'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?' |
129 br'(.*mergepoint:\s+([^;]+);)?') | 140 br'(\s+commitid:\s+([^;]+);)?' |
141 br'(.*mergepoint:\s+([^;]+);)?' | |
142 ) | |
130 re_70 = re.compile(b'branches: (.+);$') | 143 re_70 = re.compile(b'branches: (.+);$') |
131 | 144 |
132 file_added_re = re.compile(br'file [^/]+ was (initially )?added on branch') | 145 file_added_re = re.compile(br'file [^/]+ was (initially )?added on branch') |
133 | 146 |
134 prefix = '' # leading path to strip of what we get from CVS | 147 prefix = '' # leading path to strip of what we get from CVS |
135 | 148 |
136 if directory is None: | 149 if directory is None: |
137 # Current working directory | 150 # Current working directory |
138 | 151 |
139 # Get the real directory in the repository | 152 # Get the real directory in the repository |
149 if prefix and not prefix.endswith(pycompat.ossep): | 162 if prefix and not prefix.endswith(pycompat.ossep): |
150 prefix += pycompat.ossep | 163 prefix += pycompat.ossep |
151 | 164 |
152 # Use the Root file in the sandbox, if it exists | 165 # Use the Root file in the sandbox, if it exists |
153 try: | 166 try: |
154 root = open(os.path.join('CVS','Root'), 'rb').read().strip() | 167 root = open(os.path.join('CVS', 'Root'), 'rb').read().strip() |
155 except IOError: | 168 except IOError: |
156 pass | 169 pass |
157 | 170 |
158 if not root: | 171 if not root: |
159 root = encoding.environ.get('CVSROOT', '') | 172 root = encoding.environ.get('CVSROOT', '') |
176 # and | 189 # and |
177 # /pserver/user/server/path | 190 # /pserver/user/server/path |
178 # are mapped to different cache file names. | 191 # are mapped to different cache file names. |
179 cachefile = root.split(":") + [directory, "cache"] | 192 cachefile = root.split(":") + [directory, "cache"] |
180 cachefile = ['-'.join(re.findall(br'\w+', s)) for s in cachefile if s] | 193 cachefile = ['-'.join(re.findall(br'\w+', s)) for s in cachefile if s] |
181 cachefile = os.path.join(cachedir, | 194 cachefile = os.path.join( |
182 '.'.join([s for s in cachefile if s])) | 195 cachedir, '.'.join([s for s in cachefile if s]) |
196 ) | |
183 | 197 |
184 if cache == 'update': | 198 if cache == 'update': |
185 try: | 199 try: |
186 ui.note(_('reading cvs log cache %s\n') % cachefile) | 200 ui.note(_('reading cvs log cache %s\n') % cachefile) |
187 oldlog = pickle.load(open(cachefile, 'rb')) | 201 oldlog = pickle.load(open(cachefile, 'rb')) |
188 for e in oldlog: | 202 for e in oldlog: |
189 if not (util.safehasattr(e, 'branchpoints') and | 203 if not ( |
190 util.safehasattr(e, 'commitid') and | 204 util.safehasattr(e, 'branchpoints') |
191 util.safehasattr(e, 'mergepoint')): | 205 and util.safehasattr(e, 'commitid') |
206 and util.safehasattr(e, 'mergepoint') | |
207 ): | |
192 ui.status(_('ignoring old cache\n')) | 208 ui.status(_('ignoring old cache\n')) |
193 oldlog = [] | 209 oldlog = [] |
194 break | 210 break |
195 | 211 |
196 ui.note(_('cache has %d log entries\n') % len(oldlog)) | 212 ui.note(_('cache has %d log entries\n') % len(oldlog)) |
197 except Exception as e: | 213 except Exception as e: |
198 ui.note(_('error reading cache: %r\n') % e) | 214 ui.note(_('error reading cache: %r\n') % e) |
199 | 215 |
200 if oldlog: | 216 if oldlog: |
201 date = oldlog[-1].date # last commit date as a (time,tz) tuple | 217 date = oldlog[-1].date # last commit date as a (time,tz) tuple |
202 date = dateutil.datestr(date, '%Y/%m/%d %H:%M:%S %1%2') | 218 date = dateutil.datestr(date, '%Y/%m/%d %H:%M:%S %1%2') |
203 | 219 |
204 # build the CVS commandline | 220 # build the CVS commandline |
205 cmd = ['cvs', '-q'] | 221 cmd = ['cvs', '-q'] |
206 if root: | 222 if root: |
218 # no space between option and date string | 234 # no space between option and date string |
219 cmd.append('-d>%s' % date) | 235 cmd.append('-d>%s' % date) |
220 cmd.append(directory) | 236 cmd.append(directory) |
221 | 237 |
222 # state machine begins here | 238 # state machine begins here |
223 tags = {} # dictionary of revisions on current file with their tags | 239 tags = {} # dictionary of revisions on current file with their tags |
224 branchmap = {} # mapping between branch names and revision numbers | 240 branchmap = {} # mapping between branch names and revision numbers |
225 rcsmap = {} | 241 rcsmap = {} |
226 state = 0 | 242 state = 0 |
227 store = False # set when a new record can be appended | 243 store = False # set when a new record can be appended |
228 | 244 |
229 cmd = [procutil.shellquote(arg) for arg in cmd] | 245 cmd = [procutil.shellquote(arg) for arg in cmd] |
230 ui.note(_("running %s\n") % (' '.join(cmd))) | 246 ui.note(_("running %s\n") % (' '.join(cmd))) |
231 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root)) | 247 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root)) |
232 | 248 |
237 if line == '': | 253 if line == '': |
238 break | 254 break |
239 peek = util.fromnativeeol(pfp.readline()) | 255 peek = util.fromnativeeol(pfp.readline()) |
240 if line.endswith('\n'): | 256 if line.endswith('\n'): |
241 line = line[:-1] | 257 line = line[:-1] |
242 #ui.debug('state=%d line=%r\n' % (state, line)) | 258 # ui.debug('state=%d line=%r\n' % (state, line)) |
243 | 259 |
244 if state == 0: | 260 if state == 0: |
245 # initial state, consume input until we see 'RCS file' | 261 # initial state, consume input until we see 'RCS file' |
246 match = re_00.match(line) | 262 match = re_00.match(line) |
247 if match: | 263 if match: |
248 rcs = match.group(1) | 264 rcs = match.group(1) |
249 tags = {} | 265 tags = {} |
250 if rlog: | 266 if rlog: |
251 filename = util.normpath(rcs[:-2]) | 267 filename = util.normpath(rcs[:-2]) |
252 if filename.startswith(prefix): | 268 if filename.startswith(prefix): |
253 filename = filename[len(prefix):] | 269 filename = filename[len(prefix) :] |
254 if filename.startswith('/'): | 270 if filename.startswith('/'): |
255 filename = filename[1:] | 271 filename = filename[1:] |
256 if filename.startswith('Attic/'): | 272 if filename.startswith('Attic/'): |
257 filename = filename[6:] | 273 filename = filename[6:] |
258 else: | 274 else: |
308 elif state == 4: | 324 elif state == 4: |
309 # expecting '------' separator before first revision | 325 # expecting '------' separator before first revision |
310 if re_31.match(line): | 326 if re_31.match(line): |
311 state = 5 | 327 state = 5 |
312 else: | 328 else: |
313 assert not re_32.match(line), _('must have at least ' | 329 assert not re_32.match(line), _( |
314 'some revisions') | 330 'must have at least ' 'some revisions' |
331 ) | |
315 | 332 |
316 elif state == 5: | 333 elif state == 5: |
317 # expecting revision number and possibly (ignored) lock indication | 334 # expecting revision number and possibly (ignored) lock indication |
318 # we create the logentry here from values stored in states 0 to 4, | 335 # we create the logentry here from values stored in states 0 to 4, |
319 # as this state is re-entered for subsequent revisions of a file. | 336 # as this state is re-entered for subsequent revisions of a file. |
320 match = re_50.match(line) | 337 match = re_50.match(line) |
321 assert match, _('expected revision number') | 338 assert match, _('expected revision number') |
322 e = logentry(rcs=scache(rcs), | 339 e = logentry( |
323 file=scache(filename), | 340 rcs=scache(rcs), |
324 revision=tuple([int(x) for x in | 341 file=scache(filename), |
325 match.group(1).split('.')]), | 342 revision=tuple([int(x) for x in match.group(1).split('.')]), |
326 branches=[], | 343 branches=[], |
327 parent=None, | 344 parent=None, |
328 commitid=None, | 345 commitid=None, |
329 mergepoint=None, | 346 mergepoint=None, |
330 branchpoints=set()) | 347 branchpoints=set(), |
348 ) | |
331 | 349 |
332 state = 6 | 350 state = 6 |
333 | 351 |
334 elif state == 6: | 352 elif state == 6: |
335 # expecting date, author, state, lines changed | 353 # expecting date, author, state, lines changed |
341 d = '19' + d | 359 d = '19' + d |
342 | 360 |
343 if len(d.split()) != 3: | 361 if len(d.split()) != 3: |
344 # cvs log dates always in GMT | 362 # cvs log dates always in GMT |
345 d = d + ' UTC' | 363 d = d + ' UTC' |
346 e.date = dateutil.parsedate(d, ['%y/%m/%d %H:%M:%S', | 364 e.date = dateutil.parsedate( |
347 '%Y/%m/%d %H:%M:%S', | 365 d, |
348 '%Y-%m-%d %H:%M:%S']) | 366 ['%y/%m/%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y-%m-%d %H:%M:%S'], |
367 ) | |
349 e.author = scache(match.group(2)) | 368 e.author = scache(match.group(2)) |
350 e.dead = match.group(3).lower() == 'dead' | 369 e.dead = match.group(3).lower() == 'dead' |
351 | 370 |
352 if match.group(5): | 371 if match.group(5): |
353 if match.group(6): | 372 if match.group(6): |
357 elif match.group(6): | 376 elif match.group(6): |
358 e.lines = (0, int(match.group(6))) | 377 e.lines = (0, int(match.group(6))) |
359 else: | 378 else: |
360 e.lines = None | 379 e.lines = None |
361 | 380 |
362 if match.group(7): # cvs 1.12 commitid | 381 if match.group(7): # cvs 1.12 commitid |
363 e.commitid = match.group(8) | 382 e.commitid = match.group(8) |
364 | 383 |
365 if match.group(9): # cvsnt mergepoint | 384 if match.group(9): # cvsnt mergepoint |
366 myrev = match.group(10).split('.') | 385 myrev = match.group(10).split('.') |
367 if len(myrev) == 2: # head | 386 if len(myrev) == 2: # head |
368 e.mergepoint = 'HEAD' | 387 e.mergepoint = 'HEAD' |
369 else: | 388 else: |
370 myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]]) | 389 myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]]) |
371 branches = [b for b in branchmap if branchmap[b] == myrev] | 390 branches = [b for b in branchmap if branchmap[b] == myrev] |
372 assert len(branches) == 1, ('unknown branch: %s' | 391 assert len(branches) == 1, ( |
373 % e.mergepoint) | 392 'unknown branch: %s' % e.mergepoint |
393 ) | |
374 e.mergepoint = branches[0] | 394 e.mergepoint = branches[0] |
375 | 395 |
376 e.comment = [] | 396 e.comment = [] |
377 state = 7 | 397 state = 7 |
378 | 398 |
379 elif state == 7: | 399 elif state == 7: |
380 # read the revision numbers of branches that start at this revision | 400 # read the revision numbers of branches that start at this revision |
381 # or store the commit log message otherwise | 401 # or store the commit log message otherwise |
382 m = re_70.match(line) | 402 m = re_70.match(line) |
383 if m: | 403 if m: |
384 e.branches = [tuple([int(y) for y in x.strip().split('.')]) | 404 e.branches = [ |
385 for x in m.group(1).split(';')] | 405 tuple([int(y) for y in x.strip().split('.')]) |
406 for x in m.group(1).split(';') | |
407 ] | |
386 state = 8 | 408 state = 8 |
387 elif re_31.match(line) and re_50.match(peek): | 409 elif re_31.match(line) and re_50.match(peek): |
388 state = 5 | 410 state = 5 |
389 store = True | 411 store = True |
390 elif re_32.match(line): | 412 elif re_32.match(line): |
415 # Likewise, if you merge such a file to a later branch B2 (one | 437 # Likewise, if you merge such a file to a later branch B2 (one |
416 # that already existed when the file was added on B1), CVS | 438 # that already existed when the file was added on B1), CVS |
417 # creates a synthetic dead revision 1.1.x.1 on B2. Don't drop | 439 # creates a synthetic dead revision 1.1.x.1 on B2. Don't drop |
418 # these revisions now, but mark them synthetic so | 440 # these revisions now, but mark them synthetic so |
419 # createchangeset() can take care of them. | 441 # createchangeset() can take care of them. |
420 if (store and | 442 if ( |
421 e.dead and | 443 store |
422 e.revision[-1] == 1 and # 1.1 or 1.1.x.1 | 444 and e.dead |
423 len(e.comment) == 1 and | 445 and e.revision[-1] == 1 |
424 file_added_re.match(e.comment[0])): | 446 and len(e.comment) == 1 # 1.1 or 1.1.x.1 |
425 ui.debug('found synthetic revision in %s: %r\n' | 447 and file_added_re.match(e.comment[0]) |
426 % (e.rcs, e.comment[0])) | 448 ): |
449 ui.debug( | |
450 'found synthetic revision in %s: %r\n' % (e.rcs, e.comment[0]) | |
451 ) | |
427 e.synthetic = True | 452 e.synthetic = True |
428 | 453 |
429 if store: | 454 if store: |
430 # clean up the results and save in the log. | 455 # clean up the results and save in the log. |
431 store = False | 456 store = False |
440 | 465 |
441 # find the branches starting from this revision | 466 # find the branches starting from this revision |
442 branchpoints = set() | 467 branchpoints = set() |
443 for branch, revision in branchmap.iteritems(): | 468 for branch, revision in branchmap.iteritems(): |
444 revparts = tuple([int(i) for i in revision.split('.')]) | 469 revparts = tuple([int(i) for i in revision.split('.')]) |
445 if len(revparts) < 2: # bad tags | 470 if len(revparts) < 2: # bad tags |
446 continue | 471 continue |
447 if revparts[-2] == 0 and revparts[-1] % 2 == 0: | 472 if revparts[-2] == 0 and revparts[-1] % 2 == 0: |
448 # normal branch | 473 # normal branch |
449 if revparts[:-2] == e.revision: | 474 if revparts[:-2] == e.revision: |
450 branchpoints.add(branch) | 475 branchpoints.add(branch) |
451 elif revparts == (1, 1, 1): # vendor branch | 476 elif revparts == (1, 1, 1): # vendor branch |
452 if revparts in e.branches: | 477 if revparts in e.branches: |
453 branchpoints.add(branch) | 478 branchpoints.add(branch) |
454 e.branchpoints = branchpoints | 479 e.branchpoints = branchpoints |
455 | 480 |
456 log.append(e) | 481 log.append(e) |
457 | 482 |
458 rcsmap[e.rcs.replace('/Attic/', '/')] = e.rcs | 483 rcsmap[e.rcs.replace('/Attic/', '/')] = e.rcs |
459 | 484 |
460 if len(log) % 100 == 0: | 485 if len(log) % 100 == 0: |
461 ui.status(stringutil.ellipsis('%d %s' % (len(log), e.file), 80) | 486 ui.status( |
462 + '\n') | 487 stringutil.ellipsis('%d %s' % (len(log), e.file), 80) + '\n' |
488 ) | |
463 | 489 |
464 log.sort(key=lambda x: (x.rcs, x.revision)) | 490 log.sort(key=lambda x: (x.rcs, x.revision)) |
465 | 491 |
466 # find parent revisions of individual files | 492 # find parent revisions of individual files |
467 versions = {} | 493 versions = {} |
485 if log: | 511 if log: |
486 # join up the old and new logs | 512 # join up the old and new logs |
487 log.sort(key=lambda x: x.date) | 513 log.sort(key=lambda x: x.date) |
488 | 514 |
489 if oldlog and oldlog[-1].date >= log[0].date: | 515 if oldlog and oldlog[-1].date >= log[0].date: |
490 raise logerror(_('log cache overlaps with new log entries,' | 516 raise logerror( |
491 ' re-run without cache.')) | 517 _( |
518 'log cache overlaps with new log entries,' | |
519 ' re-run without cache.' | |
520 ) | |
521 ) | |
492 | 522 |
493 log = oldlog + log | 523 log = oldlog + log |
494 | 524 |
495 # write the new cachefile | 525 # write the new cachefile |
496 ui.note(_('writing cvs log cache %s\n') % cachefile) | 526 ui.note(_('writing cvs log cache %s\n') % cachefile) |
500 | 530 |
501 ui.status(_('%d log entries\n') % len(log)) | 531 ui.status(_('%d log entries\n') % len(log)) |
502 | 532 |
503 encodings = ui.configlist('convert', 'cvsps.logencoding') | 533 encodings = ui.configlist('convert', 'cvsps.logencoding') |
504 if encodings: | 534 if encodings: |
535 | |
505 def revstr(r): | 536 def revstr(r): |
506 # this is needed, because logentry.revision is a tuple of "int" | 537 # this is needed, because logentry.revision is a tuple of "int" |
507 # (e.g. (1, 2) for "1.2") | 538 # (e.g. (1, 2) for "1.2") |
508 return '.'.join(pycompat.maplist(pycompat.bytestr, r)) | 539 return '.'.join(pycompat.maplist(pycompat.bytestr, r)) |
509 | 540 |
510 for entry in log: | 541 for entry in log: |
511 comment = entry.comment | 542 comment = entry.comment |
512 for e in encodings: | 543 for e in encodings: |
513 try: | 544 try: |
514 entry.comment = comment.decode( | 545 entry.comment = comment.decode(pycompat.sysstr(e)).encode( |
515 pycompat.sysstr(e)).encode('utf-8') | 546 'utf-8' |
547 ) | |
516 if ui.debugflag: | 548 if ui.debugflag: |
517 ui.debug("transcoding by %s: %s of %s\n" % | 549 ui.debug( |
518 (e, revstr(entry.revision), entry.file)) | 550 "transcoding by %s: %s of %s\n" |
551 % (e, revstr(entry.revision), entry.file) | |
552 ) | |
519 break | 553 break |
520 except UnicodeDecodeError: | 554 except UnicodeDecodeError: |
521 pass # try next encoding | 555 pass # try next encoding |
522 except LookupError as inst: # unknown encoding, maybe | 556 except LookupError as inst: # unknown encoding, maybe |
523 raise error.Abort(inst, | 557 raise error.Abort( |
524 hint=_('check convert.cvsps.logencoding' | 558 inst, |
525 ' configuration')) | 559 hint=_( |
560 'check convert.cvsps.logencoding' ' configuration' | |
561 ), | |
562 ) | |
526 else: | 563 else: |
527 raise error.Abort(_("no encoding can transcode" | 564 raise error.Abort( |
528 " CVS log message for %s of %s") | 565 _( |
529 % (revstr(entry.revision), entry.file), | 566 "no encoding can transcode" |
530 hint=_('check convert.cvsps.logencoding' | 567 " CVS log message for %s of %s" |
531 ' configuration')) | 568 ) |
569 % (revstr(entry.revision), entry.file), | |
570 hint=_('check convert.cvsps.logencoding' ' configuration'), | |
571 ) | |
532 | 572 |
533 hook.hook(ui, None, "cvslog", True, log=log) | 573 hook.hook(ui, None, "cvslog", True, log=log) |
534 | 574 |
535 return log | 575 return log |
536 | 576 |
548 .tags - list of tags on this changeset | 588 .tags - list of tags on this changeset |
549 .synthetic - from synthetic revision "file ... added on branch ..." | 589 .synthetic - from synthetic revision "file ... added on branch ..." |
550 .mergepoint- the branch that has been merged from or None | 590 .mergepoint- the branch that has been merged from or None |
551 .branchpoints- the branches that start at the current entry or empty | 591 .branchpoints- the branches that start at the current entry or empty |
552 ''' | 592 ''' |
593 | |
553 def __init__(self, **entries): | 594 def __init__(self, **entries): |
554 self.id = None | 595 self.id = None |
555 self.synthetic = False | 596 self.synthetic = False |
556 self.__dict__.update(entries) | 597 self.__dict__.update(entries) |
557 | 598 |
558 def __repr__(self): | 599 def __repr__(self): |
559 items = ("%s=%r"%(k, self.__dict__[k]) for k in sorted(self.__dict__)) | 600 items = ("%s=%r" % (k, self.__dict__[k]) for k in sorted(self.__dict__)) |
560 return "%s(%s)"%(type(self).__name__, ", ".join(items)) | 601 return "%s(%s)" % (type(self).__name__, ", ".join(items)) |
602 | |
561 | 603 |
562 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None): | 604 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None): |
563 '''Convert log into changesets.''' | 605 '''Convert log into changesets.''' |
564 | 606 |
565 ui.status(_('creating changesets\n')) | 607 ui.status(_('creating changesets\n')) |
572 mindate[e.commitid] = e.date | 614 mindate[e.commitid] = e.date |
573 else: | 615 else: |
574 mindate[e.commitid] = min(e.date, mindate[e.commitid]) | 616 mindate[e.commitid] = min(e.date, mindate[e.commitid]) |
575 | 617 |
576 # Merge changesets | 618 # Merge changesets |
577 log.sort(key=lambda x: (mindate.get(x.commitid, (-1, 0)), | 619 log.sort( |
578 x.commitid or '', x.comment, | 620 key=lambda x: ( |
579 x.author, x.branch or '', x.date, x.branchpoints)) | 621 mindate.get(x.commitid, (-1, 0)), |
622 x.commitid or '', | |
623 x.comment, | |
624 x.author, | |
625 x.branch or '', | |
626 x.date, | |
627 x.branchpoints, | |
628 ) | |
629 ) | |
580 | 630 |
581 changesets = [] | 631 changesets = [] |
582 files = set() | 632 files = set() |
583 c = None | 633 c = None |
584 for i, e in enumerate(log): | 634 for i, e in enumerate(log): |
597 # Here foo is part only of MYBRANCH, but not MYBRANCH2, e.g. a | 647 # Here foo is part only of MYBRANCH, but not MYBRANCH2, e.g. a |
598 # later version of foo may be in MYBRANCH2, so foo should be the | 648 # later version of foo may be in MYBRANCH2, so foo should be the |
599 # first changeset and bar the next and MYBRANCH and MYBRANCH2 | 649 # first changeset and bar the next and MYBRANCH and MYBRANCH2 |
600 # should both start off of the bar changeset. No provisions are | 650 # should both start off of the bar changeset. No provisions are |
601 # made to ensure that this is, in fact, what happens. | 651 # made to ensure that this is, in fact, what happens. |
602 if not (c and e.branchpoints == c.branchpoints and | 652 if not ( |
603 (# cvs commitids | 653 c |
604 (e.commitid is not None and e.commitid == c.commitid) or | 654 and e.branchpoints == c.branchpoints |
605 (# no commitids, use fuzzy commit detection | 655 and ( # cvs commitids |
606 (e.commitid is None or c.commitid is None) and | 656 (e.commitid is not None and e.commitid == c.commitid) |
607 e.comment == c.comment and | 657 or ( # no commitids, use fuzzy commit detection |
608 e.author == c.author and | 658 (e.commitid is None or c.commitid is None) |
609 e.branch == c.branch and | 659 and e.comment == c.comment |
610 ((c.date[0] + c.date[1]) <= | 660 and e.author == c.author |
611 (e.date[0] + e.date[1]) <= | 661 and e.branch == c.branch |
612 (c.date[0] + c.date[1]) + fuzz) and | 662 and ( |
613 e.file not in files))): | 663 (c.date[0] + c.date[1]) |
614 c = changeset(comment=e.comment, author=e.author, | 664 <= (e.date[0] + e.date[1]) |
615 branch=e.branch, date=e.date, | 665 <= (c.date[0] + c.date[1]) + fuzz |
616 entries=[], mergepoint=e.mergepoint, | 666 ) |
617 branchpoints=e.branchpoints, commitid=e.commitid) | 667 and e.file not in files |
668 ) | |
669 ) | |
670 ): | |
671 c = changeset( | |
672 comment=e.comment, | |
673 author=e.author, | |
674 branch=e.branch, | |
675 date=e.date, | |
676 entries=[], | |
677 mergepoint=e.mergepoint, | |
678 branchpoints=e.branchpoints, | |
679 commitid=e.commitid, | |
680 ) | |
618 changesets.append(c) | 681 changesets.append(c) |
619 | 682 |
620 files = set() | 683 files = set() |
621 if len(changesets) % 100 == 0: | 684 if len(changesets) % 100 == 0: |
622 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1]) | 685 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1]) |
623 ui.status(stringutil.ellipsis(t, 80) + '\n') | 686 ui.status(stringutil.ellipsis(t, 80) + '\n') |
624 | 687 |
625 c.entries.append(e) | 688 c.entries.append(e) |
626 files.add(e.file) | 689 files.add(e.file) |
627 c.date = e.date # changeset date is date of latest commit in it | 690 c.date = e.date # changeset date is date of latest commit in it |
628 | 691 |
629 # Mark synthetic changesets | 692 # Mark synthetic changesets |
630 | 693 |
631 for c in changesets: | 694 for c in changesets: |
632 # Synthetic revisions always get their own changeset, because | 695 # Synthetic revisions always get their own changeset, because |
663 c.entries.sort(key=functools.cmp_to_key(entitycompare)) | 726 c.entries.sort(key=functools.cmp_to_key(entitycompare)) |
664 | 727 |
665 # Sort changesets by date | 728 # Sort changesets by date |
666 | 729 |
667 odd = set() | 730 odd = set() |
731 | |
668 def cscmp(l, r): | 732 def cscmp(l, r): |
669 d = sum(l.date) - sum(r.date) | 733 d = sum(l.date) - sum(r.date) |
670 if d: | 734 if d: |
671 return d | 735 return d |
672 | 736 |
743 if mergefrom is None: | 807 if mergefrom is None: |
744 mergefrom = br'{{mergefrombranch ([-\w]+)}}' | 808 mergefrom = br'{{mergefrombranch ([-\w]+)}}' |
745 if mergefrom: | 809 if mergefrom: |
746 mergefrom = re.compile(mergefrom) | 810 mergefrom = re.compile(mergefrom) |
747 | 811 |
748 versions = {} # changeset index where we saw any particular file version | 812 versions = {} # changeset index where we saw any particular file version |
749 branches = {} # changeset index where we saw a branch | 813 branches = {} # changeset index where we saw a branch |
750 n = len(changesets) | 814 n = len(changesets) |
751 i = 0 | 815 i = 0 |
752 while i < n: | 816 while i < n: |
753 c = changesets[i] | 817 c = changesets[i] |
754 | 818 |
775 if p is not None: | 839 if p is not None: |
776 p = changesets[p] | 840 p = changesets[p] |
777 | 841 |
778 # Ensure no changeset has a synthetic changeset as a parent. | 842 # Ensure no changeset has a synthetic changeset as a parent. |
779 while p.synthetic: | 843 while p.synthetic: |
780 assert len(p.parents) <= 1, ( | 844 assert len(p.parents) <= 1, _( |
781 _('synthetic changeset cannot have multiple parents')) | 845 'synthetic changeset cannot have multiple parents' |
846 ) | |
782 if p.parents: | 847 if p.parents: |
783 p = p.parents[0] | 848 p = p.parents[0] |
784 else: | 849 else: |
785 p = None | 850 p = None |
786 break | 851 break |
800 if m == 'HEAD': | 865 if m == 'HEAD': |
801 m = None | 866 m = None |
802 try: | 867 try: |
803 candidate = changesets[branches[m]] | 868 candidate = changesets[branches[m]] |
804 except KeyError: | 869 except KeyError: |
805 ui.warn(_("warning: CVS commit message references " | 870 ui.warn( |
806 "non-existent branch %r:\n%s\n") | 871 _( |
807 % (pycompat.bytestr(m), c.comment)) | 872 "warning: CVS commit message references " |
873 "non-existent branch %r:\n%s\n" | |
874 ) | |
875 % (pycompat.bytestr(m), c.comment) | |
876 ) | |
808 if m in branches and c.branch != m and not candidate.synthetic: | 877 if m in branches and c.branch != m and not candidate.synthetic: |
809 c.parents.append(candidate) | 878 c.parents.append(candidate) |
810 | 879 |
811 if mergeto: | 880 if mergeto: |
812 m = mergeto.search(c.comment) | 881 m = mergeto.search(c.comment) |
814 if m.groups(): | 883 if m.groups(): |
815 m = m.group(1) | 884 m = m.group(1) |
816 if m == 'HEAD': | 885 if m == 'HEAD': |
817 m = None | 886 m = None |
818 else: | 887 else: |
819 m = None # if no group found then merge to HEAD | 888 m = None # if no group found then merge to HEAD |
820 if m in branches and c.branch != m: | 889 if m in branches and c.branch != m: |
821 # insert empty changeset for merge | 890 # insert empty changeset for merge |
822 cc = changeset( | 891 cc = changeset( |
823 author=c.author, branch=m, date=c.date, | 892 author=c.author, |
893 branch=m, | |
894 date=c.date, | |
824 comment='convert-repo: CVS merge from branch %s' | 895 comment='convert-repo: CVS merge from branch %s' |
825 % c.branch, | 896 % c.branch, |
826 entries=[], tags=[], | 897 entries=[], |
827 parents=[changesets[branches[m]], c]) | 898 tags=[], |
899 parents=[changesets[branches[m]], c], | |
900 ) | |
828 changesets.insert(i + 1, cc) | 901 changesets.insert(i + 1, cc) |
829 branches[m] = i + 1 | 902 branches[m] = i + 1 |
830 | 903 |
831 # adjust our loop counters now we have inserted a new entry | 904 # adjust our loop counters now we have inserted a new entry |
832 n += 1 | 905 n += 1 |
851 c.id = i + 1 | 924 c.id = i + 1 |
852 | 925 |
853 if odd: | 926 if odd: |
854 for l, r in odd: | 927 for l, r in odd: |
855 if l.id is not None and r.id is not None: | 928 if l.id is not None and r.id is not None: |
856 ui.warn(_('changeset %d is both before and after %d\n') | 929 ui.warn( |
857 % (l.id, r.id)) | 930 _('changeset %d is both before and after %d\n') |
931 % (l.id, r.id) | |
932 ) | |
858 | 933 |
859 ui.status(_('%d changeset entries\n') % len(changesets)) | 934 ui.status(_('%d changeset entries\n') % len(changesets)) |
860 | 935 |
861 hook.hook(ui, None, "cvschangesets", True, changesets=changesets) | 936 hook.hook(ui, None, "cvschangesets", True, changesets=changesets) |
862 | 937 |
884 for d in args: | 959 for d in args: |
885 log += createlog(ui, d, root=opts["root"], cache=cache) | 960 log += createlog(ui, d, root=opts["root"], cache=cache) |
886 else: | 961 else: |
887 log = createlog(ui, root=opts["root"], cache=cache) | 962 log = createlog(ui, root=opts["root"], cache=cache) |
888 except logerror as e: | 963 except logerror as e: |
889 ui.write("%r\n"%e) | 964 ui.write("%r\n" % e) |
890 return | 965 return |
891 | 966 |
892 changesets = createchangeset(ui, log, opts["fuzz"]) | 967 changesets = createchangeset(ui, log, opts["fuzz"]) |
893 del log | 968 del log |
894 | 969 |
895 # Print changesets (optionally filtered) | 970 # Print changesets (optionally filtered) |
896 | 971 |
897 off = len(revisions) | 972 off = len(revisions) |
898 branches = {} # latest version number in each branch | 973 branches = {} # latest version number in each branch |
899 ancestors = {} # parent branch | 974 ancestors = {} # parent branch |
900 for cs in changesets: | 975 for cs in changesets: |
901 | 976 |
902 if opts["ancestors"]: | 977 if opts["ancestors"]: |
903 if cs.branch not in branches and cs.parents and cs.parents[0].id: | 978 if cs.branch not in branches and cs.parents and cs.parents[0].id: |
904 ancestors[cs.branch] = (changesets[cs.parents[0].id - 1].branch, | 979 ancestors[cs.branch] = ( |
905 cs.parents[0].id) | 980 changesets[cs.parents[0].id - 1].branch, |
981 cs.parents[0].id, | |
982 ) | |
906 branches[cs.branch] = cs.id | 983 branches[cs.branch] = cs.id |
907 | 984 |
908 # limit by branches | 985 # limit by branches |
909 if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]: | 986 if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]: |
910 continue | 987 continue |
912 if not off: | 989 if not off: |
913 # Note: trailing spaces on several lines here are needed to have | 990 # Note: trailing spaces on several lines here are needed to have |
914 # bug-for-bug compatibility with cvsps. | 991 # bug-for-bug compatibility with cvsps. |
915 ui.write('---------------------\n') | 992 ui.write('---------------------\n') |
916 ui.write(('PatchSet %d \n' % cs.id)) | 993 ui.write(('PatchSet %d \n' % cs.id)) |
917 ui.write(('Date: %s\n' % dateutil.datestr(cs.date, | 994 ui.write( |
918 '%Y/%m/%d %H:%M:%S %1%2'))) | 995 ( |
996 'Date: %s\n' | |
997 % dateutil.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2') | |
998 ) | |
999 ) | |
919 ui.write(('Author: %s\n' % cs.author)) | 1000 ui.write(('Author: %s\n' % cs.author)) |
920 ui.write(('Branch: %s\n' % (cs.branch or 'HEAD'))) | 1001 ui.write(('Branch: %s\n' % (cs.branch or 'HEAD'))) |
921 ui.write(('Tag%s: %s \n' % (['', 's'][len(cs.tags) > 1], | 1002 ui.write( |
922 ','.join(cs.tags) or '(none)'))) | 1003 ( |
1004 'Tag%s: %s \n' | |
1005 % ( | |
1006 ['', 's'][len(cs.tags) > 1], | |
1007 ','.join(cs.tags) or '(none)', | |
1008 ) | |
1009 ) | |
1010 ) | |
923 if cs.branchpoints: | 1011 if cs.branchpoints: |
924 ui.write(('Branchpoints: %s \n') % | 1012 ui.write( |
925 ', '.join(sorted(cs.branchpoints))) | 1013 'Branchpoints: %s \n' % ', '.join(sorted(cs.branchpoints)) |
1014 ) | |
926 if opts["parents"] and cs.parents: | 1015 if opts["parents"] and cs.parents: |
927 if len(cs.parents) > 1: | 1016 if len(cs.parents) > 1: |
928 ui.write(('Parents: %s\n' % | 1017 ui.write( |
929 (','.join([(b"%d" % p.id) for p in cs.parents])))) | 1018 ( |
1019 'Parents: %s\n' | |
1020 % (','.join([(b"%d" % p.id) for p in cs.parents])) | |
1021 ) | |
1022 ) | |
930 else: | 1023 else: |
931 ui.write(('Parent: %d\n' % cs.parents[0].id)) | 1024 ui.write(('Parent: %d\n' % cs.parents[0].id)) |
932 | 1025 |
933 if opts["ancestors"]: | 1026 if opts["ancestors"]: |
934 b = cs.branch | 1027 b = cs.branch |
937 b, c = ancestors[b] | 1030 b, c = ancestors[b] |
938 r.append('%s:%d:%d' % (b or "HEAD", c, branches[b])) | 1031 r.append('%s:%d:%d' % (b or "HEAD", c, branches[b])) |
939 if r: | 1032 if r: |
940 ui.write(('Ancestors: %s\n' % (','.join(r)))) | 1033 ui.write(('Ancestors: %s\n' % (','.join(r)))) |
941 | 1034 |
942 ui.write(('Log:\n')) | 1035 ui.write('Log:\n') |
943 ui.write('%s\n\n' % cs.comment) | 1036 ui.write('%s\n\n' % cs.comment) |
944 ui.write(('Members: \n')) | 1037 ui.write('Members: \n') |
945 for f in cs.entries: | 1038 for f in cs.entries: |
946 fn = f.file | 1039 fn = f.file |
947 if fn.startswith(opts["prefix"]): | 1040 if fn.startswith(opts["prefix"]): |
948 fn = fn[len(opts["prefix"]):] | 1041 fn = fn[len(opts["prefix"]) :] |
949 ui.write('\t%s:%s->%s%s \n' % ( | 1042 ui.write( |
1043 '\t%s:%s->%s%s \n' | |
1044 % ( | |
950 fn, | 1045 fn, |
951 '.'.join([b"%d" % x for x in f.parent]) or 'INITIAL', | 1046 '.'.join([b"%d" % x for x in f.parent]) or 'INITIAL', |
952 '.'.join([(b"%d" % x) for x in f.revision]), | 1047 '.'.join([(b"%d" % x) for x in f.revision]), |
953 ['', '(DEAD)'][f.dead])) | 1048 ['', '(DEAD)'][f.dead], |
1049 ) | |
1050 ) | |
954 ui.write('\n') | 1051 ui.write('\n') |
955 | 1052 |
956 # have we seen the start tag? | 1053 # have we seen the start tag? |
957 if revisions and off: | 1054 if revisions and off: |
958 if (revisions[0] == (b"%d" % cs.id) or | 1055 if revisions[0] == (b"%d" % cs.id) or revisions[0] in cs.tags: |
959 revisions[0] in cs.tags): | |
960 off = False | 1056 off = False |
961 | 1057 |
962 # see if we reached the end tag | 1058 # see if we reached the end tag |
963 if len(revisions) > 1 and not off: | 1059 if len(revisions) > 1 and not off: |
964 if (revisions[1] == (b"%d" % cs.id) or | 1060 if revisions[1] == (b"%d" % cs.id) or revisions[1] in cs.tags: |
965 revisions[1] in cs.tags): | |
966 break | 1061 break |