{"thread":{"id":"10184","subject":"[StGit PATCH 0/5] Make push and refresh subdirectory safe","startedAt":"2007-10-07T23:24:35Z","lastAt":"2007-10-07T23:25:05Z","messageCount":6,"participants":["Karl Hasselström"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"55119","messageId":"20071007231949.13070.49517.stgit@yoghurt","threadId":"10184","inReplyTo":null,"subject":"[StGit PATCH 0/5] Make push and refresh subdirectory safe","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-10-07T23:24:35Z","receivedAt":"2007-10-07T23:24:35Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"This series makes push and refresh safe to use from subdirectories, by\nusing the infrastructure of the first patch to cd to the top of the\nworktree.\n\nAs stated in the commit messages, this may not be an optimal long-term\nsolution, but we need to fix this quick. And the infrastructure isn't\ngood only for cd'ing to the top of the worktree.\n\nThis series is also available from\n\n  git://repo.or.cz/stgit/kha.git safe\n\n---\n\nKarl Hasselström (5):\n      Make \"stg refresh\" subdirectory safe\n      New test: try \"stg refresh\" in a subdirectory\n      Make \"stg push\" subdirectory safe\n      New test: Try \"stg push\" in a subdirectory\n      Infrastructure for current directory handling\n\n\n stgit/commands/add.py        |    1 +\n stgit/commands/applied.py    |    1 +\n stgit/commands/assimilate.py |    1 +\n stgit/commands/branch.py     |    1 +\n stgit/commands/clean.py      |    1 +\n stgit/commands/clone.py      |    1 +\n stgit/commands/commit.py     |    1 +\n stgit/commands/common.py     |   68 ++++++++++++++++++++++++++++++++++++++++++\n stgit/commands/copy.py       |    1 +\n stgit/commands/delete.py     |    1 +\n stgit/commands/diff.py       |    1 +\n stgit/commands/edit.py       |    1 +\n stgit/commands/export.py     |    1 +\n stgit/commands/files.py      |    1 +\n stgit/commands/float.py      |    1 +\n stgit/commands/fold.py       |    1 +\n stgit/commands/goto.py       |    1 +\n stgit/commands/hide.py       |    1 +\n stgit/commands/id.py         |    1 +\n stgit/commands/imprt.py      |    1 +\n stgit/commands/init.py       |    1 +\n stgit/commands/log.py        |    1 +\n stgit/commands/mail.py       |    1 +\n stgit/commands/new.py        |    1 +\n stgit/commands/patches.py    |    1 +\n stgit/commands/pick.py       |    1 +\n stgit/commands/pop.py        |    1 +\n stgit/commands/pull.py       |    1 +\n stgit/commands/push.py       |    1 +\n stgit/commands/rebase.py     |    1 +\n stgit/commands/refresh.py    |    1 +\n stgit/commands/rename.py     |    1 +\n stgit/commands/resolved.py   |    1 +\n stgit/commands/rm.py         |    1 +\n stgit/commands/series.py     |    1 +\n stgit/commands/show.py       |    1 +\n stgit/commands/sink.py       |    1 +\n stgit/commands/status.py     |    1 +\n stgit/commands/sync.py       |    1 +\n stgit/commands/top.py        |    1 +\n stgit/commands/unapplied.py  |    1 +\n stgit/commands/uncommit.py   |    1 +\n stgit/commands/unhide.py     |    1 +\n stgit/main.py                |    2 +\n t/t1205-push-subdir.sh       |   55 ++++++++++++++++++++++++++++++++++\n t/t2300-refresh-subdir.sh    |   27 +++++++++++++++++\n 46 files changed, 194 insertions(+), 0 deletions(-)\n create mode 100755 t/t1205-push-subdir.sh\n create mode 100755 t/t2300-refresh-subdir.sh\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"55120","messageId":"20071007232441.13070.665.stgit@yoghurt","threadId":"10184","inReplyTo":"20071007231949.13070.49517.stgit@yoghurt","subject":"[StGit PATCH 1/5] Infrastructure for current directory handling","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-10-07T23:24:41Z","receivedAt":"2007-10-07T23:24:41Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"Add infrastructure to allow commands to specify if they need a git\nrepository, if they need to be called from within the working tree,\nand if they should cd to the root of the working tree before doing\nanything else.\n\nFor now, all commands are set to just require a repository (except\n\"stg clone\", which is set to require nothing), which means the only\nthing that's added is some very light error checking. The idea is to\ntighten this for commands that turn out to need it.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n\n---\n\n stgit/commands/add.py        |    1 +\n stgit/commands/applied.py    |    1 +\n stgit/commands/assimilate.py |    1 +\n stgit/commands/branch.py     |    1 +\n stgit/commands/clean.py      |    1 +\n stgit/commands/clone.py      |    1 +\n stgit/commands/commit.py     |    1 +\n stgit/commands/common.py     |   68 ++++++++++++++++++++++++++++++++++++++++++\n stgit/commands/copy.py       |    1 +\n stgit/commands/delete.py     |    1 +\n stgit/commands/diff.py       |    1 +\n stgit/commands/edit.py       |    1 +\n stgit/commands/export.py     |    1 +\n stgit/commands/files.py      |    1 +\n stgit/commands/float.py      |    1 +\n stgit/commands/fold.py       |    1 +\n stgit/commands/goto.py       |    1 +\n stgit/commands/hide.py       |    1 +\n stgit/commands/id.py         |    1 +\n stgit/commands/imprt.py      |    1 +\n stgit/commands/init.py       |    1 +\n stgit/commands/log.py        |    1 +\n stgit/commands/mail.py       |    1 +\n stgit/commands/new.py        |    1 +\n stgit/commands/patches.py    |    1 +\n stgit/commands/pick.py       |    1 +\n stgit/commands/pop.py        |    1 +\n stgit/commands/pull.py       |    1 +\n stgit/commands/push.py       |    1 +\n stgit/commands/rebase.py     |    1 +\n stgit/commands/refresh.py    |    1 +\n stgit/commands/rename.py     |    1 +\n stgit/commands/resolved.py   |    1 +\n stgit/commands/rm.py         |    1 +\n stgit/commands/series.py     |    1 +\n stgit/commands/show.py       |    1 +\n stgit/commands/sink.py       |    1 +\n stgit/commands/status.py     |    1 +\n stgit/commands/sync.py       |    1 +\n stgit/commands/top.py        |    1 +\n stgit/commands/unapplied.py  |    1 +\n stgit/commands/uncommit.py   |    1 +\n stgit/commands/unhide.py     |    1 +\n stgit/main.py                |    2 +\n 44 files changed, 112 insertions(+), 0 deletions(-)\n\n\ndiff --git a/stgit/commands/add.py b/stgit/commands/add.py\nindex fc9c5a7..264ab9f 100644\n--- a/stgit/commands/add.py\n+++ b/stgit/commands/add.py\n@@ -31,6 +31,7 @@ Add the files or directories passed as arguments to the\n repository. When a directory name is given, all the files and\n subdirectories are recursively added.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = []\n \n \ndiff --git a/stgit/commands/applied.py b/stgit/commands/applied.py\nindex b9bb716..45d0926 100644\n--- a/stgit/commands/applied.py\n+++ b/stgit/commands/applied.py\n@@ -32,6 +32,7 @@ List the patches from the series which were already pushed onto the\n stack.  They are listed in the order in which they were pushed, the\n last one being the current (topmost) patch.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one'),\n            make_option('-c', '--count',\ndiff --git a/stgit/commands/assimilate.py b/stgit/commands/assimilate.py\nindex 43672fd..db8a95c 100644\n--- a/stgit/commands/assimilate.py\n+++ b/stgit/commands/assimilate.py\n@@ -53,6 +53,7 @@ Note that these are \"inconsistencies\", not \"errors\"; furthermore,\n with the way \"assimilate\" handles them, you have no reason to avoid\n causing them in the first place if that is convenient for you.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = []\n \n class Commit(object):\ndiff --git a/stgit/commands/branch.py b/stgit/commands/branch.py\nindex c16fc69..6e0a6d8 100644\n--- a/stgit/commands/branch.py\n+++ b/stgit/commands/branch.py\n@@ -40,6 +40,7 @@ When displaying the branches, the names can be prefixed with\n \n If not given any options, switch to the named branch.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-c', '--create',\n                        help = 'create a new development branch',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/clean.py b/stgit/commands/clean.py\nindex 2e3b202..d8bbe71 100644\n--- a/stgit/commands/clean.py\n+++ b/stgit/commands/clean.py\n@@ -31,6 +31,7 @@ Delete the empty patches in the whole series or only those applied or\n unapplied. A patch is considered empty if the two commit objects\n representing its boundaries refer to the same tree object.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-a', '--applied',\n                        help = 'delete the empty applied patches',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/clone.py b/stgit/commands/clone.py\nindex 15139c8..a150010 100644\n--- a/stgit/commands/clone.py\n+++ b/stgit/commands/clone.py\n@@ -29,6 +29,7 @@ usage = \"\"\"%prog [options] <repository> <dir>\n Clone a GIT <repository> into the local <dir> and initialise the\n patch stack.\"\"\"\n \n+directory = DirectoryAnywhere()\n options = []\n \n \ndiff --git a/stgit/commands/commit.py b/stgit/commands/commit.py\nindex 0b76c56..2b45c0d 100644\n--- a/stgit/commands/commit.py\n+++ b/stgit/commands/commit.py\n@@ -32,6 +32,7 @@ remove them from the series while advancing the base.\n Use this command only if you want to permanently store the applied\n patches and no longer manage them with StGIT.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = []\n \n \ndiff --git a/stgit/commands/common.py b/stgit/commands/common.py\nindex 9815400..27ef465 100644\n--- a/stgit/commands/common.py\n+++ b/stgit/commands/common.py\n@@ -24,6 +24,7 @@ from optparse import OptionParser, make_option\n from stgit.exception import *\n from stgit.utils import *\n from stgit.out import *\n+from stgit.run import *\n from stgit import stack, git, basedir\n from stgit.config import config, file_extensions\n \n@@ -480,3 +481,70 @@ def parse_patch(fobj):\n     # we don't yet have an agreed place for the creation date.\n     # Just return None\n     return (descr, authname, authemail, authdate, diff)\n+\n+def readonly_constant_property(f):\n+    \"\"\"Decorator that converts a function that computes a value to an\n+    attribute that returns the value. The value is computed only once,\n+    the first time it is accessed.\"\"\"\n+    def new_f(self):\n+        n = '__' + f.__name__\n+        if not hasattr(self, n):\n+            setattr(self, n, f(self))\n+        return getattr(self, n)\n+    return property(new_f)\n+\n+class DirectoryException(StgException):\n+    pass\n+\n+class _Directory(object):\n+    @readonly_constant_property\n+    def git_dir(self):\n+        try:\n+            return Run('git-rev-parse', '--git-dir'\n+                       ).discard_stderr().output_one_line()\n+        except RunException:\n+            raise DirectoryException('No git repository found')\n+    @readonly_constant_property\n+    def __topdir_path(self):\n+        try:\n+            lines = Run('git-rev-parse', '--show-cdup'\n+                        ).discard_stderr().output_lines()\n+            if len(lines) == 0:\n+                return '.'\n+            elif len(lines) == 1:\n+                return lines[0]\n+            else:\n+                raise RunException('Too much output')\n+        except RunException:\n+            raise DirectoryException('No git repository found')\n+    @readonly_constant_property\n+    def is_inside_git_dir(self):\n+        return { 'true': True, 'false': False\n+                 }[Run('git-rev-parse', '--is-inside-git-dir'\n+                       ).output_one_line()]\n+    @readonly_constant_property\n+    def is_inside_worktree(self):\n+        return { 'true': True, 'false': False\n+                 }[Run('git-rev-parse', '--is-inside-work-tree'\n+                       ).output_one_line()]\n+    def cd_to_topdir(self):\n+        os.chdir(self.__topdir_path)\n+\n+class DirectoryAnywhere(_Directory):\n+    def setup(self):\n+        pass\n+\n+class DirectoryHasRepository(_Directory):\n+    def setup(self):\n+        self.git_dir # might throw an exception\n+\n+class DirectoryInWorktree(DirectoryHasRepository):\n+    def setup(self):\n+        DirectoryHasRepository.setup(self)\n+        if not self.is_inside_worktree:\n+            raise DirectoryException('Not inside a git worktree')\n+\n+class DirectoryGotoToplevel(DirectoryInWorktree):\n+    def setup(self):\n+        DirectoryInWorktree.setup(self)\n+        self.cd_to_topdir()\ndiff --git a/stgit/commands/copy.py b/stgit/commands/copy.py\nindex 76e3bf9..e94dd66 100644\n--- a/stgit/commands/copy.py\n+++ b/stgit/commands/copy.py\n@@ -30,6 +30,7 @@ usage = \"\"\"%prog [options] [<file/dir> <newname> | <files/dirs...> <dir>]\n Copy of the files and dirs passed as arguments under another name or\n location inside the same repository.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = []\n \n def func(parser, options, args):\ndiff --git a/stgit/commands/delete.py b/stgit/commands/delete.py\nindex 2121015..8462857 100644\n--- a/stgit/commands/delete.py\n+++ b/stgit/commands/delete.py\n@@ -36,6 +36,7 @@ patches are deleted, they are popped from the stack.\n \n Note that the 'delete' operation is irreversible.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one')]\n \ndiff --git a/stgit/commands/diff.py b/stgit/commands/diff.py\nindex aeca4ab..f3b0ea2 100644\n--- a/stgit/commands/diff.py\n+++ b/stgit/commands/diff.py\n@@ -42,6 +42,7 @@ rev = '([patch][//[bottom | top]]) | <tree-ish> | base'\n If neither bottom nor top are given but a '//' is present, the command\n shows the specified patch (defaulting to the current one).\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-r', '--range',\n                        metavar = 'rev1[..[rev2]]', dest = 'revs',\n                        help = 'show the diff between revisions'),\ndiff --git a/stgit/commands/edit.py b/stgit/commands/edit.py\nindex 223c628..02970bc 100644\n--- a/stgit/commands/edit.py\n+++ b/stgit/commands/edit.py\n@@ -57,6 +57,7 @@ rejected patch is stored in the .stgit-failed.patch file (and also in\n these files using the '--file' and '--diff' options.\n \"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-d', '--diff',\n                        help = 'edit the patch diff',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/export.py b/stgit/commands/export.py\nindex 62be394..d8ce86d 100644\n--- a/stgit/commands/export.py\n+++ b/stgit/commands/export.py\n@@ -49,6 +49,7 @@ file:\n   %(commemail)s   - committer's e-mail\n \"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-d', '--dir',\n                        help = 'export patches to DIR instead of the default'),\n            make_option('-p', '--patch',\ndiff --git a/stgit/commands/files.py b/stgit/commands/files.py\nindex 1a10023..07cc955 100644\n--- a/stgit/commands/files.py\n+++ b/stgit/commands/files.py\n@@ -34,6 +34,7 @@ given patch. Note that this command doesn't show the files modified in\n the working tree and not yet included in the patch by a 'refresh'\n command. Use the 'diff' or 'status' commands for these files.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-s', '--stat',\n                        help = 'show the diff stat',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/float.py b/stgit/commands/float.py\nindex 0e32f6b..d5299fb 100644\n--- a/stgit/commands/float.py\n+++ b/stgit/commands/float.py\n@@ -31,6 +31,7 @@ necessary pop and push operations will be performed to accomplish\n this. The '--series' option can be used to rearrange the (top) patches\n as specified by the given series file (or the standard input).\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-s', '--series',\n                        help = 'rearrange according to a series file',\n                        action = 'store_true')]\ndiff --git a/stgit/commands/fold.py b/stgit/commands/fold.py\nindex d97185e..6e43101 100644\n--- a/stgit/commands/fold.py\n+++ b/stgit/commands/fold.py\n@@ -34,6 +34,7 @@ performed with the current top. With the --base option, the patch is\n applied onto the specified base and a three-way merged is performed\n with the current top.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-t', '--threeway',\n                        help = 'perform a three-way merge with the current patch',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/goto.py b/stgit/commands/goto.py\nindex a4427fa..9e008a9 100644\n--- a/stgit/commands/goto.py\n+++ b/stgit/commands/goto.py\n@@ -31,6 +31,7 @@ line becomes current. This is a shortcut for the 'push --to' or 'pop\n --to' commands. There is no '--undo' option for 'goto'. Use the 'push'\n command for this.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-k', '--keep',\n                        help = 'keep the local changes when popping patches',\n                        action = 'store_true')]\ndiff --git a/stgit/commands/hide.py b/stgit/commands/hide.py\nindex de19c19..1a38907 100644\n--- a/stgit/commands/hide.py\n+++ b/stgit/commands/hide.py\n@@ -30,6 +30,7 @@ usage = \"\"\"%prog [options] <patch-range>\n Hide a range of unapplied patches so that they are no longer shown in\n the plain 'series' command output.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one')]\n \ndiff --git a/stgit/commands/id.py b/stgit/commands/id.py\nindex f72d2f3..3e28f2f 100644\n--- a/stgit/commands/id.py\n+++ b/stgit/commands/id.py\n@@ -33,6 +33,7 @@ the standard GIT id's like heads and tags, this command also accepts\n 'top' or 'bottom' are passed and <patch> is a valid patch name, 'top'\n will be used by default.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one')]\n \ndiff --git a/stgit/commands/imprt.py b/stgit/commands/imprt.py\nindex 717f373..045f185 100644\n--- a/stgit/commands/imprt.py\n+++ b/stgit/commands/imprt.py\n@@ -44,6 +44,7 @@ stack.\n The patch description has to be separated from the data with a '---'\n line.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-m', '--mail',\n                        help = 'import the patch from a standard e-mail file',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/init.py b/stgit/commands/init.py\nindex 46562d0..475a4ce 100644\n--- a/stgit/commands/init.py\n+++ b/stgit/commands/init.py\n@@ -31,6 +31,7 @@ Initialise the current GIT branch to be used as an StGIT stack. Note\n that you must already be in a GIT repository and .git/HEAD must point\n to a valid file in refs/heads/.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = []\n \n \ndiff --git a/stgit/commands/log.py b/stgit/commands/log.py\nindex 4d6b022..f8337f9 100644\n--- a/stgit/commands/log.py\n+++ b/stgit/commands/log.py\n@@ -43,6 +43,7 @@ represent the changes to the entire base of the current\n patch. Conflicts reset the patch content and a subsequent 'refresh'\n will show the entire patch.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one'),\n            make_option('-p', '--patch',\ndiff --git a/stgit/commands/mail.py b/stgit/commands/mail.py\nindex c32894e..4a4158a 100644\n--- a/stgit/commands/mail.py\n+++ b/stgit/commands/mail.py\n@@ -88,6 +88,7 @@ the following:\n   %(prefix)s       - 'prefix ' string passed on the command line\n   %(shortdescr)s   - the first line of the patch description\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-a', '--all',\n                        help = 'e-mail all the applied patches',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/new.py b/stgit/commands/new.py\nindex 59671ab..ccc8141 100644\n--- a/stgit/commands/new.py\n+++ b/stgit/commands/new.py\n@@ -38,6 +38,7 @@ needed for this.\n If no name is given for the new patch, one is generated from the first\n line of the commit message.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-m', '--message',\n                        help = 'use MESSAGE as the patch description'),\n            make_option('-s', '--showpatch',\ndiff --git a/stgit/commands/patches.py b/stgit/commands/patches.py\nindex fb65b62..0b618fe 100644\n--- a/stgit/commands/patches.py\n+++ b/stgit/commands/patches.py\n@@ -33,6 +33,7 @@ it shows the patches affected by the local tree modifications. The\n '--diff' option also lists the patch log and the diff for the given\n files.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-d', '--diff',\n                        help = 'show the diff for the given files',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/pick.py b/stgit/commands/pick.py\nindex 1c3ef11..3acec32 100644\n--- a/stgit/commands/pick.py\n+++ b/stgit/commands/pick.py\n@@ -34,6 +34,7 @@ the name of the current patch. It can be overridden with the '--name'\n option. A commit object can be reverted with the '--reverse'\n option. The log and author information are those of the commit object.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-n', '--name',\n                        help = 'use NAME as the patch name'),\n            make_option('-r', '--reverse',\ndiff --git a/stgit/commands/pop.py b/stgit/commands/pop.py\nindex 0dfaad9..a1d73e4 100644\n--- a/stgit/commands/pop.py\n+++ b/stgit/commands/pop.py\n@@ -36,6 +36,7 @@ patches passed on the command line are popped from the stack. Some of\n the push operations may fail because of conflicts (push --undo would\n revert the last push operation).\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-a', '--all',\n                        help = 'pop all the applied patches',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/pull.py b/stgit/commands/pull.py\nindex 237bdd9..5fcf2cc 100644\n--- a/stgit/commands/pull.py\n+++ b/stgit/commands/pull.py\n@@ -38,6 +38,7 @@ resolved and the patch pushed again.\n \n Check the 'git fetch' documentation for the <repository> format.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-n', '--nopush',\n                        help = 'do not push the patches back after pulling',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/push.py b/stgit/commands/push.py\nindex 53cdb8f..b91bc5e 100644\n--- a/stgit/commands/push.py\n+++ b/stgit/commands/push.py\n@@ -39,6 +39,7 @@ command run.\n The command also notifies when the patch becomes empty (fully merged\n upstream) or is modified (three-way merged) by the 'push' operation.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-a', '--all',\n                        help = 'push all the unapplied patches',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/rebase.py b/stgit/commands/rebase.py\nindex 513729a..bbb3e12 100644\n--- a/stgit/commands/rebase.py\n+++ b/stgit/commands/rebase.py\n@@ -29,6 +29,7 @@ usage = \"\"\"%prog [options] <new-base-id>\n Pop all patches from current stack, move the stack base to the given\n <new-base-id> and push the patches back.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-n', '--nopush',\n                        help = 'do not push the patches back after rebasing',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/refresh.py b/stgit/commands/refresh.py\nindex 241f065..b283892 100644\n--- a/stgit/commands/refresh.py\n+++ b/stgit/commands/refresh.py\n@@ -37,6 +37,7 @@ options. The '--force' option is useful when a commit object was\n created with a different tool but the changes need to be included in\n the current patch.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-f', '--force',\n                        help = 'force the refresh even if HEAD and '\\\n                        'top differ',\ndiff --git a/stgit/commands/rename.py b/stgit/commands/rename.py\nindex 2830e72..e2b0fa4 100644\n--- a/stgit/commands/rename.py\n+++ b/stgit/commands/rename.py\n@@ -29,6 +29,7 @@ usage = \"\"\"%prog [options] <oldpatch> <newpatch>\n \n Rename <oldpatch> into <newpatch> in a series.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one')]\n \ndiff --git a/stgit/commands/resolved.py b/stgit/commands/resolved.py\nindex 1130641..c2ef678 100644\n--- a/stgit/commands/resolved.py\n+++ b/stgit/commands/resolved.py\n@@ -34,6 +34,7 @@ Mark a merge conflict as resolved. The conflicts can be seen with the\n 'C'. This command also removes any <file>.{ancestor,current,patched}\n files.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-a', '--all',\n                        help = 'mark all conflicts as solved',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/rm.py b/stgit/commands/rm.py\nindex 91908a1..59d098b 100644\n--- a/stgit/commands/rm.py\n+++ b/stgit/commands/rm.py\n@@ -30,6 +30,7 @@ usage = \"\"\"%prog [options] <files...>\n Remove given files from the repository. The command doesn't remove the\n working copy of the file.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-f', '--force',\n                        help = 'force removing even if the file exists',\n                        action = 'store_true')]\ndiff --git a/stgit/commands/series.py b/stgit/commands/series.py\nindex 00a3372..2c75876 100644\n--- a/stgit/commands/series.py\n+++ b/stgit/commands/series.py\n@@ -34,6 +34,7 @@ range. The applied patches are prefixed with a '+', the unapplied ones\n with a '-' and the hidden ones with a '!'. The current patch is\n prefixed with a '>'. Empty patches are prefixed with a '0'.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one'),\n            make_option('-a', '--all',\ndiff --git a/stgit/commands/show.py b/stgit/commands/show.py\nindex 45ca253..7efb4e1 100644\n--- a/stgit/commands/show.py\n+++ b/stgit/commands/show.py\n@@ -30,6 +30,7 @@ Show the commit log and the diff corresponding to the given\n patches. The output is similar to that generated by the 'git show'\n command.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one'),\n            make_option('-a', '--applied',\ndiff --git a/stgit/commands/sink.py b/stgit/commands/sink.py\nindex 2a18ebc..737dde0 100644\n--- a/stgit/commands/sink.py\n+++ b/stgit/commands/sink.py\n@@ -32,6 +32,7 @@ push the specified <patches> (the current patch by default), and\n then push back into place the formerly-applied patches (unless -n\n is also given).\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-n', '--nopush',\n                        help = 'do not push the patches back after sinking',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/status.py b/stgit/commands/status.py\nindex b2835ab..a688f7e 100644\n--- a/stgit/commands/status.py\n+++ b/stgit/commands/status.py\n@@ -40,6 +40,7 @@ under revision control. The files are prefixed as follows:\n A 'refresh' command clears the status of the modified, new and deleted\n files.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-m', '--modified',\n                        help = 'show modified files only',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/sync.py b/stgit/commands/sync.py\nindex 580b5bd..8a31c29 100644\n--- a/stgit/commands/sync.py\n+++ b/stgit/commands/sync.py\n@@ -36,6 +36,7 @@ in the series must apply cleanly.\n \n The sync operation can be reverted for individual patches with --undo.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-a', '--all',\n                        help = 'synchronise all the patches',\n                        action = 'store_true'),\ndiff --git a/stgit/commands/top.py b/stgit/commands/top.py\nindex 1a9267a..e7cb275 100644\n--- a/stgit/commands/top.py\n+++ b/stgit/commands/top.py\n@@ -30,6 +30,7 @@ usage = \"\"\"%prog [options]\n \n Print the name of the current (topmost) patch.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one')]\n \ndiff --git a/stgit/commands/unapplied.py b/stgit/commands/unapplied.py\nindex c6408a3..d5bb43e 100644\n--- a/stgit/commands/unapplied.py\n+++ b/stgit/commands/unapplied.py\n@@ -31,6 +31,7 @@ usage = \"\"\"%prog [options]\n List the patches from the series which are not pushed onto the stack.\n They are listed in the reverse order in which they were popped.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one'),\n            make_option('-c', '--count',\ndiff --git a/stgit/commands/uncommit.py b/stgit/commands/uncommit.py\nindex c22d3ea..a23ae20 100644\n--- a/stgit/commands/uncommit.py\n+++ b/stgit/commands/uncommit.py\n@@ -48,6 +48,7 @@ given commit should be uncommitted.\n Only commits with exactly one parent can be uncommitted; in other\n words, you can't uncommit a merge.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-n', '--number', type = 'int',\n                        help = 'uncommit the specified number of commits'),\n            make_option('-t', '--to',\ndiff --git a/stgit/commands/unhide.py b/stgit/commands/unhide.py\nindex 0a1dcaf..665d664 100644\n--- a/stgit/commands/unhide.py\n+++ b/stgit/commands/unhide.py\n@@ -30,6 +30,7 @@ usage = \"\"\"%prog [options] <patch-range>\n Unhide a hidden range of patches so that they are shown in the plain\n 'series' command output.\"\"\"\n \n+directory = DirectoryHasRepository()\n options = [make_option('-b', '--branch',\n                        help = 'use BRANCH instead of the default one')]\n \ndiff --git a/stgit/main.py b/stgit/main.py\nindex 19ba2bd..15582dd 100644\n--- a/stgit/main.py\n+++ b/stgit/main.py\n@@ -252,6 +252,7 @@ def main():\n     usage = command.usage.split('\\n')[0].strip()\n     parser = OptionParser(usage = usage, option_list = command.options)\n     options, args = parser.parse_args()\n+    directory = command.directory\n \n     # These modules are only used from this point onwards and do not\n     # need to be imported earlier\n@@ -267,6 +268,7 @@ def main():\n         sys.exit(1)\n \n     try:\n+        directory.setup()\n         config_setup()\n \n         # 'clone' doesn't expect an already initialised GIT tree. A Series\n"},{"id":"55121","messageId":"20071007232447.13070.53630.stgit@yoghurt","threadId":"10184","inReplyTo":"20071007231949.13070.49517.stgit@yoghurt","subject":"[StGit PATCH 2/5] New test: Try \"stg push\" in a subdirectory","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-10-07T23:24:48Z","receivedAt":"2007-10-07T23:24:48Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"This currently fails for the non-fast-forward cases.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n\n---\n\n t/t1205-push-subdir.sh |   55 ++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 55 insertions(+), 0 deletions(-)\n create mode 100755 t/t1205-push-subdir.sh\n\n\ndiff --git a/t/t1205-push-subdir.sh b/t/t1205-push-subdir.sh\nnew file mode 100755\nindex 0000000..6502c20\n--- /dev/null\n+++ b/t/t1205-push-subdir.sh\n@@ -0,0 +1,55 @@\n+#!/bin/sh\n+test_description='Test the push command from a subdirectory'\n+. ./test-lib.sh\n+stg init\n+\n+test_expect_success 'Create some patches' '\n+    mkdir foo\n+    for i in 0 1 2; do\n+        stg new p$i -m p$i &&\n+        echo x$i >> x.txt &&\n+        echo y$i >> foo/y.txt &&\n+        stg add x.txt foo/y.txt &&\n+        stg refresh\n+    done &&\n+    [ \"$(echo $(stg applied))\" = \"p0 p1 p2\" ] &&\n+    [ \"$(echo $(stg unapplied))\" = \"\" ]\n+'\n+\n+test_expect_success 'Fast-forward push from a subdir' '\n+    stg pop &&\n+    [ \"$(echo $(cat x.txt))\" = \"x0 x1\" ] &&\n+    [ \"$(echo $(cat foo/y.txt))\" = \"y0 y1\" ] &&\n+    cd foo &&\n+    stg push &&\n+    cd .. &&\n+    [ \"$(echo $(cat x.txt))\" = \"x0 x1 x2\" ] &&\n+    [ \"$(echo $(cat foo/y.txt))\" = \"y0 y1 y2\" ]\n+'\n+\n+test_expect_failure 'Modifying push from a subdir' '\n+    stg pop &&\n+    [ \"$(echo $(cat x.txt))\" = \"x0 x1\" ] &&\n+    [ \"$(echo $(cat foo/y.txt))\" = \"y0 y1\" ] &&\n+    stg new extra -m extra &&\n+    echo extra >> extra.txt &&\n+    stg add extra.txt &&\n+    stg refresh &&\n+    cd foo &&\n+    stg push &&\n+    cd .. &&\n+    [ \"$(echo $(cat x.txt))\" = \"x0 x1 x2\" ] &&\n+    [ \"$(echo $(cat foo/y.txt))\" = \"y0 y1 y2\" ]\n+'\n+\n+test_expect_failure 'Conflicting push from subdir' '\n+    stg pop p1 p2 &&\n+    [ \"$(echo $(cat x.txt))\" = \"x0\" ] &&\n+    [ \"$(echo $(cat foo/y.txt))\" = \"y0\" ] &&\n+    cd foo &&\n+    ! stg push p2 &&\n+    cd .. &&\n+    [ \"$(echo $(stg status --conflict))\" = \"foo/y.txt x.txt\" ]\n+'\n+\n+test_done\n"},{"id":"55122","messageId":"20071007232453.13070.2930.stgit@yoghurt","threadId":"10184","inReplyTo":"20071007231949.13070.49517.stgit@yoghurt","subject":"[StGit PATCH 3/5] Make \"stg push\" subdirectory safe","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-10-07T23:24:54Z","receivedAt":"2007-10-07T23:24:54Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"Make \"stg push\" subdirectory safe by letting it internally cd up to\nthe top of the worktree. This is possibly not the best long-term fix;\none could argue that the push subroutine should instead be safe to run\nfrom a subdirectory. However, pushing from a subdirectory currently\nerases the parts of a patch that doesn't touch that subdirectory, and\nthat has to be fixed.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n\n---\n\n stgit/commands/push.py |    2 +-\n t/t1205-push-subdir.sh |    4 ++--\n 2 files changed, 3 insertions(+), 3 deletions(-)\n\n\ndiff --git a/stgit/commands/push.py b/stgit/commands/push.py\nindex b91bc5e..4d5de26 100644\n--- a/stgit/commands/push.py\n+++ b/stgit/commands/push.py\n@@ -39,7 +39,7 @@ command run.\n The command also notifies when the patch becomes empty (fully merged\n upstream) or is modified (three-way merged) by the 'push' operation.\"\"\"\n \n-directory = DirectoryHasRepository()\n+directory = DirectoryGotoToplevel()\n options = [make_option('-a', '--all',\n                        help = 'push all the unapplied patches',\n                        action = 'store_true'),\ndiff --git a/t/t1205-push-subdir.sh b/t/t1205-push-subdir.sh\nindex 6502c20..f9a84f6 100755\n--- a/t/t1205-push-subdir.sh\n+++ b/t/t1205-push-subdir.sh\n@@ -27,7 +27,7 @@ test_expect_success 'Fast-forward push from a subdir' '\n     [ \"$(echo $(cat foo/y.txt))\" = \"y0 y1 y2\" ]\n '\n \n-test_expect_failure 'Modifying push from a subdir' '\n+test_expect_success 'Modifying push from a subdir' '\n     stg pop &&\n     [ \"$(echo $(cat x.txt))\" = \"x0 x1\" ] &&\n     [ \"$(echo $(cat foo/y.txt))\" = \"y0 y1\" ] &&\n@@ -42,7 +42,7 @@ test_expect_failure 'Modifying push from a subdir' '\n     [ \"$(echo $(cat foo/y.txt))\" = \"y0 y1 y2\" ]\n '\n \n-test_expect_failure 'Conflicting push from subdir' '\n+test_expect_success 'Conflicting push from subdir' '\n     stg pop p1 p2 &&\n     [ \"$(echo $(cat x.txt))\" = \"x0\" ] &&\n     [ \"$(echo $(cat foo/y.txt))\" = \"y0\" ] &&\n"},{"id":"55124","messageId":"20071007232500.13070.5063.stgit@yoghurt","threadId":"10184","inReplyTo":"20071007231949.13070.49517.stgit@yoghurt","subject":"[StGit PATCH 4/5] New test: try \"stg refresh\" in a subdirectory","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-10-07T23:25:00Z","receivedAt":"2007-10-07T23:25:00Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"Currently, it doesn't work. Or rather, it does work for changes that\nare already in the index, which is the case for newly added files; but\nit doesn't work for changes that aren't in the index.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n\n---\n\n t/t2300-refresh-subdir.sh |   27 +++++++++++++++++++++++++++\n 1 files changed, 27 insertions(+), 0 deletions(-)\n create mode 100755 t/t2300-refresh-subdir.sh\n\n\ndiff --git a/t/t2300-refresh-subdir.sh b/t/t2300-refresh-subdir.sh\nnew file mode 100755\nindex 0000000..d1c7168\n--- /dev/null\n+++ b/t/t2300-refresh-subdir.sh\n@@ -0,0 +1,27 @@\n+#!/bin/sh\n+test_description='Test the refresh command from a subdirectory'\n+. ./test-lib.sh\n+stg init\n+\n+test_expect_success 'Refresh from a subdirectory' '\n+    stg new foo -m foo &&\n+    echo foo >> foo.txt &&\n+    mkdir bar &&\n+    echo bar >> bar/bar.txt &&\n+    stg add foo.txt bar/bar.txt &&\n+    cd bar &&\n+    stg refresh &&\n+    cd .. &&\n+    [ \"$(stg status)\" = \"\" ]\n+'\n+\n+test_expect_failure 'Refresh again' '\n+    echo foo2 >> foo.txt &&\n+    echo bar2 >> bar/bar.txt &&\n+    cd bar &&\n+    stg refresh &&\n+    cd .. &&\n+    [ \"$(stg status)\" = \"\" ]\n+'\n+\n+test_done\n"},{"id":"55123","messageId":"20071007232505.13070.72826.stgit@yoghurt","threadId":"10184","inReplyTo":"20071007231949.13070.49517.stgit@yoghurt","subject":"[StGit PATCH 5/5] Make \"stg refresh\" subdirectory safe","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2007-10-07T23:25:05Z","receivedAt":"2007-10-07T23:25:05Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"Make \"stg refresh\" subdirectory safe by letting it internally cd up to\nthe top of the worktree. This is possibly not the best long-term fix;\none could argue that the refresh subroutine should instead be safe to\nrun from a subdirectory. However, refreshing from a subdirectory\ncurrently only refreshes changes that are in the index, and not\nchanges in the working directory, and that has to be fixed.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n\n---\n\n stgit/commands/refresh.py |    2 +-\n t/t2300-refresh-subdir.sh |    2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\n\ndiff --git a/stgit/commands/refresh.py b/stgit/commands/refresh.py\nindex b283892..f032375 100644\n--- a/stgit/commands/refresh.py\n+++ b/stgit/commands/refresh.py\n@@ -37,7 +37,7 @@ options. The '--force' option is useful when a commit object was\n created with a different tool but the changes need to be included in\n the current patch.\"\"\"\n \n-directory = DirectoryHasRepository()\n+directory = DirectoryGotoToplevel()\n options = [make_option('-f', '--force',\n                        help = 'force the refresh even if HEAD and '\\\n                        'top differ',\ndiff --git a/t/t2300-refresh-subdir.sh b/t/t2300-refresh-subdir.sh\nindex d1c7168..bdd27c5 100755\n--- a/t/t2300-refresh-subdir.sh\n+++ b/t/t2300-refresh-subdir.sh\n@@ -15,7 +15,7 @@ test_expect_success 'Refresh from a subdirectory' '\n     [ \"$(stg status)\" = \"\" ]\n '\n \n-test_expect_failure 'Refresh again' '\n+test_expect_success 'Refresh again' '\n     echo foo2 >> foo.txt &&\n     echo bar2 >> bar/bar.txt &&\n     cd bar &&\n"}]}