Preserve bzr commit metadata (as commit message trailers). XXX: Use git notes instead of appending trailers to commit messages! XXX: Does not preserve dotted revnos of merged unsquashed commits. diff -ur fastimport-virgin/exporter.py fastimport/exporter.py --- fastimport-virgin/exporter.py 2017-03-14 08:30:30.000000000 -0600 +++ fastimport/exporter.py 2017-03-14 13:23:56.000000000 -0600 @@ -319,7 +319,7 @@ mark = 1 self.revid_to_mark[revid] = mark file_cmds = self._get_filecommands(bzrlib.revision.NULL_REVISION, revid) - self.print_cmd(self._get_commit_command(ref, mark, revobj, file_cmds)) + self.print_cmd(self._get_commit_command(ref, mark, revobj, file_cmds, revid)); def emit_commit(self, revid, ref): if revid in self.revid_to_mark or revid in self.excluded_revisions: @@ -355,7 +355,8 @@ mark = ncommits + 1 self.revid_to_mark[revid] = mark file_cmds = self._get_filecommands(parent, revid) - self.print_cmd(self._get_commit_command(ref, mark, revobj, file_cmds)) + #sys.stderr.write("parent revid: %s %s\n" % (parent, revid)) + self.print_cmd(self._get_commit_command(ref, mark, revobj, file_cmds, revid)); # Report progress and checkpoint if it's time for that self.report_progress(ncommits) @@ -377,7 +378,15 @@ name, email = parseaddr(user) return name.encode("utf-8"), email.encode("utf-8") - def _get_commit_command(self, git_ref, mark, revobj, file_cmds): + def _revid_to_revno(self, revid): + # XXX: The callers do not know how to get revno for merged revisions + # because those revisions do not belong to self.branch. + try: + return self.branch.revision_id_to_revno(revid); + except: + return None; + + def _get_commit_command(self, git_ref, mark, revobj, file_cmds, revid): # Get the committer and author info committer = revobj.committer name, email = self._get_name_email(committer) @@ -427,6 +436,63 @@ # don't repeat it here. if self.plain_format: properties = None + trailer = "" + revno = self._revid_to_revno(revid); + if revno: + trailer += ("bzr::revno: %s\n" % revno); + #sys.stderr.write("properties: %s\n" % revobj.properties) + if not revobj.properties["branch-nick"]: + raise RuntimeError("missing branch-nick in %s" % revobj.properties); + for propName, propValue in revobj.properties.items(): + if propName == "author": + raise RuntimeError("unexpected author property: %s\n" % revobj.properties) + + elif propName == "authors": + # handled further below using pre-computed more_authors + if not more_authors and len(propValue.split("\n")) > 1: + raise RuntimeError("missing more_authors for authors property: %s\n" % revobj.properties) + + elif propName == "branch-nick": + # TODO: Exclude HEAD and trunk? + trailer += ("bzr::branch-nick: %s\n" % propValue) + + elif propName == "bugs": + # https://bugs.launchpad.net/bzr-fastimport/+bug/1606973 + # property bugs 54 http://bugs.squid-cache.org/show_bug.cgi?id=3728 fixed + fixed = [] + for bugstatement in propValue.split("\n"): + bugurl, comment = bugstatement.split() + if not bugurl.startswith("http://bugs.squid-cache.org/show_bug.cgi?id="): + raise RuntimeError("unknown bug url: %s\n" % bugurl) + if comment == "fixed": + fixed.append(bugurl) + else: + raise RuntimeError("unknown bug action: %s\n" % comment) + + if not fixed: + raise RuntimeError("empty bugs: %s\n" % bugstatement) + + trailer += ("bzr::fixes: %s\n" % ', '.join(fixed)) + + elif propName == "rebase-of": + continue # TODO: Anything we can do about it? + + else: + # XXX: we need to iterate and check all properties! + raise RuntimeError("unknown property: %s\n" % revobj.properties) + + if more_authors: + for auth in more_authors: + trailer += ("bzr::co-author: %s" % auth) + + if trailer: + # separate commit message and trailer with exactly one empty line, + # assuming the commit message has no, one, or two new lines. + if not revobj.message.endswith("\n"): + revobj.message += "\n\n"; + elif not revobj.message.endswith("\n\n"): + revobj.message += "\n"; + revobj.message += trailer; else: properties = revobj.properties for prop in self.properties_to_exclude: