Commit cca15f81 authored by Matthias Putz's avatar Matthias Putz

adapted style to PEP8 style guide

parent 294579e8
...@@ -18,148 +18,155 @@ import sys ...@@ -18,148 +18,155 @@ import sys
import pager import pager
COLORS = {None :-1, COLORS = {None: -1,
'normal' :-1, 'normal': -1,
'black' : 0, 'black': 0,
'red' : 1, 'red': 1,
'green' : 2, 'green': 2,
'yellow' : 3, 'yellow': 3,
'blue' : 4, 'blue': 4,
'magenta': 5, 'magenta': 5,
'cyan' : 6, 'cyan': 6,
'white' : 7} 'white': 7}
ATTRS = {None :-1, ATTRS = {None: -1,
'bold' : 1, 'bold': 1,
'dim' : 2, 'dim': 2,
'ul' : 4, 'ul': 4,
'blink' : 5, 'blink': 5,
'reverse': 7} 'reverse': 7}
RESET = "\033[m" # pylint: disable=W1401 RESET = "\033[m" # pylint: disable=W1401
# backslash is not anomalous # backslash is not anomalous
def is_color(s): def is_color(s):
return s in COLORS return s in COLORS
def is_attr(s): def is_attr(s):
return s in ATTRS return s in ATTRS
def _Color(fg = None, bg = None, attr = None):
fg = COLORS[fg] def _Color(fg=None, bg=None, attr=None):
bg = COLORS[bg] fg = COLORS[fg]
attr = ATTRS[attr] bg = COLORS[bg]
attr = ATTRS[attr]
if attr >= 0 or fg >= 0 or bg >= 0:
need_sep = False if attr >= 0 or fg >= 0 or bg >= 0:
code = "\033[" #pylint: disable=W1401 need_sep = False
code = "\033[" #pylint: disable=W1401
if attr >= 0:
code += chr(ord('0') + attr) if attr >= 0:
need_sep = True code += chr(ord('0') + attr)
need_sep = True
if fg >= 0:
if need_sep: if fg >= 0:
code += ';' if need_sep:
need_sep = True code += ';'
need_sep = True
if fg < 8:
code += '3%c' % (ord('0') + fg) if fg < 8:
else: code += '3%c' % (ord('0') + fg)
code += '38;5;%d' % fg else:
code += '38;5;%d' % fg
if bg >= 0:
if need_sep: if bg >= 0:
code += ';' if need_sep:
need_sep = True code += ';'
need_sep = True
if bg < 8:
code += '4%c' % (ord('0') + bg) if bg < 8:
else: code += '4%c' % (ord('0') + bg)
code += '48;5;%d' % bg else:
code += 'm' code += '48;5;%d' % bg
else: code += 'm'
code = '' else:
return code code = ''
return code
class Coloring(object): class Coloring(object):
def __init__(self, config, section_type): def __init__(self, config, section_type):
self._section = 'color.%s' % section_type self._section = 'color.%s' % section_type
self._config = config self._config = config
self._out = sys.stdout self._out = sys.stdout
on = self._config.GetString(self._section) on = self._config.GetString(self._section)
if on is None: if on is None:
on = self._config.GetString('color.ui') on = self._config.GetString('color.ui')
if on == 'auto': if on == 'auto':
if pager.active or os.isatty(1): if pager.active or os.isatty(1):
self._on = True self._on = True
else: else:
self._on = False self._on = False
elif on in ('true', 'always'): elif on in ('true', 'always'):
self._on = True self._on = True
else:
self._on = False
def redirect(self, out):
self._out = out
@property
def is_on(self):
return self._on
def write(self, fmt, *args):
self._out.write(fmt % args)
def flush(self):
self._out.flush()
def nl(self):
self._out.write('\n')
def printer(self, opt=None, fg=None, bg=None, attr=None):
s = self
c = self.colorer(opt, fg, bg, attr)
def f(fmt, *args):
s._out.write(c(fmt, *args))
return f
def colorer(self, opt=None, fg=None, bg=None, attr=None):
if self._on:
c = self._parse(opt, fg, bg, attr)
def f(fmt, *args):
output = fmt % args
return ''.join([c, output, RESET])
return f
else:
def f(fmt, *args):
return fmt % args
return f
def _parse(self, opt, fg, bg, attr):
if not opt:
return _Color(fg, bg, attr)
v = self._config.GetString('%s.%s' % (self._section, opt))
if v is None:
return _Color(fg, bg, attr)
v = v.strip().lower()
if v == "reset":
return RESET
elif v == '':
return _Color(fg, bg, attr)
have_fg = False
for a in v.split(' '):
if is_color(a):
if have_fg:
bg = a
else: else:
fg = a self._on = False
elif is_attr(a):
attr = a def redirect(self, out):
self._out = out
@property
def is_on(self):
return self._on
def write(self, fmt, *args):
self._out.write(fmt % args)
def flush(self):
self._out.flush()
return _Color(fg, bg, attr) def nl(self):
self._out.write('\n')
def printer(self, opt=None, fg=None, bg=None, attr=None):
s = self
c = self.colorer(opt, fg, bg, attr)
def f(fmt, *args):
s._out.write(c(fmt, *args))
return f
def colorer(self, opt=None, fg=None, bg=None, attr=None):
if self._on:
c = self._parse(opt, fg, bg, attr)
def f(fmt, *args):
output = fmt % args
return ''.join([c, output, RESET])
return f
else:
def f(fmt, *args):
return fmt % args
return f
def _parse(self, opt, fg, bg, attr):
if not opt:
return _Color(fg, bg, attr)
v = self._config.GetString('%s.%s' % (self._section, opt))
if v is None:
return _Color(fg, bg, attr)
v = v.strip().lower()
if v == "reset":
return RESET
elif v == '':
return _Color(fg, bg, attr)
have_fg = False
for a in v.split(' '):
if is_color(a):
if have_fg:
bg = a
else:
fg = a
elif is_attr(a):
attr = a
return _Color(fg, bg, attr)
This diff is collapsed.
...@@ -22,89 +22,90 @@ import tempfile ...@@ -22,89 +22,90 @@ import tempfile
from error import EditorError from error import EditorError
class Editor(object): class Editor(object):
"""Manages the user's preferred text editor.""" """Manages the user's preferred text editor."""
_editor = None _editor = None
globalConfig = None globalConfig = None
@classmethod @classmethod
def _GetEditor(cls): def _GetEditor(cls):
if cls._editor is None: if cls._editor is None:
cls._editor = cls._SelectEditor() cls._editor = cls._SelectEditor()
return cls._editor return cls._editor
@classmethod @classmethod
def _SelectEditor(cls): def _SelectEditor(cls):
e = os.getenv('GIT_EDITOR') e = os.getenv('GIT_EDITOR')
if e: if e:
return e return e
if cls.globalConfig: if cls.globalConfig:
e = cls.globalConfig.GetString('core.editor') e = cls.globalConfig.GetString('core.editor')
if e: if e:
return e return e
e = os.getenv('VISUAL') e = os.getenv('VISUAL')
if e: if e:
return e return e
e = os.getenv('EDITOR') e = os.getenv('EDITOR')
if e: if e:
return e return e
if os.getenv('TERM') == 'dumb': if os.getenv('TERM') == 'dumb':
print( print(
"""No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR. """No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR.
Tried to fall back to vi but terminal is dumb. Please configure at Tried to fall back to vi but terminal is dumb. Please configure at
least one of these before using this command.""", file=sys.stderr) least one of these before using this command.""", file=sys.stderr)
sys.exit(1) sys.exit(1)
return 'vi' return 'vi'
@classmethod @classmethod
def EditString(cls, data): def EditString(cls, data):
"""Opens an editor to edit the given content. """Opens an editor to edit the given content.
Args: Args:
data : the text to edit data : the text to edit
Returns: Returns:
new value of edited text; None if editing did not succeed new value of edited text; None if editing did not succeed
""" """
editor = cls._GetEditor() editor = cls._GetEditor()
if editor == ':': if editor == ':':
return data return data
fd, path = tempfile.mkstemp() fd, path = tempfile.mkstemp()
try: try:
os.write(fd, data) os.write(fd, data)
os.close(fd) os.close(fd)
fd = None fd = None
if re.compile("^.*[$ \t'].*$").match(editor): if re.compile("^.*[$ \t'].*$").match(editor):
args = [editor + ' "$@"', 'sh'] args = [editor + ' "$@"', 'sh']
shell = True shell = True
else: else:
args = [editor] args = [editor]
shell = False shell = False
args.append(path) args.append(path)
try: try:
rc = subprocess.Popen(args, shell=shell).wait() rc = subprocess.Popen(args, shell=shell).wait()
except OSError as e: except OSError as e:
raise EditorError('editor failed, %s: %s %s' raise EditorError('editor failed, %s: %s %s'
% (str(e), editor, path)) % (str(e), editor, path))
if rc != 0: if rc != 0:
raise EditorError('editor failed with exit status %d: %s %s' raise EditorError('editor failed with exit status %d: %s %s'
% (rc, editor, path)) % (rc, editor, path))
fd2 = open(path) fd2 = open(path)
try: try:
return fd2.read() return fd2.read()
finally: finally:
fd2.close() fd2.close()
finally: finally:
if fd: if fd:
os.close(fd) os.close(fd)
os.remove(path) os.remove(path)
...@@ -14,93 +14,109 @@ ...@@ -14,93 +14,109 @@
# limitations under the License. # limitations under the License.
class ManifestParseError(Exception): class ManifestParseError(Exception):
"""Failed to parse the manifest file. """Failed to parse the manifest file.
""" """
class ManifestInvalidRevisionError(Exception): class ManifestInvalidRevisionError(Exception):
"""The revision value in a project is incorrect. """The revision value in a project is incorrect.
""" """
class NoManifestException(Exception): class NoManifestException(Exception):
"""The required manifest does not exist. """The required manifest does not exist.
""" """
class EditorError(Exception): class EditorError(Exception):
"""Unspecified error from the user's text editor. """Unspecified error from the user's text editor.
""" """
def __init__(self, reason):
super(EditorError, self).__init__() def __init__(self, reason):
self.reason = reason super(EditorError, self).__init__()
self.reason = reason
def __str__(self):
return self.reason
def __str__(self):
return self.reason
class GitError(Exception): class GitError(Exception):
"""Unspecified internal error from git. """Unspecified internal error from git.
""" """
def __init__(self, command):
super(GitError, self).__init__() def __init__(self, command):
self.command = command super(GitError, self).__init__()
self.command = command
def __str__(self):
return self.command
def __str__(self):
return self.command
class UploadError(Exception): class UploadError(Exception):
"""A bundle upload to Gerrit did not succeed. """A bundle upload to Gerrit did not succeed.
""" """
def __init__(self, reason):
super(UploadError, self).__init__() def __init__(self, reason):
self.reason = reason super(UploadError, self).__init__()
self.reason = reason
def __str__(self):
return self.reason
def __str__(self):
return self.reason
class DownloadError(Exception): class DownloadError(Exception):
"""Cannot download a repository. """Cannot download a repository.
""" """
def __init__(self, reason):
super(DownloadError, self).__init__() def __init__(self, reason):
self.reason = reason super(DownloadError, self).__init__()
self.reason = reason
def __str__(self):
return self.reason
def __str__(self):
return self.reason
class NoSuchProjectError(Exception): class NoSuchProjectError(Exception):
"""A specified project does not exist in the work tree. """A specified project does not exist in the work tree.
""" """
def __init__(self, name=None):
super(NoSuchProjectError, self).__init__() def __init__(self, name=None):
self.name = name super(NoSuchProjectError, self).__init__()
self.name = name
def __str__(self): def __str__(self):
if self.Name is None: if self.Name is None:
return 'in current directory' return 'in current directory'
return self.name return self.name
class InvalidProjectGroupsError(Exception): class InvalidProjectGroupsError(Exception):
"""A specified project is not suitable for the specified groups """A specified project is not suitable for the specified groups
""" """
def __init__(self, name=None):
super(InvalidProjectGroupsError, self).__init__() def __init__(self, name=None):
self.name = name super(InvalidProjectGroupsError, self).__init__()
self.name = name
def __str__(self):
if self.Name is None:
return 'in current directory'
return self.name
def __str__(self):
if self.Name is None:
return 'in current directory'
return self.name
class RepoChangedException(Exception): class RepoChangedException(Exception):
"""Thrown if 'repo sync' results in repo updating its internal """Thrown if 'repo sync' results in repo updating its internal
repo or manifest repositories. In this special case we must repo or manifest repositories. In this special case we must
use exec to re-execute repo with the new code and manifest. use exec to re-execute repo with the new code and manifest.
""" """
def __init__(self, extra_args=None):
super(RepoChangedException, self).__init__() def __init__(self, extra_args=None):
self.extra_args = extra_args or [] super(RepoChangedException, self).__init__()
self.extra_args = extra_args or []
class HookError(Exception): class HookError(Exception):
"""Thrown if a 'repo-hook' could not be run. """Thrown if a 'repo-hook' could not be run.
The common case is that the file wasn't present when we tried to run it. The common case is that the file wasn't present when we tried to run it.
""" """
This diff is collapsed.
This diff is collapsed.
...@@ -16,146 +16,146 @@ ...@@ -16,146 +16,146 @@
import os import os
from repo_trace import Trace from repo_trace import Trace
HEAD = 'HEAD' HEAD = 'HEAD'
R_HEADS = 'refs/heads/' R_HEADS = 'refs/heads/'
R_TAGS = 'refs/tags/' R_TAGS = 'refs/tags/'
R_PUB = 'refs/published/' R_PUB = 'refs/published/'
R_M = 'refs/remotes/m/' R_M = 'refs/remotes/m/'
class GitRefs(object): class GitRefs(object):
def __init__(self, gitdir): def __init__(self, gitdir):
self._gitdir = gitdir self._gitdir = gitdir
self._phyref = None self._phyref = None
self._symref = None self._symref = None
self._mtime = {} self._mtime = {}
@property @property
def all(self): def all(self):
self._EnsureLoaded() self._EnsureLoaded()
return self._phyref return self._phyref
def get(self, name): def get(self, name):
try: try:
return self.all[name] return self.all[name]
except KeyError: except KeyError:
return '' return ''
def deleted(self, name): def deleted(self, name):
if self._phyref is not None: if self._phyref is not None:
if name in self._phyref: if name in self._phyref:
del self._phyref[name] del self._phyref[name]
if name in self._symref: if name in self._symref:
del self._symref[name] del self._symref[name]
if name in self._mtime: if name in self._mtime:
del self._mtime[name] del self._mtime[name]
def symref(self, name): def symref(self, name):
try: try:
self._EnsureLoaded() self._EnsureLoaded()
return self._symref[name] return self._symref[name]
except KeyError: except KeyError:
return '' return ''
def _EnsureLoaded(self): def _EnsureLoaded(self):
if self._phyref is None or self._NeedUpdate(): if self._phyref is None or self._NeedUpdate():
self._LoadAll() self._LoadAll()
def _NeedUpdate(self): def _NeedUpdate(self):
Trace(': scan refs %s', self._gitdir) Trace(': scan refs %s', self._gitdir)
for name, mtime in list(self._mtime.items()): for name, mtime in list(self._mtime.items()):
try: try:
if mtime != os.path.getmtime(os.path.join(self._gitdir, name)): if mtime != os.path.getmtime(os.path.join(self._gitdir, name)):
return True return True
except OSError: except OSError:
return True return True
return False return False
def _LoadAll(self): def _LoadAll(self):
Trace(': load refs %s', self._gitdir) Trace(': load refs %s', self._gitdir)
self._phyref = {} self._phyref = {}
self._symref = {} self._symref = {}
self._mtime = {} self._mtime = {}
self._ReadPackedRefs() self._ReadPackedRefs()
self._ReadLoose('refs/') self._ReadLoose('refs/')
self._ReadLoose1(os.path.join(self._gitdir, HEAD), HEAD) self._ReadLoose1(os.path.join(self._gitdir, HEAD), HEAD)
scan = self._symref scan = self._symref
attempts = 0 attempts = 0
while scan and attempts < 5: while scan and attempts < 5:
scan_next = {} scan_next = {}
for name, dest in list(scan.items()): for name, dest in list(scan.items()):
if dest in self._phyref: if dest in self._phyref:
self._phyref[name] = self._phyref[dest] self._phyref[name] = self._phyref[dest]
else:
scan_next[name] = dest
scan = scan_next
attempts += 1
def _ReadPackedRefs(self):
path = os.path.join(self._gitdir, 'packed-refs')
try:
fd = open(path, 'rt')
mtime = os.path.getmtime(path)
except IOError:
return
except OSError:
return
try:
for line in fd:
if line[0] == '#':
continue
if line[0] == '^':
continue
line = line[:-1]
p = line.split(' ')
ref_id = p[0]
name = p[1]
self._phyref[name] = ref_id
finally:
fd.close()
self._mtime['packed-refs'] = mtime
def _ReadLoose(self, prefix):
base = os.path.join(self._gitdir, prefix)
for name in os.listdir(base):
p = os.path.join(base, name)
if os.path.isdir(p):
self._mtime[prefix] = os.path.getmtime(base)
self._ReadLoose(prefix + name + '/')
elif name.endswith('.lock'):
pass
else:
self._ReadLoose1(p, prefix + name)
def _ReadLoose1(self, path, name):
try:
fd = open(path, 'rt')
except IOError:
return
try:
try:
mtime = os.path.getmtime(path)
ref_id = fd.readline()
except (IOError, OSError):
return
finally:
fd.close()
if not ref_id:
return
ref_id = ref_id[:-1]
if ref_id.startswith('ref: '):
self._symref[name] = ref_id[5:]
else: else:
scan_next[name] = dest self._phyref[name] = ref_id
scan = scan_next self._mtime[name] = mtime
attempts += 1
def _ReadPackedRefs(self):
path = os.path.join(self._gitdir, 'packed-refs')
try:
fd = open(path, 'rt')
mtime = os.path.getmtime(path)
except IOError:
return
except OSError:
return
try:
for line in fd:
if line[0] == '#':
continue
if line[0] == '^':
continue
line = line[:-1]
p = line.split(' ')
ref_id = p[0]
name = p[1]
self._phyref[name] = ref_id
finally:
fd.close()
self._mtime['packed-refs'] = mtime
def _ReadLoose(self, prefix):
base = os.path.join(self._gitdir, prefix)
for name in os.listdir(base):
p = os.path.join(base, name)
if os.path.isdir(p):
self._mtime[prefix] = os.path.getmtime(base)
self._ReadLoose(prefix + name + '/')
elif name.endswith('.lock'):
pass
else:
self._ReadLoose1(p, prefix + name)
def _ReadLoose1(self, path, name):
try:
fd = open(path, 'rt')
except IOError:
return
try:
try:
mtime = os.path.getmtime(path)
ref_id = fd.readline()
except (IOError, OSError):
return
finally:
fd.close()
if not ref_id:
return
ref_id = ref_id[:-1]
if ref_id.startswith('ref: '):
self._symref[name] = ref_id[5:]
else:
self._phyref[name] = ref_id
self._mtime[name] = mtime
This diff is collapsed.
This diff is collapsed.
...@@ -20,66 +20,69 @@ import sys ...@@ -20,66 +20,69 @@ import sys
active = False active = False
def RunPager(globalConfig): def RunPager(globalConfig):
global active global active
if not os.isatty(0) or not os.isatty(1): if not os.isatty(0) or not os.isatty(1):
return return
pager = _SelectPager(globalConfig) pager = _SelectPager(globalConfig)
if pager == '' or pager == 'cat': if pager == '' or pager == 'cat':
return return
# This process turns into the pager; a child it forks will # This process turns into the pager; a child it forks will
# do the real processing and output back to the pager. This # do the real processing and output back to the pager. This
# is necessary to keep the pager in control of the tty. # is necessary to keep the pager in control of the tty.
# #
try: try:
r, w = os.pipe() r, w = os.pipe()
pid = os.fork() pid = os.fork()
if not pid: if not pid:
os.dup2(w, 1) os.dup2(w, 1)
os.dup2(w, 2) os.dup2(w, 2)
os.close(r) os.close(r)
os.close(w) os.close(w)
active = True active = True
return return
os.dup2(r, 0) os.dup2(r, 0)
os.close(r) os.close(r)
os.close(w) os.close(w)
_BecomePager(pager) _BecomePager(pager)
except Exception: except Exception:
print("fatal: cannot start pager '%s'" % pager, file=sys.stderr) print("fatal: cannot start pager '%s'" % pager, file=sys.stderr)
sys.exit(255) sys.exit(255)
def _SelectPager(globalConfig): def _SelectPager(globalConfig):
try: try:
return os.environ['GIT_PAGER'] return os.environ['GIT_PAGER']
except KeyError: except KeyError:
pass pass
pager = globalConfig.GetString('core.pager')
if pager:
return pager
pager = globalConfig.GetString('core.pager') try:
if pager: return os.environ['PAGER']
return pager except KeyError:
pass
try: return 'less'
return os.environ['PAGER']
except KeyError:
pass
return 'less'
def _BecomePager(pager): def _BecomePager(pager):
# Delaying execution of the pager until we have output # Delaying execution of the pager until we have output
# ready works around a long-standing bug in popularly # ready works around a long-standing bug in popularly
# available versions of 'less', a better 'more'. # available versions of 'less', a better 'more'.
# #
_a, _b, _c = select.select([0], [], [0]) _a, _b, _c = select.select([0], [], [0])
os.environ['LESS'] = 'FRSX' os.environ['LESS'] = 'FRSX'
try: try:
os.execvp(pager, [pager]) os.execvp(pager, [pager])
except OSError: except OSError:
os.execv('/bin/sh', ['sh', '-c', pager]) os.execv('/bin/sh', ['sh', '-c', pager])
...@@ -14,58 +14,65 @@ from repo_trace import REPO_TRACE, IsTrace, Trace ...@@ -14,58 +14,65 @@ from repo_trace import REPO_TRACE, IsTrace, Trace
SUBPROCESSES = [] SUBPROCESSES = []
def terminateHandle(signal, frame): def terminateHandle(signal, frame):
for cmd in SUBPROCESSES: for cmd in SUBPROCESSES:
if cmd: if cmd:
cmd.terminate() cmd.terminate()
sys.exit(0) sys.exit(0)
def stream2str(stream): def stream2str(stream):
return str(stream, encoding='UTF-8') return str(stream, encoding='UTF-8')
def isUnix(): def isUnix():
if platform.system() == "Windows": if platform.system() == "Windows":
return False return False
else: else:
return True return True
def isPosix(): def isPosix():
return platform.system() != "Windows" return platform.system() != "Windows"
def toUnixPath(path): def toUnixPath(path):
return path.replace('\\', '/') return path.replace('\\', '/')
def toWindowsPath(path): def toWindowsPath(path):
return path.replace('/', '\\') return path.replace('/', '\\')
def os_link(src, dst): def os_link(src, dst):
if isUnix(): if isUnix():
# requires src in relation to dst # requires src in relation to dst
src = os.path.relpath(src, os.path.dirname(dst)) src = os.path.relpath(src, os.path.dirname(dst))
os.symlink(src, dst) os.symlink(src, dst)
else:
isDir = True if os.path.isdir(src) else False
src = os.path.relpath(src, os.path.dirname(dst))
src = toWindowsPath(src)
dst = toWindowsPath(dst)
# ln in MinGW does not create hard links? - it copies
# call windows cmd tool 'mklink' from git bash (mingw)
if isDir:
cmd = 'cmd /c mklink /D "%s" "%s"' % (dst, src)
if IsTrace():
Trace(cmd)
subprocess.Popen(cmd, stdout=subprocess.PIPE).wait()
else: else:
cmd = 'cmd /c mklink "%s" "%s"' % (dst, src) isDir = True if os.path.isdir(src) else False
if IsTrace(): src = os.path.relpath(src, os.path.dirname(dst))
Trace(cmd) src = toWindowsPath(src)
subprocess.Popen(cmd, stdout=subprocess.PIPE).wait() dst = toWindowsPath(dst)
# ln in MinGW does not create hard links? - it copies
# call windows cmd tool 'mklink' from git bash (mingw)
if isDir:
cmd = 'cmd /c mklink /D "%s" "%s"' % (dst, src)
if IsTrace():
Trace(cmd)
subprocess.Popen(cmd, stdout=subprocess.PIPE).wait()
else:
cmd = 'cmd /c mklink "%s" "%s"' % (dst, src)
if IsTrace():
Trace(cmd)
subprocess.Popen(cmd, stdout=subprocess.PIPE).wait()
def removeReadOnlyFilesHandler(fn, path, excinfo): def removeReadOnlyFilesHandler(fn, path, excinfo):
removeReadOnlyFiles(fn, path) removeReadOnlyFiles(fn, path)
def removeReadOnlyFiles(fn, path): def removeReadOnlyFiles(fn, path):
if not os.access(path, os.W_OK): if not os.access(path, os.W_OK):
os.chmod(path, stat.S_IWUSR) os.chmod(path, stat.S_IWUSR)
......
...@@ -20,59 +20,60 @@ from repo_trace import IsTrace ...@@ -20,59 +20,60 @@ from repo_trace import IsTrace
_NOT_TTY = not os.isatty(2) _NOT_TTY = not os.isatty(2)
class Progress(object): class Progress(object):
def __init__(self, title, total=0, units=''): def __init__(self, title, total=0, units=''):
self._title = title self._title = title
self._total = total self._total = total
self._done = 0 self._done = 0
self._lastp = -1 self._lastp = -1
self._start = time() self._start = time()
self._show = False self._show = False
self._units = units self._units = units
def update(self, inc=1): def update(self, inc=1):
self._done += inc self._done += inc
if _NOT_TTY or IsTrace(): if _NOT_TTY or IsTrace():
return return
if not self._show: if not self._show:
if 0.5 <= time() - self._start: if 0.5 <= time() - self._start:
self._show = True self._show = True
else: else:
return return
if self._total <= 0: if self._total <= 0:
sys.stderr.write('\r%s: %d, ' % ( sys.stderr.write('\r%s: %d, ' % (
self._title, self._title,
self._done)) self._done))
sys.stderr.flush() sys.stderr.flush()
else: else:
p = (100 * self._done) / self._total p = (100 * self._done) / self._total
if self._lastp != p: if self._lastp != p:
self._lastp = p self._lastp = p
sys.stderr.write('\r%s: %3d%% (%d%s/%d%s) ' % ( sys.stderr.write('\r%s: %3d%% (%d%s/%d%s) ' % (
self._title, self._title,
p, p,
self._done, self._units, self._done, self._units,
self._total, self._units)) self._total, self._units))
sys.stderr.flush() sys.stderr.flush()
def end(self): def end(self):
if _NOT_TTY or IsTrace() or not self._show: if _NOT_TTY or IsTrace() or not self._show:
return return
if self._total <= 0: if self._total <= 0:
sys.stderr.write('\r%s: %d, done. \n' % ( sys.stderr.write('\r%s: %d, done. \n' % (
self._title, self._title,
self._done)) self._done))
sys.stderr.flush() sys.stderr.flush()
else: else:
p = (100 * self._done) / self._total p = (100 * self._done) / self._total
sys.stderr.write('\r%s: %3d%% (%d%s/%d%s), done. \n' % ( sys.stderr.write('\r%s: %3d%% (%d%s/%d%s), done. \n' % (
self._title, self._title,
p, p,
self._done, self._units, self._done, self._units,
self._total, self._units)) self._total, self._units))
sys.stderr.flush() sys.stderr.flush()
This diff is collapsed.
This diff is collapsed.
...@@ -16,20 +16,24 @@ ...@@ -16,20 +16,24 @@
import sys import sys
import os import os
REPO_TRACE = 'REPO_TRACE' REPO_TRACE = 'REPO_TRACE'
try: try:
_TRACE = os.environ[REPO_TRACE] == '1' _TRACE = os.environ[REPO_TRACE] == '1'
except KeyError: except KeyError:
_TRACE = False _TRACE = False
def IsTrace(): def IsTrace():
return _TRACE return _TRACE
def SetTrace(): def SetTrace():
global _TRACE global _TRACE
_TRACE = True _TRACE = True
def Trace(fmt, *args): def Trace(fmt, *args):
if IsTrace(): if IsTrace():
print(fmt % args, file=sys.stderr) print(fmt % args, file=sys.stderr)
...@@ -19,31 +19,31 @@ all_commands = {} ...@@ -19,31 +19,31 @@ all_commands = {}
my_dir = os.path.dirname(__file__) my_dir = os.path.dirname(__file__)
for py in os.listdir(my_dir): for py in os.listdir(my_dir):
if py == '__init__.py': if py == '__init__.py':
continue continue
if py.endswith('.py'): if py.endswith('.py'):
name = py[:-3] name = py[:-3]
clsn = name.capitalize() clsn = name.capitalize()
while clsn.find('_') > 0: while clsn.find('_') > 0:
h = clsn.index('_') h = clsn.index('_')
clsn = clsn[0:h] + clsn[h + 1:].capitalize() clsn = clsn[0:h] + clsn[h + 1:].capitalize()
mod = __import__(__name__, mod = __import__(__name__,
globals(), globals(),
locals(), locals(),
['%s' % name]) ['%s' % name])
mod = getattr(mod, name) mod = getattr(mod, name)
try: try:
cmd = getattr(mod, clsn)() cmd = getattr(mod, clsn)()
except AttributeError: except AttributeError:
raise SyntaxError('%s/%s does not define class %s' % ( raise SyntaxError('%s/%s does not define class %s' % (
__name__, py, clsn)) __name__, py, clsn))
name = name.replace('_', '-') name = name.replace('_', '-')
cmd.NAME = name cmd.NAME = name
all_commands[name] = cmd all_commands[name] = cmd
if 'help' in all_commands: if 'help' in all_commands:
all_commands['help'].commands = all_commands all_commands['help'].commands = all_commands
...@@ -19,10 +19,11 @@ from command import Command ...@@ -19,10 +19,11 @@ from command import Command
from git_command import git from git_command import git
from progress import Progress from progress import Progress
class Abandon(Command): class Abandon(Command):
common = True common = True
helpSummary = "Permanently abandon a development branch" helpSummary = "Permanently abandon a development branch"
helpUsage = """ helpUsage = """
%prog <branchname> [<project>...] %prog <branchname> [<project>...]
This subcommand permanently abandons a development branch by This subcommand permanently abandons a development branch by
...@@ -31,41 +32,41 @@ deleting it (and all its history) from your local repository. ...@@ -31,41 +32,41 @@ deleting it (and all its history) from your local repository.
It is equivalent to "git branch -D <branchname>". It is equivalent to "git branch -D <branchname>".
""" """
def Execute(self, opt, args): def Execute(self, opt, args):
if not args: if not args:
self.Usage() self.Usage()
nb = args[0] nb = args[0]
if not git.check_ref_format('heads/%s' % nb): if not git.check_ref_format('heads/%s' % nb):
print("error: '%s' is not a valid name" % nb, file=sys.stderr) print("error: '%s' is not a valid name" % nb, file=sys.stderr)
sys.exit(1) sys.exit(1)
nb = args[0] nb = args[0]
err = [] err = []
success = [] success = []
all_projects = self.GetProjects(args[1:]) all_projects = self.GetProjects(args[1:])
pm = Progress('Abandon %s' % nb, len(all_projects)) pm = Progress('Abandon %s' % nb, len(all_projects))
for project in all_projects: for project in all_projects:
pm.update() pm.update()
status = project.AbandonBranch(nb) status = project.AbandonBranch(nb)
if status is not None: if status is not None:
if status: if status:
success.append(project) success.append(project)
else: else:
err.append(project) err.append(project)
pm.end() pm.end()
if err: if err:
for p in err: for p in err:
print("error: %s/: cannot abandon %s" % (p.relpath, nb), print("error: %s/: cannot abandon %s" % (p.relpath, nb),
file=sys.stderr) file=sys.stderr)
sys.exit(1) sys.exit(1)
elif not success: elif not success:
print('error: no project has branch %s' % nb, file=sys.stderr) print('error: no project has branch %s' % nb, file=sys.stderr)
sys.exit(1) sys.exit(1)
else: else:
print('Abandoned in %d project(s):\n %s' print('Abandoned in %d project(s):\n %s'
% (len(success), '\n '.join(p.relpath for p in success)), % (len(success), '\n '.join(p.relpath for p in success)),
file=sys.stderr) file=sys.stderr)
...@@ -18,47 +18,49 @@ import sys ...@@ -18,47 +18,49 @@ import sys
from color import Coloring from color import Coloring
from command import Command from command import Command
class BranchColoring(Coloring): class BranchColoring(Coloring):
def __init__(self, config): def __init__(self, config):
Coloring.__init__(self, config, 'branch') Coloring.__init__(self, config, 'branch')
self.current = self.printer('current', fg='green') self.current = self.printer('current', fg='green')
self.local = self.printer('local') self.local = self.printer('local')
self.notinproject = self.printer('notinproject', fg='red') self.notinproject = self.printer('notinproject', fg='red')
class BranchInfo(object): class BranchInfo(object):
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
self.current = 0 self.current = 0
self.published = 0 self.published = 0
self.published_equal = 0 self.published_equal = 0
self.projects = [] self.projects = []
def add(self, b): def add(self, b):
if b.current: if b.current:
self.current += 1 self.current += 1
if b.published: if b.published:
self.published += 1 self.published += 1
if b.revision == b.published: if b.revision == b.published:
self.published_equal += 1 self.published_equal += 1
self.projects.append(b) self.projects.append(b)
@property @property
def IsCurrent(self): def IsCurrent(self):
return self.current > 0 return self.current > 0
@property @property
def IsPublished(self): def IsPublished(self):
return self.published > 0 return self.published > 0
@property @property
def IsPublishedEqual(self): def IsPublishedEqual(self):
return self.published_equal == len(self.projects) return self.published_equal == len(self.projects)
class Branches(Command): class Branches(Command):
common = True common = True
helpSummary = "View current topic branches" helpSummary = "View current topic branches"
helpUsage = """ helpUsage = """
%prog [<project>...] %prog [<project>...]
Summarizes the currently available topic branches. Summarizes the currently available topic branches.
...@@ -91,77 +93,77 @@ is shown, then the branch appears in all projects. ...@@ -91,77 +93,77 @@ is shown, then the branch appears in all projects.
""" """
def Execute(self, opt, args): def Execute(self, opt, args):
projects = self.GetProjects(args) projects = self.GetProjects(args)
out = BranchColoring(self.manifest.manifestProject.config) out = BranchColoring(self.manifest.manifestProject.config)
all_branches = {} all_branches = {}
project_cnt = len(projects) project_cnt = len(projects)
for project in projects: for project in projects:
for name, b in list(project.GetBranches().items()): for name, b in list(project.GetBranches().items()):
b.project = project b.project = project
if name not in all_branches: if name not in all_branches:
all_branches[name] = BranchInfo(name) all_branches[name] = BranchInfo(name)
all_branches[name].add(b) all_branches[name].add(b)
names = list(all_branches.keys()) names = list(all_branches.keys())
names.sort() names.sort()
if not names: if not names:
print(' (no branches)', file=sys.stderr) print(' (no branches)', file=sys.stderr)
return return
width = 25 width = 25
for name in names: for name in names:
if width < len(name): if width < len(name):
width = len(name) width = len(name)
for name in names: for name in names:
i = all_branches[name] i = all_branches[name]
in_cnt = len(i.projects) in_cnt = len(i.projects)
if i.IsCurrent: if i.IsCurrent:
current = '*' current = '*'
hdr = out.current hdr = out.current
else: else:
current = ' ' current = ' '
hdr = out.local hdr = out.local
if i.IsPublishedEqual: if i.IsPublishedEqual:
published = 'P' published = 'P'
elif i.IsPublished: elif i.IsPublished:
published = 'p' published = 'p'
else: else:
published = ' ' published = ' '
hdr('%c%c %-*s' % (current, published, width, name)) hdr('%c%c %-*s' % (current, published, width, name))
out.write(' |') out.write(' |')
if in_cnt < project_cnt: if in_cnt < project_cnt:
fmt = out.write fmt = out.write
paths = [] paths = []
if in_cnt < project_cnt - in_cnt: if in_cnt < project_cnt - in_cnt:
in_type = 'in' in_type = 'in'
for b in i.projects: for b in i.projects:
paths.append(b.project.relpath) paths.append(b.project.relpath)
else: else:
fmt = out.notinproject fmt = out.notinproject
in_type = 'not in' in_type = 'not in'
have = set() have = set()
for b in i.projects: for b in i.projects:
have.add(b.project) have.add(b.project)
for p in projects: for p in projects:
if not p in have: if not p in have:
paths.append(p.relpath) paths.append(p.relpath)
s = ' %s %s' % (in_type, ', '.join(paths)) s = ' %s %s' % (in_type, ', '.join(paths))
if width + 7 + len(s) < 80: if width + 7 + len(s) < 80:
fmt(s) fmt(s)
else: else:
fmt(' %s:' % in_type) fmt(' %s:' % in_type)
for p in paths: for p in paths:
out.nl()
fmt(width * ' ' + ' %s' % p)
else:
out.write(' in all projects')
out.nl() out.nl()
fmt(width*' ' + ' %s' % p)
else:
out.write(' in all projects')
out.nl()
...@@ -18,13 +18,14 @@ import sys ...@@ -18,13 +18,14 @@ import sys
from command import Command from command import Command
from progress import Progress from progress import Progress
class Checkout(Command): class Checkout(Command):
common = True common = True
helpSummary = "Checkout a branch for development" helpSummary = "Checkout a branch for development"
helpUsage = """ helpUsage = """
%prog <branchname> [<project>...] %prog <branchname> [<project>...]
""" """
helpDescription = """ helpDescription = """
The '%prog' command checks out an existing branch that was previously The '%prog' command checks out an existing branch that was previously
created by 'repo start'. created by 'repo start'.
...@@ -33,32 +34,32 @@ The command is equivalent to: ...@@ -33,32 +34,32 @@ The command is equivalent to:
repo forall [<project>...] -c git checkout <branchname> repo forall [<project>...] -c git checkout <branchname>
""" """
def Execute(self, opt, args): def Execute(self, opt, args):
if not args: if not args:
self.Usage() self.Usage()
nb = args[0] nb = args[0]
err = [] err = []
success = [] success = []
all_projects = self.GetProjects(args[1:]) all_projects = self.GetProjects(args[1:])
pm = Progress('Checkout %s' % nb, len(all_projects)) pm = Progress('Checkout %s' % nb, len(all_projects))
for project in all_projects: for project in all_projects:
pm.update() pm.update()
status = project.CheckoutBranch(nb) status = project.CheckoutBranch(nb)
if status is not None: if status is not None:
if status: if status:
success.append(project) success.append(project)
else: else:
err.append(project) err.append(project)
pm.end() pm.end()
if err: if err:
for p in err: for p in err:
print("error: %s/: cannot checkout %s" % (p.relpath, nb), print("error: %s/: cannot checkout %s" % (p.relpath, nb),
file=sys.stderr) file=sys.stderr)
sys.exit(1) sys.exit(1)
elif not success: elif not success:
print('error: no project has branch %s' % nb, file=sys.stderr) print('error: no project has branch %s' % nb, file=sys.stderr)
sys.exit(1) sys.exit(1)
...@@ -22,94 +22,95 @@ import portable ...@@ -22,94 +22,95 @@ import portable
CHANGE_ID_RE = re.compile(r'^\s*Change-Id: I([0-9a-f]{40})\s*$') CHANGE_ID_RE = re.compile(r'^\s*Change-Id: I([0-9a-f]{40})\s*$')
class CherryPick(Command): class CherryPick(Command):
common = True common = True
helpSummary = "Cherry-pick a change." helpSummary = "Cherry-pick a change."
helpUsage = """ helpUsage = """
%prog <sha1> %prog <sha1>
""" """
helpDescription = """ helpDescription = """
'%prog' cherry-picks a change from one branch to another. '%prog' cherry-picks a change from one branch to another.
The change id will be updated, and a reference to the old The change id will be updated, and a reference to the old
change id will be added. change id will be added.
""" """
def _Options(self, p): def _Options(self, p):
pass pass
def Execute(self, opt, args): def Execute(self, opt, args):
if len(args) != 1: if len(args) != 1:
self.Usage() self.Usage()
reference = args[0] reference = args[0]
p = GitCommand(None, p = GitCommand(None,
['rev-parse', '--verify', reference], ['rev-parse', '--verify', reference],
capture_stdout = True, capture_stdout=True,
capture_stderr = True) capture_stderr=True)
if p.Wait() != 0: if p.Wait() != 0:
print(p.stderr, file=sys.stderr) print(p.stderr, file=sys.stderr)
sys.exit(1) sys.exit(1)
sha1 = p.stdout.strip() sha1 = p.stdout.strip()
p = GitCommand(None, ['cat-file', 'commit', sha1], capture_stdout=True) p = GitCommand(None, ['cat-file', 'commit', sha1], capture_stdout=True)
if p.Wait() != 0: if p.Wait() != 0:
print("error: Failed to retrieve old commit message", file=sys.stderr) print("error: Failed to retrieve old commit message", file=sys.stderr)
sys.exit(1) sys.exit(1)
old_msg = self._StripHeader(portable.stream2str(p.stdout)) old_msg = self._StripHeader(portable.stream2str(p.stdout))
p = GitCommand(None, p = GitCommand(None,
['cherry-pick', sha1], ['cherry-pick', sha1],
capture_stdout = True, capture_stdout=True,
capture_stderr = True) capture_stderr=True)
status = p.Wait() status = p.Wait()
print(p.stdout, file=sys.stdout) print(p.stdout, file=sys.stdout)
print(p.stderr, file=sys.stderr) print(p.stderr, file=sys.stderr)
if status == 0: if status == 0:
# The cherry-pick was applied correctly. We just need to edit the # The cherry-pick was applied correctly. We just need to edit the
# commit message. # commit message.
new_msg = self._Reformat(old_msg, sha1) new_msg = self._Reformat(old_msg, sha1)
p = GitCommand(None, ['commit', '--amend', '-F', '-'], p = GitCommand(None, ['commit', '--amend', '-F', '-'],
provide_stdin = True, provide_stdin=True,
capture_stdout = True, capture_stdout=True,
capture_stderr = True) capture_stderr=True)
p.stdin.write(new_msg) p.stdin.write(new_msg)
if p.Wait() != 0: if p.Wait() != 0:
print("error: Failed to update commit message", file=sys.stderr) print("error: Failed to update commit message", file=sys.stderr)
sys.exit(1) sys.exit(1)
else: else:
print('NOTE: When committing (please see above) and editing the commit' print('NOTE: When committing (please see above) and editing the commit'
'message, please remove the old Change-Id-line and add:') 'message, please remove the old Change-Id-line and add:')
print(self._GetReference(sha1), file=stderr) print(self._GetReference(sha1), file=stderr)
print(file=stderr) print(file=stderr)
def _IsChangeId(self, line): def _IsChangeId(self, line):
return CHANGE_ID_RE.match(line) return CHANGE_ID_RE.match(line)
def _GetReference(self, sha1): def _GetReference(self, sha1):
return "(cherry picked from commit %s)" % sha1 return "(cherry picked from commit %s)" % sha1
def _StripHeader(self, commit_msg): def _StripHeader(self, commit_msg):
lines = commit_msg.splitlines() lines = commit_msg.splitlines()
return "\n".join(lines[lines.index("")+1:]) return "\n".join(lines[lines.index("") + 1:])
def _Reformat(self, old_msg, sha1): def _Reformat(self, old_msg, sha1):
new_msg = [] new_msg = []
for line in old_msg.splitlines(): for line in old_msg.splitlines():
if not self._IsChangeId(line): if not self._IsChangeId(line):
new_msg.append(line) new_msg.append(line)
# Add a blank line between the message and the change id/reference # Add a blank line between the message and the change id/reference
try: try:
if new_msg[-1].strip() != "": if new_msg[-1].strip() != "":
new_msg.append("") new_msg.append("")
except IndexError: except IndexError:
pass pass
new_msg.append(self._GetReference(sha1)) new_msg.append(self._GetReference(sha1))
return "\n".join(new_msg) return "\n".join(new_msg)
...@@ -15,10 +15,11 @@ ...@@ -15,10 +15,11 @@
from command import PagedCommand from command import PagedCommand
class Diff(PagedCommand): class Diff(PagedCommand):
common = True common = True
helpSummary = "Show changes between commit and working tree" helpSummary = "Show changes between commit and working tree"
helpUsage = """ helpUsage = """
%prog [<project>...] %prog [<project>...]
The -u option causes '%prog' to generate diff output with file paths The -u option causes '%prog' to generate diff output with file paths
...@@ -26,15 +27,16 @@ relative to the repository root, so the output can be applied ...@@ -26,15 +27,16 @@ relative to the repository root, so the output can be applied
to the Unix 'patch' command. to the Unix 'patch' command.
""" """
def _Options(self, p): def _Options(self, p):
def cmd(option, opt_str, value, parser): def cmd(option, opt_str, value, parser):
setattr(parser.values, option.dest, list(parser.rargs)) setattr(parser.values, option.dest, list(parser.rargs))
while parser.rargs: while parser.rargs:
del parser.rargs[0] del parser.rargs[0]
p.add_option('-u', '--absolute',
dest='absolute', action='store_true', p.add_option('-u', '--absolute',
help='Paths are relative to the repository root') dest='absolute', action='store_true',
help='Paths are relative to the repository root')
def Execute(self, opt, args): def Execute(self, opt, args):
for project in self.GetProjects(args): for project in self.GetProjects(args):
project.PrintWorkTreeDiff(opt.absolute) project.PrintWorkTreeDiff(opt.absolute)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -18,62 +18,63 @@ import re ...@@ -18,62 +18,63 @@ import re
from command import Command, MirrorSafeCommand from command import Command, MirrorSafeCommand
class List(Command, MirrorSafeCommand): class List(Command, MirrorSafeCommand):
common = True common = True
helpSummary = "List projects and their associated directories" helpSummary = "List projects and their associated directories"
helpUsage = """ helpUsage = """
%prog [-f] [<project>...] %prog [-f] [<project>...]
%prog [-f] -r str1 [str2]..." %prog [-f] -r str1 [str2]..."
""" """
helpDescription = """ helpDescription = """
List all projects; pass '.' to list the project for the cwd. List all projects; pass '.' to list the project for the cwd.
This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'. This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
""" """
def _Options(self, p, show_smart=True): def _Options(self, p, show_smart=True):
p.add_option('-r', '--regex', p.add_option('-r', '--regex',
dest='regex', action='store_true', dest='regex', action='store_true',
help="Filter the project list based on regex or wildcard matching of strings") help="Filter the project list based on regex or wildcard matching of strings")
p.add_option('-f', '--fullpath', p.add_option('-f', '--fullpath',
dest='fullpath', action='store_true', dest='fullpath', action='store_true',
help="Display the full work tree path instead of the relative path") help="Display the full work tree path instead of the relative path")
def Execute(self, opt, args): def Execute(self, opt, args):
"""List all projects and the associated directories. """List all projects and the associated directories.
This may be possible to do with 'repo forall', but repo newbies have This may be possible to do with 'repo forall', but repo newbies have
trouble figuring that out. The idea here is that it should be more trouble figuring that out. The idea here is that it should be more
discoverable. discoverable.
Args: Args:
opt: The options. opt: The options.
args: Positional args. Can be a list of projects to list, or empty. args: Positional args. Can be a list of projects to list, or empty.
""" """
if not opt.regex: if not opt.regex:
projects = self.GetProjects(args) projects = self.GetProjects(args)
else: else:
projects = self.FindProjects(args) projects = self.FindProjects(args)
def _getpath(x): def _getpath(x):
if opt.fullpath: if opt.fullpath:
return x.worktree return x.worktree
return x.relpath return x.relpath
lines = [] lines = []
for project in projects: for project in projects:
lines.append("%s : %s" % (_getpath(project), project.name)) lines.append("%s : %s" % (_getpath(project), project.name))
lines.sort() lines.sort()
print('\n'.join(lines)) print('\n'.join(lines))
def FindProjects(self, args): def FindProjects(self, args):
result = [] result = []
for project in self.GetProjects(''): for project in self.GetProjects(''):
for arg in args: for arg in args:
pattern = re.compile(r'%s' % arg, re.IGNORECASE) pattern = re.compile(r'%s' % arg, re.IGNORECASE)
if pattern.search(project.name) or pattern.search(project.relpath): if pattern.search(project.name) or pattern.search(project.relpath):
result.append(project) result.append(project)
break break
result.sort(key=lambda project: project.relpath) result.sort(key=lambda project: project.relpath)
return result return result
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment