chiark / gitweb /
Infrastructure for current directory handling
authorKarl Hasselström <kha@treskal.com>
Sun, 7 Oct 2007 12:52:26 +0000 (14:52 +0200)
committerKarl Hasselström <kha@treskal.com>
Sun, 7 Oct 2007 22:14:11 +0000 (00:14 +0200)
Add infrastructure to allow commands to specify if they need a git
repository, if they need to be called from within the working tree,
and if they should cd to the root of the working tree before doing
anything else.

For now, all commands are set to just require a repository (except
"stg clone", which is set to require nothing), which means the only
thing that's added is some very light error checking. The idea is to
tighten this for commands that turn out to need it.

Signed-off-by: Karl Hasselström <kha@treskal.com>
44 files changed:
stgit/commands/add.py
stgit/commands/applied.py
stgit/commands/assimilate.py
stgit/commands/branch.py
stgit/commands/clean.py
stgit/commands/clone.py
stgit/commands/commit.py
stgit/commands/common.py
stgit/commands/copy.py
stgit/commands/delete.py
stgit/commands/diff.py
stgit/commands/edit.py
stgit/commands/export.py
stgit/commands/files.py
stgit/commands/float.py
stgit/commands/fold.py
stgit/commands/goto.py
stgit/commands/hide.py
stgit/commands/id.py
stgit/commands/imprt.py
stgit/commands/init.py
stgit/commands/log.py
stgit/commands/mail.py
stgit/commands/new.py
stgit/commands/patches.py
stgit/commands/pick.py
stgit/commands/pop.py
stgit/commands/pull.py
stgit/commands/push.py
stgit/commands/rebase.py
stgit/commands/refresh.py
stgit/commands/rename.py
stgit/commands/resolved.py
stgit/commands/rm.py
stgit/commands/series.py
stgit/commands/show.py
stgit/commands/sink.py
stgit/commands/status.py
stgit/commands/sync.py
stgit/commands/top.py
stgit/commands/unapplied.py
stgit/commands/uncommit.py
stgit/commands/unhide.py
stgit/main.py

index fc9c5a73a29673f06b683a85134531ad6d9520ea..264ab9ffb53b38a9978e80ee6d88dc3fd05b6fbf 100644 (file)
@@ -31,6 +31,7 @@ Add the files or directories passed as arguments to the
 repository. When a directory name is given, all the files and
 subdirectories are recursively added."""
 
+directory = DirectoryHasRepository()
 options = []
 
 
index b9bb71608df6378c0d3bd4cbfd023b6b97e9d8e0..45d09262626094f537426d87cdb979ec43ce91c3 100644 (file)
@@ -32,6 +32,7 @@ List the patches from the series which were already pushed onto the
 stack.  They are listed in the order in which they were pushed, the
 last one being the current (topmost) patch."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one'),
            make_option('-c', '--count',
index 43672fd5bab022bb8f1d809659c6d91684d82e79..db8a95cdb7ae9850d98fcd81ad2206ec835c9f84 100644 (file)
@@ -53,6 +53,7 @@ Note that these are "inconsistencies", not "errors"; furthermore,
 with the way "assimilate" handles them, you have no reason to avoid
 causing them in the first place if that is convenient for you."""
 
+directory = DirectoryHasRepository()
 options = []
 
 class Commit(object):
index c16fc69d824c54cbda2ff5e2051590fd789c28a1..6e0a6d89ff732f1a8376d2823e2b66f469f00392 100644 (file)
@@ -40,6 +40,7 @@ When displaying the branches, the names can be prefixed with
 
 If not given any options, switch to the named branch."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-c', '--create',
                        help = 'create a new development branch',
                        action = 'store_true'),
index 2e3b202c086bafaced469a01d680b6149c24b99f..d8bbe7171fa5728a59e8a15b942d88a7ee78512d 100644 (file)
@@ -31,6 +31,7 @@ Delete the empty patches in the whole series or only those applied or
 unapplied. A patch is considered empty if the two commit objects
 representing its boundaries refer to the same tree object."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-a', '--applied',
                        help = 'delete the empty applied patches',
                        action = 'store_true'),
index 15139c83f58aaf062a39d01afb0930fb4b6a1a56..a150010676afa379061381b76bf9904f8e325c12 100644 (file)
@@ -29,6 +29,7 @@ usage = """%prog [options] <repository> <dir>
 Clone a GIT <repository> into the local <dir> and initialise the
 patch stack."""
 
+directory = DirectoryAnywhere()
 options = []
 
 
index 0b76c564699b197a687a34c2f76f0229529f505c..2b45c0df90ab1d937f7b98b37487e1fea6250fe0 100644 (file)
@@ -32,6 +32,7 @@ remove them from the series while advancing the base.
 Use this command only if you want to permanently store the applied
 patches and no longer manage them with StGIT."""
 
+directory = DirectoryHasRepository()
 options = []
 
 
index 98154009f71de58a5a3c4d8fc29191725c237fc9..27ef465785217ca40a76cdc7676de6b9a43e52a1 100644 (file)
@@ -24,6 +24,7 @@ from optparse import OptionParser, make_option
 from stgit.exception import *
 from stgit.utils import *
 from stgit.out import *
+from stgit.run import *
 from stgit import stack, git, basedir
 from stgit.config import config, file_extensions
 
@@ -480,3 +481,70 @@ def parse_patch(fobj):
     # we don't yet have an agreed place for the creation date.
     # Just return None
     return (descr, authname, authemail, authdate, diff)
+
+def readonly_constant_property(f):
+    """Decorator that converts a function that computes a value to an
+    attribute that returns the value. The value is computed only once,
+    the first time it is accessed."""
+    def new_f(self):
+        n = '__' + f.__name__
+        if not hasattr(self, n):
+            setattr(self, n, f(self))
+        return getattr(self, n)
+    return property(new_f)
+
+class DirectoryException(StgException):
+    pass
+
+class _Directory(object):
+    @readonly_constant_property
+    def git_dir(self):
+        try:
+            return Run('git-rev-parse', '--git-dir'
+                       ).discard_stderr().output_one_line()
+        except RunException:
+            raise DirectoryException('No git repository found')
+    @readonly_constant_property
+    def __topdir_path(self):
+        try:
+            lines = Run('git-rev-parse', '--show-cdup'
+                        ).discard_stderr().output_lines()
+            if len(lines) == 0:
+                return '.'
+            elif len(lines) == 1:
+                return lines[0]
+            else:
+                raise RunException('Too much output')
+        except RunException:
+            raise DirectoryException('No git repository found')
+    @readonly_constant_property
+    def is_inside_git_dir(self):
+        return { 'true': True, 'false': False
+                 }[Run('git-rev-parse', '--is-inside-git-dir'
+                       ).output_one_line()]
+    @readonly_constant_property
+    def is_inside_worktree(self):
+        return { 'true': True, 'false': False
+                 }[Run('git-rev-parse', '--is-inside-work-tree'
+                       ).output_one_line()]
+    def cd_to_topdir(self):
+        os.chdir(self.__topdir_path)
+
+class DirectoryAnywhere(_Directory):
+    def setup(self):
+        pass
+
+class DirectoryHasRepository(_Directory):
+    def setup(self):
+        self.git_dir # might throw an exception
+
+class DirectoryInWorktree(DirectoryHasRepository):
+    def setup(self):
+        DirectoryHasRepository.setup(self)
+        if not self.is_inside_worktree:
+            raise DirectoryException('Not inside a git worktree')
+
+class DirectoryGotoToplevel(DirectoryInWorktree):
+    def setup(self):
+        DirectoryInWorktree.setup(self)
+        self.cd_to_topdir()
index 76e3bf98739b99592696c4bcaf2c2d42b6f927c3..e94dd668891751fa08809300e640bb08766bc97c 100644 (file)
@@ -30,6 +30,7 @@ usage = """%prog [options] [<file/dir> <newname> | <files/dirs...> <dir>]
 Copy of the files and dirs passed as arguments under another name or
 location inside the same repository."""
 
+directory = DirectoryHasRepository()
 options = []
 
 def func(parser, options, args):
index 2121015461f27ad43dec499fa2babd70f2476b17..84628571c21ed4ee2300308af6f4b5bcd1cc830a 100644 (file)
@@ -36,6 +36,7 @@ patches are deleted, they are popped from the stack.
 
 Note that the 'delete' operation is irreversible."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one')]
 
index aeca4ab72a48bf1765d7d33ed9e4f5f0470ad2e1..f3b0ea29472e2652925510767725746db6ecb1b1 100644 (file)
@@ -42,6 +42,7 @@ rev = '([patch][//[bottom | top]]) | <tree-ish> | base'
 If neither bottom nor top are given but a '//' is present, the command
 shows the specified patch (defaulting to the current one)."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-r', '--range',
                        metavar = 'rev1[..[rev2]]', dest = 'revs',
                        help = 'show the diff between revisions'),
index 223c628addabfefb5e0357817595d397b11f36cf..02970bc8623758e10b454e2af52d58d85f44a4ca 100644 (file)
@@ -57,6 +57,7 @@ rejected patch is stored in the .stgit-failed.patch file (and also in
 these files using the '--file' and '--diff' options.
 """
 
