chiark / gitweb /
Automatic bash completion
authorKarl Hasselström <kha@treskal.com>
Sun, 21 Sep 2008 12:20:41 +0000 (14:20 +0200)
committerKarl Hasselström <kha@treskal.com>
Sun, 21 Sep 2008 12:20:41 +0000 (14:20 +0200)
Teach the build process to create a bash tab completion script. This
has three benefits:

  1. The tab completion gets faster, since it no longer has to run stg
     to figure out the set of available flags for each command.

  2. The tab completion script used to encode the list of stg
     subcommands, and the kind of arguments each subcommand expected.
     This information now lives in just one place: the subcommand's
     module.

  3. The tab completion script now knows what kind of argument each
     flag wants, and can tab complete those as well. So "stg refresh
     <tab>" will complete dirty files, but "stg refresh -p <tab>" will
     complete patch names.

Signed-off-by: Karl Hasselström <kha@treskal.com>
48 files changed:
.gitignore
Makefile
contrib/stgit-completion.bash [deleted file]
setup.py
stg-build
stgit/argparse.py
stgit/commands/branch.py
stgit/commands/clean.py
stgit/commands/clone.py
stgit/commands/coalesce.py
stgit/commands/commit.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/redo.py
stgit/commands/refresh.py
stgit/commands/rename.py
stgit/commands/repair.py
stgit/commands/reset.py
stgit/commands/resolved.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/uncommit.py
stgit/commands/undo.py
stgit/commands/unhide.py
stgit/completion.py [new file with mode: 0644]

index 91dbad2466fee68a2193dd45b0e604e7b4945931..e7fffb06e3be39fbac8c58cf539113d181767046 100644 (file)
@@ -6,3 +6,4 @@ patches-*
 release.sh
 setup.cfg.rpm
 snapshot.sh
+stgit-completion.bash
index 288622a320f0b6f24be8a0a3924da5cc7cddd840..71836705ea197a7296caff0e2e5456dcfbdaae04 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -7,13 +7,16 @@ TEST_PATCHES ?= ..
 all: build
        $(PYTHON) setup.py build
 
-build: stgit/commands/cmdlist.py
+build: stgit/commands/cmdlist.py stgit-completion.bash
 
 ALL_PY = $(shell find stgit -name '*.py')
 
 stgit/commands/cmdlist.py: $(ALL_PY)
        $(PYTHON) stg-build --py-cmd-list > $@
 
+stgit-completion.bash: $(ALL_PY)
+       $(PYTHON) stg-build --bash-completion > $@
+
 install: build
        $(PYTHON) setup.py install --prefix=$(prefix) --root=$(DESTDIR) --force
 
