gnuarch.py
378 lines
| 13.0 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. | ||
timeless
|
r28366 | from __future__ import absolute_import | ||
Aleix Conchillo Flaque
|
r6035 | |||
timeless
|
r28366 | import os | ||
import shutil | ||||
import stat | ||||
import tempfile | ||||
Yuya Nishihara
|
r29205 | |||
from mercurial.i18n import _ | ||||
timeless
|
r28366 | from mercurial import ( | ||
encoding, | ||||
error, | ||||
Denis Laxalde
|
r43699 | mail, | ||
Matt Harbison
|
r39851 | pycompat, | ||
Matt Harbison
|
r39865 | util, | ||
timeless
|
r28366 | ) | ||
Yuya Nishihara
|
r37138 | from mercurial.utils import ( | ||
dateutil, | ||||
procutil, | ||||
) | ||||
timeless
|
r28366 | from . import common | ||
Aleix Conchillo Flaque
|
r6035 | |||
Augie Fackler
|
r43346 | |||
timeless
|
r28366 | class gnuarch_source(common.converter_source, common.commandline): | ||
Benoit Boissinot
|
r8778 | class gnuarch_rev(object): | ||
Aleix Conchillo Flaque
|
r6035 | def __init__(self, rev): | ||
self.rev = rev | ||||
Augie Fackler
|
r43347 | self.summary = b'' | ||
Aleix Conchillo Flaque
|
r6035 | self.date = None | ||
Augie Fackler
|
r43347 | self.author = b'' | ||
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 = {} | ||||
Matt Harbison
|
r35168 | def __init__(self, ui, repotype, path, revs=None): | ||
super(gnuarch_source, self).__init__(ui, repotype, path, revs=revs) | ||||
Aleix Conchillo Flaque
|
r6035 | |||
Augie Fackler
|
r43347 | if not os.path.exists(os.path.join(path, b'{arch}')): | ||
Augie Fackler
|
r43346 | raise common.NoRepo( | ||
Augie Fackler
|
r43347 | _(b"%s does not look like a GNU Arch repository") % path | ||
Augie Fackler
|
r43346 | ) | ||
Aleix Conchillo Flaque
|
r6035 | |||
# Could use checktool, but we want to check for baz or tla. | ||||
self.execmd = None | ||||
Augie Fackler
|
r43347 | if procutil.findexe(b'baz'): | ||
self.execmd = b'baz' | ||||
Aleix Conchillo Flaque
|
r6035 | else: | ||
Augie Fackler
|
r43347 | if procutil.findexe(b'tla'): | ||
self.execmd = b'tla' | ||||
Aleix Conchillo Flaque
|
r6035 | else: | ||
Augie Fackler
|
r43347 | raise error.Abort(_(b'cannot find a GNU Arch tool')) | ||
Aleix Conchillo Flaque
|
r6035 | |||
timeless
|
r28366 | common.commandline.__init__(self, ui, self.execmd) | ||
Aleix Conchillo Flaque
|
r6035 | |||
Matt Mackall
|
r15381 | self.path = os.path.realpath(path) | ||
Aleix Conchillo Flaque
|
r6035 | self.tmppath = None | ||
self.treeversion = None | ||||
self.lastrev = None | ||||
self.changes = {} | ||||
self.parents = {} | ||||
self.tags = {} | ||||
Brodie Rao
|
r11987 | self.encoding = encoding.encoding | ||
Edouard Gomez
|
r7584 | self.archives = [] | ||
Aleix Conchillo Flaque
|
r6035 | |||
def before(self): | ||||
Edouard Gomez
|
r7584 | # Get registered archives | ||
Augie Fackler
|
r43346 | self.archives = [ | ||
Augie Fackler
|
r43347 | i.rstrip(b'\n') for i in self.runlines0(b'archives', b'-n') | ||
Augie Fackler
|
r43346 | ] | ||
Edouard Gomez
|
r7584 | |||
Augie Fackler
|
r43347 | if self.execmd == b'tla': | ||
output = self.run0(b'tree-version', self.path) | ||||
Aleix Conchillo Flaque
|
r6035 | else: | ||
Augie Fackler
|
r43347 | output = self.run0(b'tree-version', b'-d', self.path) | ||
Aleix Conchillo Flaque
|
r6035 | self.treeversion = output.strip() | ||
# Get name of temporary directory | ||||
Augie Fackler
|
r43347 | version = self.treeversion.split(b'/') | ||
Augie Fackler
|
r43346 | self.tmppath = os.path.join( | ||
Augie Fackler
|
r43347 | pycompat.fsencode(tempfile.gettempdir()), b'hg-%s' % version[1] | ||
Augie Fackler
|
r43346 | ) | ||
Aleix Conchillo Flaque
|
r6035 | |||
# Generate parents dictionary | ||||
Edouard Gomez
|
r7585 | self.parents[None] = [] | ||
treeversion = self.treeversion | ||||
child = None | ||||
while treeversion: | ||||
Augie Fackler
|
r43347 | self.ui.status(_(b'analyzing tree version %s...\n') % treeversion) | ||
Edouard Gomez
|
r7585 | |||
Augie Fackler
|
r43347 | archive = treeversion.split(b'/')[0] | ||
Edouard Gomez
|
r7585 | if archive not in self.archives: | ||
Augie Fackler
|
r43346 | self.ui.status( | ||
_( | ||||
Augie Fackler
|
r43347 | b'tree analysis stopped because it points to ' | ||
b'an unregistered archive %s...\n' | ||||
Augie Fackler
|
r43346 | ) | ||
% archive | ||||
) | ||||
Edouard Gomez
|
r7585 | break | ||
# Get the complete list of revisions for that tree version | ||||
Augie Fackler
|
r43347 | output, status = self.runlines( | ||
b'revisions', b'-r', b'-f', treeversion | ||||
) | ||||
Augie Fackler
|
r43346 | self.checkexit( | ||
Augie Fackler
|
r43347 | status, b'failed retrieving revisions for %s' % treeversion | ||
Augie Fackler
|
r43346 | ) | ||
Edouard Gomez
|
r7585 | |||
# 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 | ||
Augie Fackler
|
r43347 | catlog, status = self.run(b'cat-log', b'-d', self.path, rev) | ||
Edouard Gomez
|
r7585 | if status: | ||
Augie Fackler
|
r43347 | catlog = self.run0(b'cat-archive-log', rev) | ||
Edouard Gomez
|
r7585 | 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: | ||||
Augie Fackler
|
r43347 | treeversion = b'--'.join( | ||
self.changes[rev].continuationof.split(b'--')[:-1] | ||||
Augie Fackler
|
r43346 | ) | ||
Edouard Gomez
|
r7585 | break | ||
# If we reached a base-0 revision w/o any continuation-of | ||||
# header, it means the tree history ends here. | ||||
Augie Fackler
|
r43347 | if rev[-6:] == b'base-0': | ||
Edouard Gomez
|
r7585 | break | ||
Aleix Conchillo Flaque
|
r6035 | |||
def after(self): | ||||
Augie Fackler
|
r43347 | self.ui.debug(b'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: | ||||
Augie Fackler
|
r43347 | raise error.Abort(_(b'internal calling inconsistency')) | ||
Aleix Conchillo Flaque
|
r6035 | |||
Patrick Mezard
|
r12344 | if not os.path.lexists(os.path.join(self.tmppath, name)): | ||
Mads Kiilerich
|
r22296 | return None, None | ||
Aleix Conchillo Flaque
|
r6035 | |||
Patrick Mezard
|
r11134 | return self._getfile(name, rev) | ||
Aleix Conchillo Flaque
|
r6035 | |||
Mads Kiilerich
|
r22300 | def getchanges(self, rev, full): | ||
if full: | ||||
Augie Fackler
|
r43347 | raise error.Abort(_(b"convert from arch does not support --full")) | ||
Aleix Conchillo Flaque
|
r6035 | 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] | ||||
Benoit Boissinot
|
r10394 | chgs, cps = self._rendirchanges(src, to) | ||
Aleix Conchillo Flaque
|
r6035 | changes += [(f, rev) for f in chgs] | ||
Patrick Mezard
|
r7567 | copies.update(cps) | ||
Aleix Conchillo Flaque
|
r6035 | |||
self.lastrev = rev | ||||
Mads Kiilerich
|
r24395 | return sorted(set(changes)), copies, set() | ||
Aleix Conchillo Flaque
|
r6035 | |||
def getcommit(self, rev): | ||||
changes = self.changes[rev] | ||||
Augie Fackler
|
r43346 | return common.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 | ||||
Yuya Nishihara
|
r37138 | cmdline = [procutil.shellquote(arg) for arg in cmdline] | ||
Ian Moody
|
r43569 | bdevnull = pycompat.bytestr(os.devnull) | ||
cmdline += [b'>', bdevnull, b'2>', bdevnull] | ||||
Manuel Jacob
|
r45403 | cmdline = b' '.join(cmdline) | ||
Augie Fackler
|
r43347 | self.ui.debug(cmdline, b'\n') | ||
Matt Harbison
|
r39851 | return os.system(pycompat.rapply(procutil.tonativestr, cmdline)) | ||
Aleix Conchillo Flaque
|
r6035 | |||
def _update(self, rev): | ||||
Augie Fackler
|
r43347 | self.ui.debug(b'applying revision %s...\n' % rev) | ||
changeset, status = self.runlines(b'replay', b'-d', self.tmppath, rev) | ||||
Edouard Gomez
|
r7585 | 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] | ||
Augie Fackler
|
r43346 | self.ui.debug( | ||
Augie Fackler
|
r43347 | b'computing changeset between %s and %s...\n' % (old_rev, rev) | ||
Augie Fackler
|
r43346 | ) | ||
Edouard Gomez
|
r7585 | 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): | ||||
Matt Harbison
|
r39940 | data = util.readlink(os.path.join(self.tmppath, name)) | ||
Jordi Gutiérrez Hermoso
|
r24306 | if mode: | ||
Augie Fackler
|
r43347 | mode = b'l' | ||
Jordi Gutiérrez Hermoso
|
r24306 | else: | ||
Augie Fackler
|
r43347 | mode = b'' | ||
Aleix Conchillo Flaque
|
r6035 | else: | ||
Matt Harbison
|
r39865 | data = util.readfile(os.path.join(self.tmppath, name)) | ||
Augie Fackler
|
r43347 | mode = (mode & 0o111) and b'x' or b'' | ||
Aleix Conchillo Flaque
|
r6035 | return data, mode | ||
def _exclude(self, name): | ||||
Augie Fackler
|
r43347 | exclude = [b'{arch}', b'.arch-ids', b'.arch-inventory'] | ||
Aleix Conchillo Flaque
|
r6035 | 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): | ||
Augie Fackler
|
r43347 | self.ui.debug(b'obtaining revision %s...\n' % rev) | ||
output = self._execute(b'get', rev, self.tmppath) | ||||
Aleix Conchillo Flaque
|
r6049 | self.checkexit(output) | ||
Augie Fackler
|
r43347 | self.ui.debug(b'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): | ||
Augie Fackler
|
r43347 | if path.startswith(b'./'): | ||
Aleix Conchillo Flaque
|
r6079 | return path[2:] | ||
return path | ||||
Aleix Conchillo Flaque
|
r6035 | def _parsecatlog(self, data, rev): | ||
Edouard Gomez
|
r7578 | try: | ||
Denis Laxalde
|
r43699 | catlog = mail.parsebytes(data) | ||
Edouard Gomez
|
r7592 | |||
# Commit date | ||||
Boris Feld
|
r36625 | self.changes[rev].date = dateutil.datestr( | ||
Augie Fackler
|
r43906 | dateutil.strdate(catlog['Standard-date'], b'%Y-%m-%d %H:%M:%S') | ||
Augie Fackler
|
r43346 | ) | ||
Edouard Gomez
|
r7592 | |||
# Commit author | ||||
Augie Fackler
|
r43906 | self.changes[rev].author = self.recode(catlog['Creator']) | ||
Edouard Gomez
|
r7592 | |||
# Commit description | ||||
Augie Fackler
|
r43347 | self.changes[rev].summary = b'\n\n'.join( | ||
Denis Laxalde
|
r43701 | ( | ||
Augie Fackler
|
r43906 | self.recode(catlog['Summary']), | ||
Denis Laxalde
|
r43701 | self.recode(catlog.get_payload()), | ||
) | ||||
Augie Fackler
|
r43346 | ) | ||
Edouard Gomez
|
r7592 | self.changes[rev].summary = self.recode(self.changes[rev].summary) | ||
# Commit revision origin when dealing with a branch or tag | ||||
Augie Fackler
|
r43906 | if 'Continuation-of' in catlog: | ||
Matt Mackall
|
r10282 | self.changes[rev].continuationof = self.recode( | ||
Augie Fackler
|
r43906 | catlog['Continuation-of'] | ||
Augie Fackler
|
r43346 | ) | ||
Peter Arrenbrecht
|
r7875 | except Exception: | ||
Augie Fackler
|
r43347 | raise error.Abort(_(b'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) | ||
Augie Fackler
|
r43347 | if l.startswith(b'A') and not l.startswith(b'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) | ||
Augie Fackler
|
r43347 | elif l.startswith(b'D') and not l.startswith(b'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 | ||||
Augie Fackler
|
r43347 | elif l.startswith(b'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 | ||||
Augie Fackler
|
r43347 | elif l.startswith(b'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 | ||||
Augie Fackler
|
r43347 | elif l.startswith(b'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) | ||||
Augie Fackler
|
r43347 | elif l.startswith(b'=>'): | ||
files = l[2:].strip().split(b' ') | ||||
Aleix Conchillo Flaque
|
r6055 | if len(files) == 1: | ||
Augie Fackler
|
r43347 | files = l[2:].strip().split(b'\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) | ||
Augie Fackler
|
r43347 | elif l.startswith(b'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 | ||||
Augie Fackler
|
r43347 | elif l.startswith(b'/>'): | ||
dirs = l[2:].strip().split(b' ') | ||||
Aleix Conchillo Flaque
|
r6035 | if len(dirs) == 1: | ||
Augie Fackler
|
r43347 | dirs = l[2:].strip().split(b'\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 | ||||