release.sh
setup.cfg.rpm
snapshot.sh
+stgit-completion.bash
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
+++ /dev/null
-# 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
('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
# -*- python -*-
import optparse, sys
import stgit.main
-from stgit import argparse, commands
+from stgit import argparse, commands, completion
def main():
op = optparse.OptionParser()
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')
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')
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:
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])
'--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.""")]
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.)""")]
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):
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)])
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, ...'
'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 = """
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'),
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'
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)
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)
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'
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'),
from stgit.argparse import opt
from stgit.commands import common
from stgit.lib import transaction
+from stgit import argparse
help = 'Delete patches'
kind = 'patch'
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()
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'),
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'),
%(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'),
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'),
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'),
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'
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')]
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'
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)
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'
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()
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'
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)
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'
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()
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'),
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'),
branch (and the git repository it is in) must already exist and
contain at least one commit."""
+args = []
options = []
directory = common.DirectoryHasRepositoryLib()
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
"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'),
%(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'),
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')
'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())
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'
'--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)
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'
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'),
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'
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'),
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'
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'),
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'
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'),
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'
$ 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'),
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'),
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'),
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'),
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'
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)
repair" is _not_ what you want. In that case, what you want is option
(1) above."""
+args = []
options = []
directory = DirectoryGotoToplevel(log = True)
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'
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')]
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
'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')]
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'
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'),
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'),
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'),
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'
(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.""")]
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'
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'),
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'
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)
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'
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()
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'
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')]
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'),
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'
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)
--- /dev/null
+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, ''))