Mercurial > hg > mercurial-crew-with-dirclash
view hgext/convert/common.py @ 5192:33015dac5df5
convert: fix mercurial_sink.putcommit
Changeset 4ebc8693ce72 added some code to putcommit to avoid creating a
revision that touches no files, but this can break regular conversions
from some repositories:
- conceptually, since we're converting a repo, we should try to make
the new hg repo as similar as possible to the original repo - we
should create a new changeset, even if the original revision didn't
touch any files (maybe the commit message had some important bit);
- even if a "regular" revision that doesn't touch any file may seem
weird (and maybe even broken), it's completely legitimate for a merge
revision to not touch any file, and, if we just skip it, the
converted repo will end up with wrong history and possibly an extra
head.
As an example, say the crew and main hg repos are sync'ed. Somebody
sends an important patch to the mailing list. Matt quickly applies
and pushes it. But at the same time somebody also applies it to crew
and pushes it. Suppose the commit message ended up being a bit
different (say, there was a typo and somebody didn't fix it) or that
the date ended up being different (because of different patch-applying
scripts): the changeset hashes will be different, but the manifests
will be the same.
Since both changesets were pushed to public repos, it's hard to recall
them. If both are merged, the manifest from the resulting merge
revision will have the exact same contents as its parents - i.e. the
merge revision really doesn't touch any file at all.
To keep the file filtering stuff "working", the generic code was changed
to skip empty revisions if we're filtering the repo, fixing a bug in the
process (we want parents[0] instead of tip).
author | Alexis S. L. Carvalho <alexis@cecm.usp.br> |
---|---|
date | Fri, 17 Aug 2007 20:18:05 -0300 |
parents | 6b4c332f241b |
children | c6f932d3e0f6 |
line wrap: on
line source
# common code for the convert extension import base64 import cPickle as pickle def encodeargs(args): def encodearg(s): lines = base64.encodestring(s) lines = [l.splitlines()[0] for l in lines] return ''.join(lines) s = pickle.dumps(args) return encodearg(s) def decodeargs(s): s = base64.decodestring(s) return pickle.loads(s) class NoRepo(Exception): pass class commit(object): def __init__(self, author, date, desc, parents, branch=None, rev=None): self.author = author self.date = date self.desc = desc self.parents = parents self.branch = branch self.rev = rev class converter_source(object): """Conversion source interface""" def __init__(self, ui, path, rev=None): """Initialize conversion source (or raise NoRepo("message") exception if path is not a valid repository)""" self.ui = ui self.path = path self.rev = rev self.encoding = 'utf-8' def setrevmap(self, revmap): """set the map of already-converted revisions""" pass def getheads(self): """Return a list of this repository's heads""" raise NotImplementedError() def getfile(self, name, rev): """Return file contents as a string""" raise NotImplementedError() def getmode(self, name, rev): """Return file mode, eg. '', 'x', or 'l'""" raise NotImplementedError() def getchanges(self, version): """Returns a tuple of (files, copies) Files is a sorted list of (filename, id) tuples for all files changed in version, where id is the source revision id of the file. copies is a dictionary of dest: source """ raise NotImplementedError() def getcommit(self, version): """Return the commit object for version""" raise NotImplementedError() def gettags(self): """Return the tags as a dictionary of name: revision""" raise NotImplementedError() def recode(self, s, encoding=None): if not encoding: encoding = self.encoding or 'utf-8' try: return s.decode(encoding).encode("utf-8") except: try: return s.decode("latin-1").encode("utf-8") except: return s.decode(encoding, "replace").encode("utf-8") class converter_sink(object): """Conversion sink (target) interface""" def __init__(self, ui, path): """Initialize conversion sink (or raise NoRepo("message") exception if path is not a valid repository)""" raise NotImplementedError() def getheads(self): """Return a list of this repository's heads""" raise NotImplementedError() def revmapfile(self): """Path to a file that will contain lines source_rev_id sink_rev_id mapping equivalent revision identifiers for each system.""" raise NotImplementedError() def authorfile(self): """Path to a file that will contain lines srcauthor=dstauthor mapping equivalent authors identifiers for each system.""" return None def putfile(self, f, e, data): """Put file for next putcommit(). f: path to file e: '', 'x', or 'l' (regular file, executable, or symlink) data: file contents""" raise NotImplementedError() def delfile(self, f): """Delete file for next putcommit(). f: path to file""" raise NotImplementedError() def putcommit(self, files, parents, commit): """Create a revision with all changed files listed in 'files' and having listed parents. 'commit' is a commit object containing at a minimum the author, date, and message for this changeset. Called after putfile() and delfile() calls. Note that the sink repository is not told to update itself to a particular revision (or even what that revision would be) before it receives the file data.""" raise NotImplementedError() def puttags(self, tags): """Put tags into sink. tags: {tagname: sink_rev_id, ...}""" raise NotImplementedError() def setbranch(self, branch, pbranch, parents): """Set the current branch name. Called before the first putfile on the branch. branch: branch name for subsequent commits pbranch: branch name of parent commit parents: destination revisions of parent""" pass