+directory = DirectoryHasRepository()
 options = [make_option('-d', '--diff',
                        help = 'edit the patch diff',
                        action = 'store_true'),
index 62be39472e1c3f98131030e9d69a6c9210fca7d7..d8ce86d9f1604fe0c297080970b7b7ebfd189308 100644 (file)
@@ -49,6 +49,7 @@ file:
   %(commemail)s   - committer's e-mail
 """
 
+directory = DirectoryHasRepository()
 options = [make_option('-d', '--dir',
                        help = 'export patches to DIR instead of the default'),
            make_option('-p', '--patch',
index 1a100237a59b32b85affcd693523e3d8e2ff9f53..07cc955f32cad2314098209eeacfc0153e573f94 100644 (file)
@@ -34,6 +34,7 @@ given patch. Note that this command doesn't show the files modified in
 the working tree and not yet included in the patch by a 'refresh'
 command. Use the 'diff' or 'status' commands for these files."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-s', '--stat',
                        help = 'show the diff stat',
                        action = 'store_true'),
index 0e32f6b3e34fa6020700c36a6cb37c45e6ad4123..d5299fbdc6fcbcb473313feb15383aaf5ce60fc1 100644 (file)
@@ -31,6 +31,7 @@ necessary pop and push operations will be performed to accomplish
 this. The '--series' option can be used to rearrange the (top) patches
 as specified by the given series file (or the standard input)."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-s', '--series',
                        help = 'rearrange according to a series file',
                        action = 'store_true')]
index d97185e2aeda6d5e522f0f412ec0bc557c0115cc..6e431012f4644a55bec353b7449c8d0e6694f154 100644 (file)
@@ -34,6 +34,7 @@ performed with the current top. With the --base option, the patch is
 applied onto the specified base and a three-way merged is performed
 with the current top."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-t', '--threeway',
                        help = 'perform a three-way merge with the current patch',
                        action = 'store_true'),
index a4427faae58d6415611bceb7c48771586cca9aec..9e008a9e1a660e3f7719aca2be10df4d79747179 100644 (file)
@@ -31,6 +31,7 @@ line becomes current. This is a shortcut for the 'push --to' or 'pop
 --to' commands. There is no '--undo' option for 'goto'. Use the 'push'
 command for this."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-k', '--keep',
                        help = 'keep the local changes when popping patches',
                        action = 'store_true')]
index de19c192d4f7067bd526df5c899eafd662080096..1a389078aa6cc141f3b2e2d4b34db66269fef0a5 100644 (file)
@@ -30,6 +30,7 @@ usage = """%prog [options] <patch-range>
 Hide a range of unapplied patches so that they are no longer shown in
 the plain 'series' command output."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one')]
 
index f72d2f3a01c7c2482abb698734d0f70b6828ca4b..3e28f2f13effc57695fb5d8a147511f52337c37f 100644 (file)
@@ -33,6 +33,7 @@ the standard GIT id's like heads and tags, this command also accepts
 'top' or 'bottom' are passed and <patch> is a valid patch name, 'top'
 will be used by default."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one')]
 
index 717f3739c0da1aaa276de153e97eab0e39ecf896..045f18513213b4ccc4a7c7e8605fe0b0e4b05470 100644 (file)
@@ -44,6 +44,7 @@ stack.
 The patch description has to be separated from the data with a '---'
 line."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-m', '--mail',
                        help = 'import the patch from a standard e-mail file',
                        action = 'store_true'),
index 46562d0ee73a2278a6b136951e4c805a6da2b1db..475a4ce59f44a893b092caf5228690196a6ab5a4 100644 (file)
@@ -31,6 +31,7 @@ Initialise the current GIT branch to be used as an StGIT stack. Note
 that you must already be in a GIT repository and .git/HEAD must point
 to a valid file in refs/heads/."""
 
+directory = DirectoryHasRepository()
 options = []
 
 
index 4d6b022e6dc5194cd65b4f944605ac0a6b21d2e0..f8337f98c11ca1933c17f1e163a761116a98b279 100644 (file)
@@ -43,6 +43,7 @@ represent the changes to the entire base of the current
 patch. Conflicts reset the patch content and a subsequent 'refresh'
 will show the entire patch."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one'),
            make_option('-p', '--patch',
index c32894eb3df1d7da112014e09468035d5e64193d..4a4158a32c85300ef97b545e5b2a4a3102ebae82 100644 (file)
@@ -88,6 +88,7 @@ the following:
   %(prefix)s       - 'prefix ' string passed on the command line
   %(shortdescr)s   - the first line of the patch description"""
 
+directory = DirectoryHasRepository()
 options = [make_option('-a', '--all',
                        help = 'e-mail all the applied patches',
                        action = 'store_true'),
index 59671abecd0feed9866b6f39117b6e6d71b80edf..ccc8141d5419ead7492792ebb6a921107aaf85ea 100644 (file)
@@ -38,6 +38,7 @@ needed for this.
 If no name is given for the new patch, one is generated from the first
 line of the commit message."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-m', '--message',
                        help = 'use MESSAGE as the patch description'),
            make_option('-s', '--showpatch',
index fb65b621c4e90cc0ed309e80c09d91e59ea00c55..0b618fefa4ac6a1e2014a82f10beee1cd76b82f9 100644 (file)
@@ -33,6 +33,7 @@ it shows the patches affected by the local tree modifications. The
 '--diff' option also lists the patch log and the diff for the given
 files."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-d', '--diff',
                        help = 'show the diff for the given files',
                        action = 'store_true'),
index 1c3ef119dded4ccdf7740f315c09664715015dcf..3acec32780232c0f17fb1971609da7648fde93e0 100644 (file)
@@ -34,6 +34,7 @@ the name of the current patch. It can be overridden with the '--name'
 option. A commit object can be reverted with the '--reverse'
 option. The log and author information are those of the commit object."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-n', '--name',
                        help = 'use NAME as the patch name'),
            make_option('-r', '--reverse',
index 0dfaad9f778e6585f28e1bd00446686689e76b6e..a1d73e4b69409906024b89023087431472932937 100644 (file)
@@ -36,6 +36,7 @@ patches passed on the command line are popped from the stack. Some of
 the push operations may fail because of conflicts (push --undo would
 revert the last push operation)."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-a', '--all',
                        help = 'pop all the applied patches',
                        action = 'store_true'),
index 237bdd9e57f798046d43ba1ddfe22644851a601d..5fcf2cc06eef144dfe44ecfb7459f39c07ba8f52 100644 (file)
@@ -38,6 +38,7 @@ resolved and the patch pushed again.
 
 Check the 'git fetch' documentation for the <repository> format."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-n', '--nopush',
                        help = 'do not push the patches back after pulling',
                        action = 'store_true'),
index 53cdb8fb31dc0ec9f14b5cf52d50ce4e1dfe7b5a..b91bc5e23536ab54d27917e590529659931d5df5 100644 (file)
@@ -39,6 +39,7 @@ command run.
 The command also notifies when the patch becomes empty (fully merged
 upstream) or is modified (three-way merged) by the 'push' operation."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-a', '--all',
                        help = 'push all the unapplied patches',
                        action = 'store_true'),
index 513729a50bffaba52cd7a42bb9d7cc7ab414ae9a..bbb3e1223470ae84911174ab503076e1dee7023a 100644 (file)
@@ -29,6 +29,7 @@ usage = """%prog [options] <new-base-id>
 Pop all patches from current stack, move the stack base to the given
 <new-base-id> and push the patches back."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-n', '--nopush',
                        help = 'do not push the patches back after rebasing',
                        action = 'store_true'),
index 241f065a46a72860b0e8ddb63c32235995e008e9..b283892762d2860e63d7a88171e72e9da9476c57 100644 (file)
@@ -37,6 +37,7 @@ options. The '--force' option is useful when a commit object was
 created with a different tool but the changes need to be included in
 the current patch."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-f', '--force',
                        help = 'force the refresh even if HEAD and '\
                        'top differ',
index 2830e72682df5eba4617cee3a50bee5ae2e65221..e2b0fa4160a913af944064fa3ea10be9c61fe4c1 100644 (file)
@@ -29,6 +29,7 @@ usage = """%prog [options] <oldpatch> <newpatch>
 
 Rename <oldpatch> into <newpatch> in a series."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one')]
 
index 1130641a727a0ca65ad452130e91e2b73d11d88f..c2ef678bc8e35aa38aacff6aa3065d46387699f5 100644 (file)
@@ -34,6 +34,7 @@ Mark a merge conflict as resolved. The conflicts can be seen with the
 'C'. This command also removes any <file>.{ancestor,current,patched}
 files."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-a', '--all',
                        help = 'mark all conflicts as solved',
                        action = 'store_true'),
index 91908a1b242ab30c6e3f90dfee7f26e8b42e0f88..59d098b626849ff8a67a979616a81d9788b4d425 100644 (file)
@@ -30,6 +30,7 @@ usage = """%prog [options] <files...>
 Remove given files from the repository. The command doesn't remove the
 working copy of the file."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-f', '--force',
                        help = 'force removing even if the file exists',
                        action = 'store_true')]
index 00a3372e2d7eff28aef78a9faa1c951ea88d9833..2c758768b754a5a2a2906571263414d2c0fdc2ed 100644 (file)
@@ -34,6 +34,7 @@ range. The applied patches are prefixed with a '+', the unapplied ones
 with a '-' and the hidden ones with a '!'. The current patch is
 prefixed with a '>'. Empty patches are prefixed with a '0'."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one'),
            make_option('-a', '--all',
index 45ca253a58cdfe90bd2b5e9557db68bce691733b..7efb4e16ccb7e036e1e57c6a956478ce3d5b2c6e 100644 (file)
@@ -30,6 +30,7 @@ Show the commit log and the diff corresponding to the given
 patches. The output is similar to that generated by the 'git show'
 command."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one'),
            make_option('-a', '--applied',
index 2a18ebceb20bced0b52c980bda63f428bb140ba5..737dde033d4542d9ec6f11a3c809ee5562ec5ae1 100644 (file)
@@ -32,6 +32,7 @@ push the specified <patches> (the current patch by default), and
 then push back into place the formerly-applied patches (unless -n
 is also given)."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-n', '--nopush',
                        help = 'do not push the patches back after sinking',
                        action = 'store_true'),
index b2835abf463e2a40051712fe2658a743ab29c709..a688f7ee46fc157240dfd2896849a2072158accc 100644 (file)
@@ -40,6 +40,7 @@ under revision control. The files are prefixed as follows:
 A 'refresh' command clears the status of the modified, new and deleted
 files."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-m', '--modified',
                        help = 'show modified files only',
                        action = 'store_true'),
index 580b5bd00fb7c96a839f555e31d11d7062d9cefe..8a31c29301bf2ee5ed6bf28a6d34c6cffe266603 100644 (file)
@@ -36,6 +36,7 @@ in the series must apply cleanly.
 
 The sync operation can be reverted for individual patches with --undo."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-a', '--all',
                        help = 'synchronise all the patches',
                        action = 'store_true'),
index 1a9267a4f4d7d2fcee2f71543a71131a8035ecd9..e7cb275f849e372099cd59f4e25b0108484bac80 100644 (file)
@@ -30,6 +30,7 @@ usage = """%prog [options]
 
 Print the name of the current (topmost) patch."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one')]
 
index c6408a3778967900ab15f5e1d457da5ee3318f70..d5bb43e3004f1b7aabd71dacaf1319703e440415 100644 (file)
@@ -31,6 +31,7 @@ usage = """%prog [options]
 List the patches from the series which are not pushed onto the stack.
 They are listed in the reverse order in which they were popped."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one'),
            make_option('-c', '--count',
index c22d3ea614126671a3b70fa4b9bd267014c76750..a23ae20d8bc4c39fc0e60edfe4eed584cdf37950 100644 (file)
@@ -48,6 +48,7 @@ given commit should be uncommitted.
 Only commits with exactly one parent can be uncommitted; in other
 words, you can't uncommit a merge."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-n', '--number', type = 'int',
                        help = 'uncommit the specified number of commits'),
            make_option('-t', '--to',
index 0a1dcafde81764e6e6e3da36ff7272e884b707aa..665d664c05f11a4ce41b915120a671c9fc52201a 100644 (file)
@@ -30,6 +30,7 @@ usage = """%prog [options] <patch-range>
 Unhide a hidden range of patches so that they are shown in the plain
 'series' command output."""
 
+directory = DirectoryHasRepository()
 options = [make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one')]
 
index 19ba2bd63e000a44125197b0a6995b3a76272501..15582ddff9e21f426ff47bbae2af1114d3afacf4 100644 (file)
@@ -252,6 +252,7 @@ def main():
     usage = command.usage.split('\n')[0].strip()
     parser = OptionParser(usage = usage, option_list = command.options)
     options, args = parser.parse_args()
+    directory = command.directory
 
     # These modules are only used from this point onwards and do not
     # need to be imported earlier
@@ -267,6 +268,7 @@ def main():
         sys.exit(1)
 
     try:
+        directory.setup()
         config_setup()
 
         # 'clone' doesn't expect an already initialised GIT tree. A Series