diff --git a/contrib/stgit-completion.bash b/contrib/stgit-completion.bash
deleted file mode 100644 (file)
index 1467c28..0000000
+++ /dev/null
@@ -1,270 +0,0 @@
-# bash completion support for StGIT                        -*- shell-script -*-
-#
-# Copyright (C) 2006, Karl Hasselström <kha@treskal.com>
-# Based on git-completion.sh
-#
-# To use these routines:
-#
-#    1. Copy this file to somewhere (e.g. ~/.stgit-completion.bash).
-#
-#    2. Add the following line to your .bashrc:
-#         . ~/.stgit-completion.bash
-
-_stg_commands="
-    branch
-    delete
-    diff
-    clean
-    clone
-    coalesce
-    commit
-    edit
-    export
-    files
-    float
-    fold
-    goto
-    hide
-    id
-    import
-    init
-    log
-    mail
-    new
-    patches
-    pick
-    pop
-    pull
-    push
-    rebase
-    refresh
-    rename
-    repair
-    resolved
-    series
-    show
-    sink
-    status
-    sync
-    top
-    uncommit
-    unhide
-"
-
-# The path to .git, or empty if we're not in a repository.
-_gitdir ()
-{
-    echo "$(git rev-parse --git-dir 2>/dev/null)"
-}
-
-# Name of the current branch, or empty if there isn't one.
-_current_branch ()
-{
-    local b=$(git symbolic-ref HEAD 2>/dev/null)
-    echo ${b#refs/heads/}
-}
-
-# List of all applied patches.
-_applied_patches ()
-{
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$(_current_branch)/applied"
-}
-
-# List of all unapplied patches.
-_unapplied_patches ()
-{
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$(_current_branch)/unapplied"
-}
-
-# List of all applied patches.
-_hidden_patches ()
-{
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$(_current_branch)/hidden"
-}
-
-# List of all patches.
-_all_patches ()
-{
-    local b=$(_current_branch)
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$b/applied" "$g/patches/$b/unapplied"
-}
-
-# List of all patches except the current patch.
-_all_other_patches ()
-{
-    local b=$(_current_branch)
-    local g=$(_gitdir)
-    [ "$g" ] && cat "$g/patches/$b/applied" "$g/patches/$b/unapplied" \
-        | grep -v "^$(cat $g/patches/$b/current 2> /dev/null)$"
-}
-
-_all_branches ()
-{
-    local g=$(_gitdir)
-    [ "$g" ] && (cd $g/patches/ && echo *)
-}
-
-_conflicting_files ()
-{
-    local g=$(_gitdir)
-    [ "$g" ] && stg status --conflict
-}
-
-_dirty_files ()
-{
-    local g=$(_gitdir)
-    [ "$g" ] && stg status --modified --new --deleted
-}
-
-_unknown_files ()
-{
-    local g=$(_gitdir)
-    [ "$g" ] && stg status --unknown
-}
-
-_known_files ()
-{
-    local g=$(_gitdir)
-    [ "$g" ] && git ls-files
-}
-
-# List the command options
-_cmd_options ()
-{
-    stg $1 --help 2>/dev/null | grep -e " --[A-Za-z]" | sed -e "s/.*\(--[^ =]\+\).*/\1/"
-}
-
-# Generate completions for patches and patch ranges from the given
-# patch list function, and options from the given list.
-_complete_patch_range ()
-{
-    local patchlist="$1" options="$2"
-    local pfx cur="${COMP_WORDS[COMP_CWORD]}"
-    case "$cur" in
-        *..*)
-            pfx="${cur%..*}.."
-            cur="${cur#*..}"
-            COMPREPLY=($(compgen -P "$pfx" -W "$($patchlist)" -- "$cur"))
-            ;;
-        *)
-            COMPREPLY=($(compgen -W "$options $($patchlist)" -- "$cur"))
-            ;;
-    esac
-}
-
-_complete_patch_range_options ()
-{
-    local patchlist="$1" options="$2" patch_options="$3"
-    local prev="${COMP_WORDS[COMP_CWORD-1]}"
-    local cur="${COMP_WORDS[COMP_CWORD]}"
-    local popt
-    for popt in $patch_options; do
-        if [ $prev == $popt ]; then
-            _complete_patch_range $patchlist
-            return
-        fi
-    done
-    COMPREPLY=($(compgen -W "$options" -- "$cur"))
-}
-
-_complete_branch ()
-{
-     COMPREPLY=($(compgen -W "$(_cmd_options $1) $($2)" -- "${COMP_WORDS[COMP_CWORD]}"))
-}
-
-# Generate completions for options from the given list.
-_complete_options ()
-{
-    local options="$1"
-    COMPREPLY=($(compgen -W "$options" -- "${COMP_WORDS[COMP_CWORD]}"))
-}
-
-_complete_files ()
-{
-    COMPREPLY=($(compgen -W "$(_cmd_options $1) $2" -- "${COMP_WORDS[COMP_CWORD]}"))
-}
-
-_stg_common ()
-{
-    _complete_options "$(_cmd_options $1)"
-}
-
-_stg_patches ()
-{
-    _complete_patch_range "$2" "$(_cmd_options $1)"
-}
-
-_stg_patches_options ()
-{
-    _complete_patch_range_options "$2" "$(_cmd_options $1)" "$3"
-}
-
-_stg_help ()
-{
-    _complete_options "$_stg_commands"
-}
-
-_stg ()
-{
-    local i c=1 command
-
-    while [ $c -lt $COMP_CWORD ]; do
-        if [ $c == 1 ]; then
-            command="${COMP_WORDS[c]}"
-        fi
-        c=$((++c))
-    done
-
-    # Complete name of subcommand.
-    if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
-        COMPREPLY=($(compgen \
-            -W "--help --version copyright help $_stg_commands" \
-            -- "${COMP_WORDS[COMP_CWORD]}"))
-        return;
-    fi
-
-    # Complete arguments to subcommands.
-    case "$command" in
-        # generic commands
-        help)   _stg_help ;;
-        # repository commands
-        id)     _stg_patches $command _all_patches ;;
-        # stack commands
-        coalesce) _stg_patches $command _applied_patches ;;
-        float)  _stg_patches $command _all_patches ;;
-        goto)   _stg_patches $command _all_other_patches ;;
-        hide)   _stg_patches $command _unapplied_patches ;;
-        pop)    _stg_patches $command _applied_patches ;;
-        push)   _stg_patches $command _unapplied_patches ;;
-        series) _stg_patches $command _all_patches ;;
-        sink)   _stg_patches $command _all_patches ;;
-        unhide) _stg_patches $command _hidden_patches ;;
-        # patch commands
-        delete) _stg_patches $command _all_patches ;;
-        edit)   _stg_patches $command _applied_patches ;;
-        export) _stg_patches $command _all_patches ;;
-        files)  _stg_patches $command _all_patches ;;
-        log)    _stg_patches $command _all_patches ;;
-        mail)   _stg_patches $command _all_patches ;;
-        pick)   _stg_patches $command _unapplied_patches ;;
-#      refresh)_stg_patches_options $command _applied_patches "-p --patch" ;;
-        refresh) _complete_files $command "$(_dirty_files)" ;;
-        rename) _stg_patches $command _all_patches ;;
-        show)   _stg_patches $command _all_patches ;;
-        sync)   _stg_patches $command _applied_patches ;;
-        # working-copy commands
-        diff)   _stg_patches_options $command _applied_patches "-r --range" ;;
-       resolved) _complete_files $command "$(_conflicting_files)" ;;
-       # commands that usually raher accept branches
-       branch) _complete_branch $command _all_branches ;;
-       rebase) _complete_branch $command _all_branches ;;
-        # all the other commands
-        *)      _stg_common $command ;;
-    esac
-}
-
-complete -o default -F _stg stg
index 81854d3573155549b96dfbbc7f3a00cb2d5847c3..fb67958991077ada0c6e4df46e31ce96c526680e 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -59,8 +59,8 @@ def __run_setup():
             ('share/stgit/examples', glob.glob('examples/*.tmpl')),
             ('share/stgit/examples', ['examples/gitconfig']),
             ('share/stgit/contrib', ['contrib/diffcol.sh',
-                                     'contrib/stgbashprompt.sh',
-                                     'contrib/stgit-completion.bash']),
+                                     'contrib/stgbashprompt.sh']),
+            ('share/stgit/completion', ['stgit-completion.bash'])
             ])
 
 # Check the minimum versions required
index 3c9dbfa4aba3a631a0758b72007e21cd7b4e92e2..2af652342fb450d949b1502f6d16d89f9394f6c5 100755 (executable)
--- a/stg-build
+++ b/stg-build
@@ -2,7 +2,7 @@
 # -*- python -*-
 import optparse, sys
 import stgit.main
-from stgit import argparse, commands
+from stgit import argparse, commands, completion
 
 def main():
     op = optparse.OptionParser()
@@ -14,6 +14,8 @@ def main():
                   help = 'Print asciidoc command list')
     op.add_option('--py-cmd-list', action = 'store_true',
                   help = 'Write Python command list')
+    op.add_option('--bash-completion', action = 'store_true',
+                  help = 'Write bash completion code')
     options, args = op.parse_args()
     if args:
         op.error('Wrong number of arguments')
@@ -30,6 +32,8 @@ def main():
     elif options.py_cmd_list:
         commands.py_commands(commands.get_commands(allow_cached = False),
                              sys.stdout)
+    elif options.bash_completion:
+        completion.write_completion(sys.stdout)
     else:
         op.error('No command')
 
index bd71817f918dc8fed5d3f4ccda2167e3301bf1cc..406ada3ae053f5f62a2d4a23fd7c0ccd2c746155 100644 (file)
@@ -26,28 +26,27 @@ def _paragraphs(s):
 
 class opt(object):
     """Represents a command-line flag."""
-    def __init__(self, *args, **kwargs):
-        self.args = args
+    def __init__(self, *pargs, **kwargs):
+        self.pargs = pargs
         self.kwargs = kwargs
     def get_option(self):
         kwargs = dict(self.kwargs)
         kwargs['help'] = kwargs['short']
-        del kwargs['short']
-        if 'long' in kwargs:
-            del kwargs['long']
-        return optparse.make_option(*self.args, **kwargs)
+        for k in ['short', 'long', 'args']:
+            kwargs.pop(k, None)
+        return optparse.make_option(*self.pargs, **kwargs)
     def metavar(self):
         o = self.get_option()
         if not o.nargs:
             return None
         if o.metavar:
             return o.metavar
-        for flag in self.args:
+        for flag in self.pargs:
             if flag.startswith('--'):
                 return utils.strip_prefix('--', flag).upper()
         raise Exception('Cannot determine metavar')
     def write_asciidoc(self, f):
-        for flag in self.args:
+        for flag in self.pargs:
             f.write(flag)
             m = self.metavar()
             if m:
@@ -60,6 +59,16 @@ class opt(object):
             f.write('+\n')
             for line in para:
                 f.write(line + '\n')
+    @property
+    def flags(self):
+        return self.pargs
+    @property
+    def args(self):
+        if self.kwargs.get('action', None) in ['store_true', 'store_false']:
+            default = []
+        else:
+            default = [files]
+        return self.kwargs.get('args', default)
 
 def _cmd_name(cmd_mod):
     return getattr(cmd_mod, 'name', cmd_mod.__name__.split('.')[-1])
@@ -103,11 +112,11 @@ def sign_options():
                 '--ack and --sign were both specified')
         parser.values.sign_str = sign_str
     return [
-        opt('--sign', action = 'callback', dest = 'sign_str',
+        opt('--sign', action = 'callback', dest = 'sign_str', args = [],
             callback = callback, callback_args = ('Signed-off-by',),
             short = 'Add "Signed-off-by:" line', long = """
             Add a "Signed-off-by:" to the end of the patch."""),
-        opt('--ack', action = 'callback', dest = 'sign_str',
+        opt('--ack', action = 'callback', dest = 'sign_str', args = [],
             callback = callback, callback_args = ('Acked-by',),
             short = 'Add "Acked-by:" line', long = """
             Add an "Acked-by:" line to the end of the patch.""")]
@@ -151,7 +160,7 @@ def message_options(save_template):
             callback = msg_callback, dest = 'message', type = 'string',
             short = 'Use MESSAGE instead of invoking the editor'),
         opt('-f', '--file', action = 'callback', callback = file_callback,
-            dest = 'message', type = 'string',
+            dest = 'message', type = 'string', args = [files],
             short = 'Use FILE instead of invoking the editor', long = """
             Use the contents of FILE instead of invoking the editor.
             (If FILE is "-", write to stdout.)""")]
@@ -181,6 +190,7 @@ def diff_opts_option():
             default = (config.get('stgit.diff-opts') or '').split(),
             action = 'callback', callback = diff_opts_callback,
             type = 'string', metavar = 'OPTIONS',
+            args = [strings('-M', '-C')],
             short = 'Extra options to pass to "git diff"')]
 
 def _person_opts(person, short):
@@ -212,3 +222,63 @@ def author_options():
 
 def author_committer_options():
     return _person_opts('author', 'auth') + _person_opts('committer', 'comm')
+
+class CompgenBase(object):
+    def actions(self, var): return set()
+    def words(self, var): return set()
+    def command(self, var):
+        cmd = ['compgen']
+        for act in self.actions(var):
+            cmd += ['-A', act]
+        words = self.words(var)
+        if words:
+            cmd += ['-W', '"%s"' % ' '.join(words)]
+        cmd += ['--', '"%s"' % var]
+        return ' '.join(cmd)
+
+class CompgenJoin(CompgenBase):
+    def __init__(self, a, b):
+        assert isinstance(a, CompgenBase)
+        assert isinstance(b, CompgenBase)
+        self.__a = a
+        self.__b = b
+    def words(self, var): return self.__a.words(var) | self.__b.words(var)
+    def actions(self, var): return self.__a.actions(var) | self.__b.actions(var)
+
+class Compgen(CompgenBase):
+    def __init__(self, words = frozenset(), actions = frozenset()):
+        self.__words = set(words)
+        self.__actions = set(actions)
+    def actions(self, var): return self.__actions
+    def words(self, var): return self.__words
+
+def compjoin(compgens):
+    comp = Compgen()
+    for c in compgens:
+        comp = CompgenJoin(comp, c)
+    return comp
+
+all_branches = Compgen(['$(_all_branches)'])
+stg_branches = Compgen(['$(_stg_branches)'])
+applied_patches = Compgen(['$(_applied_patches)'])
+other_applied_patches = Compgen(['$(_other_applied_patches)'])
+unapplied_patches = Compgen(['$(_unapplied_patches)'])
+hidden_patches = Compgen(['$(_hidden_patches)'])
+commit = Compgen(['$(_all_branches) $(_tags) $(_remotes)'])
+conflicting_files = Compgen(['$(_conflicting_files)'])
+dirty_files = Compgen(['$(_dirty_files)'])
+unknown_files = Compgen(['$(_unknown_files)'])
+known_files = Compgen(['$(_known_files)'])
+repo = Compgen(actions = ['directory'])
+dir = Compgen(actions = ['directory'])
+files = Compgen(actions = ['file'])
+def strings(*ss): return Compgen(ss)
+class patch_range(CompgenBase):
+    def __init__(self, *endpoints):
+        self.__endpoints = endpoints
+    def words(self, var):
+        words = set()
+        for e in self.__endpoints:
+            assert not e.actions(var)
+            words |= e.words(var)
+        return set(['$(_patch_range "%s" "%s")' % (' '.join(words), var)])
index ef715476f10b75f868894b335c9ade5ccb466c49..3d912fc3f8ce4938900200ba6e1fd5b205c9c64f 100644 (file)
@@ -20,7 +20,7 @@ from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
 from stgit.out import *
-from stgit import stack, git, basedir
+from stgit import argparse, stack, git, basedir
 from stgit.lib import log
 
 help = 'Branch operations: switch, list, create, rename, delete, ...'
@@ -45,6 +45,7 @@ within a git repository.
 'stg branch' <branch>::
         Switch to the given branch."""
 
+args = [argparse.all_branches]
 options = [
     opt('-l', '--list', action = 'store_true',
         short = 'List the branches contained in this repository', long = """
index 27a77168eefad1e1f8d139c0e78f9499261b55d4..9b48e7b75ae546d7e3cafe92ba10447d9ba1e43c 100644 (file)
@@ -28,6 +28,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."""
 
+args = []
 options = [
     opt('-a', '--applied', action = 'store_true',
         short = 'Delete the empty applied patches'),
index 659712d95133a68f68aca68586adc6d7f43feedc..efb7198be6cb7fca824fd2ffb7e6b08ceb08fb52 100644 (file)
@@ -18,7 +18,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 import sys, os
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Make a local clone of a remote repository'
 kind = 'repo'
@@ -35,6 +35,7 @@ commands of stglink:branch[].
 The target directory <dir> will be created by this command, and must
 not already exist."""
 
+args = [argparse.repo, argparse.dir]
 options = []
 
 directory = DirectoryAnywhere(needs_current_series = False, log = False)
