common.py
368 lines
| 11.3 KiB
| text/x-python
|
PythonLexer
Martin Geisler
|
r8250 | # common.py - common code for the convert extension | ||
# | ||||
# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2, incorporated herein by reference. | ||||
Bryan O'Sullivan
|
r5510 | import base64, errno | ||
Maxim Dounin
|
r5832 | import os | ||
Patrick Mezard
|
r5127 | import cPickle as pickle | ||
Patrick Mezard
|
r7073 | from mercurial import util, strutil | ||
Bryan O'Sullivan
|
r5513 | from mercurial.i18n import _ | ||
Patrick Mezard
|
r5127 | |||
def encodeargs(args): | ||||
def encodearg(s): | ||||
lines = base64.encodestring(s) | ||||
lines = [l.splitlines()[0] for l in lines] | ||||
return ''.join(lines) | ||||
Thomas Arendsen Hein
|
r5143 | |||
Patrick Mezard
|
r5127 | s = pickle.dumps(args) | ||
return encodearg(s) | ||||
def decodeargs(s): | ||||
s = base64.decodestring(s) | ||||
return pickle.loads(s) | ||||
Brendan Cully
|
r4536 | |||
Patrick Mezard
|
r6332 | class MissingTool(Exception): pass | ||
def checktool(exe, name=None, abort=True): | ||||
Patrick Mezard
|
r5497 | name = name or exe | ||
if not util.find_exe(exe): | ||||
Patrick Mezard
|
r6332 | exc = abort and util.Abort or MissingTool | ||
raise exc(_('cannot find required "%s" tool') % name) | ||||
Patrick Mezard
|
r5497 | |||
Brendan Cully
|
r4536 | class NoRepo(Exception): pass | ||
Alexis S. L. Carvalho
|
r5400 | SKIPREV = 'SKIP' | ||
Alexis S. L. Carvalho
|
r5374 | |||
Brendan Cully
|
r4536 | class commit(object): | ||
Bryan O'Sullivan
|
r5439 | def __init__(self, author, date, desc, parents, branch=None, rev=None, | ||
extra={}): | ||||
Alexis S. L. Carvalho
|
r5984 | self.author = author or 'unknown' | ||
self.date = date or '0 0' | ||||
Bryan O'Sullivan
|
r5024 | self.desc = desc | ||
Bryan O'Sullivan
|
r5012 | self.parents = parents | ||
self.branch = branch | ||||
self.rev = rev | ||||
Bryan O'Sullivan
|
r5439 | self.extra = extra | ||
Brendan Cully
|
r4536 | |||
class converter_source(object): | ||||
"""Conversion source interface""" | ||||
Bryan O'Sullivan
|
r5556 | def __init__(self, ui, path=None, rev=None): | ||
Brendan Cully
|
r4536 | """Initialize conversion source (or raise NoRepo("message") | ||
exception if path is not a valid repository)""" | ||||
Brendan Cully
|
r4810 | self.ui = ui | ||
self.path = path | ||||
self.rev = rev | ||||
self.encoding = 'utf-8' | ||||
Brendan Cully
|
r4812 | |||
Bryan O'Sullivan
|
r5356 | def before(self): | ||
pass | ||||
def after(self): | ||||
pass | ||||
Bryan O'Sullivan
|
r5510 | def setrevmap(self, revmap): | ||
"""set the map of already-converted revisions""" | ||||
Brendan Cully
|
r4813 | pass | ||
Brendan Cully
|
r4536 | |||
def getheads(self): | ||||
"""Return a list of this repository's heads""" | ||||
raise NotImplementedError() | ||||
def getfile(self, name, rev): | ||||
Patrick Mezard
|
r7055 | """Return file contents as a string. rev is the identifier returned | ||
Greg Ward
|
r8444 | by a previous call to getchanges(). Raise IOError to indicate that | ||
name was deleted in rev. | ||||
Patrick Mezard
|
r7055 | """ | ||
Brendan Cully
|
r4536 | raise NotImplementedError() | ||
def getmode(self, name, rev): | ||||
Patrick Mezard
|
r7055 | """Return file mode, eg. '', 'x', or 'l'. rev is the identifier | ||
returned by a previous call to getchanges(). | ||||
""" | ||||
Brendan Cully
|
r4536 | raise NotImplementedError() | ||
def getchanges(self, version): | ||||
Dirkjan Ochtman
|
r7186 | """Returns a tuple of (files, copies). | ||
Patrick Mezard
|
r7055 | |||
files is a sorted list of (filename, id) tuples for all files | ||||
Greg Ward
|
r8444 | changed between version and its first parent returned by | ||
Patrick Mezard
|
r7055 | getcommit(). id is the source revision id of the file. | ||
Brendan Cully
|
r4536 | |||
Brendan Cully
|
r5121 | copies is a dictionary of dest: source | ||
""" | ||||
Brendan Cully
|
r4536 | 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() | ||||
Brendan Cully
|
r4759 | def recode(self, s, encoding=None): | ||
if not encoding: | ||||
Brendan Cully
|
r4810 | encoding = self.encoding or 'utf-8' | ||
Thomas Arendsen Hein
|
r4957 | |||
Thomas Arendsen Hein
|
r5287 | if isinstance(s, unicode): | ||
return s.encode("utf-8") | ||||
Brendan Cully
|
r4759 | 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") | ||||
Alexis S. L. Carvalho
|
r5377 | def getchangedfiles(self, rev, i): | ||
"""Return the files changed by rev compared to parent[i]. | ||||
Thomas Arendsen Hein
|
r5760 | |||
Alexis S. L. Carvalho
|
r5377 | i is an index selecting one of the parents of rev. The return | ||
value should be the list of files that are different in rev and | ||||
this parent. | ||||
If rev has no parents, i is None. | ||||
Thomas Arendsen Hein
|
r5760 | |||
Alexis S. L. Carvalho
|
r5377 | This function is only needed to support --filemap | ||
""" | ||||
raise NotImplementedError() | ||||
Bryan O'Sullivan
|
r5554 | def converted(self, rev, sinkrev): | ||
'''Notify the source that a revision has been converted.''' | ||||
pass | ||||
Brendan Cully
|
r4536 | class converter_sink(object): | ||
"""Conversion sink (target) interface""" | ||||
def __init__(self, ui, path): | ||||
"""Initialize conversion sink (or raise NoRepo("message") | ||||
Bryan O'Sullivan
|
r5441 | exception if path is not a valid repository) | ||
created is a list of paths to remove if a fatal error occurs | ||||
later""" | ||||
self.ui = ui | ||||
Bryan O'Sullivan
|
r5440 | self.path = path | ||
Bryan O'Sullivan
|
r5441 | self.created = [] | ||
Brendan Cully
|
r4536 | |||
def getheads(self): | ||||
"""Return a list of this repository's heads""" | ||||
raise NotImplementedError() | ||||
Bryan O'Sullivan
|
r5011 | def revmapfile(self): | ||
Brendan Cully
|
r4536 | """Path to a file that will contain lines | ||
source_rev_id sink_rev_id | ||||
mapping equivalent revision identifiers for each system.""" | ||||
raise NotImplementedError() | ||||
Edouard Gomez
|
r4589 | def authorfile(self): | ||
"""Path to a file that will contain lines | ||||
srcauthor=dstauthor | ||||
mapping equivalent authors identifiers for each system.""" | ||||
Brendan Cully
|
r4590 | return None | ||
Edouard Gomez
|
r4589 | |||
Patrick Mezard
|
r6716 | def putcommit(self, files, copies, parents, commit, source): | ||
Brendan Cully
|
r4536 | """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. | ||||
Patrick Mezard
|
r6716 | 'files' is a list of (path, version) tuples, 'copies'is a dictionary | ||
mapping destinations to sources, and 'source' is the source repository. | ||||
Only getfile() and getmode() should be called on 'source'. | ||||
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. | ||||
""" | ||||
Brendan Cully
|
r4536 | raise NotImplementedError() | ||
def puttags(self, tags): | ||||
"""Put tags into sink. | ||||
tags: {tagname: sink_rev_id, ...}""" | ||||
raise NotImplementedError() | ||||
Patrick Mezard
|
r5127 | |||
Patrick Mezard
|
r5934 | def setbranch(self, branch, pbranches): | ||
Patrick Mezard
|
r6716 | """Set the current branch name. Called before the first putcommit | ||
Brendan Cully
|
r5173 | on the branch. | ||
branch: branch name for subsequent commits | ||||
Patrick Mezard
|
r5934 | pbranches: (converted parent revision, parent branch) tuples""" | ||
Brendan Cully
|
r5173 | pass | ||
Alexis S. L. Carvalho
|
r5378 | |||
def setfilemapmode(self, active): | ||||
"""Tell the destination that we're using a filemap | ||||
Some converter_sources (svn in particular) can claim that a file | ||||
was changed in a revision, even if there was no change. This method | ||||
tells the destination that we're using a filemap and that it should | ||||
filter empty revisions. | ||||
""" | ||||
pass | ||||
Bryan O'Sullivan
|
r5510 | |||
Bryan O'Sullivan
|
r5512 | def before(self): | ||
pass | ||||
def after(self): | ||||
pass | ||||
class commandline(object): | ||||
def __init__(self, ui, command): | ||||
self.ui = ui | ||||
self.command = command | ||||
def prerun(self): | ||||
pass | ||||
def postrun(self): | ||||
pass | ||||
Maxim Dounin
|
r5832 | def _cmdline(self, cmd, *args, **kwargs): | ||
Bryan O'Sullivan
|
r5512 | cmdline = [self.command, cmd] + list(args) | ||
for k, v in kwargs.iteritems(): | ||||
if len(k) == 1: | ||||
cmdline.append('-' + k) | ||||
else: | ||||
cmdline.append('--' + k.replace('_', '-')) | ||||
try: | ||||
if len(k) == 1: | ||||
cmdline.append('' + v) | ||||
else: | ||||
cmdline[-1] += '=' + v | ||||
except TypeError: | ||||
pass | ||||
cmdline = [util.shellquote(arg) for arg in cmdline] | ||||
Patrick Mezard
|
r7611 | if not self.ui.debugflag: | ||
Bryan O'Sullivan
|
r7610 | cmdline += ['2>', util.nulldev] | ||
cmdline += ['<', util.nulldev] | ||||
Patrick Mezard
|
r5529 | cmdline = ' '.join(cmdline) | ||
Maxim Dounin
|
r5832 | return cmdline | ||
Bryan O'Sullivan
|
r5512 | |||
Maxim Dounin
|
r5832 | def _run(self, cmd, *args, **kwargs): | ||
cmdline = self._cmdline(cmd, *args, **kwargs) | ||||
Martin Geisler
|
r6956 | self.ui.debug(_('running: %s\n') % (cmdline,)) | ||
Bryan O'Sullivan
|
r5512 | self.prerun() | ||
try: | ||||
return util.popen(cmdline) | ||||
finally: | ||||
self.postrun() | ||||
def run(self, cmd, *args, **kwargs): | ||||
fp = self._run(cmd, *args, **kwargs) | ||||
output = fp.read() | ||||
self.ui.debug(output) | ||||
return output, fp.close() | ||||
Aleix Conchillo Flaque
|
r6035 | def runlines(self, cmd, *args, **kwargs): | ||
fp = self._run(cmd, *args, **kwargs) | ||||
output = fp.readlines() | ||||
Aleix Conchillo Flaque
|
r6049 | self.ui.debug(''.join(output)) | ||
Aleix Conchillo Flaque
|
r6035 | return output, fp.close() | ||
Bryan O'Sullivan
|
r5512 | def checkexit(self, status, output=''): | ||
if status: | ||||
if output: | ||||
self.ui.warn(_('%s error:\n') % self.command) | ||||
self.ui.warn(output) | ||||
msg = util.explain_exit(status)[0] | ||||
raise util.Abort(_('%s %s') % (self.command, msg)) | ||||
def run0(self, cmd, *args, **kwargs): | ||||
output, status = self.run(cmd, *args, **kwargs) | ||||
self.checkexit(status, output) | ||||
return output | ||||
Aleix Conchillo Flaque
|
r6035 | def runlines0(self, cmd, *args, **kwargs): | ||
output, status = self.runlines(cmd, *args, **kwargs) | ||||
Aleix Conchillo Flaque
|
r6049 | self.checkexit(status, ''.join(output)) | ||
Aleix Conchillo Flaque
|
r6035 | return output | ||
Maxim Dounin
|
r5832 | def getargmax(self): | ||
if '_argmax' in self.__dict__: | ||||
return self._argmax | ||||
# POSIX requires at least 4096 bytes for ARG_MAX | ||||
self._argmax = 4096 | ||||
try: | ||||
self._argmax = os.sysconf("SC_ARG_MAX") | ||||
except: | ||||
pass | ||||
# Windows shells impose their own limits on command line length, | ||||
# down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes | ||||
# for older 4nt.exe. See http://support.microsoft.com/kb/830473 for | ||||
# details about cmd.exe limitations. | ||||
# Since ARG_MAX is for command line _and_ environment, lower our limit | ||||
# (and make happy Windows shells while doing this). | ||||
self._argmax = self._argmax/2 - 1 | ||||
return self._argmax | ||||
def limit_arglist(self, arglist, cmd, *args, **kwargs): | ||||
limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs)) | ||||
bytes = 0 | ||||
fl = [] | ||||
for fn in arglist: | ||||
b = len(fn) + 3 | ||||
if bytes + b < limit or len(fl) == 0: | ||||
fl.append(fn) | ||||
bytes += b | ||||
else: | ||||
yield fl | ||||
fl = [fn] | ||||
bytes = b | ||||
if fl: | ||||
yield fl | ||||
def xargs(self, arglist, cmd, *args, **kwargs): | ||||
for l in self.limit_arglist(arglist, cmd, *args, **kwargs): | ||||
self.run0(cmd, *(list(args) + l), **kwargs) | ||||
Bryan O'Sullivan
|
r5510 | |||
class mapfile(dict): | ||||
def __init__(self, ui, path): | ||||
super(mapfile, self).__init__() | ||||
self.ui = ui | ||||
self.path = path | ||||
self.fp = None | ||||
self.order = [] | ||||
self._read() | ||||
def _read(self): | ||||
Stefan Rusek
|
r7774 | if not self.path: | ||
Bryan O'Sullivan
|
r5996 | return | ||
Bryan O'Sullivan
|
r5510 | try: | ||
fp = open(self.path, 'r') | ||||
except IOError, err: | ||||
if err.errno != errno.ENOENT: | ||||
raise | ||||
return | ||||
Patrick Mezard
|
r8047 | for i, line in enumerate(fp): | ||
try: | ||||
Martin Geisler
|
r8155 | key, value = line[:-1].rsplit(' ', 1) | ||
Patrick Mezard
|
r8047 | except ValueError: | ||
raise util.Abort(_('syntax error in %s(%d): key/value pair expected') | ||||
% (self.path, i+1)) | ||||
Bryan O'Sullivan
|
r5510 | if key not in self: | ||
self.order.append(key) | ||||
super(mapfile, self).__setitem__(key, value) | ||||
fp.close() | ||||
Thomas Arendsen Hein
|
r5760 | |||
Bryan O'Sullivan
|
r5510 | def __setitem__(self, key, value): | ||
if self.fp is None: | ||||
try: | ||||
self.fp = open(self.path, 'a') | ||||
except IOError, err: | ||||
raise util.Abort(_('could not open map file %r: %s') % | ||||
(self.path, err.strerror)) | ||||
self.fp.write('%s %s\n' % (key, value)) | ||||
self.fp.flush() | ||||
super(mapfile, self).__setitem__(key, value) | ||||
def close(self): | ||||
Bryan O'Sullivan
|
r5512 | if self.fp: | ||
self.fp.close() | ||||
self.fp = None | ||||