aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README2
-rw-r--r--doc/index.txt3
-rw-r--r--doc/spam.txt4
-rw-r--r--doc/tutorial.txt3
-rw-r--r--libbe/command/diff.py2
-rw-r--r--libbe/storage/vcs/__init__.py1
-rw-r--r--libbe/storage/vcs/arch.py446
-rw-r--r--libbe/storage/vcs/base.py4
-rw-r--r--libbe/ui/util/user.py4
-rwxr-xr-xtest_usage.sh34
10 files changed, 10 insertions, 493 deletions
diff --git a/README b/README
index 90f1c63..4f97835 100644
--- a/README
+++ b/README
@@ -2,7 +2,7 @@ Bugs Everywhere
===============
This is Bugs Everywhere (BE), a bugtracker built on distributed version
-control. It works with Arch, Bazaar, Darcs, Git, Mercurial, and Monotone
+control. It works with Bazaar, Darcs, Git, Mercurial, and Monotone
at the moment, but is easily extensible. It can also function with no
VCS at all.
diff --git a/doc/index.txt b/doc/index.txt
index 7eb19b7..d52e7ce 100644
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -2,11 +2,10 @@ Welcome to the bugs-everywhere documentation!
=============================================
Bugs Everywhere (BE) is a bugtracker built on distributed version
-control. It works with Arch_, Bazaar_, Darcs_, Git_, Mercurial_, and
+control. It works with Bazaar_, Darcs_, Git_, Mercurial_, and
Monotone_ at the moment, but is easily extensible. It can also
function with no VCS at all.
-.. _Arch: http://www.gnu.org/software/gnu-arch/
.. _Bazaar: http://bazaar.canonical.com/
.. _Darcs: http://darcs.net/
.. _Git: http://git-scm.com/
diff --git a/doc/spam.txt b/doc/spam.txt
index 80a43ff..523f0c2 100644
--- a/doc/spam.txt
+++ b/doc/spam.txt
@@ -11,8 +11,6 @@ If the offending commit is the last commit
==========================================
+-------+--------------------------------------------------------------+
-| arch | |
-+-------+--------------------------------------------------------------+
| bzr | bzr uncommit && bzr revert |
+-------+--------------------------------------------------------------+
| darcs | darcs obliterate --last=1 |
@@ -28,8 +26,6 @@ If the offending commit is not the last commit
==============================================
+----------+-----------------------------------------------+
-| arch | |
-+----------+-----------------------------------------------+
| bzr [#]_ | bzr rebase -r <XYZ+1>..-1 --onto before:XYZ . |
+----------+-----------------------------------------------+
| darcs | darcs obliterate --matches 'name XYZ' |
diff --git a/doc/tutorial.txt b/doc/tutorial.txt
index a3452fc..443ac2a 100644
--- a/doc/tutorial.txt
+++ b/doc/tutorial.txt
@@ -75,7 +75,7 @@ will give help on the ``init`` command.
Initialization
--------------
-You're happily coding in your Arch_ / Bazaar_ / Darcs_ / Git_ /
+You're happily coding in your Bazaar_ / Darcs_ / Git_ /
Mercurial_ / Monotone_ versioned project and you discover a bug.
You think, "Hmm, I'll need a simple way to track these things". This
is where BE comes in. One of the benefits of distributed versioning
@@ -95,7 +95,6 @@ be versioned starting with your next commit. See::
for specific details about where the ``.be`` directory will end up
if you call it from a directory besides your project's root.
-.. _Arch: http://www.gnu.org/software/gnu-arch/
.. _Bazaar: http://bazaar.canonical.com/
.. _Darcs: http://darcs.net/
.. _Git: http://git-scm.com/
diff --git a/libbe/command/diff.py b/libbe/command/diff.py
index 1e9c396..1368654 100644
--- a/libbe/command/diff.py
+++ b/libbe/command/diff.py
@@ -138,8 +138,6 @@ tree, and prints a pretty report. If REVISION is given, it is a
specifier for the particular previous tree to use. Specifiers are
specific to their storage backend.
-For Arch your specifier must be a fully-qualified revision name.
-
Besides the standard summary output, you can use the options to output
UUIDS for the different categories. This output can be used as the
input to 'be show' to get an understanding of the current status.
diff --git a/libbe/storage/vcs/__init__.py b/libbe/storage/vcs/__init__.py
index 9dadd73..2fd8dc4 100644
--- a/libbe/storage/vcs/__init__.py
+++ b/libbe/storage/vcs/__init__.py
@@ -23,7 +23,6 @@
There is a base class (:py:class:`~libbe.storage.vcs.VCS`) translating
Storage language to VCS language, and a number of `VCS` implementations:
-* :py:class:`~libbe.storage.vcs.arch.Arch`
* :py:class:`~libbe.storage.vcs.bzr.Bzr`
* :py:class:`~libbe.storage.vcs.darcs.Darcs`
* :py:class:`~libbe.storage.vcs.git.Git`
diff --git a/libbe/storage/vcs/arch.py b/libbe/storage/vcs/arch.py
deleted file mode 100644
index 9874345..0000000
--- a/libbe/storage/vcs/arch.py
+++ /dev/null
@@ -1,446 +0,0 @@
-# Copyright (C) 2005-2012 Aaron Bentley <abentley@panoramicfeedback.com>
-# Ben Finney <benf@cybersource.com.au>
-# Chris Ball <cjb@laptop.org>
-# Gianluca Montecchi <gian@grys.it>
-# James Rowe <jnrowe@ukfsn.org>
-# W. Trevor King <wking@tremily.us>
-#
-# This file is part of Bugs Everywhere.
-#
-# Bugs Everywhere is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the Free
-# Software Foundation, either version 2 of the License, or (at your option) any
-# later version.
-#
-# Bugs Everywhere is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-# more details.
-#
-# You should have received a copy of the GNU General Public License along with
-# Bugs Everywhere. If not, see <http://www.gnu.org/licenses/>.
-
-"""GNU Arch_ (tla) backend.
-
-.. _Arch: http://www.gnu.org/software/gnu-arch/
-"""
-
-import codecs
-import os
-import os.path
-import re
-import shutil
-import sys
-import time # work around http://mercurial.selenic.com/bts/issue618
-
-import libbe
-from ...ui.util import user as _user
-from ...util.id import uuid_gen
-from ...util.subproc import CommandError
-from ..util import config as _config
-from . import base
-
-if libbe.TESTING == True:
- import unittest
- import doctest
-
-
-class CantAddFile(Exception):
- def __init__(self, file):
- self.file = file
- Exception.__init__(self, "Can't automatically add file %s" % file)
-
-DEFAULT_CLIENT = 'tla'
-
-client = _config.get_val(
- 'arch_client', default=DEFAULT_CLIENT)
-
-def new():
- return Arch()
-
-class Arch(base.VCS):
- """:py:class:`base.VCS` implementation for GNU Arch.
- """
- name = 'arch'
- client = client
- _archive_name = None
- _archive_dir = None
- _tmp_archive = False
- _project_name = None
- _tmp_project = False
- _arch_paramdir = os.path.expanduser('~/.arch-params')
-
- def __init__(self, *args, **kwargs):
- base.VCS.__init__(self, *args, **kwargs)
- self.versioned = True
- self.interspersed_vcs_files = True
- self.paranoid = False
- self.__updated = [] # work around http://mercurial.selenic.com/bts/issue618
-
- def _vcs_version(self):
- try:
- status,output,error = self._u_invoke_client('--version')
- except CommandError: # command not found?
- return None
- version = '\n'.join(output.splitlines()[:2])
- return version
-
- def _vcs_detect(self, path):
- """Detect whether a directory is revision-controlled using Arch"""
- if self._u_search_parent_directories(path, '{arch}') != None :
- _config.set_val('arch_client', client)
- return True
- return False
-
- def _vcs_init(self, path):
- self._create_archive(path)
- self._create_project(path)
- self._add_project_code(path)
-
- def _create_archive(self, path):
- """Create a temporary Arch archive in the directory PATH. This
- archive will be removed by::
-
- destroy->_vcs_destroy->_remove_archive
- """
- # http://regexps.srparish.net/tutorial-tla/new-archive.html#Creating_a_New_Archive
- assert self._archive_name == None
- id = self.get_user_id()
- name, email = _user.parse_user_id(id)
- if email == None:
- email = '%s@example.com' % name
- trailer = '%s-%s' % ('bugs-everywhere-auto', uuid_gen()[0:8])
- self._archive_name = '%s--%s' % (email, trailer)
- self._archive_dir = '/tmp/%s' % trailer
- self._tmp_archive = True
- self._u_invoke_client('make-archive', self._archive_name,
- self._archive_dir, cwd=path)
-
- def _invoke_client(self, *args, **kwargs):
- """Invoke the client on our archive.
- """
- assert self._archive_name != None
- command = args[0]
- if len(args) > 1:
- tailargs = args[1:]
- else:
- tailargs = []
- arglist = [command, '-A', self._archive_name]
- arglist.extend(tailargs)
- args = tuple(arglist)
- return self._u_invoke_client(*args, **kwargs)
-
- def _remove_archive(self):
- assert self._tmp_archive == True
- assert self._archive_dir != None
- assert self._archive_name != None
- os.remove(os.path.join(self._arch_paramdir,
- '=locations', self._archive_name))
- shutil.rmtree(self._archive_dir)
- self._tmp_archive = False
- self._archive_dir = False
- self._archive_name = False
-
- def _create_project(self, path):
- """
- Create a temporary Arch project in the directory PATH. This
- project will be removed by
- destroy->_vcs_destroy->_remove_project
- """
- # http://mwolson.org/projects/GettingStartedWithArch.html
- # http://regexps.srparish.net/tutorial-tla/new-project.html#Starting_a_New_Project
- category = 'bugs-everywhere'
- branch = 'mainline'
- version = '0.1'
- self._project_name = '%s--%s--%s' % (category, branch, version)
- self._invoke_client('archive-setup', self._project_name,
- cwd=path)
- self._tmp_project = True
-
- def _remove_project(self):
- assert self._tmp_project == True
- assert self._project_name != None
- assert self._archive_dir != None
- shutil.rmtree(os.path.join(self._archive_dir, self._project_name))
- self._tmp_project = False
- self._project_name = False
-
- def _archive_project_name(self):
- assert self._archive_name != None
- assert self._project_name != None
- return '%s/%s' % (self._archive_name, self._project_name)
-
- def _adjust_naming_conventions(self, path):
- """Adjust `Arch naming conventions`_ so ``.be`` is considered source
- code.
-
- By default, Arch restricts source code filenames to::
-
- ^[_=a-zA-Z0-9].*$
-
- Since our bug directory ``.be`` doesn't satisfy these conventions,
- we need to adjust them. The conventions are specified in::
-
- project-root/{arch}/=tagging-method
-
- .. _Arch naming conventions:
- http://regexps.srparish.net/tutorial-tla/naming-conventions.html
- """
- tagpath = os.path.join(path, '{arch}', '=tagging-method')
- lines_out = []
- f = codecs.open(tagpath, 'r', self.encoding)
- for line in f:
- if line.startswith('source '):
- lines_out.append('source ^[._=a-zA-X0-9].*$\n')
- else:
- lines_out.append(line)
- f.close()
- f = codecs.open(tagpath, 'w', self.encoding)
- f.write(''.join(lines_out))
- f.close()
-
- def _add_project_code(self, path):
- # http://mwolson.org/projects/GettingStartedWithArch.html
- # http://regexps.srparish.net/tutorial-tla/new-source.html
- # http://regexps.srparish.net/tutorial-tla/importing-first.html
- self._invoke_client('init-tree', self._project_name,
- cwd=path)
- self._adjust_naming_conventions(path)
- self._invoke_client('import', '--summary', 'Began versioning',
- cwd=path)
-
- def _vcs_destroy(self):
- if self._tmp_project == True:
- self._remove_project()
- if self._tmp_archive == True:
- self._remove_archive()
- vcs_dir = os.path.join(self.repo, '{arch}')
- if os.path.exists(vcs_dir):
- shutil.rmtree(vcs_dir)
- self._archive_name = None
-
- def _vcs_root(self, path):
- if not os.path.isdir(path):
- dirname = os.path.dirname(path)
- else:
- dirname = path
- status,output,error = self._u_invoke_client('tree-root', dirname)
- root = output.rstrip('\n')
-
- self._get_archive_project_name(root)
-
- return root
-
- def _get_archive_name(self, root):
- status,output,error = self._u_invoke_client('archives')
- lines = output.split('\n')
- # e.g. output:
- # jdoe@example.com--bugs-everywhere-auto-2008.22.24.52
- # /tmp/BEtestXXXXXX/rootdir
- # (+ repeats)
- for archive,location in zip(lines[::2], lines[1::2]):
- if os.path.realpath(location) == os.path.realpath(root):
- self._archive_name = archive
- assert self._archive_name != None
-
- def _get_archive_project_name(self, root):
- # get project names
- status,output,error = self._u_invoke_client('tree-version', cwd=root)
- # e.g output
- # jdoe@example.com--bugs-everywhere-auto-2008.22.24.52/be--mainline--0.1
- archive_name,project_name = output.rstrip('\n').split('/')
- self._archive_name = archive_name
- self._project_name = project_name
-
- def _vcs_get_user_id(self):
- try:
- status,output,error = self._u_invoke_client('my-id')
- return output.rstrip('\n')
- except Exception, e:
- if 'no arch user id set' in e.args[0]:
- return None
- else:
- raise
-
- def _vcs_add(self, path):
- self._u_invoke_client('add-id', path)
- realpath = os.path.realpath(self._u_abspath(path))
- pathAdded = realpath in self._list_added(self.repo)
- if self.paranoid and not pathAdded:
- self._force_source(path)
-
- def _list_added(self, root):
- assert os.path.exists(root)
- assert os.access(root, os.X_OK)
- root = os.path.realpath(root)
- status,output,error = self._u_invoke_client('inventory', '--source',
- '--both', '--all', root)
- inv_str = output.rstrip('\n')
- return [os.path.join(root, p) for p in inv_str.split('\n')]
-
- def _add_dir_rule(self, rule, dirname, root):
- inv_path = os.path.join(dirname, '.arch-inventory')
- f = codecs.open(inv_path, 'a', self.encoding)
- f.write(rule)
- f.close()
- if os.path.realpath(inv_path) not in self._list_added(root):
- paranoid = self.paranoid
- self.paranoid = False
- self.add(inv_path)
- self.paranoid = paranoid
-
- def _force_source(self, path):
- rule = 'source %s\n' % self._u_rel_path(path)
- self._add_dir_rule(rule, os.path.dirname(path), self.repo)
- if os.path.realpath(path) not in self._list_added(self.repo):
- raise CantAddFile(path)
-
- def _vcs_remove(self, path):
- if self._vcs_is_versioned(path):
- self._u_invoke_client('delete-id', path)
- arch_ids = os.path.join(self.repo, path, '.arch-ids')
- if os.path.exists(arch_ids):
- shutil.rmtree(arch_ids)
-
- def _vcs_update(self, path):
- self.__updated.append(path) # work around http://mercurial.selenic.com/bts/issue618
-
- def _vcs_is_versioned(self, path):
- if '.arch-ids' in path:
- return False
- return True
-
- def _vcs_get_file_contents(self, path, revision=None):
- if revision == None:
- return base.VCS._vcs_get_file_contents(self, path, revision)
- else:
- relpath = self._file_find(path, revision, relpath=True)
- return base.VCS._vcs_get_file_contents(self, relpath)
-
- def _file_find(self, path, revision, relpath=False):
- try:
- status,output,error = \
- self._invoke_client(
- 'file-find', '--unescaped', path, revision)
- path = output.rstrip('\n').splitlines()[-1]
- except CommandError, e:
- if e.status == 2 \
- and 'illegally formed changeset index' in e.stderr:
- raise NotImplementedError(
-"""Outstanding tla bug, see
- https://bugs.launchpad.net/ubuntu/+source/tla/+bug/513472
-""")
- raise
- if relpath == True:
- return path
- return os.path.abspath(os.path.join(self.repo, path))
-
- def _vcs_path(self, id, revision):
- return self._u_find_id(id, revision)
-
- def _vcs_isdir(self, path, revision):
- abspath = self._file_find(path, revision)
- return os.path.isdir(abspath)
-
- def _vcs_listdir(self, path, revision):
- abspath = self._file_find(path, revision)
- return [p for p in os.listdir(abspath) if self._vcs_is_versioned(p)]
-
- def _vcs_commit(self, commitfile, allow_empty=False):
- if allow_empty == False:
- # arch applies empty commits without complaining, so check first
- status,output,error = self._u_invoke_client('changes',expect=(0,1))
- if status == 0:
- # work around http://mercurial.selenic.com/bts/issue618
- time.sleep(1)
- for path in self.__updated:
- os.utime(os.path.join(self.repo, path), None)
- self.__updated = []
- status,output,error = self._u_invoke_client('changes',expect=(0,1))
- if status == 0:
- # end work around
- raise base.EmptyCommit()
- summary,body = self._u_parse_commitfile(commitfile)
- args = ['commit', '--summary', summary]
- if body != None:
- args.extend(['--log-message',body])
- status,output,error = self._u_invoke_client(*args)
- revision = None
- revline = re.compile('[*] committed (.*)')
- match = revline.search(output)
- assert match != None, output+error
- assert len(match.groups()) == 1
- revpath = match.groups()[0]
- assert not " " in revpath, revpath
- assert revpath.startswith(self._archive_project_name()+'--')
- revision = revpath[len(self._archive_project_name()+'--'):]
- return revpath
-
- def _vcs_revision_id(self, index):
- status,output,error = self._u_invoke_client('logs')
- logs = output.splitlines()
- first_log = logs.pop(0)
- assert first_log == 'base-0', first_log
- try:
- if index > 0:
- log = logs[index-1]
- elif index < 0:
- log = logs[index]
- else:
- return None
- except IndexError:
- return None
- return '%s--%s' % (self._archive_project_name(), log)
-
- def _diff(self, revision):
- status,output,error = self._u_invoke_client(
- 'diff', '--summary', '--unescaped', revision, expect=(0,1))
- return output
-
- def _parse_diff(self, diff_text):
- """
- Example diff text:
-
- * local directory is at ...
- * build pristine tree for ...
- * from import revision: ...
- * patching for revision: ...
- * comparing to ...
- D .be/dir/bugs/.arch-ids/moved.id
- D .be/dir/bugs/.arch-ids/removed.id
- D .be/dir/bugs/moved
- D .be/dir/bugs/removed
- A .be/dir/bugs/.arch-ids/moved2.id
- A .be/dir/bugs/.arch-ids/new.id
- A .be/dir/bugs/moved2
- A .be/dir/bugs/new
- A {arch}/bugs-everywhere/bugs-everywhere--mainline/...
- M .be/dir/bugs/modified
- """
- new = []
- modified = []
- removed = []
- lines = diff_text.splitlines()
- for i,line in enumerate(lines):
- if line.startswith('* ') or '/.arch-ids/' in line:
- continue
- change,file = line.split(' ',1)
- if file.startswith('{arch}/'):
- continue
- if change == 'A':
- new.append(file)
- elif change == 'M':
- modified.append(file)
- elif change == 'D':
- removed.append(file)
- return (new,modified,removed)
-
- def _vcs_changed(self, revision):
- return self._parse_diff(self._diff(revision))
-
-
-if libbe.TESTING == True:
- base.make_vcs_testcase_subclasses(Arch, sys.modules[__name__])
-
- unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
- suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])
diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py
index 671df43..e9995b0 100644
--- a/libbe/storage/vcs/base.py
+++ b/libbe/storage/vcs/base.py
@@ -50,7 +50,7 @@ if libbe.TESTING == True:
import libbe.ui.util.user
-VCS_ORDER = ['arch', 'bzr', 'darcs', 'git', 'hg', 'monotone']
+VCS_ORDER = ['bzr', 'darcs', 'git', 'hg', 'monotone']
"""List VCS modules in order of preference.
Don't list this module, it is implicitly last.
@@ -436,7 +436,7 @@ class VCS (libbe.storage.base.VersionedStorage):
otherwise. You only need to set this if the VCS goes about
dumping VCS-specific files into the .be directory.
- If you do need to implement this method (e.g. Arch), set
+ If you do need to implement this method, set
self.interspersed_vcs_files = True
"""
assert self.interspersed_vcs_files == False
diff --git a/libbe/ui/util/user.py b/libbe/ui/util/user.py
index 8b0f3e0..5306593 100644
--- a/libbe/ui/util/user.py
+++ b/libbe/ui/util/user.py
@@ -18,9 +18,7 @@
"""Tools for getting, setting, creating, and parsing the user's ID.
-IDs will look like 'John Doe <jdoe@example.com>'. Note that the
-:py:mod:`libbe.storage.vcs.arch <Arch VCS backend>` *enforces* IDs with
-this format.
+IDs will look like 'John Doe <jdoe@example.com>'.
Do not confuse the user IDs discussed in this module, which refer to
humans, with the "user IDs" discussed in :py:mod:`libbe.util.id`, which
diff --git a/test_usage.sh b/test_usage.sh
index df45f55..621864b 100755
--- a/test_usage.sh
+++ b/test_usage.sh
@@ -6,7 +6,7 @@
#
# usage: test_usage.sh VCS
# where VCS is one of:
-# bzr, git, hg, arch, none
+# bzr, git, hg, none
#
# Note that this script uses the *installed* version of be, not the
# one in your working tree.
@@ -23,14 +23,14 @@ then
echo "usage: test_usage.sh [VCS]"
echo ""
echo "where VCS is one of"
- for VCS in arch bzr darcs git hg none
+ for VCS in bzr darcs git hg none
do
echo " $VCS"
done
exit 1
elif [ $# -eq 0 ]
then
- for VCS in arch bzr darcs git hg none
+ for VCS in bzr darcs git hg none
do
echo -e "\n\nTesting $VCS\n\n"
$0 "$VCS" || exit 1
@@ -44,26 +44,7 @@ TESTDIR=`mktemp -d /tmp/BEtest.XXXXXXXXXX`
cd $TESTDIR
# Initialize the VCS repository
-if [ "$VCS" == "arch" ]
-then
- ID=`tla my-id`
- ARCH_PARAM_DIR="$HOME/.arch-params"
- ARCH_ARCHIVE_ROOT=`mktemp -d /tmp/BEtest.XXXXXXXXXX`
- UNIQUE=`echo "$ARCH_ARCHIVE_ROOT" | sed 's/\/tmp\/BEtest.//;s/[0-9]//g'`
- ARCH_ARCHIVE="j@x.com--BE-test-usage-$UNIQUE"
- ARCH_PROJECT="BE-test-usage--twig--99.5"
- ARCH_ARCHIVE_DIR="$ARCH_ARCHIVE_ROOT/$ARCH_PROJECT"
- echo "tla make-archive $ARCH_ARCHIVE $ARCH_ARCHIVE_DIR"
- tla make-archive $ARCH_ARCHIVE $ARCH_ARCHIVE_DIR
- echo "tla archive-setup -A $ARCH_ARCHIVE $ARCH_PROJECT"
- tla archive-setup -A $ARCH_ARCHIVE $ARCH_PROJECT
- echo "tla init-tree -A $ARCH_ARCHIVE $ARCH_PROJECT"
- tla init-tree -A $ARCH_ARCHIVE $ARCH_PROJECT
- echo "Adjusing the naming conventions to allow .files"
- sed -i 's/^source .*/source ^[._=a-zA-X0-9].*$/' '{arch}/=tagging-method'
- echo "tla import -A $ARCH_ARCHIVE --summary 'Began versioning'"
- tla import -A $ARCH_ARCHIVE --summary 'Began versioning'
-elif [ "$VCS" == "bzr" ]
+if [ "$VCS" == "bzr" ]
then
ID=`bzr whoami`
bzr init
@@ -144,11 +125,4 @@ be commit "But this will fail" || echo "Failed"
cd /
rm -rf $TESTDIR
-if [ "$VCS" == "arch" ]
-then
- # Cleanup everything outside of TESTDIR
- rm -rf "$ARCH_ARCHIVE_ROOT"
- rm -rf "$ARCH_PARAM_DIR/=locations/$ARCH_ARCHIVE"
-fi
-
exec 2>&6 6>&- # restore stderr and close fd 6