index ef8e912a9cdd31735c393ba8053b6b1d268944aa..4b5c00a9fac241fb5393fd18a302de41049e9c0f 100644 (file)
@@ -34,6 +34,8 @@ If there are conflicts when reordering the patches to match the order
 you specify, you will have to resolve them manually just as if you had
 done a sequence of pushes and pops yourself."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches)]
 options = [opt('-n', '--name', short = 'Name of coalesced patch')
            ] + argparse.message_options(save_template = True)
 
index 99b7b5de18ed2370e8a77a1d766e2dcb0fd3b2c2..dd8d6e6ce84a51fdc257c7174507d1fc5806349f 100644 (file)
@@ -19,6 +19,7 @@ from stgit.argparse import opt
 from stgit.commands import common
 from stgit.lib import transaction
 from stgit.out import *
+from stgit import argparse
 
 help = 'Permanently store the applied patches into the stack base'
 kind = 'stack'
@@ -40,6 +41,8 @@ The -n/--number option specifies the number of applied patches to
 commit (counting from the bottom of the stack). If -a/--all is given,
 all applied patches are committed."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches)]
 options = [
     opt('-n', '--number', type = 'int',
         short = 'Commit the specified number of patches'),
index b92a039b3f23e0c407973e6e829d3bfde7f76787..40cef3fadec90c25d6facab27ae118acd33a06ad 100644 (file)
@@ -19,6 +19,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 from stgit.argparse import opt
 from stgit.commands import common
 from stgit.lib import transaction
+from stgit import argparse
 
 help = 'Delete patches'
 kind = 'patch'
@@ -28,8 +29,10 @@ Delete the patches passed as arguments.
 
 Note that the 'delete' operation is irreversible."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches)]
 options = [
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'Use BRANCH instead of the default branch')]
 
 directory = common.DirectoryHasRepositoryLib()
index 05f4f4cf495e95e215d6fb06364f56d06d46a12a..7d2f7190b7833d5effbcaa81006bf5598d5c1770 100644 (file)
@@ -38,8 +38,12 @@ representing the bottom of the current stack.
 
 rev = '[branch:](<patch>|{base}) | <tree-ish>'"""
 
+args = [argparse.known_files, argparse.dirty_files]
 options = [
     opt('-r', '--range', metavar = 'rev1[..[rev2]]', dest = 'revs',
+        args = [argparse.patch_range(argparse.applied_patches,
+                                     argparse.unapplied_patches,
+                                     argparse.hidden_patches)],
         short = 'Show the diff between revisions'),
     opt('-s', '--stat', action = 'store_true',
         short = 'Show the stat instead of the diff'),
index b370f5cbeb916be1d2695f6c5c8772285586be7f..4904f6807fcaed2aa4cdd1a8f3fac75fb2453e02 100644 (file)
@@ -54,6 +54,8 @@ If the patch diff is edited but does not apply, no changes are made to
 the patch at all. The edited patch is saved to a file which you can
 feed to "stg edit --file", once you have made sure it does apply."""
 
