cvsps.py
1071 lines
| 35.4 KiB
| text/x-python
|
PythonLexer
Frank Kingswood
|
r6687 | # Mercurial built-in replacement for cvsps. | ||
# | ||||
# Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk> | ||||
# | ||||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Frank Kingswood
|
r6687 | |||
Augie Fackler
|
r37903 | import functools | ||
Frank Kingswood
|
r6687 | import os | ||
Gregory Szorc
|
r49725 | import pickle | ||
Frank Kingswood
|
r6687 | import re | ||
timeless
|
r28369 | |||
Yuya Nishihara
|
r29205 | from mercurial.i18n import _ | ||
Gregory Szorc
|
r43355 | from mercurial.pycompat import open | ||
timeless
|
r28369 | from mercurial import ( | ||
Pulkit Goyal
|
r30638 | encoding, | ||
FUJIWARA Katsunori
|
r33388 | error, | ||
timeless
|
r28369 | hook, | ||
Pulkit Goyal
|
r30616 | pycompat, | ||
timeless
|
r28369 | util, | ||
) | ||||
Yuya Nishihara
|
r37102 | from mercurial.utils import ( | ||
dateutil, | ||||
Yuya Nishihara
|
r37138 | procutil, | ||
Yuya Nishihara
|
r37102 | stringutil, | ||
) | ||||
Frank Kingswood
|
r6687 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class logentry: | ||
Augie Fackler
|
r46554 | """Class logentry has the following attributes: | ||
.author - author name as CVS knows it | ||||
.branch - name of branch this revision is on | ||||
.branches - revision tuple of branches starting at this revision | ||||
.comment - commit message | ||||
.commitid - CVS commitid or None | ||||
.date - the commit date as a (time, tz) tuple | ||||
.dead - true if file revision is dead | ||||
.file - Name of file | ||||
.lines - a tuple (+lines, -lines) or None | ||||
.parent - Previous revision of this entry | ||||
.rcs - name of file as returned from CVS | ||||
.revision - revision number as tuple | ||||
.tags - list of tags on the file | ||||
.synthetic - is this a synthetic "file ... added on ..." revision? | ||||
.mergepoint - the branch that has been merged from (if present in | ||||
rlog output) or None | ||||
.branchpoints - the branches that start at the current entry or empty | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
Frank Kingswood
|
r6687 | def __init__(self, **entries): | ||
Benoit Boissinot
|
r10701 | self.synthetic = False | ||
Frank Kingswood
|
r6687 | self.__dict__.update(entries) | ||
Greg Ward
|
r8080 | def __repr__(self): | ||
Augie Fackler
|
r43809 | items = ("%s=%r" % (k, self.__dict__[k]) for k in sorted(self.__dict__)) | ||
return "%s(%s)" % (type(self).__name__, ", ".join(items)) | ||||
Augie Fackler
|
r43346 | |||
Greg Ward
|
r8080 | |||
Frank Kingswood
|
r6687 | class logerror(Exception): | ||
pass | ||||
Augie Fackler
|
r43346 | |||
Patrick Mezard
|
r7097 | def getrepopath(cvspath): | ||
"""Return the repository path from a CVS path. | ||||
Yuya Nishihara
|
r34133 | >>> getrepopath(b'/foo/bar') | ||
Patrick Mezard
|
r7097 | '/foo/bar' | ||
Yuya Nishihara
|
r34133 | >>> getrepopath(b'c:/foo/bar') | ||
Blesso
|
r19145 | '/foo/bar' | ||
Yuya Nishihara
|
r34133 | >>> getrepopath(b':pserver:10/foo/bar') | ||
Patrick Mezard
|
r7097 | '/foo/bar' | ||
Yuya Nishihara
|
r34133 | >>> getrepopath(b':pserver:10c:/foo/bar') | ||
Patrick Mezard
|
r7097 | '/foo/bar' | ||
Yuya Nishihara
|
r34133 | >>> getrepopath(b':pserver:/foo/bar') | ||
Patrick Mezard
|
r7097 | '/foo/bar' | ||
Yuya Nishihara
|
r34133 | >>> getrepopath(b':pserver:c:/foo/bar') | ||
Blesso
|
r19145 | '/foo/bar' | ||
Yuya Nishihara
|
r34133 | >>> getrepopath(b':pserver:truc@foo.bar:/foo/bar') | ||
Patrick Mezard
|
r7097 | '/foo/bar' | ||
Yuya Nishihara
|
r34133 | >>> getrepopath(b':pserver:truc@foo.bar:c:/foo/bar') | ||
Blesso
|
r19145 | '/foo/bar' | ||
Yuya Nishihara
|
r34133 | >>> getrepopath(b'user@server/path/to/repository') | ||
Blesso
|
r19145 | '/path/to/repository' | ||
Patrick Mezard
|
r7097 | """ | ||
# According to CVS manual, CVS paths are expressed like: | ||||
# [:method:][[user][:password]@]hostname[:[port]]/path/to/repository | ||||
# | ||||
Blesso
|
r19145 | # CVSpath is splitted into parts and then position of the first occurrence | ||
# of the '/' char after the '@' is located. The solution is the rest of the | ||||
# string after that '/' sign including it | ||||
Augie Fackler
|
r43347 | parts = cvspath.split(b':') | ||
atposition = parts[-1].find(b'@') | ||||
Blesso
|
r19145 | start = 0 | ||
if atposition != -1: | ||||
start = atposition | ||||
Augie Fackler
|
r43347 | repopath = parts[-1][parts[-1].find(b'/', start) :] | ||
Blesso
|
r19145 | return repopath | ||
Patrick Mezard
|
r7097 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | def createlog(ui, directory=None, root=b"", rlog=True, cache=None): | ||
Frank Kingswood
|
r6687 | '''Collect the CVS rlog''' | ||
# Because we store many duplicate commit log messages, reusing strings | ||||
# saves a lot of memory and pickle storage space. | ||||
_scache = {} | ||||
Augie Fackler
|
r43346 | |||
Frank Kingswood
|
r6687 | def scache(s): | ||
Matt Harbison
|
r44226 | """return a shared version of a string""" | ||
Frank Kingswood
|
r6687 | return _scache.setdefault(s, s) | ||
Augie Fackler
|
r43347 | ui.status(_(b'collecting CVS rlog\n')) | ||
Frank Kingswood
|
r6687 | |||
Augie Fackler
|
r43346 | log = [] # list of logentry objects containing the CVS state | ||
Frank Kingswood
|
r6687 | |||
# patterns to match in CVS (r)log output, by state of use | ||||
Augie Fackler
|
r37901 | re_00 = re.compile(b'RCS file: (.+)$') | ||
re_01 = re.compile(b'cvs \\[r?log aborted\\]: (.+)$') | ||||
re_02 = re.compile(b'cvs (r?log|server): (.+)\n$') | ||||
Augie Fackler
|
r43346 | re_03 = re.compile( | ||
Martin von Zweigbergk
|
r43387 | b"(Cannot access.+CVSROOT)|(can't create temporary directory.+)$" | ||
Augie Fackler
|
r43346 | ) | ||
Augie Fackler
|
r37901 | re_10 = re.compile(b'Working file: (.+)$') | ||
re_20 = re.compile(b'symbolic names:') | ||||
re_30 = re.compile(b'\t(.+): ([\\d.]+)$') | ||||
re_31 = re.compile(b'----------------------------$') | ||||
Augie Fackler
|
r43346 | re_32 = re.compile( | ||
b'=======================================' | ||||
b'======================================$' | ||||
) | ||||
Gregory Szorc
|
r41673 | re_50 = re.compile(br'revision ([\d.]+)(\s+locked by:\s+.+;)?$') | ||
Augie Fackler
|
r43346 | re_60 = re.compile( | ||
br'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);' | ||||
br'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?' | ||||
br'(\s+commitid:\s+([^;]+);)?' | ||||
br'(.*mergepoint:\s+([^;]+);)?' | ||||
) | ||||
Augie Fackler
|
r37901 | re_70 = re.compile(b'branches: (.+);$') | ||
Frank Kingswood
|
r6687 | |||
Augie Fackler
|
r37901 | file_added_re = re.compile(br'file [^/]+ was (initially )?added on branch') | ||
Greg Ward
|
r7862 | |||
Augie Fackler
|
r43347 | prefix = b'' # leading path to strip of what we get from CVS | ||
Frank Kingswood
|
r6687 | |||
if directory is None: | ||||
# Current working directory | ||||
# Get the real directory in the repository | ||||
try: | ||||
Augie Fackler
|
r43347 | with open(os.path.join(b'CVS', b'Repository'), b'rb') as f: | ||
Augie Fackler
|
r43322 | prefix = f.read().strip() | ||
Mathieu Clabaut
|
r10695 | directory = prefix | ||
Augie Fackler
|
r43347 | if prefix == b".": | ||
prefix = b"" | ||||
Frank Kingswood
|
r6687 | except IOError: | ||
Augie Fackler
|
r43347 | raise logerror(_(b'not a CVS sandbox')) | ||
Frank Kingswood
|
r6687 | |||
Pulkit Goyal
|
r30616 | if prefix and not prefix.endswith(pycompat.ossep): | ||
prefix += pycompat.ossep | ||||
Frank Kingswood
|
r6687 | |||
# Use the Root file in the sandbox, if it exists | ||||
try: | ||||
Augie Fackler
|
r43347 | root = open(os.path.join(b'CVS', b'Root'), b'rb').read().strip() | ||
Frank Kingswood
|
r6687 | except IOError: | ||
pass | ||||
if not root: | ||||
Augie Fackler
|
r43347 | root = encoding.environ.get(b'CVSROOT', b'') | ||
Frank Kingswood
|
r6687 | |||
# read log cache if one exists | ||||
oldlog = [] | ||||
date = None | ||||
if cache: | ||||
Augie Fackler
|
r43347 | cachedir = os.path.expanduser(b'~/.hg.cvsps') | ||
Frank Kingswood
|
r6687 | if not os.path.exists(cachedir): | ||
os.mkdir(cachedir) | ||||
# The cvsps cache pickle needs a uniquified name, based on the | ||||
# repository location. The address may have all sort of nasties | ||||
# in it, slashes, colons and such. So here we take just the | ||||
Mads Kiilerich
|
r17424 | # alphanumeric characters, concatenated in a way that does not | ||
# mix up the various components, so that | ||||
Frank Kingswood
|
r6687 | # :pserver:user@server:/path | ||
# and | ||||
# /pserver/user/server/path | ||||
# are mapped to different cache file names. | ||||
Augie Fackler
|
r43347 | cachefile = root.split(b":") + [directory, b"cache"] | ||
cachefile = [b'-'.join(re.findall(br'\w+', s)) for s in cachefile if s] | ||||
Augie Fackler
|
r43346 | cachefile = os.path.join( | ||
Augie Fackler
|
r43347 | cachedir, b'.'.join([s for s in cachefile if s]) | ||
Augie Fackler
|
r43346 | ) | ||
Frank Kingswood
|
r6687 | |||
Augie Fackler
|
r43347 | if cache == b'update': | ||
Frank Kingswood
|
r6687 | try: | ||
Augie Fackler
|
r43347 | ui.note(_(b'reading cvs log cache %s\n') % cachefile) | ||
oldlog = pickle.load(open(cachefile, b'rb')) | ||||
Frank Kingswood
|
r18261 | for e in oldlog: | ||
Augie Fackler
|
r43346 | if not ( | ||
Martin von Zweigbergk
|
r52022 | hasattr(e, 'branchpoints') | ||
and hasattr(e, 'commitid') | ||||
and hasattr(e, 'mergepoint') | ||||
Augie Fackler
|
r43346 | ): | ||
Augie Fackler
|
r43347 | ui.status(_(b'ignoring old cache\n')) | ||
Idan Kamara
|
r18286 | oldlog = [] | ||
break | ||||
Frank Kingswood
|
r18261 | |||
Augie Fackler
|
r43347 | ui.note(_(b'cache has %d log entries\n') % len(oldlog)) | ||
Gregory Szorc
|
r25660 | except Exception as e: | ||
Augie Fackler
|
r43347 | ui.note(_(b'error reading cache: %r\n') % e) | ||
Frank Kingswood
|
r6687 | |||
if oldlog: | ||||
Augie Fackler
|
r43346 | date = oldlog[-1].date # last commit date as a (time,tz) tuple | ||
Augie Fackler
|
r43347 | date = dateutil.datestr(date, b'%Y/%m/%d %H:%M:%S %1%2') | ||
Frank Kingswood
|
r6687 | |||
# build the CVS commandline | ||||
Augie Fackler
|
r43347 | cmd = [b'cvs', b'-q'] | ||
Frank Kingswood
|
r6687 | if root: | ||
Augie Fackler
|
r43347 | cmd.append(b'-d%s' % root) | ||
Patrick Mezard
|
r7097 | p = util.normpath(getrepopath(root)) | ||
Augie Fackler
|
r43347 | if not p.endswith(b'/'): | ||
p += b'/' | ||||
Mathieu Clabaut
|
r10695 | if prefix: | ||
# looks like normpath replaces "" by "." | ||||
prefix = p + util.normpath(prefix) | ||||
else: | ||||
prefix = p | ||||
Augie Fackler
|
r43347 | cmd.append([b'log', b'rlog'][rlog]) | ||
Frank Kingswood
|
r6687 | if date: | ||
# no space between option and date string | ||||
Augie Fackler
|
r43347 | cmd.append(b'-d>%s' % date) | ||
Frank Kingswood
|
r6687 | cmd.append(directory) | ||
# state machine begins here | ||||
Augie Fackler
|
r43346 | tags = {} # dictionary of revisions on current file with their tags | ||
branchmap = {} # mapping between branch names and revision numbers | ||||
Emanuele Giaquinta
|
r26593 | rcsmap = {} | ||
Frank Kingswood
|
r6687 | state = 0 | ||
Augie Fackler
|
r43346 | store = False # set when a new record can be appended | ||
Frank Kingswood
|
r6687 | |||
Yuya Nishihara
|
r37138 | cmd = [procutil.shellquote(arg) for arg in cmd] | ||
Augie Fackler
|
r43347 | ui.note(_(b"running %s\n") % (b' '.join(cmd))) | ||
ui.debug(b"prefix=%r directory=%r root=%r\n" % (prefix, directory, root)) | ||||
Frank Kingswood
|
r6687 | |||
Augie Fackler
|
r43347 | pfp = procutil.popen(b' '.join(cmd), b'rb') | ||
Yuya Nishihara
|
r37476 | peek = util.fromnativeeol(pfp.readline()) | ||
David Champion
|
r7593 | while True: | ||
line = peek | ||||
Augie Fackler
|
r43347 | if line == b'': | ||
David Champion
|
r7593 | break | ||
Yuya Nishihara
|
r37476 | peek = util.fromnativeeol(pfp.readline()) | ||
Augie Fackler
|
r43347 | if line.endswith(b'\n'): | ||
Frank Kingswood
|
r6687 | line = line[:-1] | ||
Augie Fackler
|
r43346 | # ui.debug('state=%d line=%r\n' % (state, line)) | ||
Frank Kingswood
|
r6687 | |||
if state == 0: | ||||
# initial state, consume input until we see 'RCS file' | ||||
match = re_00.match(line) | ||||
if match: | ||||
rcs = match.group(1) | ||||
tags = {} | ||||
if rlog: | ||||
Patrick Mezard
|
r7097 | filename = util.normpath(rcs[:-2]) | ||
Frank Kingswood
|
r6687 | if filename.startswith(prefix): | ||
Augie Fackler
|
r43346 | filename = filename[len(prefix) :] | ||
Augie Fackler
|
r43347 | if filename.startswith(b'/'): | ||
Frank Kingswood
|
r6687 | filename = filename[1:] | ||
Augie Fackler
|
r43347 | if filename.startswith(b'Attic/'): | ||
Frank Kingswood
|
r6687 | filename = filename[6:] | ||
else: | ||||
Augie Fackler
|
r43347 | filename = filename.replace(b'/Attic/', b'/') | ||
Frank Kingswood
|
r6687 | state = 2 | ||
continue | ||||
state = 1 | ||||
continue | ||||
match = re_01.match(line) | ||||
if match: | ||||
Matt Mackall
|
r11122 | raise logerror(match.group(1)) | ||
Frank Kingswood
|
r6687 | match = re_02.match(line) | ||
if match: | ||||
Matt Mackall
|
r11122 | raise logerror(match.group(2)) | ||
Frank Kingswood
|
r6687 | if re_03.match(line): | ||
Matt Mackall
|
r11122 | raise logerror(line) | ||
Frank Kingswood
|
r6687 | |||
elif state == 1: | ||||
# expect 'Working file' (only when using log instead of rlog) | ||||
match = re_10.match(line) | ||||
Augie Fackler
|
r43347 | assert match, _(b'RCS file must be followed by working file') | ||
Patrick Mezard
|
r7097 | filename = util.normpath(match.group(1)) | ||
Frank Kingswood
|
r6687 | state = 2 | ||
elif state == 2: | ||||
# expect 'symbolic names' | ||||
if re_20.match(line): | ||||
Henrik Stuart
|
r7956 | branchmap = {} | ||
Frank Kingswood
|
r6687 | state = 3 | ||
elif state == 3: | ||||
# read the symbolic names and store as tags | ||||
match = re_30.match(line) | ||||
if match: | ||||
Augie Fackler
|
r43347 | rev = [int(x) for x in match.group(2).split(b'.')] | ||
Frank Kingswood
|
r6687 | |||
# Convert magic branch number to an odd-numbered one | ||||
revn = len(rev) | ||||
Matt Mackall
|
r6688 | if revn > 3 and (revn % 2) == 0 and rev[-2] == 0: | ||
rev = rev[:-2] + rev[-1:] | ||||
Frank Kingswood
|
r6687 | rev = tuple(rev) | ||
if rev not in tags: | ||||
tags[rev] = [] | ||||
tags[rev].append(match.group(1)) | ||||
Henrik Stuart
|
r7956 | branchmap[match.group(1)] = match.group(2) | ||
Frank Kingswood
|
r6687 | |||
elif re_31.match(line): | ||||
state = 5 | ||||
elif re_32.match(line): | ||||
state = 0 | ||||
elif state == 4: | ||||
# expecting '------' separator before first revision | ||||
if re_31.match(line): | ||||
state = 5 | ||||
else: | ||||
Augie Fackler
|
r43346 | assert not re_32.match(line), _( | ||
Martin von Zweigbergk
|
r43387 | b'must have at least some revisions' | ||
Augie Fackler
|
r43346 | ) | ||
Frank Kingswood
|
r6687 | |||
elif state == 5: | ||||
# expecting revision number and possibly (ignored) lock indication | ||||
# we create the logentry here from values stored in states 0 to 4, | ||||
# as this state is re-entered for subsequent revisions of a file. | ||||
match = re_50.match(line) | ||||
Augie Fackler
|
r43347 | assert match, _(b'expected revision number') | ||
Augie Fackler
|
r43346 | e = logentry( | ||
rcs=scache(rcs), | ||||
file=scache(filename), | ||||
Augie Fackler
|
r43347 | revision=tuple([int(x) for x in match.group(1).split(b'.')]), | ||
Augie Fackler
|
r43346 | branches=[], | ||
parent=None, | ||||
commitid=None, | ||||
mergepoint=None, | ||||
branchpoints=set(), | ||||
) | ||||
Frank Kingswood
|
r18261 | |||
Frank Kingswood
|
r6687 | state = 6 | ||
elif state == 6: | ||||
# expecting date, author, state, lines changed | ||||
match = re_60.match(line) | ||||
Augie Fackler
|
r43347 | assert match, _(b'revision must be followed by date line') | ||
Frank Kingswood
|
r6687 | d = match.group(1) | ||
Augie Fackler
|
r43347 | if d[2] == b'/': | ||
Frank Kingswood
|
r6687 | # Y2K | ||
Augie Fackler
|
r43347 | d = b'19' + d | ||
Frank Kingswood
|
r6687 | |||
if len(d.split()) != 3: | ||||
# cvs log dates always in GMT | ||||
Augie Fackler
|
r43347 | d = d + b' UTC' | ||
Augie Fackler
|
r43346 | e.date = dateutil.parsedate( | ||
d, | ||||
Augie Fackler
|
r43347 | [ | ||
b'%y/%m/%d %H:%M:%S', | ||||
b'%Y/%m/%d %H:%M:%S', | ||||
b'%Y-%m-%d %H:%M:%S', | ||||
], | ||||
Augie Fackler
|
r43346 | ) | ||
Frank Kingswood
|
r6687 | e.author = scache(match.group(2)) | ||
Augie Fackler
|
r43347 | e.dead = match.group(3).lower() == b'dead' | ||
Frank Kingswood
|
r6687 | |||
if match.group(5): | ||||
if match.group(6): | ||||
e.lines = (int(match.group(5)), int(match.group(6))) | ||||
else: | ||||
e.lines = (int(match.group(5)), 0) | ||||
elif match.group(6): | ||||
e.lines = (0, int(match.group(6))) | ||||
else: | ||||
e.lines = None | ||||
Henrik Stuart
|
r7956 | |||
Augie Fackler
|
r43346 | if match.group(7): # cvs 1.12 commitid | ||
Frank Kingswood
|
r18261 | e.commitid = match.group(8) | ||
Augie Fackler
|
r43346 | if match.group(9): # cvsnt mergepoint | ||
Augie Fackler
|
r43347 | myrev = match.group(10).split(b'.') | ||
Augie Fackler
|
r43346 | if len(myrev) == 2: # head | ||
Augie Fackler
|
r43347 | e.mergepoint = b'HEAD' | ||
Henrik Stuart
|
r7956 | else: | ||
Augie Fackler
|
r43347 | myrev = b'.'.join(myrev[:-2] + [b'0', myrev[-2]]) | ||
Henrik Stuart
|
r7956 | branches = [b for b in branchmap if branchmap[b] == myrev] | ||
Augie Fackler
|
r43346 | assert len(branches) == 1, ( | ||
Augie Fackler
|
r43347 | b'unknown branch: %s' % e.mergepoint | ||
Augie Fackler
|
r43346 | ) | ||
Henrik Stuart
|
r7956 | e.mergepoint = branches[0] | ||
Frank Kingswood
|
r18261 | |||
Frank Kingswood
|
r6687 | e.comment = [] | ||
state = 7 | ||||
elif state == 7: | ||||
Matt Mackall
|
r6688 | # read the revision numbers of branches that start at this revision | ||
Frank Kingswood
|
r6687 | # or store the commit log message otherwise | ||
m = re_70.match(line) | ||||
if m: | ||||
Augie Fackler
|
r43346 | e.branches = [ | ||
Augie Fackler
|
r43347 | tuple([int(y) for y in x.strip().split(b'.')]) | ||
for x in m.group(1).split(b';') | ||||
Augie Fackler
|
r43346 | ] | ||
Frank Kingswood
|
r6687 | state = 8 | ||
David Champion
|
r7593 | elif re_31.match(line) and re_50.match(peek): | ||
Frank Kingswood
|
r6687 | state = 5 | ||
store = True | ||||
elif re_32.match(line): | ||||
state = 0 | ||||
store = True | ||||
else: | ||||
e.comment.append(line) | ||||
elif state == 8: | ||||
# store commit log message | ||||
if re_31.match(line): | ||||
jakob krainz
|
r15205 | cpeek = peek | ||
Augie Fackler
|
r43347 | if cpeek.endswith(b'\n'): | ||
jakob krainz
|
r15205 | cpeek = cpeek[:-1] | ||
if re_50.match(cpeek): | ||||
state = 5 | ||||
store = True | ||||
else: | ||||
e.comment.append(line) | ||||
Frank Kingswood
|
r6687 | elif re_32.match(line): | ||
state = 0 | ||||
store = True | ||||
else: | ||||
e.comment.append(line) | ||||
Greg Ward
|
r7862 | # When a file is added on a branch B1, CVS creates a synthetic | ||
# dead trunk revision 1.1 so that the branch has a root. | ||||
# Likewise, if you merge such a file to a later branch B2 (one | ||||
# that already existed when the file was added on B1), CVS | ||||
# creates a synthetic dead revision 1.1.x.1 on B2. Don't drop | ||||
# these revisions now, but mark them synthetic so | ||||
# createchangeset() can take care of them. | ||||
Augie Fackler
|
r43346 | if ( | ||
store | ||||
and e.dead | ||||
and e.revision[-1] == 1 | ||||
and len(e.comment) == 1 # 1.1 or 1.1.x.1 | ||||
and file_added_re.match(e.comment[0]) | ||||
): | ||||
ui.debug( | ||||
Augie Fackler
|
r43347 | b'found synthetic revision in %s: %r\n' % (e.rcs, e.comment[0]) | ||
Augie Fackler
|
r43346 | ) | ||
Greg Ward
|
r7862 | e.synthetic = True | ||
Frank Kingswood
|
r6687 | if store: | ||
# clean up the results and save in the log. | ||||
store = False | ||||
Matt Mackall
|
r8209 | e.tags = sorted([scache(x) for x in tags.get(e.revision, [])]) | ||
Augie Fackler
|
r43347 | e.comment = scache(b'\n'.join(e.comment)) | ||
Frank Kingswood
|
r6687 | |||
revn = len(e.revision) | ||||
Matt Mackall
|
r6688 | if revn > 3 and (revn % 2) == 0: | ||
Frank Kingswood
|
r6687 | e.branch = tags.get(e.revision[:-1], [None])[0] | ||
else: | ||||
e.branch = None | ||||
Henrik Stuart
|
r8756 | # find the branches starting from this revision | ||
branchpoints = set() | ||||
Gregory Szorc
|
r49768 | for branch, revision in branchmap.items(): | ||
Augie Fackler
|
r43347 | revparts = tuple([int(i) for i in revision.split(b'.')]) | ||
Augie Fackler
|
r43346 | if len(revparts) < 2: # bad tags | ||
Brandon Parsons
|
r10950 | continue | ||
Henrik Stuart
|
r8756 | if revparts[-2] == 0 and revparts[-1] % 2 == 0: | ||
# normal branch | ||||
if revparts[:-2] == e.revision: | ||||
branchpoints.add(branch) | ||||
Augie Fackler
|
r43346 | elif revparts == (1, 1, 1): # vendor branch | ||
Henrik Stuart
|
r8756 | if revparts in e.branches: | ||
branchpoints.add(branch) | ||||
e.branchpoints = branchpoints | ||||
Frank Kingswood
|
r6687 | log.append(e) | ||
Augie Fackler
|
r43347 | rcsmap[e.rcs.replace(b'/Attic/', b'/')] = e.rcs | ||
Emanuele Giaquinta
|
r26593 | |||
Matt Mackall
|
r6688 | if len(log) % 100 == 0: | ||
Augie Fackler
|
r43346 | ui.status( | ||
Augie Fackler
|
r43347 | stringutil.ellipsis(b'%d %s' % (len(log), e.file), 80) | ||
+ b'\n' | ||||
Augie Fackler
|
r43346 | ) | ||
Frank Kingswood
|
r6687 | |||
Alejandro Santos
|
r9032 | log.sort(key=lambda x: (x.rcs, x.revision)) | ||
Frank Kingswood
|
r6687 | |||
# find parent revisions of individual files | ||||
versions = {} | ||||
Emanuele Giaquinta
|
r26593 | for e in sorted(oldlog, key=lambda x: (x.rcs, x.revision)): | ||
Augie Fackler
|
r43347 | rcs = e.rcs.replace(b'/Attic/', b'/') | ||
Emanuele Giaquinta
|
r26593 | if rcs in rcsmap: | ||
e.rcs = rcsmap[rcs] | ||||
branch = e.revision[:-1] | ||||
versions[(e.rcs, branch)] = e.revision | ||||
Frank Kingswood
|
r6687 | for e in log: | ||
branch = e.revision[:-1] | ||||
p = versions.get((e.rcs, branch), None) | ||||
if p is None: | ||||
p = e.revision[:-2] | ||||
e.parent = p | ||||
versions[(e.rcs, branch)] = e.revision | ||||
# update the log cache | ||||
if cache: | ||||
if log: | ||||
# join up the old and new logs | ||||
Alejandro Santos
|
r9032 | log.sort(key=lambda x: x.date) | ||
Frank Kingswood
|
r6687 | |||
if oldlog and oldlog[-1].date >= log[0].date: | ||||
Augie Fackler
|
r43346 | raise logerror( | ||
_( | ||||
Augie Fackler
|
r43347 | b'log cache overlaps with new log entries,' | ||
b' re-run without cache.' | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
Frank Kingswood
|
r6687 | |||
Matt Mackall
|
r6688 | log = oldlog + log | ||
Frank Kingswood
|
r6687 | |||
# write the new cachefile | ||||
Augie Fackler
|
r43347 | ui.note(_(b'writing cvs log cache %s\n') % cachefile) | ||
pickle.dump(log, open(cachefile, b'wb')) | ||||
Frank Kingswood
|
r6687 | else: | ||
log = oldlog | ||||
Augie Fackler
|
r43347 | ui.status(_(b'%d log entries\n') % len(log)) | ||
Frank Kingswood
|
r6687 | |||
Augie Fackler
|
r43347 | encodings = ui.configlist(b'convert', b'cvsps.logencoding') | ||
FUJIWARA Katsunori
|
r33388 | if encodings: | ||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r33388 | def revstr(r): | ||
# this is needed, because logentry.revision is a tuple of "int" | ||||
# (e.g. (1, 2) for "1.2") | ||||
Augie Fackler
|
r43347 | return b'.'.join(pycompat.maplist(pycompat.bytestr, r)) | ||
FUJIWARA Katsunori
|
r33388 | |||
for entry in log: | ||||
comment = entry.comment | ||||
for e in encodings: | ||||
try: | ||||
Augie Fackler
|
r43346 | entry.comment = comment.decode(pycompat.sysstr(e)).encode( | ||
'utf-8' | ||||
) | ||||
FUJIWARA Katsunori
|
r33388 | if ui.debugflag: | ||
Augie Fackler
|
r43346 | ui.debug( | ||
Augie Fackler
|
r43347 | b"transcoding by %s: %s of %s\n" | ||
Augie Fackler
|
r43346 | % (e, revstr(entry.revision), entry.file) | ||
) | ||||
FUJIWARA Katsunori
|
r33388 | break | ||
except UnicodeDecodeError: | ||||
Augie Fackler
|
r43346 | pass # try next encoding | ||
except LookupError as inst: # unknown encoding, maybe | ||||
raise error.Abort( | ||||
Martin von Zweigbergk
|
r46273 | pycompat.bytestr(inst), | ||
Augie Fackler
|
r43346 | hint=_( | ||
Martin von Zweigbergk
|
r43387 | b'check convert.cvsps.logencoding configuration' | ||
Augie Fackler
|
r43346 | ), | ||
) | ||||
FUJIWARA Katsunori
|
r33388 | else: | ||
Augie Fackler
|
r43346 | raise error.Abort( | ||
_( | ||||
Augie Fackler
|
r43347 | b"no encoding can transcode" | ||
b" CVS log message for %s of %s" | ||||
Augie Fackler
|
r43346 | ) | ||
% (revstr(entry.revision), entry.file), | ||||
Martin von Zweigbergk
|
r43387 | hint=_(b'check convert.cvsps.logencoding configuration'), | ||
Augie Fackler
|
r43346 | ) | ||
FUJIWARA Katsunori
|
r33388 | |||
Augie Fackler
|
r43347 | hook.hook(ui, None, b"cvslog", True, log=log) | ||
Frank Kingswood
|
r10095 | |||
Frank Kingswood
|
r6687 | return log | ||
Gregory Szorc
|
r49801 | class changeset: | ||
Augie Fackler
|
r46554 | """Class changeset has the following attributes: | ||
.id - integer identifying this changeset (list index) | ||||
.author - author name as CVS knows it | ||||
.branch - name of branch this changeset is on, or None | ||||
.comment - commit message | ||||
.commitid - CVS commitid or None | ||||
.date - the commit date as a (time,tz) tuple | ||||
.entries - list of logentry objects in this changeset | ||||
.parents - list of one or two parent changesets | ||||
.tags - list of tags on this changeset | ||||
.synthetic - from synthetic revision "file ... added on branch ..." | ||||
.mergepoint- the branch that has been merged from or None | ||||
.branchpoints- the branches that start at the current entry or empty | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
Frank Kingswood
|
r6687 | def __init__(self, **entries): | ||
Frank Kingswood
|
r19505 | self.id = None | ||
Benoit Boissinot
|
r10701 | self.synthetic = False | ||
Frank Kingswood
|
r6687 | self.__dict__.update(entries) | ||
Greg Ward
|
r8080 | def __repr__(self): | ||
Augie Fackler
|
r43347 | items = ( | ||
b"%s=%r" % (k, self.__dict__[k]) for k in sorted(self.__dict__) | ||||
) | ||||
return b"%s(%s)" % (type(self).__name__, b", ".join(items)) | ||||
Augie Fackler
|
r43346 | |||
Greg Ward
|
r8080 | |||
Frank Kingswood
|
r6687 | def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None): | ||
'''Convert log into changesets.''' | ||||
Augie Fackler
|
r43347 | ui.status(_(b'creating changesets\n')) | ||
Frank Kingswood
|
r6687 | |||
Matt Mackall
|
r18718 | # try to order commitids by date | ||
mindate = {} | ||||
for e in log: | ||||
if e.commitid: | ||||
Augie Fackler
|
r38313 | if e.commitid not in mindate: | ||
mindate[e.commitid] = e.date | ||||
else: | ||||
mindate[e.commitid] = min(e.date, mindate[e.commitid]) | ||||
Matt Mackall
|
r18718 | |||
Frank Kingswood
|
r6687 | # Merge changesets | ||
Augie Fackler
|
r43346 | log.sort( | ||
key=lambda x: ( | ||||
mindate.get(x.commitid, (-1, 0)), | ||||
Augie Fackler
|
r43347 | x.commitid or b'', | ||
Augie Fackler
|
r43346 | x.comment, | ||
x.author, | ||||
Augie Fackler
|
r43347 | x.branch or b'', | ||
Augie Fackler
|
r43346 | x.date, | ||
x.branchpoints, | ||||
) | ||||
) | ||||
Frank Kingswood
|
r6687 | |||
changesets = [] | ||||
Benoit Boissinot
|
r8456 | files = set() | ||
Frank Kingswood
|
r6687 | c = None | ||
for i, e in enumerate(log): | ||||
# Check if log entry belongs to the current changeset or not. | ||||
Henrik Stuart
|
r8756 | |||
Mads Kiilerich
|
r17424 | # Since CVS is file-centric, two different file revisions with | ||
Henrik Stuart
|
r8756 | # different branchpoints should be treated as belonging to two | ||
# different changesets (and the ordering is important and not | ||||
# honoured by cvsps at this point). | ||||
# | ||||
# Consider the following case: | ||||
# foo 1.1 branchpoints: [MYBRANCH] | ||||
# bar 1.1 branchpoints: [MYBRANCH, MYBRANCH2] | ||||
# | ||||
# Here foo is part only of MYBRANCH, but not MYBRANCH2, e.g. a | ||||
# later version of foo may be in MYBRANCH2, so foo should be the | ||||
# first changeset and bar the next and MYBRANCH and MYBRANCH2 | ||||
# should both start off of the bar changeset. No provisions are | ||||
# made to ensure that this is, in fact, what happens. | ||||
Augie Fackler
|
r43346 | if not ( | ||
c | ||||
and e.branchpoints == c.branchpoints | ||||
and ( # cvs commitids | ||||
(e.commitid is not None and e.commitid == c.commitid) | ||||
or ( # no commitids, use fuzzy commit detection | ||||
(e.commitid is None or c.commitid is None) | ||||
and e.comment == c.comment | ||||
and e.author == c.author | ||||
and e.branch == c.branch | ||||
and ( | ||||
(c.date[0] + c.date[1]) | ||||
<= (e.date[0] + e.date[1]) | ||||
<= (c.date[0] + c.date[1]) + fuzz | ||||
) | ||||
and e.file not in files | ||||
) | ||||
) | ||||
): | ||||
c = changeset( | ||||
comment=e.comment, | ||||
author=e.author, | ||||
branch=e.branch, | ||||
date=e.date, | ||||
entries=[], | ||||
mergepoint=e.mergepoint, | ||||
branchpoints=e.branchpoints, | ||||
commitid=e.commitid, | ||||
) | ||||
Frank Kingswood
|
r6687 | changesets.append(c) | ||
Frank Kingswood
|
r18261 | |||
Benoit Boissinot
|
r8456 | files = set() | ||
Matt Mackall
|
r6688 | if len(changesets) % 100 == 0: | ||
r50799 | t = b'%d %s' % ( | |||
len(changesets), | ||||
pycompat.byterepr(e.comment)[2:-1], | ||||
) | ||||
Augie Fackler
|
r43347 | ui.status(stringutil.ellipsis(t, 80) + b'\n') | ||
Frank Kingswood
|
r6687 | |||
c.entries.append(e) | ||||
Benoit Boissinot
|
r8456 | files.add(e.file) | ||
Augie Fackler
|
r43346 | c.date = e.date # changeset date is date of latest commit in it | ||
Frank Kingswood
|
r6687 | |||
Greg Ward
|
r7862 | # Mark synthetic changesets | ||
for c in changesets: | ||||
# Synthetic revisions always get their own changeset, because | ||||
# the log message includes the filename. E.g. if you add file3 | ||||
# and file4 on a branch, you get four log entries and three | ||||
# changesets: | ||||
# "File file3 was added on branch ..." (synthetic, 1 entry) | ||||
# "File file4 was added on branch ..." (synthetic, 1 entry) | ||||
# "Add file3 and file4 to fix ..." (real, 2 entries) | ||||
# Hence the check for 1 entry here. | ||||
Benoit Boissinot
|
r10701 | c.synthetic = len(c.entries) == 1 and c.entries[0].synthetic | ||
Greg Ward
|
r7862 | |||
Frank Kingswood
|
r6687 | # Sort files in each changeset | ||
Martin Geisler
|
r15790 | def entitycompare(l, r): | ||
Matt Harbison
|
r44226 | """Mimic cvsps sorting order""" | ||
Augie Fackler
|
r43347 | l = l.file.split(b'/') | ||
r = r.file.split(b'/') | ||||
Martin Geisler
|
r15790 | nl = len(l) | ||
nr = len(r) | ||||
n = min(nl, nr) | ||||
for i in range(n): | ||||
if i + 1 == nl and nl < nr: | ||||
return -1 | ||||
elif i + 1 == nr and nl > nr: | ||||
return +1 | ||||
elif l[i] < r[i]: | ||||
return -1 | ||||
elif l[i] > r[i]: | ||||
return +1 | ||||
return 0 | ||||
Frank Kingswood
|
r6687 | for c in changesets: | ||
Augie Fackler
|
r37903 | c.entries.sort(key=functools.cmp_to_key(entitycompare)) | ||
Frank Kingswood
|
r6687 | |||
# Sort changesets by date | ||||
Frank Kingswood
|
r19505 | odd = set() | ||
Augie Fackler
|
r43346 | |||
Pierre-Yves David
|
r31408 | def cscmp(l, r): | ||
Matt Mackall
|
r6688 | d = sum(l.date) - sum(r.date) | ||
Frank Kingswood
|
r6687 | if d: | ||
return d | ||||
# detect vendor branches and initial commits on a branch | ||||
le = {} | ||||
for e in l.entries: | ||||
le[e.rcs] = e.revision | ||||
re = {} | ||||
for e in r.entries: | ||||
re[e.rcs] = e.revision | ||||
d = 0 | ||||
for e in l.entries: | ||||
if re.get(e.rcs, None) == e.parent: | ||||
assert not d | ||||
d = 1 | ||||
break | ||||
for e in r.entries: | ||||
if le.get(e.rcs, None) == e.parent: | ||||
Frank Kingswood
|
r19505 | if d: | ||
odd.add((l, r)) | ||||
Frank Kingswood
|
r6687 | d = -1 | ||
break | ||||
Augie Fackler
|
r22267 | # By this point, the changesets are sufficiently compared that | ||
# we don't really care about ordering. However, this leaves | ||||
# some race conditions in the tests, so we compare on the | ||||
Augie Fackler
|
r24305 | # number of files modified, the files contained in each | ||
# changeset, and the branchpoints in the change to ensure test | ||||
# output remains stable. | ||||
Frank Kingswood
|
r6687 | |||
Augie Fackler
|
r22267 | # recommended replacement for cmp from | ||
# https://docs.python.org/3.0/whatsnew/3.0.html | ||||
c = lambda x, y: (x > y) - (x < y) | ||||
Augie Fackler
|
r24305 | # Sort bigger changes first. | ||
Augie Fackler
|
r22267 | if not d: | ||
d = c(len(l.entries), len(r.entries)) | ||||
Augie Fackler
|
r24305 | # Try sorting by filename in the change. | ||
if not d: | ||||
d = c([e.file for e in l.entries], [e.file for e in r.entries]) | ||||
# Try and put changes without a branch point before ones with | ||||
# a branch point. | ||||
Augie Fackler
|
r22267 | if not d: | ||
d = c(len(l.branchpoints), len(r.branchpoints)) | ||||
Frank Kingswood
|
r6687 | return d | ||
Augie Fackler
|
r37903 | changesets.sort(key=functools.cmp_to_key(cscmp)) | ||
Frank Kingswood
|
r6687 | |||
# Collect tags | ||||
globaltags = {} | ||||
for c in changesets: | ||||
for e in c.entries: | ||||
for tag in e.tags: | ||||
# remember which is the latest changeset to have this tag | ||||
globaltags[tag] = c | ||||
for c in changesets: | ||||
Benoit Boissinot
|
r8456 | tags = set() | ||
Frank Kingswood
|
r6687 | for e in c.entries: | ||
Martin Geisler
|
r8483 | tags.update(e.tags) | ||
Frank Kingswood
|
r6687 | # remember tags only if this is the latest changeset to have it | ||
Benoit Boissinot
|
r8456 | c.tags = sorted(tag for tag in tags if globaltags[tag] is c) | ||
Frank Kingswood
|
r6687 | |||
# Find parent changesets, handle {{mergetobranch BRANCHNAME}} | ||||
# by inserting dummy changesets with two parents, and handle | ||||
# {{mergefrombranch BRANCHNAME}} by setting two parents. | ||||
if mergeto is None: | ||||
Augie Fackler
|
r37901 | mergeto = br'{{mergetobranch ([-\w]+)}}' | ||
Frank Kingswood
|
r6687 | if mergeto: | ||
mergeto = re.compile(mergeto) | ||||
if mergefrom is None: | ||||
Augie Fackler
|
r37901 | mergefrom = br'{{mergefrombranch ([-\w]+)}}' | ||
Frank Kingswood
|
r6687 | if mergefrom: | ||
mergefrom = re.compile(mergefrom) | ||||
Augie Fackler
|
r43346 | versions = {} # changeset index where we saw any particular file version | ||
branches = {} # changeset index where we saw a branch | ||||
Frank Kingswood
|
r6687 | n = len(changesets) | ||
i = 0 | ||||
Matt Mackall
|
r10282 | while i < n: | ||
Frank Kingswood
|
r6687 | c = changesets[i] | ||
for f in c.entries: | ||||
versions[(f.rcs, f.revision)] = i | ||||
p = None | ||||
if c.branch in branches: | ||||
p = branches[c.branch] | ||||
else: | ||||
Henrik Stuart
|
r8756 | # first changeset on a new branch | ||
# the parent is a changeset with the branch in its | ||||
# branchpoints such that it is the latest possible | ||||
# commit without any intervening, unrelated commits. | ||||
Manuel Jacob
|
r50179 | for candidate in range(i): | ||
Henrik Stuart
|
r8756 | if c.branch not in changesets[candidate].branchpoints: | ||
if p is not None: | ||||
break | ||||
continue | ||||
p = candidate | ||||
Frank Kingswood
|
r6687 | |||
c.parents = [] | ||||
if p is not None: | ||||
Greg Ward
|
r7862 | p = changesets[p] | ||
# Ensure no changeset has a synthetic changeset as a parent. | ||||
while p.synthetic: | ||||
Augie Fackler
|
r43346 | assert len(p.parents) <= 1, _( | ||
Augie Fackler
|
r43347 | b'synthetic changeset cannot have multiple parents' | ||
Augie Fackler
|
r43346 | ) | ||
Greg Ward
|
r7862 | if p.parents: | ||
p = p.parents[0] | ||||
else: | ||||
p = None | ||||
break | ||||
if p is not None: | ||||
c.parents.append(p) | ||||
Frank Kingswood
|
r6687 | |||
Henrik Stuart
|
r7956 | if c.mergepoint: | ||
Augie Fackler
|
r43347 | if c.mergepoint == b'HEAD': | ||
Henrik Stuart
|
r7956 | c.mergepoint = None | ||
c.parents.append(changesets[branches[c.mergepoint]]) | ||||
Frank Kingswood
|
r6687 | if mergefrom: | ||
m = mergefrom.search(c.comment) | ||||
if m: | ||||
m = m.group(1) | ||||
Augie Fackler
|
r43347 | if m == b'HEAD': | ||
Frank Kingswood
|
r6687 | m = None | ||
Greg Ward
|
r8171 | try: | ||
candidate = changesets[branches[m]] | ||||
except KeyError: | ||||
Augie Fackler
|
r43346 | ui.warn( | ||
_( | ||||
Augie Fackler
|
r43347 | b"warning: CVS commit message references " | ||
b"non-existent branch %r:\n%s\n" | ||||
Augie Fackler
|
r43346 | ) | ||
% (pycompat.bytestr(m), c.comment) | ||||
) | ||||
Greg Ward
|
r7950 | if m in branches and c.branch != m and not candidate.synthetic: | ||
c.parents.append(candidate) | ||||
Frank Kingswood
|
r6687 | |||
if mergeto: | ||||
m = mergeto.search(c.comment) | ||||
if m: | ||||
Brodie Rao
|
r16688 | if m.groups(): | ||
Frank Kingswood
|
r6687 | m = m.group(1) | ||
Augie Fackler
|
r43347 | if m == b'HEAD': | ||
Frank Kingswood
|
r6687 | m = None | ||
Brodie Rao
|
r16688 | else: | ||
Augie Fackler
|
r43346 | m = None # if no group found then merge to HEAD | ||
Frank Kingswood
|
r6687 | if m in branches and c.branch != m: | ||
# insert empty changeset for merge | ||||
Matt Mackall
|
r10282 | cc = changeset( | ||
Augie Fackler
|
r43346 | author=c.author, | ||
branch=m, | ||||
date=c.date, | ||||
Augie Fackler
|
r43347 | comment=b'convert-repo: CVS merge from branch %s' | ||
Matt Mackall
|
r10282 | % c.branch, | ||
Augie Fackler
|
r43346 | entries=[], | ||
tags=[], | ||||
parents=[changesets[branches[m]], c], | ||||
) | ||||
Matt Mackall
|
r6688 | changesets.insert(i + 1, cc) | ||
branches[m] = i + 1 | ||||
Frank Kingswood
|
r6687 | |||
# adjust our loop counters now we have inserted a new entry | ||||
n += 1 | ||||
i += 2 | ||||
continue | ||||
branches[c.branch] = i | ||||
i += 1 | ||||
Greg Ward
|
r7862 | # Drop synthetic changesets (safe now that we have ensured no other | ||
# changesets can have them as parents). | ||||
i = 0 | ||||
while i < len(changesets): | ||||
if changesets[i].synthetic: | ||||
del changesets[i] | ||||
else: | ||||
i += 1 | ||||
Frank Kingswood
|
r6687 | # Number changesets | ||
for i, c in enumerate(changesets): | ||||
Matt Mackall
|
r6688 | c.id = i + 1 | ||
Frank Kingswood
|
r6687 | |||
Frank Kingswood
|
r19505 | if odd: | ||
for l, r in odd: | ||||
if l.id is not None and r.id is not None: | ||||
Augie Fackler
|
r43346 | ui.warn( | ||
Augie Fackler
|
r43347 | _(b'changeset %d is both before and after %d\n') | ||
Augie Fackler
|
r43346 | % (l.id, r.id) | ||
) | ||||
Frank Kingswood
|
r19505 | |||
Augie Fackler
|
r43347 | ui.status(_(b'%d changeset entries\n') % len(changesets)) | ||
Frank Kingswood
|
r6687 | |||
Augie Fackler
|
r43347 | hook.hook(ui, None, b"cvschangesets", True, changesets=changesets) | ||
Frank Kingswood
|
r10095 | |||
Frank Kingswood
|
r6687 | return changesets | ||
Frank Kingswood
|
r7502 | |||
def debugcvsps(ui, *args, **opts): | ||||
Augie Fackler
|
r46554 | """Read CVS rlog for current directory or named path in | ||
Martin Geisler
|
r8661 | repository, and convert the log to changesets based on matching | ||
commit log entries and dates. | ||||
Augie Fackler
|
r46554 | """ | ||
Pulkit Goyal
|
r36347 | opts = pycompat.byteskwargs(opts) | ||
Augie Fackler
|
r43347 | if opts[b"new_cache"]: | ||
cache = b"write" | ||||
elif opts[b"update_cache"]: | ||||
cache = b"update" | ||||
Frank Kingswood
|
r7502 | else: | ||
cache = None | ||||
Augie Fackler
|
r43347 | revisions = opts[b"revisions"] | ||
Frank Kingswood
|
r7502 | |||
try: | ||||
if args: | ||||
log = [] | ||||
for d in args: | ||||
Augie Fackler
|
r43347 | log += createlog(ui, d, root=opts[b"root"], cache=cache) | ||
Frank Kingswood
|
r7502 | else: | ||
Augie Fackler
|
r43347 | log = createlog(ui, root=opts[b"root"], cache=cache) | ||
Gregory Szorc
|
r25660 | except logerror as e: | ||
Augie Fackler
|
r43347 | ui.write(b"%r\n" % e) | ||
Frank Kingswood
|
r7502 | return | ||
Augie Fackler
|
r43347 | changesets = createchangeset(ui, log, opts[b"fuzz"]) | ||
Frank Kingswood
|
r7502 | del log | ||
# Print changesets (optionally filtered) | ||||
off = len(revisions) | ||||
Augie Fackler
|
r43346 | branches = {} # latest version number in each branch | ||
ancestors = {} # parent branch | ||||
Frank Kingswood
|
r7502 | for cs in changesets: | ||
Augie Fackler
|
r43347 | if opts[b"ancestors"]: | ||
Frank Kingswood
|
r7502 | if cs.branch not in branches and cs.parents and cs.parents[0].id: | ||
Augie Fackler
|
r43346 | ancestors[cs.branch] = ( | ||
changesets[cs.parents[0].id - 1].branch, | ||||
cs.parents[0].id, | ||||
) | ||||
Frank Kingswood
|
r7502 | branches[cs.branch] = cs.id | ||
# limit by branches | ||||
Augie Fackler
|
r43347 | if ( | ||
opts[b"branches"] | ||||
and (cs.branch or b'HEAD') not in opts[b"branches"] | ||||
): | ||||
Frank Kingswood
|
r7502 | continue | ||
if not off: | ||||
# Note: trailing spaces on several lines here are needed to have | ||||
# bug-for-bug compatibility with cvsps. | ||||
Augie Fackler
|
r43347 | ui.write(b'---------------------\n') | ||
ui.write((b'PatchSet %d \n' % cs.id)) | ||||
Augie Fackler
|
r43346 | ui.write( | ||
( | ||||
Augie Fackler
|
r43347 | b'Date: %s\n' | ||
% dateutil.datestr(cs.date, b'%Y/%m/%d %H:%M:%S %1%2') | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
Augie Fackler
|
r43347 | ui.write((b'Author: %s\n' % cs.author)) | ||
ui.write((b'Branch: %s\n' % (cs.branch or b'HEAD'))) | ||||
Augie Fackler
|
r43346 | ui.write( | ||
( | ||||
Augie Fackler
|
r43347 | b'Tag%s: %s \n' | ||
Augie Fackler
|
r43346 | % ( | ||
Augie Fackler
|
r43347 | [b'', b's'][len(cs.tags) > 1], | ||
b','.join(cs.tags) or b'(none)', | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
) | ||||
Frank Kingswood
|
r18261 | if cs.branchpoints: | ||
Augie Fackler
|
r43350 | ui.writenoi18n( | ||
Augie Fackler
|
r43347 | b'Branchpoints: %s \n' % b', '.join(sorted(cs.branchpoints)) | ||
Augie Fackler
|
r43346 | ) | ||
Augie Fackler
|
r43347 | if opts[b"parents"] and cs.parents: | ||
Matt Mackall
|
r10282 | if len(cs.parents) > 1: | ||
Augie Fackler
|
r43346 | ui.write( | ||
( | ||||
Augie Fackler
|
r43347 | b'Parents: %s\n' | ||
% (b','.join([(b"%d" % p.id) for p in cs.parents])) | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
Frank Kingswood
|
r7502 | else: | ||
Augie Fackler
|
r43347 | ui.write((b'Parent: %d\n' % cs.parents[0].id)) | ||
Frank Kingswood
|
r7502 | |||
Augie Fackler
|
r43347 | if opts[b"ancestors"]: | ||
Frank Kingswood
|
r7502 | b = cs.branch | ||
r = [] | ||||
while b: | ||||
b, c = ancestors[b] | ||||
Augie Fackler
|
r43347 | r.append(b'%s:%d:%d' % (b or b"HEAD", c, branches[b])) | ||
Frank Kingswood
|
r7502 | if r: | ||
Augie Fackler
|
r43347 | ui.write((b'Ancestors: %s\n' % (b','.join(r)))) | ||
Frank Kingswood
|
r7502 | |||
Augie Fackler
|
r43350 | ui.writenoi18n(b'Log:\n') | ||
Augie Fackler
|
r43347 | ui.write(b'%s\n\n' % cs.comment) | ||
Augie Fackler
|
r43350 | ui.writenoi18n(b'Members: \n') | ||
Frank Kingswood
|
r7502 | for f in cs.entries: | ||
fn = f.file | ||||
Augie Fackler
|
r43347 | if fn.startswith(opts[b"prefix"]): | ||
fn = fn[len(opts[b"prefix"]) :] | ||||
Augie Fackler
|
r43346 | ui.write( | ||
Augie Fackler
|
r43347 | b'\t%s:%s->%s%s \n' | ||
Augie Fackler
|
r43346 | % ( | ||
Augie Fackler
|
r37904 | fn, | ||
Augie Fackler
|
r43347 | b'.'.join([b"%d" % x for x in f.parent]) or b'INITIAL', | ||
b'.'.join([(b"%d" % x) for x in f.revision]), | ||||
[b'', b'(DEAD)'][f.dead], | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
Augie Fackler
|
r43347 | ui.write(b'\n') | ||
Frank Kingswood
|
r7502 | |||
# have we seen the start tag? | ||||
if revisions and off: | ||||
Augie Fackler
|
r43346 | if revisions[0] == (b"%d" % cs.id) or revisions[0] in cs.tags: | ||
Frank Kingswood
|
r7502 | off = False | ||
# see if we reached the end tag | ||||
Matt Mackall
|
r10282 | if len(revisions) > 1 and not off: | ||
Augie Fackler
|
r43346 | if revisions[1] == (b"%d" % cs.id) or revisions[1] in cs.tags: | ||
Frank Kingswood
|
r7502 | break | ||