##// END OF EJS Templates
rebase: add boolean config item rebase.store-source...
rebase: add boolean config item rebase.store-source This allows to use rebase without recording a rebase_source extra field. This is useful for example to build a mirror converted from another SCM (such as svn) by converting only new revisions, and then incrementally add them to the destination by pulling from the newly converted (unrelated) repo and rebasing the new revisions onto the last old already stored changeset. Without this patch the rebased changesets would always receive some rebase_source that would depend on the particular history of the conversion process, instead of only depending on the original source revisions. This is used to implement a hg mirror repo of SvarDOS (a partially nonfree but completely redistributable DOS distribution) in the scripts at https://hg.pushbx.org/ecm/svardos.scr/ In particular, cre.sh creates an svn mirror, upd.sh recreates an entire hg repo from the svn mirror (which takes too long to do in a regular job), and akt.sh uses hg convert with the config item convert.svn.startrev to incrementally convert only the two most recent revisions already found in the mirror destination plus any possible new revisions. If any are found, the temporary repo's changesets are pulled into the destination (as changesets from an unrelated repository). Then the changesets corresponding to the new revisions are rebased onto the prior final changeset. (Finally, the two remaining duplicates of the prior head and its parent are stripped from the destination repository.) Without this patch, the particular rebase_source extra field would depend on the order and times at which akt.sh was used, instead of only depending on the source repository. In other words, whatever sequence of upd.sh and akt.sh is used at whatever times, it is desired that the final output repositories always match each other exactly.

File last commit:

r50267:270f8e89 stable
r50343:cfff73ca default
Show More
darcs.py
223 lines | 7.6 KiB | text/x-python | PythonLexer
# darcs.py - darcs support for the convert extension
#
# Copyright 2007-2009 Olivia Mackall <olivia@selenic.com> and others
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
import os
import re
import shutil
from xml.etree.ElementTree import (
ElementTree,
XMLParser,
)
from mercurial.i18n import _
from mercurial import (
error,
pycompat,
util,
)
from mercurial.utils import dateutil
from . import common
NoRepo = common.NoRepo
class darcs_source(common.converter_source, common.commandline):
def __init__(self, ui, repotype, path, revs=None):
common.converter_source.__init__(self, ui, repotype, path, revs=revs)
common.commandline.__init__(self, ui, b'darcs')
# check for _darcs, ElementTree so that we can easily skip
# test-convert-darcs if ElementTree is not around
if not os.path.exists(os.path.join(path, b'_darcs')):
raise NoRepo(_(b"%s does not look like a darcs repository") % path)
common.checktool(b'darcs')
version = self.run0(b'--version').splitlines()[0].strip()
if version < b'2.1':
raise error.Abort(
_(b'darcs version 2.1 or newer needed (found %r)') % version
)
if "ElementTree" not in globals():
raise error.Abort(_(b"Python ElementTree module is not available"))
self.path = os.path.realpath(path)
self.lastrev = None
self.changes = {}
self.parents = {}
self.tags = {}
# Check darcs repository format
format = self.format()
if format:
if format in (b'darcs-1.0', b'hashed'):
raise NoRepo(
_(
b"%s repository format is unsupported, "
b"please upgrade"
)
% format
)
else:
self.ui.warn(_(b'failed to detect repository format!'))
def before(self):
self.tmppath = pycompat.mkdtemp(
prefix=b'convert-' + os.path.basename(self.path) + b'-'
)
output, status = self.run(b'init', repodir=self.tmppath)
self.checkexit(status)
tree = self.xml(
b'changes', xml_output=True, summary=True, repodir=self.path
)
tagname = None
child = None
for elt in tree.findall('patch'):
node = self.recode(elt.get('hash'))
name = self.recode(elt.findtext('name', ''))
if name.startswith(b'TAG '):
tagname = name[4:].strip()
elif tagname is not None:
self.tags[tagname] = node
tagname = None
self.changes[node] = elt
self.parents[child] = [node]
child = node
self.parents[child] = []
def after(self):
self.ui.debug(b'cleaning up %s\n' % self.tmppath)
shutil.rmtree(self.tmppath, ignore_errors=True)
def recode(self, s, encoding=None):
if isinstance(s, str):
# XMLParser returns unicode objects for anything it can't
# encode into ASCII. We convert them back to str to get
# recode's normal conversion behavior.
s = s.encode('latin-1')
return super(darcs_source, self).recode(s, encoding)
def xml(self, cmd, **kwargs):
# NOTE: darcs is currently encoding agnostic and will print
# patch metadata byte-for-byte, even in the XML changelog.
etree = ElementTree()
# While we are decoding the XML as latin-1 to be as liberal as
# possible, etree will still raise an exception if any
# non-printable characters are in the XML changelog.
parser = XMLParser(encoding='latin-1')
p = self._run(cmd, **kwargs)
etree.parse(p.stdout, parser=parser)
p.wait()
self.checkexit(p.returncode)
return etree.getroot()
def format(self):
output, status = self.run(b'show', b'repo', repodir=self.path)
self.checkexit(status)
m = re.search(br'^\s*Format:\s*(.*)$', output, re.MULTILINE)
if not m:
return None
return b','.join(sorted(f.strip() for f in m.group(1).split(b',')))
def manifest(self):
man = []
output, status = self.run(
b'show', b'files', no_directories=True, repodir=self.tmppath
)
self.checkexit(status)
for line in output.split(b'\n'):
path = line[2:]
if path:
man.append(path)
return man
def getheads(self):
return self.parents[None]
def getcommit(self, rev):
elt = self.changes[rev]
dateformat = b'%a %b %d %H:%M:%S %Z %Y'
date = dateutil.strdate(elt.get('local_date'), dateformat)
desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
# etree can return unicode objects for name, comment, and author,
# so recode() is used to ensure str objects are emitted.
newdateformat = b'%Y-%m-%d %H:%M:%S %1%2'
return common.commit(
author=self.recode(elt.get('author')),
date=dateutil.datestr(date, newdateformat),
desc=self.recode(desc).strip(),
parents=self.parents[rev],
)
def pull(self, rev):
output, status = self.run(
b'pull',
self.path,
all=True,
match=b'hash %s' % self.recode(rev),
no_test=True,
no_posthook=True,
external_merge=b'/bin/false',
repodir=self.tmppath,
)
if status:
if output.find(b'We have conflicts in') == -1:
self.checkexit(status, output)
output, status = self.run(b'revert', all=True, repodir=self.tmppath)
self.checkexit(status, output)
def getchanges(self, rev, full):
if full:
raise error.Abort(_(b"convert from darcs does not support --full"))
copies = {}
changes = []
man = None
for elt in self.changes[rev].find('summary'):
if elt.tag in ('add_directory', 'remove_directory'):
continue
if elt.tag == 'move':
if man is None:
man = self.manifest()
source = self.recode(elt.get('from'))
dest = self.recode(elt.get('to'))
if source in man:
# File move
changes.append((source, rev))
changes.append((dest, rev))
copies[dest] = source
else:
# Directory move, deduce file moves from manifest
source = source + b'/'
for f in man:
if not f.startswith(source):
continue
fdest = dest + b'/' + f[len(source) :]
changes.append((f, rev))
changes.append((fdest, rev))
copies[fdest] = f
else:
changes.append((self.recode(elt.text.strip()), rev))
self.pull(rev)
self.lastrev = rev
return sorted(changes), copies, set()
def getfile(self, name, rev):
if rev != self.lastrev:
raise error.Abort(_(b'internal calling inconsistency'))
path = os.path.join(self.tmppath, name)
try:
data = util.readfile(path)
mode = os.lstat(path).st_mode
except FileNotFoundError:
return None, None
mode = (mode & 0o111) and b'x' or b''
return data, mode
def gettags(self):
return self.tags