+args = [argparse.applied_patches, argparse.unapplied_patches,
+        argparse.hidden_patches]
 options = [
     opt('-d', '--diff', action = 'store_true',
         short = 'Edit the patch diff'),
index c7ed80247a062f9d7e4a4cca58f37da1fd725860..dfdcea1f5a14218656dfebdcb42358c3d511fdae 100644 (file)
@@ -47,8 +47,11 @@ file:
   %(commname)s    - committer's name
   %(commemail)s   - committer's e-mail"""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches,
+                             argparse.hidden_patches)]
 options = [
-    opt('-d', '--dir',
+    opt('-d', '--dir', args = [argparse.dir],
         short = 'Export patches to DIR instead of the default'),
     opt('-p', '--patch', action = 'store_true',
         short = 'Append .patch to the patch names'),
@@ -56,9 +59,9 @@ options = [
         short = 'Append .EXTENSION to the patch names'),
     opt('-n', '--numbered', action = 'store_true',
         short = 'Prefix the patch names with order numbers'),
-    opt('-t', '--template', metavar = 'FILE',
+    opt('-t', '--template', metavar = 'FILE', args = [argparse.files],
         short = 'Use FILE as a template'),
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'Use BRANCH instead of the default branch'),
     opt('-s', '--stdout', action = 'store_true',
         short = 'Dump the patches to the standard output'),
index d63a33e2d2cded668da578bbe142617dae7d32a2..46d43c150a58ce7919a4958e423a11d9306fca0a 100644 (file)
@@ -34,6 +34,8 @@ 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."""
 
+args = [argparse.applied_patches, argparse.unapplied_patches,
+        argparse.hidden_patches]
 options = [
     opt('-s', '--stat', action = 'store_true',
         short = 'Show the diffstat'),
index 93bb69b0720d994ff67bc71e4875ae03d9a9795f..7c3dcdf18d03b682c5d7d7f1515a4b0a2a690aa7 100644 (file)
@@ -20,7 +20,7 @@ import sys, os
 from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Push patches to the top, even if applied'
 kind = 'stack'
@@ -32,6 +32,8 @@ 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)."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches)]
 options = [
     opt('-s', '--series', action = 'store_true',
         short = 'Rearrange according to a series file')]
index 165ff52749dd6a72ac5e10070df8b768da30be25..66a2dd91b1016b3255d8f2ae2d43e03ae133448a 100644 (file)
@@ -20,7 +20,7 @@ from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
 from stgit.out import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Integrate a GNU diff patch into the current patch'
 kind = 'patch'
@@ -33,10 +33,11 @@ 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."""
 
+args = [argparse.files]
 options = [
     opt('-t', '--threeway', action = 'store_true',
         short = 'Perform a three-way merge with the current patch'),
-    opt('-b', '--base',
+    opt('-b', '--base', args = [argparse.commit],
         short = 'Use BASE instead of HEAD applying the patch')]
 
 directory = DirectoryHasRepository(log = True)
index 0d4cd29cf03d0d8bf7ef6a56df44b094cb1989bf..60a917ef7f3b25d37abac3cb1def5ddf0d4f1148 100644 (file)
@@ -17,6 +17,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
 from stgit.commands import common
 from stgit.lib import transaction
+from stgit import argparse
 
 help = 'Push or pop patches to the given one'
 kind = 'stack'
@@ -25,6 +26,7 @@ description = """
 Push/pop patches to/from the stack until the one given on the command
 line becomes current."""
 
+args = [argparse.other_applied_patches, argparse.unapplied_patches]
 options = []
 
 directory = common.DirectoryHasRepositoryLib()
index 1bcb5f1c28d092d3c59701f659a2f6b552c426b1..014febbef3a7d654718f24c06eb464c3aec96283 100644 (file)
@@ -20,7 +20,7 @@ from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
 from stgit.out import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Hide a patch in the series'
 kind = 'stack'
@@ -29,8 +29,10 @@ description = """
 Hide a range of unapplied patches so that they are no longer shown in
 the plain 'series' command output."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches)]
 options = [
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'Use BRANCH instead of the default branch')]
 
 directory = DirectoryHasRepository(log = True)
index 857ec33051c22dbe2be79e9cb5fce7c2f7586c85..566edcc4350605a14fc7ef16ec57807b55f6f498 100644 (file)
@@ -18,6 +18,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 from stgit.out import out
 from stgit.commands import common
 from stgit.lib import stack
+from stgit import argparse
 
 help = 'Print the git hash value of a StGit reference'
 kind = 'repo'
@@ -30,6 +31,8 @@ or the base of the stack. If no branch is specified, it defaults to the
 current one. The bottom of a patch is accessible with the
 '[<branch>:]<patch>^' format."""
 
+args = [argparse.applied_patches, argparse.unapplied_patches,
+        argparse.hidden_patches]
 options = []
 
 directory = common.DirectoryHasRepositoryLib()
index 9f2df05a7c125738283731205a8b1d4926766323..de5e9a506ef7f2f416cf096c99d09df4ffceb632 100644 (file)
@@ -44,6 +44,7 @@ stack.
 The patch description has to be separated from the data with a '---'
 line."""
 
+args = [argparse.files]
 options = [
     opt('-m', '--mail', action = 'store_true',
         short = 'Import the patch from a standard e-mail file'),
@@ -61,7 +62,7 @@ options = [
         short = 'Ignore the applied patches in the series'),
     opt('--replace', action = 'store_true',
         short = 'Replace the unapplied patches in the series'),
-    opt('-b', '--base',
+    opt('-b', '--base', args = [argparse.commit],
         short = 'Use BASE instead of HEAD for file importing'),
     opt('-e', '--edit', action = 'store_true',
         short = 'Invoke an editor for the patch description'),
index 67d20d1b2c8b20385427758e4d0665a7ee768d2e..6ffb93e2f9f7d029e1910cc0fd21ea88d797b5db 100644 (file)
@@ -27,6 +27,7 @@ Initialise the current git branch to be used as an StGIT stack. The
 branch (and the git repository it is in) must already exist and
 contain at least one commit."""
 
+args = []
 options = []
 
 directory = common.DirectoryHasRepositoryLib()
index 1f63ef5b773fcdcb110a79cd706f41d57616edab..39cdfe7dd6442ffd590e7ae4f3aa9d03dfc52b19 100644 (file)
@@ -20,7 +20,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
 import os.path
 from optparse import make_option
-from stgit import run
+from stgit import argparse, run
 from stgit.argparse import opt
 from stgit.commands import common
 from stgit.lib import log
@@ -37,8 +37,11 @@ the named patches.
 "stg undo" and "stg redo" let you step back and forth in the patch
 stack. "stg reset" lets you go directly to any state."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches,
+                             argparse.hidden_patches)]
 options = [
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'Use BRANCH instead of the default one'),
     opt('-p', '--patch', action = 'store_true',
         short = 'Show the refresh diffs'),
index e0a55217a17fdd1c33fd39de813f697786ae5c9f..0b3157e3814ca1d8d84d92102d66c196771f1d3a 100644 (file)
@@ -93,6 +93,9 @@ the following:
   %(prefix)s       - 'prefix ' string passed on the command line
   %(shortdescr)s   - the first line of the patch description"""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches,
+                             argparse.hidden_patches)]
 options = [
     opt('-a', '--all', action = 'store_true',
         short = 'E-mail all the applied patches'),
@@ -134,7 +137,7 @@ options = [
         short = 'Password for SMTP authentication'),
     opt('-T', '--smtp-tls', action = 'store_true',
         short = 'Use SMTP with TLS encryption'),
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'Use BRANCH instead of the default branch'),
     opt('-m', '--mbox', action = 'store_true',
         short = 'Generate an mbox file instead of sending')
index 4117e4e635497133489818212cf1a0f8d5c723c6..067882aa694ba8f9d0c1b826091232e91925c894 100644 (file)
@@ -39,6 +39,7 @@ the patch, unless the '--message' flag already specified one. The
 'patchdescr.tmpl' template file (if available) is used to pre-fill the
 editor."""
 
+args = []
 options = (argparse.author_committer_options()
            + argparse.message_options(save_template = True)
            + argparse.sign_options())
index e877171071b313cc19a40b406be9ef4f12b0045e..54fac21306d7dfaef6bd630058588421f3fb7648 100644 (file)
@@ -21,7 +21,7 @@ from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
 from stgit.out import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Show the applied patches modifying a file'
 kind = 'stack'
@@ -32,10 +32,11 @@ 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."""
 
+args = [argparse.known_files]
 options = [
     opt('-d', '--diff', action = 'store_true',
         short = 'Show the diff for the given files'),
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'Use BRANCH instead of the default branch')]
 
 directory = DirectoryHasRepository(log = False)
index e1c531d7bc153fb13f6813541234de855b686355..760918b8eff90c0942aa553833588610498c0103 100644 (file)
@@ -20,7 +20,7 @@ from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
 from stgit.out import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 from stgit.stack import Series
 
 help = 'Import a patch from a different branch or a commit object'
@@ -34,14 +34,17 @@ used as the name of the current patch. It can be overridden with the
 option. The log and author information are those of the commit
 object."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches,
+                             argparse.hidden_patches)]
 options = [
     opt('-n', '--name',
         short = 'Use NAME as the patch name'),
-    opt('-B', '--ref-branch',
+    opt('-B', '--ref-branch', args = [argparse.stg_branches],
         short = 'Pick patches from BRANCH'),
     opt('-r', '--reverse', action = 'store_true',
         short = 'Reverse the commit object before importing'),
-    opt('-p', '--parent', metavar = 'COMMITID',
+    opt('-p', '--parent', metavar = 'COMMITID', args = [argparse.commit],
         short = 'Use COMMITID as parent'),
     opt('-x', '--expose', action = 'store_true',
         short = 'Append the imported commit id to the patch log'),
index 855dc0993d597feb778b936f9bd7f8a2c54fbeee..2c78ac2ab21e3d65f8f533c981aa40ac58b63062 100644 (file)
@@ -20,7 +20,7 @@ import sys, os
 from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Pop one or more patches from the stack'
 kind = 'stack'
@@ -35,6 +35,7 @@ patches passed on the command line are popped from the stack. Some of
 the push operations may fail because of conflicts ("stg undo" would
 revert the last push operation)."""
 
+args = [argparse.patch_range(argparse.applied_patches)]
 options = [
     opt('-a', '--all', action = 'store_true',
         short = 'Pop all the applied patches'),
index 82035c675e60dc66740af93bae7f28f941fdd43d..f6d139824ba2df0246d540798f4252478406a803 100644 (file)
@@ -21,7 +21,7 @@ from stgit.commands.common import *
 from stgit.utils import *
 from stgit.out import *
 from stgit.config import GitConfigException
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Pull changes from a remote repository'
 kind = 'stack'
@@ -37,6 +37,7 @@ resolved and the patch pushed again.
 
 Check the 'git fetch' documentation for the <repository> format."""
 
+args = [argparse.repo]
 options = [
     opt('-n', '--nopush', action = 'store_true',
         short = 'Do not push the patches back after pulling'),
index a7c75788c37a5a8c6c166b7211d9f07d50944b14..818e02df3d73241096d1a42b798cd23438d0ca60 100644 (file)
@@ -21,7 +21,7 @@ from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
 from stgit.out import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Push one or more patches onto the stack'
 kind = 'stack'
@@ -38,6 +38,7 @@ conflicting push with 'stg undo').
 The command also notifies when the patch becomes empty (fully merged
 upstream) or is modified (three-way merged) by the 'push' operation."""
 
+args = [argparse.patch_range(argparse.unapplied_patches)]
 options = [
     opt('-a', '--all', action = 'store_true',
         short = 'Push all the unapplied patches'),
index 60168abd7585420cefb404bf16065a128f74eb61..a4bc6e7db0fc4d42ec0bcae2f0f7b0a225819978 100644 (file)
@@ -19,7 +19,7 @@ import sys, os
 from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Move the stack base to another point in history'
 kind = 'stack'
@@ -40,6 +40,7 @@ Or if you want to skip that patch:
         $ stg undo --hard
         $ stg push next-patch..top-patch"""
 
+args = [argparse.commit]
 options = [
     opt('-n', '--nopush', action = 'store_true',
         short = 'Do not push the patches back after rebasing'),
index 8e62a1dcbd9980bcac5292e6cbf0f09c7826ae9b..eb8b20fb23cb7ffc59c7bb3f0c6c4d01253211e0 100644 (file)
@@ -32,6 +32,7 @@ the effects of consecutive invocations of "stg undo".
 It is an error to run "stg redo" if the last command was not an
 undo."""
 
+args = []
 options = [
     opt('-n', '--number', type = 'int', metavar = 'N', default = 1,
         short = 'Undo the last N undos'),
index 3c82906ada48be49b9776c72b603dd3e7ea3ca02..27cccc5b86e3ade301196235e54af8b5737481d6 100644 (file)
@@ -48,6 +48,7 @@ the patch stack log; this means that one undo step will undo the merge
 between the other patch and the temp patch, and two undo steps will
 additionally get rid of the temp patch."""
 
+args = [argparse.dirty_files]
 options = [
     opt('-u', '--update', action = 'store_true',
         short = 'Only update the current patch files'),
@@ -55,7 +56,8 @@ options = [
         short = 'Refresh from index instead of worktree', long = """
         Instead of setting the patch top to the current contents of
         the worktree, set it to the current contents of the index."""),
-    opt('-p', '--patch',
+    opt('-p', '--patch', args = [argparse.other_applied_patches,
+                                 argparse.unapplied_patches],
         short = 'Refresh (applied) PATCH instead of the top patch'),
     opt('-e', '--edit', action = 'store_true',
         short = 'Invoke an editor for the patch description'),
index 7e0fbf57d36f36f55b204ebd6d6ead215d0194cc..8a593ac19a2f2541fd9497ba3f9dde56e9003490 100644 (file)
@@ -20,7 +20,7 @@ from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
 from stgit.out import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Rename a patch'
 kind = 'patch'
@@ -29,8 +29,10 @@ description = """
 Rename <oldpatch> into <newpatch> in a series. If <oldpatch> is not
 given, the top-most patch will be renamed."""
 
+args = [argparse.applied_patches, argparse.unapplied_patches,
+        argparse.hidden_patches]
 options = [
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'use BRANCH instead of the default one')]
 
 directory = DirectoryHasRepository(log = True)
index ff9bb6128db31d514c5d083384252f7b8544c13c..37c4bab1d0cbd350deb919f73ca6b9b7f3c89eba 100644 (file)
@@ -69,6 +69,7 @@ NOTE: If using git commands on the stack was a mistake, running "stg
 repair" is _not_ what you want. In that case, what you want is option
 (1) above."""
 
+args = []
 options = []
 
 directory = DirectoryGotoToplevel(log = True)
index 3ccbf1b9b818cfb3cdb160f191f4abaf69caaa8b..7dfd4a018c8fe11148204f6b2265682e22e4b41e 100644 (file)
@@ -21,6 +21,7 @@ from stgit.argparse import opt
 from stgit.commands import common
 from stgit.lib import git, log, transaction
 from stgit.out import out
+from stgit import argparse
 
 help = 'Reset the patch stack to an earlier state'
 kind = 'stack'
@@ -33,6 +34,9 @@ a commit id from a stack log; "stg log" lets you view this log, and
 If one or more patch names are given, reset only those patches, and
 leave the rest alone."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches,
+                             argparse.hidden_patches)]
 options = [
     opt('--hard', action = 'store_true',
         short = 'Discard changes in your index/worktree')]
index ce8630d3fb86c4aa241a01750e4e071ae4ec32f4..2ce7ec3a585c2a9d9555e694db2255a7595cebb1 100644 (file)
@@ -20,7 +20,7 @@ import sys, os
 from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git, basedir
+from stgit import argparse, stack, git, basedir
 from stgit.config import config, file_extensions
 from stgit.gitmergeonefile import interactive_merge
 
@@ -32,10 +32,12 @@ Mark a merge conflict as resolved. The conflicts can be seen with the
 'status' command, the corresponding files being prefixed with a
 'C'."""
 
+args = [argparse.conflicting_files]
 options = [
     opt('-a', '--all', action = 'store_true',
         short = 'Mark all conflicts as solved'),
     opt('-r', '--reset', metavar = '(ancestor|current|patched)',
+        args = [argparse.strings('ancestor', 'current', 'patched')],
         short = 'Reset the file(s) to the given state'),
     opt('-i', '--interactive', action = 'store_true',
         short = 'Run the interactive merging tool')]
index e9d148ae2b64908e37ac2132455db21c43eec9ce..95196d3b3843eed48a15b0957ae80bb58cf36630 100644 (file)
@@ -21,6 +21,7 @@ from stgit.commands import common
 from stgit.commands.common import parse_patches
 from stgit.out import out
 from stgit.config import config
+from stgit import argparse
 
 help = 'Print the patch series'
 kind = 'stack'
@@ -31,8 +32,11 @@ 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'."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches,
+                             argparse.hidden_patches)]
 options = [
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'Use BRANCH instead of the default branch'),
     opt('-a', '--all', action = 'store_true',
         short = 'Show all patches, including the hidden ones'),
@@ -42,7 +46,7 @@ options = [
         short = 'Show the unapplied patches only'),
     opt('-H', '--hidden', action = 'store_true',
         short = 'Show the hidden patches only'),
-    opt('-m', '--missing', metavar = 'BRANCH',
+    opt('-m', '--missing', metavar = 'BRANCH',  args = [argparse.stg_branches],
         short = 'Show patches in BRANCH missing in current'),
     opt('-c', '--count', action = 'store_true',
         short = 'Print the number of patches in the series'),
index e08551b3ccb93235e7a1852529a8e38126c8a13a..9a1f48b5bdec634f02f280506010e6e17f0e79d0 100644 (file)
@@ -28,8 +28,11 @@ description = """
 Show the commit log and the diff corresponding to the given patches.
 The output is similar to that generated by 'git show'."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches,
+                             argparse.hidden_patches)]
 options = [
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'Use BRANCH instead of the default branch'),
     opt('-a', '--applied', action = 'store_true',
         short = 'Show the applied patches'),
index 34f81c964fac0d570dd215d4e336998e59f829bb..d4561ed5110c81b2a249661c8b4fb23e9929c81c 100644 (file)
@@ -20,7 +20,7 @@ import sys, os
 from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Send patches deeper down the stack'
 kind = 'stack'
@@ -41,12 +41,14 @@ including <target patch>), then pushing the patches to sink, and then
 (unless '--nopush' is also given) pushing back into place the
 formerly-applied patches."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches)]
 options = [
     opt('-n', '--nopush', action = 'store_true',
         short = 'Do not push the patches back after sinking', long = """
         Do not push back on the stack the formerly-applied patches.
         Only the patches to sink are pushed."""),
-    opt('-t', '--to', metavar = 'TARGET',
+    opt('-t', '--to', metavar = 'TARGET', args = [argparse.applied_patches],
         short = 'Sink patches below the TARGET patch', long = """
         Specify a target patch to place the patches below, instead of
         sinking them to the bottom of the stack.""")]
index c78bc1bff425fb64d2df3116507e6c0b24d64c25..730b47c2b37af2a9f5915dd8daffbfdc0d5a3d5c 100644 (file)
@@ -20,7 +20,7 @@ import sys, os
 from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Show the tree status'
 kind = 'wc'
@@ -39,6 +39,7 @@ under revision control. The files are prefixed as follows:
 An 'stg refresh' command clears the status of the modified, new and
 deleted files."""
 
+args = [argparse.files]
 options = [
     opt('-m', '--modified', action = 'store_true',
         short = 'Show modified files only'),
index 966ac553635fece01886db34f230da86437717a5..ea949d65301f0e7c49d94f829193391d0803b35b 100644 (file)
@@ -21,7 +21,7 @@ from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
 from stgit.out import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Synchronise patches with a branch or a series'
 kind = 'patch'
@@ -33,12 +33,14 @@ for keeping patches on several branches in sync. Note that the
 operation may fail for some patches because of conflicts. The patches
 in the series must apply cleanly."""
 
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches)]
 options = [
     opt('-a', '--all', action = 'store_true',
         short = 'Synchronise all the applied patches'),
-    opt('-B', '--ref-branch',
+    opt('-B', '--ref-branch', args = [argparse.stg_branches],
         short = 'Syncronise patches with BRANCH'),
-    opt('-s', '--series',
+    opt('-s', '--series', args = [argparse.files],
         short = 'Syncronise patches with SERIES')]
 
 directory = DirectoryGotoToplevel(log = True)
index 523afa463255a2b777a411aa98021099f9a4b54d..4ec37b4e0c98770cb405252b7d0782fd3bb39713 100644 (file)
@@ -19,6 +19,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 from stgit.argparse import opt
 from stgit.commands import common
 from stgit.out import out
+from stgit import argparse
 
 help = 'Print the name of the top patch'
 kind = 'stack'
@@ -26,8 +27,9 @@ usage = ['']
 description = """
 Print the name of the current (topmost) patch."""
 
+args = []
 options = [
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'Use BRANCH instead of the default branch')]
 
 directory = common.DirectoryHasRepositoryLib()
index b9950ca02a2ae2463f61b51004938a038743a9c7..bcc8bac35c960b2e885b1435d8fe3b1994341cc8 100644 (file)
@@ -21,7 +21,7 @@ from stgit.argparse import opt
 from stgit.commands import common
 from stgit.lib import transaction
 from stgit.out import *
-from stgit import utils
+from stgit import argparse, utils
 
 help = 'Turn regular git commits into StGit patches'
 kind = 'stack'
@@ -49,10 +49,12 @@ given commit should be uncommitted.
 Only commits with exactly one parent can be uncommitted; in other
 words, you can't uncommit a merge."""
 
+args = []
 options = [
     opt('-n', '--number', type = 'int',
         short = 'Uncommit the specified number of commits'),
-    opt('-t', '--to', short = 'Uncommit to the specified commit'),
+    opt('-t', '--to', args = [argparse.commit],
+        short = 'Uncommit to the specified commit'),
     opt('-x', '--exclusive', action = 'store_true',
         short = 'Exclude the commit specified by the --to option')]
 
index b7b1b737b662dd94f93bf3a3ed2df44e45ed1935..6a043632ad85925fd964bec5dab83c401df7f98a 100644 (file)
@@ -29,6 +29,7 @@ description = """
 Reset the patch stack to the previous state. Consecutive invocations
 of "stg undo" will take you ever further into the past."""
 
+args = []
 options = [
     opt('-n', '--number', type = 'int', metavar = 'N', default = 1,
         short = 'Undo the last N commands'),
index acfef2927cd078dfb3d818c64385cbc2ad2d49b6..0c0832a840b70bd7259ee2cac96d9d2b12f56a60 100644 (file)
@@ -20,7 +20,7 @@ from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
 from stgit.out import *
-from stgit import stack, git
+from stgit import argparse, stack, git
 
 help = 'Unhide a hidden patch'
 kind = 'stack'
@@ -29,8 +29,9 @@ description = """
 Unhide a hidden range of patches so that they are shown in the plain
 'stg series' command output."""
 
+args = [argparse.patch_range(argparse.hidden_patches)]
 options = [
-    opt('-b', '--branch',
+    opt('-b', '--branch', args = [argparse.stg_branches],
         short = 'Use BRANCH instead of the default branch')]
 
 directory = DirectoryHasRepository(log = True)
diff --git a/stgit/completion.py b/stgit/completion.py
new file mode 100644 (file)
index 0000000..e461e3b
--- /dev/null
@@ -0,0 +1,140 @@
+import textwrap
+import stgit.commands
+from stgit import argparse
+
+def fun(name, *body):
+    return ['%s ()' % name, '{', list(body), '}']
+
+def fun_desc(name, desc, *body):
+    return ['# %s' % desc] + fun(name, *body)
+
+def flatten(stuff, sep):
+    r = stuff[0]
+    for s in stuff[1:]:
+        r.append(sep)
+        r.extend(s)
+    return r
+
+def write(f, stuff, indent = 0):
+    for s in stuff:
+        if isinstance(s, str):
+            f.write((' '*4*indent + s).rstrip() + '\n')
+        else:
+            write(f, s, indent + 1)
+
+def patch_list_fun(type):
+    return fun('_%s_patches' % type, 'local g=$(_gitdir)',
+               'test "$g" && cat "$g/patches/$(_current_branch)/%s"' % type)
+
+def file_list_fun(name, cmd):
+    return fun('_%s_files' % name, 'local g=$(_gitdir)',
+               'test "$g" && %s' % cmd)
+
+def ref_list_fun(name, prefix):
+    return fun(name, 'local g=$(_gitdir)',
+               ("test \"$g\" && git show-ref | grep ' %s/' | sed 's,.* %s/,,'"
+                % (prefix, prefix)))
+
+def util():
+    r = [fun_desc('_gitdir',
+                  "The path to .git, or empty if we're not in a repository.",
+                  'echo "$(git rev-parse --git-dir 2>/dev/null)"'),
+         fun_desc('_current_branch',
+                  "Name of the current branch, or empty if there isn't one.",
+                  'local b=$(git symbolic-ref HEAD 2>/dev/null)',
+                  'echo ${b#refs/heads/}'),
+         fun_desc('_other_applied_patches',
+                  'List of all applied patches except the current patch.',
+                  'local b=$(_current_branch)',
+                  'local g=$(_gitdir)',
+                  ('test "$g" && cat "$g/patches/$b/applied" | grep -v'
+                   ' "^$(tail -n 1 $g/patches/$b/applied 2> /dev/null)$"')),
+         fun('_patch_range', 'local patches="$1"', 'local cur="$2"',
+             'case "$cur" in', [
+                '*..*)', ['local pfx="${cur%..*}.."', 'cur="${cur#*..}"',
+                          'compgen -P "$pfx" -W "$patches" -- "$cur"', ';;'],
+                '*)', ['compgen -W "$patches" -- "$cur"', ';;']],
+             'esac'),
+         fun('_stg_branches',
+             'local g=$(_gitdir)', 'test "$g" && (cd $g/patches/ && echo *)'),
+         ref_list_fun('_all_branches', 'refs/heads'),
+         ref_list_fun('_tags', 'refs/tags'),
+         ref_list_fun('_remotes', 'refs/remotes')]
+    for type in ['applied', 'unapplied', 'hidden']:
+        r.append(patch_list_fun(type))
+    for name, cmd in [('conflicting',
+                       r"git ls-files --unmerged | sed 's/.*\t//g' | sort -u"),
+                      ('dirty', 'git diff-index --name-only HEAD'),
+                      ('unknown', 'git ls-files --others --exclude-standard'),
+                      ('known', 'git ls-files')]:
+        r.append(file_list_fun(name, cmd))
+    return flatten(r, '')
+
+def command_list(commands):
+    return ['_stg_commands="%s"\n' % ' '.join(sorted(commands.iterkeys()))]
+
+def command_fun(cmd, modname):
+    mod = stgit.commands.get_command(modname)
+    def cg(args, flags):
+        return argparse.compjoin(list(args) + [argparse.strings(*flags)]
+                                 ).command('$cur')
+    return fun(
+        '_stg_%s' % cmd,
+        'local flags="%s"' % ' '.join(sorted(
+                flag for opt in mod.options
+                for flag in opt.flags if flag.startswith('--'))),
+        'local prev="${COMP_WORDS[COMP_CWORD-1]}"',
+        'local cur="${COMP_WORDS[COMP_CWORD]}"',
+        'case "$prev" in', [
+            '%s) COMPREPLY=($(%s)) ;;' % ('|'.join(opt.flags), cg(opt.args, []))
+            for opt in mod.options if opt.args] + [
+            '*) COMPREPLY=($(%s)) ;;' % cg(mod.args, ['$flags'])],
+        'esac')
+
+def main_switch(commands):
+    return fun(
+        '_stg',
+        'local i',
+        'local c=1',
+        'local command',
+        '',
+        'while test $c -lt $COMP_CWORD; do', [
+            'if test $c == 1; then', [
+                'command="${COMP_WORDS[c]}"'],
+            'fi',
+            'c=$((++c))'],
+        'done',
+        '',
+        ('# Complete name of subcommand if the user has not finished'
+         ' typing it yet.'),
+        'if test $c -eq $COMP_CWORD -a -z "$command"; then', [
+            ('COMPREPLY=($(compgen -W "$_stg_commands" --'
+             ' "${COMP_WORDS[COMP_CWORD]}"))'),
+            'return'],
+        'fi',
+        '',
+        '# Complete arguments to subcommands.',
+        'case "$command" in', [
+            '%s) _stg_%s ;;' % (cmd, cmd)
+            for cmd in sorted(commands.iterkeys())],
+        'esac')
+
+def install():
+    return ['complete -o default -F _stg stg']
+
+def write_completion(f):
+    commands = stgit.commands.get_commands(allow_cached = False)
+    r = [["""# -*- shell-script -*-
+# bash completion script for StGit (automatically generated)
+#
+# To use these routines:
+#
+#    1. Copy this file to somewhere (e.g. ~/.stgit-completion.bash).
+#
+#    2. Add the following line to your .bashrc:
+#         . ~/.stgit-completion.bash"""]]
+    r += [util(), command_list(commands)]
+    for cmd, (modname, _, _) in sorted(commands.iteritems()):
+        r.append(command_fun(cmd, modname))
+    r += [main_switch(commands), install()]
+    write(f, flatten(r, ''))