gnuarch.py
342 lines
| 12.5 KiB
| text/x-python
|
PythonLexer
Martin Geisler
|
r8250 | # gnuarch.py - GNU Arch support for the convert extension | ||
# | ||||
# Copyright 2008, 2009 Aleix Conchillo Flaque <aleix@member.fsf.org> | ||||
# and others | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Aleix Conchillo Flaque
|
r6035 | |||
Joel Rosdahl
|
r6212 | from common import NoRepo, commandline, commit, converter_source | ||
Aleix Conchillo Flaque
|
r6035 | from mercurial.i18n import _ | ||
from mercurial import util | ||||
Edouard Gomez
|
r7579 | import os, shutil, tempfile, stat, locale | ||
Edouard Gomez
|
r7578 | from email.Parser import Parser | ||
Aleix Conchillo Flaque
|
r6035 | |||
class gnuarch_source(converter_source, commandline): | ||||
Benoit Boissinot
|
r8778 | class gnuarch_rev(object): | ||
Aleix Conchillo Flaque
|
r6035 | def __init__(self, rev): | ||
self.rev = rev | ||||
self.summary = '' | ||||
self.date = None | ||||
self.author = '' | ||||
Edouard Gomez
|
r7583 | self.continuationof = None | ||
Aleix Conchillo Flaque
|
r6035 | self.add_files = [] | ||
self.mod_files = [] | ||||
self.del_files = [] | ||||
self.ren_files = {} | ||||
self.ren_dirs = {} | ||||
def __init__(self, ui, path, rev=None): | ||||
super(gnuarch_source, self).__init__(ui, path, rev=rev) | ||||
if not os.path.exists(os.path.join(path, '{arch}')): | ||||
Martin Geisler
|
r6913 | raise NoRepo(_("%s does not look like a GNU Arch repo") % path) | ||
Aleix Conchillo Flaque
|
r6035 | |||
# Could use checktool, but we want to check for baz or tla. | ||||
self.execmd = None | ||||
Patrick Mezard
|
r6083 | if util.find_exe('baz'): | ||
self.execmd = 'baz' | ||||
Aleix Conchillo Flaque
|
r6035 | else: | ||
Patrick Mezard
|
r6083 | if util.find_exe('tla'): | ||
self.execmd = 'tla' | ||||
Aleix Conchillo Flaque
|
r6035 | else: | ||
raise util.Abort(_('cannot find a GNU Arch tool')) | ||||
commandline.__init__(self, ui, self.execmd) | ||||
self.path = os.path.realpath(path) | ||||
self.tmppath = None | ||||
self.treeversion = None | ||||
self.lastrev = None | ||||
self.changes = {} | ||||
self.parents = {} | ||||
self.tags = {} | ||||
self.modecache = {} | ||||
Edouard Gomez
|
r7578 | self.catlogparser = Parser() | ||
Edouard Gomez
|
r7579 | self.locale = locale.getpreferredencoding() | ||
Edouard Gomez
|
r7584 | self.archives = [] | ||
Aleix Conchillo Flaque
|
r6035 | |||
def before(self): | ||||
Edouard Gomez
|
r7584 | # Get registered archives | ||
self.archives = [i.rstrip('\n') | ||||
for i in self.runlines0('archives', '-n')] | ||||
Aleix Conchillo Flaque
|
r6035 | if self.execmd == 'tla': | ||
output = self.run0('tree-version', self.path) | ||||
else: | ||||
output = self.run0('tree-version', '-d', self.path) | ||||
self.treeversion = output.strip() | ||||
# Get name of temporary directory | ||||
version = self.treeversion.split('/') | ||||
self.tmppath = os.path.join(tempfile.gettempdir(), | ||||
'hg-%s' % version[1]) | ||||
# Generate parents dictionary | ||||
Edouard Gomez
|
r7585 | self.parents[None] = [] | ||
treeversion = self.treeversion | ||||
child = None | ||||
while treeversion: | ||||
self.ui.status(_('analyzing tree version %s...\n') % treeversion) | ||||
archive = treeversion.split('/')[0] | ||||
if archive not in self.archives: | ||||
Martin Geisler
|
r8662 | self.ui.status(_('tree analysis stopped because it points to ' | ||
'an unregistered archive %s...\n') % archive) | ||||
Edouard Gomez
|
r7585 | break | ||
# Get the complete list of revisions for that tree version | ||||
output, status = self.runlines('revisions', '-r', '-f', treeversion) | ||||
self.checkexit(status, 'failed retrieveing revisions for %s' % treeversion) | ||||
# No new iteration unless a revision has a continuation-of header | ||||
treeversion = None | ||||
for l in output: | ||||
rev = l.strip() | ||||
self.changes[rev] = self.gnuarch_rev(rev) | ||||
self.parents[rev] = [] | ||||
Aleix Conchillo Flaque
|
r6035 | |||
Edouard Gomez
|
r7585 | # Read author, date and summary | ||
catlog, status = self.run('cat-log', '-d', self.path, rev) | ||||
if status: | ||||
catlog = self.run0('cat-archive-log', rev) | ||||
self._parsecatlog(catlog, rev) | ||||
# Populate the parents map | ||||
self.parents[child].append(rev) | ||||
Aleix Conchillo Flaque
|
r6035 | |||
Edouard Gomez
|
r7585 | # Keep track of the current revision as the child of the next | ||
# revision scanned | ||||
child = rev | ||||
# Check if we have to follow the usual incremental history | ||||
# or if we have to 'jump' to a different treeversion given | ||||
# by the continuation-of header. | ||||
if self.changes[rev].continuationof: | ||||
treeversion = '--'.join(self.changes[rev].continuationof.split('--')[:-1]) | ||||
break | ||||
# If we reached a base-0 revision w/o any continuation-of | ||||
# header, it means the tree history ends here. | ||||
if rev[-6:] == 'base-0': | ||||
break | ||||
Aleix Conchillo Flaque
|
r6035 | |||
def after(self): | ||||
Martin Geisler
|
r9467 | self.ui.debug('cleaning up %s\n' % self.tmppath) | ||
Aleix Conchillo Flaque
|
r6035 | shutil.rmtree(self.tmppath, ignore_errors=True) | ||
def getheads(self): | ||||
return self.parents[None] | ||||
def getfile(self, name, rev): | ||||
if rev != self.lastrev: | ||||
raise util.Abort(_('internal calling inconsistency')) | ||||
# Raise IOError if necessary (i.e. deleted files). | ||||
if not os.path.exists(os.path.join(self.tmppath, name)): | ||||
raise IOError | ||||
data, mode = self._getfile(name, rev) | ||||
self.modecache[(name, rev)] = mode | ||||
return data | ||||
def getmode(self, name, rev): | ||||
return self.modecache[(name, rev)] | ||||
def getchanges(self, rev): | ||||
self.modecache = {} | ||||
self._update(rev) | ||||
changes = [] | ||||
copies = {} | ||||
for f in self.changes[rev].add_files: | ||||
changes.append((f, rev)) | ||||
for f in self.changes[rev].mod_files: | ||||
changes.append((f, rev)) | ||||
for f in self.changes[rev].del_files: | ||||
changes.append((f, rev)) | ||||
for src in self.changes[rev].ren_files: | ||||
to = self.changes[rev].ren_files[src] | ||||
changes.append((src, rev)) | ||||
changes.append((to, rev)) | ||||
Patrick Mezard
|
r7567 | copies[to] = src | ||
Aleix Conchillo Flaque
|
r6035 | |||
for src in self.changes[rev].ren_dirs: | ||||
to = self.changes[rev].ren_dirs[src] | ||||
chgs, cps = self._rendirchanges(src, to); | ||||
changes += [(f, rev) for f in chgs] | ||||
Patrick Mezard
|
r7567 | copies.update(cps) | ||
Aleix Conchillo Flaque
|
r6035 | |||
self.lastrev = rev | ||||
Matt Mackall
|
r8209 | return sorted(set(changes)), copies | ||
Aleix Conchillo Flaque
|
r6035 | |||
def getcommit(self, rev): | ||||
changes = self.changes[rev] | ||||
Martin Geisler
|
r8662 | return commit(author=changes.author, date=changes.date, | ||
desc=changes.summary, parents=self.parents[rev], rev=rev) | ||||
Aleix Conchillo Flaque
|
r6035 | |||
def gettags(self): | ||||
return self.tags | ||||
def _execute(self, cmd, *args, **kwargs): | ||||
cmdline = [self.execmd, cmd] | ||||
cmdline += args | ||||
cmdline = [util.shellquote(arg) for arg in cmdline] | ||||
cmdline += ['>', util.nulldev, '2>', util.nulldev] | ||||
cmdline = util.quotecommand(' '.join(cmdline)) | ||||
self.ui.debug(cmdline, '\n') | ||||
return os.system(cmdline) | ||||
def _update(self, rev): | ||||
Martin Geisler
|
r9467 | self.ui.debug('applying revision %s...\n' % rev) | ||
Edouard Gomez
|
r7585 | changeset, status = self.runlines('replay', '-d', self.tmppath, | ||
rev) | ||||
if status: | ||||
# Something went wrong while merging (baz or tla | ||||
# issue?), get latest revision and try from there | ||||
shutil.rmtree(self.tmppath, ignore_errors=True) | ||||
Aleix Conchillo Flaque
|
r6049 | self._obtainrevision(rev) | ||
Aleix Conchillo Flaque
|
r6035 | else: | ||
Edouard Gomez
|
r7585 | old_rev = self.parents[rev][0] | ||
Martin Geisler
|
r9467 | self.ui.debug('computing changeset between %s and %s...\n' | ||
Edouard Gomez
|
r7585 | % (old_rev, rev)) | ||
self._parsechangeset(changeset, rev) | ||||
Aleix Conchillo Flaque
|
r6035 | |||
def _getfile(self, name, rev): | ||||
mode = os.lstat(os.path.join(self.tmppath, name)).st_mode | ||||
if stat.S_ISLNK(mode): | ||||
data = os.readlink(os.path.join(self.tmppath, name)) | ||||
mode = mode and 'l' or '' | ||||
else: | ||||
data = open(os.path.join(self.tmppath, name), 'rb').read() | ||||
mode = (mode & 0111) and 'x' or '' | ||||
return data, mode | ||||
def _exclude(self, name): | ||||
exclude = [ '{arch}', '.arch-ids', '.arch-inventory' ] | ||||
for exc in exclude: | ||||
if name.find(exc) != -1: | ||||
return True | ||||
return False | ||||
def _readcontents(self, path): | ||||
files = [] | ||||
contents = os.listdir(path) | ||||
while len(contents) > 0: | ||||
c = contents.pop() | ||||
p = os.path.join(path, c) | ||||
Aleix Conchillo Flaque
|
r6044 | # os.walk could be used, but here we avoid internal GNU | ||
# Arch files and directories, thus saving a lot time. | ||||
Aleix Conchillo Flaque
|
r6035 | if not self._exclude(p): | ||
if os.path.isdir(p): | ||||
contents += [os.path.join(c, f) for f in os.listdir(p)] | ||||
else: | ||||
files.append(c) | ||||
return files | ||||
def _rendirchanges(self, src, dest): | ||||
changes = [] | ||||
copies = {} | ||||
files = self._readcontents(os.path.join(self.tmppath, dest)) | ||||
for f in files: | ||||
s = os.path.join(src, f) | ||||
d = os.path.join(dest, f) | ||||
changes.append(s) | ||||
changes.append(d) | ||||
Patrick Mezard
|
r7567 | copies[d] = s | ||
Aleix Conchillo Flaque
|
r6035 | return changes, copies | ||
Aleix Conchillo Flaque
|
r6049 | def _obtainrevision(self, rev): | ||
Martin Geisler
|
r9467 | self.ui.debug('obtaining revision %s...\n' % rev) | ||
Edouard Gomez
|
r7582 | output = self._execute('get', rev, self.tmppath) | ||
Aleix Conchillo Flaque
|
r6049 | self.checkexit(output) | ||
Martin Geisler
|
r9467 | self.ui.debug('analyzing revision %s...\n' % rev) | ||
Aleix Conchillo Flaque
|
r6049 | files = self._readcontents(self.tmppath) | ||
self.changes[rev].add_files += files | ||||
Aleix Conchillo Flaque
|
r6079 | def _stripbasepath(self, path): | ||
if path.startswith('./'): | ||||
return path[2:] | ||||
return path | ||||
Aleix Conchillo Flaque
|
r6035 | def _parsecatlog(self, data, rev): | ||
Edouard Gomez
|
r7578 | try: | ||
catlog = self.catlogparser.parsestr(data) | ||||
Edouard Gomez
|
r7592 | |||
# Commit date | ||||
Edouard Gomez
|
r7578 | self.changes[rev].date = util.datestr( | ||
util.strdate(catlog['Standard-date'], | ||||
'%Y-%m-%d %H:%M:%S')) | ||||
Edouard Gomez
|
r7592 | |||
# Commit author | ||||
self.changes[rev].author = self.recode(catlog['Creator']) | ||||
# Commit description | ||||
self.changes[rev].summary = '\n\n'.join((catlog['Summary'], | ||||
catlog.get_payload())) | ||||
self.changes[rev].summary = self.recode(self.changes[rev].summary) | ||||
# Commit revision origin when dealing with a branch or tag | ||||
Nicolas Dumazet
|
r9391 | if 'Continuation-of' in catlog: | ||
Edouard Gomez
|
r7592 | self.changes[rev].continuationof = self.recode(catlog['Continuation-of']) | ||
Peter Arrenbrecht
|
r7875 | except Exception: | ||
Edouard Gomez
|
r7578 | raise util.Abort(_('could not parse cat-log of %s') % rev) | ||
Aleix Conchillo Flaque
|
r6037 | |||
Aleix Conchillo Flaque
|
r6049 | def _parsechangeset(self, data, rev): | ||
Aleix Conchillo Flaque
|
r6035 | for l in data: | ||
l = l.strip() | ||||
Aleix Conchillo Flaque
|
r6055 | # Added file (ignore added directory) | ||
Aleix Conchillo Flaque
|
r6035 | if l.startswith('A') and not l.startswith('A/'): | ||
Aleix Conchillo Flaque
|
r6079 | file = self._stripbasepath(l[1:].strip()) | ||
Aleix Conchillo Flaque
|
r6035 | if not self._exclude(file): | ||
self.changes[rev].add_files.append(file) | ||||
Aleix Conchillo Flaque
|
r6055 | # Deleted file (ignore deleted directory) | ||
elif l.startswith('D') and not l.startswith('D/'): | ||||
Aleix Conchillo Flaque
|
r6079 | file = self._stripbasepath(l[1:].strip()) | ||
Aleix Conchillo Flaque
|
r6055 | if not self._exclude(file): | ||
self.changes[rev].del_files.append(file) | ||||
# Modified binary file | ||||
elif l.startswith('Mb'): | ||||
Aleix Conchillo Flaque
|
r6079 | file = self._stripbasepath(l[2:].strip()) | ||
Aleix Conchillo Flaque
|
r6055 | if not self._exclude(file): | ||
self.changes[rev].mod_files.append(file) | ||||
# Modified link | ||||
elif l.startswith('M->'): | ||||
Aleix Conchillo Flaque
|
r6079 | file = self._stripbasepath(l[3:].strip()) | ||
Aleix Conchillo Flaque
|
r6055 | if not self._exclude(file): | ||
self.changes[rev].mod_files.append(file) | ||||
# Modified file | ||||
elif l.startswith('M'): | ||||
Aleix Conchillo Flaque
|
r6079 | file = self._stripbasepath(l[1:].strip()) | ||
Aleix Conchillo Flaque
|
r6055 | if not self._exclude(file): | ||
self.changes[rev].mod_files.append(file) | ||||
# Renamed file (or link) | ||||
elif l.startswith('=>'): | ||||
files = l[2:].strip().split(' ') | ||||
if len(files) == 1: | ||||
files = l[2:].strip().split('\t') | ||||
Aleix Conchillo Flaque
|
r6079 | src = self._stripbasepath(files[0]) | ||
dst = self._stripbasepath(files[1]) | ||||
if not self._exclude(src) and not self._exclude(dst): | ||||
self.changes[rev].ren_files[src] = dst | ||||
Aleix Conchillo Flaque
|
r6055 | # Conversion from file to link or from link to file (modified) | ||
elif l.startswith('ch'): | ||||
Aleix Conchillo Flaque
|
r6079 | file = self._stripbasepath(l[2:].strip()) | ||
Aleix Conchillo Flaque
|
r6055 | if not self._exclude(file): | ||
self.changes[rev].mod_files.append(file) | ||||
# Renamed directory | ||||
Aleix Conchillo Flaque
|
r6035 | elif l.startswith('/>'): | ||
dirs = l[2:].strip().split(' ') | ||||
if len(dirs) == 1: | ||||
dirs = l[2:].strip().split('\t') | ||||
Aleix Conchillo Flaque
|
r6079 | src = self._stripbasepath(dirs[0]) | ||
dst = self._stripbasepath(dirs[1]) | ||||
if not self._exclude(src) and not self._exclude(dst): | ||||
self.changes[rev].ren_dirs[src] = dst | ||||