Mercurial > hg
view hgext/convert/cvs.py @ 6858:8f256bf98219
Add support for multiple possible bisect results (issue1228, issue1182)
The real reason for both issue is that bisect can not handle cases where there
are multiple possibilities for the result.
Example (from issue1228):
rev 0 -> good
rev 1 -> skipped
rev 2 -> skipped
rev 3 -> skipped
rev 4 -> bad
Note that this patch does not only fix the reported Assertion Error but also
the problem of a non converging bisect:
hg init
for i in `seq 3`; do echo $i > $i; hg add $i; hg ci -m$i; done
hg bisect -b 2
hg bisect -g 0
hg bisect -s
From this state on, you can:
a) mark as bad forever (non converging!)
b) mark as good to get an inconsistent state
c) skip for the Assertion Error
Minor description and code edits by pmezard.
author | Bernhard Leiner <bleiner@gmail.com> |
---|---|
date | Sat, 02 Aug 2008 22:10:10 +0200 |
parents | e1d8e79d7c8f |
children | d8159dd15db3 |
line wrap: on
line source
# CVS conversion code inspired by hg-cvs-import and git-cvsimport import os, locale, re, socket from cStringIO import StringIO from mercurial import util from common import NoRepo, commit, converter_source, checktool class convert_cvs(converter_source): def __init__(self, ui, path, rev=None): super(convert_cvs, self).__init__(ui, path, rev=rev) cvs = os.path.join(path, "CVS") if not os.path.exists(cvs): raise NoRepo("%s does not look like a CVS checkout" % path) self.cmd = ui.config('convert', 'cvsps', 'cvsps -A -u --cvs-direct -q') cvspsexe = self.cmd.split(None, 1)[0] for tool in (cvspsexe, 'cvs'): checktool(tool) self.changeset = {} self.files = {} self.tags = {} self.lastbranch = {} self.parent = {} self.socket = None self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1] self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1] self.encoding = locale.getpreferredencoding() self._parse() self._connect() def _parse(self): if self.changeset: return maxrev = 0 cmd = self.cmd if self.rev: # TODO: handle tags try: # patchset number? maxrev = int(self.rev) except ValueError: try: # date util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S']) cmd = '%s -d "1970/01/01 00:00:01" -d "%s"' % (cmd, self.rev) except util.Abort: raise util.Abort('revision %s is not a patchset number or date' % self.rev) d = os.getcwd() try: os.chdir(self.path) id = None state = 0 filerevids = {} for l in util.popen(cmd): if state == 0: # header if l.startswith("PatchSet"): id = l[9:-2] if maxrev and int(id) > maxrev: # ignore everything state = 3 elif l.startswith("Date"): date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"]) date = util.datestr(date) elif l.startswith("Branch"): branch = l[8:-1] self.parent[id] = self.lastbranch.get(branch, 'bad') self.lastbranch[branch] = id elif l.startswith("Ancestor branch"): ancestor = l[17:-1] # figure out the parent later self.parent[id] = self.lastbranch[ancestor] elif l.startswith("Author"): author = self.recode(l[8:-1]) elif l.startswith("Tag:") or l.startswith("Tags:"): t = l[l.index(':')+1:] t = [ut.strip() for ut in t.split(',')] if (len(t) > 1) or (t[0] and (t[0] != "(none)")): self.tags.update(dict.fromkeys(t, id)) elif l.startswith("Log:"): # switch to gathering log state = 1 log = "" elif state == 1: # log if l == "Members: \n": # switch to gathering members files = {} oldrevs = [] log = self.recode(log[:-1]) state = 2 else: # gather log log += l elif state == 2: # members if l == "\n": # start of next entry state = 0 p = [self.parent[id]] if id == "1": p = [] if branch == "HEAD": branch = "" if branch: latest = None # the last changeset that contains a base # file is our parent for r in oldrevs: latest = max(filerevids.get(r, None), latest) if latest: p = [latest] # add current commit to set c = commit(author=author, date=date, parents=p, desc=log, branch=branch) self.changeset[id] = c self.files[id] = files else: colon = l.rfind(':') file = l[1:colon] rev = l[colon+1:-2] oldrev, rev = rev.split("->") files[file] = rev # save some information for identifying branch points oldrevs.append("%s:%s" % (oldrev, file)) filerevids["%s:%s" % (rev, file)] = id elif state == 3: # swallow all input continue self.heads = self.lastbranch.values() finally: os.chdir(d) def _connect(self): root = self.cvsroot conntype = None user, host = None, None cmd = ['cvs', 'server'] self.ui.status("connecting to %s\n" % root) if root.startswith(":pserver:"): root = root[9:] m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', root) if m: conntype = "pserver" user, passw, serv, port, root = m.groups() if not user: user = "anonymous" if not port: port = 2401 else: port = int(port) format0 = ":pserver:%s@%s:%s" % (user, serv, root) format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root) if not passw: passw = "A" pf = open(os.path.expanduser("~/.cvspass")) for line in pf.read().splitlines(): part1, part2 = line.split(' ', 1) if part1 == '/1': # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z part1, part2 = part2.split(' ', 1) format = format1 else: # :pserver:user@example.com:/cvsroot/foo Ah<Z format = format0 if part1 == format: passw = part2 break pf.close() sck = socket.socket() sck.connect((serv, port)) sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw, "END AUTH REQUEST", ""])) if sck.recv(128) != "I LOVE YOU\n": raise util.Abort("CVS pserver authentication failed") self.writep = self.readp = sck.makefile('r+') if not conntype and root.startswith(":local:"): conntype = "local" root = root[7:] if not conntype: # :ext:user@host/home/user/path/to/cvsroot if root.startswith(":ext:"): root = root[5:] m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root) # Do not take Windows path "c:\foo\bar" for a connection strings if os.path.isdir(root) or not m: conntype = "local" else: conntype = "rsh" user, host, root = m.group(1), m.group(2), m.group(3) if conntype != "pserver": if conntype == "rsh": rsh = os.environ.get("CVS_RSH") or "ssh" if user: cmd = [rsh, '-l', user, host] + cmd else: cmd = [rsh, host] + cmd # popen2 does not support argument lists under Windows cmd = [util.shellquote(arg) for arg in cmd] cmd = util.quotecommand(' '.join(cmd)) self.writep, self.readp = os.popen2(cmd, 'b') self.realroot = root self.writep.write("Root %s\n" % root) self.writep.write("Valid-responses ok error Valid-requests Mode" " M Mbinary E Checked-in Created Updated" " Merged Removed\n") self.writep.write("valid-requests\n") self.writep.flush() r = self.readp.readline() if not r.startswith("Valid-requests"): raise util.Abort("server sucks") if "UseUnchanged" in r: self.writep.write("UseUnchanged\n") self.writep.flush() r = self.readp.readline() def getheads(self): return self.heads def _getfile(self, name, rev): def chunkedread(fp, count): # file-objects returned by socked.makefile() do not handle # large read() requests very well. chunksize = 65536 output = StringIO() while count > 0: data = fp.read(min(count, chunksize)) if not data: raise util.Abort("%d bytes missing from remote file" % count) count -= len(data) output.write(data) return output.getvalue() if rev.endswith("(DEAD)"): raise IOError args = ("-N -P -kk -r %s --" % rev).split() args.append(self.cvsrepo + '/' + name) for x in args: self.writep.write("Argument %s\n" % x) self.writep.write("Directory .\n%s\nco\n" % self.realroot) self.writep.flush() data = "" while 1: line = self.readp.readline() if line.startswith("Created ") or line.startswith("Updated "): self.readp.readline() # path self.readp.readline() # entries mode = self.readp.readline()[:-1] count = int(self.readp.readline()[:-1]) data = chunkedread(self.readp, count) elif line.startswith(" "): data += line[1:] elif line.startswith("M "): pass elif line.startswith("Mbinary "): count = int(self.readp.readline()[:-1]) data = chunkedread(self.readp, count) else: if line == "ok\n": return (data, "x" in mode and "x" or "") elif line.startswith("E "): self.ui.warn("cvs server: %s\n" % line[2:]) elif line.startswith("Remove"): l = self.readp.readline() l = self.readp.readline() if l != "ok\n": raise util.Abort("unknown CVS response: %s" % l) else: raise util.Abort("unknown CVS response: %s" % line) def getfile(self, file, rev): data, mode = self._getfile(file, rev) self.modecache[(file, rev)] = mode return data def getmode(self, file, rev): return self.modecache[(file, rev)] def getchanges(self, rev): self.modecache = {} files = self.files[rev] cl = files.items() cl.sort() return (cl, {}) def getcommit(self, rev): return self.changeset[rev] def gettags(self): return self.tags def getchangedfiles(self, rev, i): files = self.files[rev].keys() files.sort() return files