stgit-0.17.1/0000755002002200200220000000000012222320003013052 5ustar cmarinascmarinasstgit-0.17.1/INSTALL0000644002002200200220000000122511743516173014131 0ustar cmarinascmarinasFor basic installation: $ make all doc ;# as yourself $ make install install-doc ;# as yourself By default, the above command installs StGIT in the $HOME/{bin,lib,share} directories. For a different location, use the prefix option. $ make prefix=/usr all doc #; as yourself # make prefix=/usr install install-doc #; as root Issues of note: - StGit requires git version 1.6.1 or later. (Specifically, it needs a git that includes commit 140b378d: "Teach git diff-tree --stdin to diff trees".) - To build and install the documentation, you need to have the asciidoc/xmlto toolchain. The default build target ("make all") does _not_ build them. stgit-0.17.1/AUTHORS0000644002002200200220000000031112152142021014121 0ustar cmarinascmarinasCatalin Marinas http://www.procode.org/about.html Karl Wiberg http://www.treskal.com/kalle/ Peter P Waskiewicz Jr stgit-0.17.1/MANIFEST.in0000644002002200200220000000105512057664464014645 0ustar cmarinascmarinasinclude README Makefile MANIFEST.in AUTHORS COPYING INSTALL ChangeLog TODO include RELEASENOTES include stg-build include stg-prof include stg-dbg include templates/*.tmpl include examples/*.tmpl include examples/gitconfig include contrib/*.sh include contrib/*.bash include contrib/stg-* include contrib/Makefile include contrib/stgit.el recursive-include contrib/vim *.vim include t/t*.sh t/t*/* t/Makefile t/README include Documentation/*.txt Documentation/Makefile Documentation/*.conf include Documentation/build-docdep.perl Documentation/callouts.xsl stgit-0.17.1/stgit/0000755002002200200220000000000012222320003014204 5ustar cmarinascmarinasstgit-0.17.1/stgit/argparse.py0000644002002200200220000002621112221306627016403 0ustar cmarinascmarinas"""This module provides a layer on top of the standard library's C{optparse} module, so that we can easily generate both interactive help and asciidoc documentation (such as man pages).""" import optparse, sys, textwrap from stgit import utils from stgit.config import config from stgit.lib import git def _splitlist(lst, split_on): """Iterate over the sublists of lst that are separated by an element e such that split_on(e) is true.""" current = [] for e in lst: if split_on(e): yield current current = [] else: current.append(e) yield current def _paragraphs(s): """Split a string s into a list of paragraphs, each of which is a list of lines.""" lines = [line.rstrip() for line in textwrap.dedent(s).strip().splitlines()] return [p for p in _splitlist(lines, lambda line: not line.strip()) if p] class opt(object): """Represents a command-line flag.""" def __init__(self, *pargs, **kwargs): self.pargs = pargs self.kwargs = kwargs def get_option(self): kwargs = dict(self.kwargs) kwargs['help'] = kwargs['short'] 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.takes_value(): return None if o.metavar: return o.metavar 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.pargs: f.write(flag) m = self.metavar() if m: f.write(' ' + m) f.write('::\n') paras = _paragraphs(self.kwargs.get('long', self.kwargs['short'] + '.')) for line in paras[0]: f.write(' '*8 + line + '\n') for para in paras[1:]: 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]) def make_option_parser(cmd): pad = ' '*len('Usage: ') return optparse.OptionParser( prog = 'stg %s' % _cmd_name(cmd), usage = (('\n' + pad).join('%%prog %s' % u for u in cmd.usage) + '\n\n' + cmd.help), option_list = [o.get_option() for o in cmd.options]) def _write_underlined(s, u, f): f.write(s + '\n') f.write(u*len(s) + '\n') def write_asciidoc(cmd, f): _write_underlined('stg-%s(1)' % _cmd_name(cmd), '=', f) f.write('\n') _write_underlined('NAME', '-', f) f.write('stg-%s - %s\n\n' % (_cmd_name(cmd), cmd.help)) _write_underlined('SYNOPSIS', '-', f) f.write('[verse]\n') for u in cmd.usage: f.write("'stg' %s %s\n" % (_cmd_name(cmd), u)) f.write('\n') _write_underlined('DESCRIPTION', '-', f) f.write('\n%s\n\n' % cmd.description.strip('\n')) if cmd.options: _write_underlined('OPTIONS', '-', f) for o in cmd.options: o.write_asciidoc(f) f.write('\n') _write_underlined('StGit', '-', f) f.write('Part of the StGit suite - see linkman:stg[1]\n') def sign_options(): def callback(option, opt_str, value, parser, sign_str): if parser.values.sign_str not in [None, sign_str]: raise optparse.OptionValueError( '--ack and --sign were both specified') parser.values.sign_str = sign_str return [ 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', args = [], callback = callback, callback_args = ('Acked-by',), short = 'Add "Acked-by:" line', long = """ Add an "Acked-by:" line to the end of the patch.""")] def message_options(save_template): def no_dup(parser): if parser.values.message != None: raise optparse.OptionValueError( 'Cannot give more than one --message or --file') def no_combine(parser): if (save_template and parser.values.message != None and parser.values.save_template != None): raise optparse.OptionValueError( 'Cannot give both --message/--file and --save-template') def msg_callback(option, opt_str, value, parser): no_dup(parser) parser.values.message = value no_combine(parser) def file_callback(option, opt_str, value, parser): no_dup(parser) if value == '-': parser.values.message = sys.stdin.read() else: f = file(value) parser.values.message = f.read() f.close() no_combine(parser) def templ_callback(option, opt_str, value, parser): if value == '-': def w(s): sys.stdout.write(s) else: def w(s): f = file(value, 'w+') f.write(s) f.close() parser.values.save_template = w no_combine(parser) opts = [ opt('-m', '--message', action = 'callback', 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', args = [files], metavar = 'FILE', 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.)""")] if save_template: opts.append( opt('--save-template', action = 'callback', dest = 'save_template', callback = templ_callback, metavar = 'FILE', type = 'string', short = 'Save the message template to FILE and exit', long = """ Instead of running the command, just write the message template to FILE, and exit. (If FILE is "-", write to stdout.) When driving StGit from another program, it is often useful to first call a command with '--save-template', then let the user edit the message, and then call the same command with '--file'.""")) return opts def diff_opts_option(): def diff_opts_callback(option, opt_str, value, parser): if value: parser.values.diff_flags.extend(value.split()) else: parser.values.diff_flags = [] return [ opt('-O', '--diff-opts', dest = 'diff_flags', 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): """Sets options. to a function that modifies a Person according to the commandline options.""" def short_callback(option, opt_str, value, parser, field): f = getattr(parser.values, person) if field == "date": value = git.Date(value) setattr(parser.values, person, lambda p: getattr(f(p), 'set_' + field)(value)) def full_callback(option, opt_str, value, parser): ne = utils.parse_name_email(value) if not ne: raise optparse.OptionValueError( 'Bad %s specification: %r' % (opt_str, value)) name, email = ne short_callback(option, opt_str, name, parser, 'name') short_callback(option, opt_str, email, parser, 'email') return ( [opt('--%s' % person, metavar = '"NAME "', type = 'string', action = 'callback', callback = full_callback, dest = person, default = lambda p: p, short = 'Set the %s details' % person)] + [opt('--%s%s' % (short, f), metavar = f.upper(), type = 'string', action = 'callback', callback = short_callback, dest = person, callback_args = (f,), short = 'Set the %s %s' % (person, f)) for f in ['name', 'email', 'date']]) def author_options(): return _person_opts('author', 'auth') def keep_option(): return [opt('-k', '--keep', action = 'store_true', short = 'Keep the local changes', default = config.get('stgit.autokeep') == 'yes')] def merged_option(): return [opt('-m', '--merged', action = 'store_true', short = 'Check for patches merged upstream')] 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)]) stgit-0.17.1/stgit/git.py0000644002002200200220000007203112221306627015363 0ustar cmarinascmarinas"""Python GIT interface """ __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os, re from shutil import copyfile from stgit.exception import * from stgit import basedir from stgit.utils import * from stgit.out import * from stgit.run import * from stgit.config import config # git exception class class GitException(StgException): pass # When a subprocess has a problem, we want the exception to be a # subclass of GitException. class GitRunException(GitException): pass class GRun(Run): exc = GitRunException def __init__(self, *cmd): """Initialise the Run object and insert the 'git' command name. """ Run.__init__(self, 'git', *cmd) # # Classes # class Person: """An author, committer, etc.""" def __init__(self, name = None, email = None, date = '', desc = None): self.name = self.email = self.date = None if name or email or date: assert not desc self.name = name self.email = email self.date = date elif desc: assert not (name or email or date) def parse_desc(s): m = re.match(r'^(.+)<(.+)>(.*)$', s) assert m return [x.strip() or None for x in m.groups()] self.name, self.email, self.date = parse_desc(desc) def set_name(self, val): if val: self.name = val def set_email(self, val): if val: self.email = val def set_date(self, val): if val: self.date = val def __str__(self): if self.name and self.email: return '%s <%s>' % (self.name, self.email) else: raise GitException, 'not enough identity data' class Commit: """Handle the commit objects """ def __init__(self, id_hash): self.__id_hash = id_hash lines = GRun('cat-file', 'commit', id_hash).output_lines() for i in range(len(lines)): line = lines[i] if not line: break # we've seen all the header fields key, val = line.split(' ', 1) if key == 'tree': self.__tree = val elif key == 'author': self.__author = val elif key == 'committer': self.__committer = val else: pass # ignore other headers self.__log = '\n'.join(lines[i+1:]) def get_id_hash(self): return self.__id_hash def get_tree(self): return self.__tree def get_parent(self): parents = self.get_parents() if parents: return parents[0] else: return None def get_parents(self): return GRun('rev-list', '--parents', '--max-count=1', self.__id_hash ).output_one_line().split()[1:] def get_author(self): return self.__author def get_committer(self): return self.__committer def get_log(self): return self.__log def __str__(self): return self.get_id_hash() # dictionary of Commit objects, used to avoid multiple calls to git __commits = dict() # # Functions # def get_commit(id_hash): """Commit objects factory. Save/look-up them in the __commits dictionary """ global __commits if id_hash in __commits: return __commits[id_hash] else: commit = Commit(id_hash) __commits[id_hash] = commit return commit def get_conflicts(): """Return the list of file conflicts """ names = set() for line in GRun('ls-files', '-z', '--unmerged' ).raw_output().split('\0')[:-1]: stat, path = line.split('\t', 1) names.add(path) return list(names) def exclude_files(): files = [os.path.join(basedir.get(), 'info', 'exclude')] user_exclude = config.get('core.excludesfile') if user_exclude: files.append(user_exclude) return files def ls_files(files, tree = 'HEAD', full_name = True): """Return the files known to GIT or raise an error otherwise. It also converts the file to the full path relative the the .git directory. """ if not files: return [] args = [] if tree: args.append('--with-tree=%s' % tree) if full_name: args.append('--full-name') args.append('--') args.extend(files) try: # use a set to avoid file names duplication due to different stages fileset = set(GRun('ls-files', '--error-unmatch', *args).output_lines()) except GitRunException: # just hide the details of the 'git ls-files' command we use raise GitException, \ 'Some of the given paths are either missing or not known to GIT' return list(fileset) def parse_git_ls(output): """Parse the output of git diff-index, diff-files, etc. Doesn't handle rename/copy output, so don't feed it output generated with the -M or -C flags.""" t = None for line in output.split('\0'): if not line: # There's a zero byte at the end of the output, which # gives us an empty string as the last "line". continue if t == None: mode_a, mode_b, sha1_a, sha1_b, t = line.split(' ') else: yield (t, line) t = None def tree_status(files = None, tree_id = 'HEAD', unknown = False, noexclude = True, verbose = False): """Get the status of all changed files, or of a selected set of files. Returns a list of pairs - (status, filename). If 'not files', it will check all files, and optionally all unknown files. If 'files' is a list, it will only check the files in the list. """ assert not files or not unknown if verbose: out.start('Checking for changes in the working directory') refresh_index() if files is None: files = [] cache_files = [] # unknown files if unknown: cmd = ['ls-files', '-z', '--others', '--directory', '--no-empty-directory'] if not noexclude: cmd += ['--exclude=%s' % s for s in ['*.[ao]', '*.pyc', '.*', '*~', '#*', 'TAGS', 'tags']] cmd += ['--exclude-per-directory=.gitignore'] cmd += ['--exclude-from=%s' % fn for fn in exclude_files() if os.path.exists(fn)] lines = GRun(*cmd).raw_output().split('\0') cache_files += [('?', line) for line in lines if line] # conflicted files conflicts = get_conflicts() cache_files += [('C', filename) for filename in conflicts if not files or filename in files] reported_files = set(conflicts) files_left = [f for f in files if f not in reported_files] # files in the index. Only execute this code if no files were # specified when calling the function (i.e. report all files) or # files were specified but already found in the previous step if not files or files_left: args = [tree_id] if files_left: args += ['--'] + files_left for t, fn in parse_git_ls(GRun('diff-index', '-z', *args).raw_output()): # the condition is needed in case files is emtpy and # diff-index lists those already reported if not fn in reported_files: cache_files.append((t, fn)) reported_files.add(fn) files_left = [f for f in files if f not in reported_files] # files in the index but changed on (or removed from) disk. Only # execute this code if no files were specified when calling the # function (i.e. report all files) or files were specified but # already found in the previous step if not files or files_left: args = [] if files_left: args += ['--'] + files_left for t, fn in parse_git_ls(GRun('diff-files', '-z', *args).raw_output()): # the condition is needed in case files is empty and # diff-files lists those already reported if not fn in reported_files: cache_files.append((t, fn)) reported_files.add(fn) if verbose: out.done() return cache_files def local_changes(verbose = True): """Return true if there are local changes in the tree """ return len(tree_status(verbose = verbose)) != 0 def get_heads(): heads = [] hr = re.compile(r'^[0-9a-f]{40} refs/heads/(.+)$') for line in GRun('show-ref', '--heads').output_lines(): m = hr.match(line) heads.append(m.group(1)) return heads # HEAD value cached __head = None def get_head(): """Verifies the HEAD and returns the SHA1 id that represents it """ global __head if not __head: __head = rev_parse('HEAD') return __head class DetachedHeadException(GitException): def __init__(self): GitException.__init__(self, 'Not on any branch') def get_head_file(): """Return the name of the file pointed to by the HEAD symref. Throw an exception if HEAD is detached.""" try: return strip_prefix( 'refs/heads/', GRun('symbolic-ref', '-q', 'HEAD' ).output_one_line()) except GitRunException: raise DetachedHeadException() def set_head_file(ref): """Resets HEAD to point to a new ref """ # head cache flushing is needed since we might have a different value # in the new head __clear_head_cache() try: GRun('symbolic-ref', 'HEAD', 'refs/heads/%s' % ref).run() except GitRunException: raise GitException, 'Could not set head to "%s"' % ref def set_ref(ref, val): """Point ref at a new commit object.""" try: GRun('update-ref', ref, val).run() except GitRunException: raise GitException, 'Could not update %s to "%s".' % (ref, val) def set_branch(branch, val): set_ref('refs/heads/%s' % branch, val) def __set_head(val): """Sets the HEAD value """ global __head if not __head or __head != val: set_ref('HEAD', val) __head = val # only allow SHA1 hashes assert(len(__head) == 40) def __clear_head_cache(): """Sets the __head to None so that a re-read is forced """ global __head __head = None def refresh_index(): """Refresh index with stat() information from the working directory. """ GRun('update-index', '-q', '--unmerged', '--refresh').run() def rev_parse(git_id): """Parse the string and return a verified SHA1 id """ try: return GRun('rev-parse', '--verify', git_id ).discard_stderr().output_one_line() except GitRunException: raise GitException, 'Unknown revision: %s' % git_id def ref_exists(ref): try: rev_parse(ref) return True except GitException: return False def branch_exists(branch): return ref_exists('refs/heads/%s' % branch) def create_branch(new_branch, tree_id = None): """Create a new branch in the git repository """ if branch_exists(new_branch): raise GitException, 'Branch "%s" already exists' % new_branch current_head_file = get_head_file() current_head = get_head() set_head_file(new_branch) __set_head(current_head) # a checkout isn't needed if new branch points to the current head if tree_id: try: switch(tree_id) except GitException: # Tree switching failed. Revert the head file set_head_file(current_head_file) delete_branch(new_branch) raise if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')): os.remove(os.path.join(basedir.get(), 'MERGE_HEAD')) def switch_branch(new_branch): """Switch to a git branch """ global __head if not branch_exists(new_branch): raise GitException, 'Branch "%s" does not exist' % new_branch tree_id = rev_parse('refs/heads/%s^{commit}' % new_branch) if tree_id != get_head(): refresh_index() try: GRun('read-tree', '-u', '-m', get_head(), tree_id).run() except GitRunException: raise GitException, 'read-tree failed (local changes maybe?)' __head = tree_id set_head_file(new_branch) if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')): os.remove(os.path.join(basedir.get(), 'MERGE_HEAD')) def delete_ref(ref): if not ref_exists(ref): raise GitException, '%s does not exist' % ref sha1 = GRun('show-ref', '-s', ref).output_one_line() try: GRun('update-ref', '-d', ref, sha1).run() except GitRunException: raise GitException, 'Failed to delete ref %s' % ref def delete_branch(name): delete_ref('refs/heads/%s' % name) def rename_ref(from_ref, to_ref): if not ref_exists(from_ref): raise GitException, '"%s" does not exist' % from_ref if ref_exists(to_ref): raise GitException, '"%s" already exists' % to_ref sha1 = GRun('show-ref', '-s', from_ref).output_one_line() try: GRun('update-ref', to_ref, sha1, '0'*40).run() except GitRunException: raise GitException, 'Failed to create new ref %s' % to_ref try: GRun('update-ref', '-d', from_ref, sha1).run() except GitRunException: raise GitException, 'Failed to delete ref %s' % from_ref def rename_branch(from_name, to_name): """Rename a git branch.""" rename_ref('refs/heads/%s' % from_name, 'refs/heads/%s' % to_name) try: if get_head_file() == from_name: set_head_file(to_name) except DetachedHeadException: pass # detached HEAD, so the renamee can't be the current branch reflog_dir = os.path.join(basedir.get(), 'logs', 'refs', 'heads') if os.path.exists(reflog_dir) \ and os.path.exists(os.path.join(reflog_dir, from_name)): rename(reflog_dir, from_name, to_name) # Persons caching __user = None __author = None __committer = None def user(): """Return the user information. """ global __user if not __user: name=config.get('user.name') email=config.get('user.email') __user = Person(name, email) return __user; def author(): """Return the author information. """ global __author if not __author: try: # the environment variables take priority over config try: date = os.environ['GIT_AUTHOR_DATE'] except KeyError: date = '' __author = Person(os.environ['GIT_AUTHOR_NAME'], os.environ['GIT_AUTHOR_EMAIL'], date) except KeyError: __author = user() return __author def committer(): """Return the author information. """ global __committer if not __committer: try: # the environment variables take priority over config try: date = os.environ['GIT_COMMITTER_DATE'] except KeyError: date = '' __committer = Person(os.environ['GIT_COMMITTER_NAME'], os.environ['GIT_COMMITTER_EMAIL'], date) except KeyError: __committer = user() return __committer def update_cache(files = None, force = False): """Update the cache information for the given files """ cache_files = tree_status(files, verbose = False) # everything is up-to-date if len(cache_files) == 0: return False # check for unresolved conflicts if not force and [x for x in cache_files if x[0] not in ['M', 'N', 'A', 'D']]: raise GitException, 'Updating cache failed: unresolved conflicts' # update the cache add_files = [x[1] for x in cache_files if x[0] in ['N', 'A']] rm_files = [x[1] for x in cache_files if x[0] in ['D']] m_files = [x[1] for x in cache_files if x[0] in ['M']] GRun('update-index', '--add', '--').xargs(add_files) GRun('update-index', '--force-remove', '--').xargs(rm_files) GRun('update-index', '--').xargs(m_files) return True def commit(message, files = None, parents = None, allowempty = False, cache_update = True, tree_id = None, set_head = False, author_name = None, author_email = None, author_date = None, committer_name = None, committer_email = None): """Commit the current tree to repository """ if not parents: parents = [] # Get the tree status if cache_update and parents != []: changes = update_cache(files) if not changes and not allowempty: raise GitException, 'No changes to commit' # get the commit message if not message: message = '\n' elif message[-1:] != '\n': message += '\n' # write the index to repository if tree_id == None: tree_id = GRun('write-tree').output_one_line() set_head = True # the commit env = {} if author_name: env['GIT_AUTHOR_NAME'] = author_name if author_email: env['GIT_AUTHOR_EMAIL'] = author_email if author_date: env['GIT_AUTHOR_DATE'] = author_date if committer_name: env['GIT_COMMITTER_NAME'] = committer_name if committer_email: env['GIT_COMMITTER_EMAIL'] = committer_email commit_id = GRun('commit-tree', tree_id, *sum([['-p', p] for p in parents], []) ).env(env).raw_input(message).output_one_line() if set_head: __set_head(commit_id) return commit_id def apply_diff(rev1, rev2, check_index = True, files = None): """Apply the diff between rev1 and rev2 onto the current index. This function doesn't need to raise an exception since it is only used for fast-pushing a patch. If this operation fails, the pushing would fall back to the three-way merge. """ if check_index: index_opt = ['--index'] else: index_opt = [] if not files: files = [] diff_str = diff(files, rev1, rev2) if diff_str: try: GRun('apply', *index_opt).raw_input( diff_str).discard_stderr().no_output() except GitRunException: return False return True stages_re = re.compile('^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) def merge_recursive(base, head1, head2): """Perform a 3-way merge between base, head1 and head2 into the local tree """ refresh_index() p = GRun('merge-recursive', base, '--', head1, head2).env( { 'GITHEAD_%s' % base: 'ancestor', 'GITHEAD_%s' % head1: 'current', 'GITHEAD_%s' % head2: 'patched'}).returns([0, 1]) output = p.output_lines() if p.exitcode: # There were conflicts if config.get('stgit.autoimerge') == 'yes': mergetool() else: conflicts = [l for l in output if l.startswith('CONFLICT')] out.info(*conflicts) raise GitException, "%d conflict(s)" % len(conflicts) def mergetool(files = ()): """Invoke 'git mergetool' to resolve any outstanding conflicts. If 'not files', all the files in an unmerged state will be processed.""" GRun('mergetool', *list(files)).returns([0, 1]).run() # check for unmerged entries (prepend 'CONFLICT ' for consistency with # merge_recursive()) conflicts = ['CONFLICT ' + f for f in get_conflicts()] if conflicts: out.info(*conflicts) raise GitException, "%d conflict(s)" % len(conflicts) def diff(files = None, rev1 = 'HEAD', rev2 = None, diff_flags = [], binary = True): """Show the diff between rev1 and rev2 """ if not files: files = [] if binary and '--binary' not in diff_flags: diff_flags = diff_flags + ['--binary'] if rev1 and rev2: return GRun('diff-tree', '-p', *(diff_flags + [rev1, rev2, '--'] + files)).raw_output() elif rev1 or rev2: refresh_index() if rev2: return GRun('diff-index', '-p', '-R', *(diff_flags + [rev2, '--'] + files)).raw_output() else: return GRun('diff-index', '-p', *(diff_flags + [rev1, '--'] + files)).raw_output() else: return '' def files(rev1, rev2, diff_flags = []): """Return the files modified between rev1 and rev2 """ result = [] for line in GRun('diff-tree', *(diff_flags + ['-r', rev1, rev2]) ).output_lines(): result.append('%s %s' % tuple(line.split(' ', 4)[-1].split('\t', 1))) return '\n'.join(result) def barefiles(rev1, rev2): """Return the files modified between rev1 and rev2, without status info """ result = [] for line in GRun('diff-tree', '-r', rev1, rev2).output_lines(): result.append(line.split(' ', 4)[-1].split('\t', 1)[-1]) return '\n'.join(result) def pretty_commit(commit_id = 'HEAD', flags = []): """Return a given commit (log + diff) """ return GRun('show', *(flags + [commit_id])).raw_output() def checkout(files = None, tree_id = None, force = False): """Check out the given or all files """ if tree_id: try: GRun('read-tree', '--reset', tree_id).run() except GitRunException: raise GitException, 'Failed "git read-tree" --reset %s' % tree_id cmd = ['checkout-index', '-q', '-u'] if force: cmd.append('-f') if files: GRun(*(cmd + ['--'])).xargs(files) else: GRun(*(cmd + ['-a'])).run() def switch(tree_id, keep = False): """Switch the tree to the given id """ if keep: # only update the index while keeping the local changes GRun('read-tree', tree_id).run() else: refresh_index() try: GRun('read-tree', '-u', '-m', get_head(), tree_id).run() except GitRunException: raise GitException, 'read-tree failed (local changes maybe?)' __set_head(tree_id) def reset(files = None, tree_id = None, check_out = True): """Revert the tree changes relative to the given tree_id. It removes any local changes """ if not tree_id: tree_id = get_head() if check_out: checkout(files, tree_id, True) # if the reset refers to the whole tree, switch the HEAD as well if not files: __set_head(tree_id) def resolved(filenames, reset = None): if reset: stage = {'ancestor': 1, 'current': 2, 'patched': 3}[reset] GRun('checkout-index', '--no-create', '--stage=%d' % stage, '--stdin', '-z').input_nulterm(filenames).no_output() GRun('update-index', '--add', '--').xargs(filenames) for filename in filenames: # update the access and modificatied times os.utime(filename, None) def fetch(repository = 'origin', refspec = None): """Fetches changes from the remote repository, using 'git fetch' by default. """ # we update the HEAD __clear_head_cache() args = [repository] if refspec: args.append(refspec) command = config.get('branch.%s.stgit.fetchcmd' % get_head_file()) or \ config.get('stgit.fetchcmd') Run(*(command.split() + args)).run() def pull(repository = 'origin', refspec = None): """Fetches changes from the remote repository, using 'git pull' by default. """ # we update the HEAD __clear_head_cache() args = [repository] if refspec: args.append(refspec) command = config.get('branch.%s.stgit.pullcmd' % get_head_file()) or \ config.get('stgit.pullcmd') Run(*(command.split() + args)).run() def rebase(tree_id = None): """Rebase the current tree to the give tree_id. The tree_id argument may be something other than a GIT id if an external command is invoked. """ command = config.get('branch.%s.stgit.rebasecmd' % get_head_file()) \ or config.get('stgit.rebasecmd') if tree_id: args = [tree_id] elif command: args = [] else: raise GitException, 'Default rebasing requires a commit id' if command: # clear the HEAD cache as the custom rebase command will update it __clear_head_cache() Run(*(command.split() + args)).run() else: # default rebasing reset(tree_id = tree_id) def repack(): """Repack all objects into a single pack """ GRun('repack', '-a', '-d', '-f').run() def apply_patch(filename = None, diff = None, base = None, reject = False, strip = None): """Apply a patch onto the current or given index. There must not be any local changes in the tree, otherwise the command fails """ if diff is None: if filename: f = file(filename) else: f = sys.stdin diff = f.read() if filename: f.close() if base: orig_head = get_head() switch(base) else: refresh_index() cmd = ['apply', '--index'] if reject: cmd += ['--reject'] if strip != None: cmd += ['-p%s' % (strip,)] try: GRun(*cmd).raw_input(diff).no_output() except GitRunException: if base: switch(orig_head) raise GitException('Diff does not apply cleanly') if base: top = commit(message = 'temporary commit used for applying a patch', parents = [base]) switch(orig_head) merge_recursive(base, orig_head, top) def clone(repository, local_dir): """Clone a remote repository. At the moment, just use the 'git clone' script """ GRun('clone', repository, local_dir).run() def modifying_revs(files, base_rev, head_rev): """Return the revisions from the list modifying the given files.""" return GRun('rev-list', '%s..%s' % (base_rev, head_rev), '--', *files ).output_lines() def refspec_localpart(refspec): m = re.match('^[^:]*:([^:]*)$', refspec) if m: return m.group(1) else: raise GitException, 'Cannot parse refspec "%s"' % line def refspec_remotepart(refspec): m = re.match('^([^:]*):[^:]*$', refspec) if m: return m.group(1) else: raise GitException, 'Cannot parse refspec "%s"' % line def __remotes_from_config(): return config.sections_matching(r'remote\.(.*)\.url') def __remotes_from_dir(dir): d = os.path.join(basedir.get(), dir) if os.path.exists(d): return os.listdir(d) else: return [] def remotes_list(): """Return the list of remotes in the repository """ return (set(__remotes_from_config()) | set(__remotes_from_dir('remotes')) | set(__remotes_from_dir('branches'))) def remotes_local_branches(remote): """Returns the list of local branches fetched from given remote """ branches = [] if remote in __remotes_from_config(): for line in config.getall('remote.%s.fetch' % remote): branches.append(refspec_localpart(line)) elif remote in __remotes_from_dir('remotes'): stream = open(os.path.join(basedir.get(), 'remotes', remote), 'r') for line in stream: # Only consider Pull lines m = re.match('^Pull: (.*)\n$', line) if m: branches.append(refspec_localpart(m.group(1))) stream.close() elif remote in __remotes_from_dir('branches'): # old-style branches only declare one branch branches.append('refs/heads/'+remote); else: raise GitException, 'Unknown remote "%s"' % remote return branches def identify_remote(branchname): """Return the name for the remote to pull the given branchname from, or None if we believe it is a local branch. """ for remote in remotes_list(): if branchname in remotes_local_branches(remote): return remote # if we get here we've found nothing, the branch is a local one return None def fetch_head(): """Return the git id for the tip of the parent branch as left by 'git fetch'. """ fetch_head=None stream = open(os.path.join(basedir.get(), 'FETCH_HEAD'), "r") for line in stream: # Only consider lines not tagged not-for-merge m = re.match('^([^\t]*)\t\t', line) if m: if fetch_head: raise GitException, 'StGit does not support multiple FETCH_HEAD' else: fetch_head=m.group(1) stream.close() if not fetch_head: out.warn('No for-merge remote head found in FETCH_HEAD') # here we are sure to have a single fetch_head return fetch_head def all_refs(): """Return a list of all refs in the current repository. """ return [line.split()[1] for line in GRun('show-ref').output_lines()] stgit-0.17.1/stgit/stack.py0000644002002200200220000012006211743516173015712 0ustar cmarinascmarinas"""Basic quilt-like functionality """ __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os, re from email.Utils import formatdate from stgit.exception import * from stgit.utils import * from stgit.out import * from stgit.run import * from stgit import git, basedir, templates from stgit.config import config from shutil import copyfile from stgit.lib import git as libgit, stackupgrade # stack exception class class StackException(StgException): pass class FilterUntil: def __init__(self): self.should_print = True def __call__(self, x, until_test, prefix): if until_test(x): self.should_print = False if self.should_print: return x[0:len(prefix)] != prefix return False # # Functions # __comment_prefix = 'STG:' __patch_prefix = 'STG_PATCH:' def __clean_comments(f): """Removes lines marked for status in a commit file """ f.seek(0) # remove status-prefixed lines lines = f.readlines() patch_filter = FilterUntil() until_test = lambda t: t == (__patch_prefix + '\n') lines = [l for l in lines if patch_filter(l, until_test, __comment_prefix)] # remove empty lines at the end while len(lines) != 0 and lines[-1] == '\n': del lines[-1] f.seek(0); f.truncate() f.writelines(lines) # TODO: move this out of the stgit.stack module, it is really for # higher level commands to handle the user interaction def edit_file(series, line, comment, show_patch = True): fname = '.stgitmsg.txt' tmpl = templates.get_template('patchdescr.tmpl') f = file(fname, 'w+') if line: print >> f, line elif tmpl: print >> f, tmpl, else: print >> f print >> f, __comment_prefix, comment print >> f, __comment_prefix, \ 'Lines prefixed with "%s" will be automatically removed.' \ % __comment_prefix print >> f, __comment_prefix, \ 'Trailing empty lines will be automatically removed.' if show_patch: print >> f, __patch_prefix # series.get_patch(series.get_current()).get_top() diff_str = git.diff(rev1 = series.get_patch(series.get_current()).get_bottom()) f.write(diff_str) #Vim modeline must be near the end. print >> f, __comment_prefix, 'vi: set textwidth=75 filetype=diff nobackup:' f.close() call_editor(fname) f = file(fname, 'r+') __clean_comments(f) f.seek(0) result = f.read() f.close() os.remove(fname) return result # # Classes # class StgitObject: """An object with stgit-like properties stored as files in a directory """ def _set_dir(self, dir): self.__dir = dir def _dir(self): return self.__dir def create_empty_field(self, name): create_empty_file(os.path.join(self.__dir, name)) def _get_field(self, name, multiline = False): id_file = os.path.join(self.__dir, name) if os.path.isfile(id_file): line = read_string(id_file, multiline) if line == '': return None else: return line else: return None def _set_field(self, name, value, multiline = False): fname = os.path.join(self.__dir, name) if value and value != '': write_string(fname, value, multiline) elif os.path.isfile(fname): os.remove(fname) class Patch(StgitObject): """Basic patch implementation """ def __init_refs(self): self.__top_ref = self.__refs_base + '/' + self.__name self.__log_ref = self.__top_ref + '.log' def __init__(self, name, series_dir, refs_base): self.__series_dir = series_dir self.__name = name self._set_dir(os.path.join(self.__series_dir, self.__name)) self.__refs_base = refs_base self.__init_refs() def create(self): os.mkdir(self._dir()) def delete(self, keep_log = False): if os.path.isdir(self._dir()): for f in os.listdir(self._dir()): os.remove(os.path.join(self._dir(), f)) os.rmdir(self._dir()) else: out.warn('Patch directory "%s" does not exist' % self._dir()) try: # the reference might not exist if the repository was corrupted git.delete_ref(self.__top_ref) except git.GitException, e: out.warn(str(e)) if not keep_log and git.ref_exists(self.__log_ref): git.delete_ref(self.__log_ref) def get_name(self): return self.__name def rename(self, newname): olddir = self._dir() old_top_ref = self.__top_ref old_log_ref = self.__log_ref self.__name = newname self._set_dir(os.path.join(self.__series_dir, self.__name)) self.__init_refs() git.rename_ref(old_top_ref, self.__top_ref) if git.ref_exists(old_log_ref): git.rename_ref(old_log_ref, self.__log_ref) os.rename(olddir, self._dir()) def __update_top_ref(self, ref): git.set_ref(self.__top_ref, ref) self._set_field('top', ref) self._set_field('bottom', git.get_commit(ref).get_parent()) def __update_log_ref(self, ref): git.set_ref(self.__log_ref, ref) def get_old_bottom(self): return git.get_commit(self.get_old_top()).get_parent() def get_bottom(self): return git.get_commit(self.get_top()).get_parent() def get_old_top(self): return self._get_field('top.old') def get_top(self): return git.rev_parse(self.__top_ref) def set_top(self, value, backup = False): if backup: curr_top = self.get_top() self._set_field('top.old', curr_top) self._set_field('bottom.old', git.get_commit(curr_top).get_parent()) self.__update_top_ref(value) def restore_old_boundaries(self): top = self._get_field('top.old') if top: self.__update_top_ref(top) return True else: return False def get_description(self): return self._get_field('description', True) def set_description(self, line): self._set_field('description', line, True) def get_authname(self): return self._get_field('authname') def set_authname(self, name): self._set_field('authname', name or git.author().name) def get_authemail(self): return self._get_field('authemail') def set_authemail(self, email): self._set_field('authemail', email or git.author().email) def get_authdate(self): date = self._get_field('authdate') if not date: return date if re.match('[0-9]+\s+[+-][0-9]+', date): # Unix time (seconds) + time zone secs_tz = date.split() date = formatdate(int(secs_tz[0]))[:-5] + secs_tz[1] return date def set_authdate(self, date): self._set_field('authdate', date or git.author().date) def get_commname(self): return self._get_field('commname') def set_commname(self, name): self._set_field('commname', name or git.committer().name) def get_commemail(self): return self._get_field('commemail') def set_commemail(self, email): self._set_field('commemail', email or git.committer().email) def get_log(self): return self._get_field('log') def set_log(self, value, backup = False): self._set_field('log', value) self.__update_log_ref(value) class PatchSet(StgitObject): def __init__(self, name = None): try: if name: self.set_name (name) else: self.set_name (git.get_head_file()) self.__base_dir = basedir.get() except git.GitException, ex: raise StackException, 'GIT tree not initialised: %s' % ex self._set_dir(os.path.join(self.__base_dir, 'patches', self.get_name())) def get_name(self): return self.__name def set_name(self, name): self.__name = name def _basedir(self): return self.__base_dir def get_head(self): """Return the head of the branch """ crt = self.get_current_patch() if crt: return crt.get_top() else: return self.get_base() def get_protected(self): return os.path.isfile(os.path.join(self._dir(), 'protected')) def protect(self): protect_file = os.path.join(self._dir(), 'protected') if not os.path.isfile(protect_file): create_empty_file(protect_file) def unprotect(self): protect_file = os.path.join(self._dir(), 'protected') if os.path.isfile(protect_file): os.remove(protect_file) def __branch_descr(self): return 'branch.%s.description' % self.get_name() def get_description(self): return config.get(self.__branch_descr()) or '' def set_description(self, line): if line: config.set(self.__branch_descr(), line) else: config.unset(self.__branch_descr()) def head_top_equal(self): """Return true if the head and the top are the same """ crt = self.get_current_patch() if not crt: # we don't care, no patches applied return True return git.get_head() == crt.get_top() def is_initialised(self): """Checks if series is already initialised """ return config.get(stackupgrade.format_version_key(self.get_name()) ) != None def shortlog(patches): log = ''.join(Run('git', 'log', '--pretty=short', p.get_top(), '^%s' % p.get_bottom()).raw_output() for p in patches) return Run('git', 'shortlog').raw_input(log).raw_output() class Series(PatchSet): """Class including the operations on series """ def __init__(self, name = None): """Takes a series name as the parameter. """ PatchSet.__init__(self, name) # Update the branch to the latest format version if it is # initialized, but don't touch it if it isn't. stackupgrade.update_to_current_format_version( libgit.Repository.default(), self.get_name()) self.__refs_base = 'refs/patches/%s' % self.get_name() self.__applied_file = os.path.join(self._dir(), 'applied') self.__unapplied_file = os.path.join(self._dir(), 'unapplied') self.__hidden_file = os.path.join(self._dir(), 'hidden') # where this series keeps its patches self.__patch_dir = os.path.join(self._dir(), 'patches') # trash directory self.__trash_dir = os.path.join(self._dir(), 'trash') def __patch_name_valid(self, name): """Raise an exception if the patch name is not valid. """ if not name or re.search('[^\w.-]', name): raise StackException, 'Invalid patch name: "%s"' % name def get_patch(self, name): """Return a Patch object for the given name """ return Patch(name, self.__patch_dir, self.__refs_base) def get_current_patch(self): """Return a Patch object representing the topmost patch, or None if there is no such patch.""" crt = self.get_current() if not crt: return None return self.get_patch(crt) def get_current(self): """Return the name of the topmost patch, or None if there is no such patch.""" try: applied = self.get_applied() except StackException: # No "applied" file: branch is not initialized. return None try: return applied[-1] except IndexError: # No patches applied. return None def get_applied(self): if not os.path.isfile(self.__applied_file): raise StackException, 'Branch "%s" not initialised' % self.get_name() return read_strings(self.__applied_file) def set_applied(self, applied): write_strings(self.__applied_file, applied) def get_unapplied(self): if not os.path.isfile(self.__unapplied_file): raise StackException, 'Branch "%s" not initialised' % self.get_name() return read_strings(self.__unapplied_file) def set_unapplied(self, unapplied): write_strings(self.__unapplied_file, unapplied) def get_hidden(self): if not os.path.isfile(self.__hidden_file): return [] return read_strings(self.__hidden_file) def set_hidden(self, hidden): write_strings(self.__hidden_file, hidden) def get_base(self): # Return the parent of the bottommost patch, if there is one. if os.path.isfile(self.__applied_file): bottommost = file(self.__applied_file).readline().strip() if bottommost: return self.get_patch(bottommost).get_bottom() # No bottommost patch, so just return HEAD return git.get_head() def get_parent_remote(self): value = config.get('branch.%s.remote' % self.get_name()) if value: return value elif 'origin' in git.remotes_list(): out.note(('No parent remote declared for stack "%s",' ' defaulting to "origin".' % self.get_name()), ('Consider setting "branch.%s.remote" and' ' "branch.%s.merge" with "git config".' % (self.get_name(), self.get_name()))) return 'origin' else: raise StackException, 'Cannot find a parent remote for "%s"' % self.get_name() def __set_parent_remote(self, remote): value = config.set('branch.%s.remote' % self.get_name(), remote) def get_parent_branch(self): value = config.get('branch.%s.stgit.parentbranch' % self.get_name()) if value: return value elif git.rev_parse('heads/origin'): out.note(('No parent branch declared for stack "%s",' ' defaulting to "heads/origin".' % self.get_name()), ('Consider setting "branch.%s.stgit.parentbranch"' ' with "git config".' % self.get_name())) return 'heads/origin' else: raise StackException, 'Cannot find a parent branch for "%s"' % self.get_name() def __set_parent_branch(self, name): if config.get('branch.%s.remote' % self.get_name()): # Never set merge if remote is not set to avoid # possibly-erroneous lookups into 'origin' config.set('branch.%s.merge' % self.get_name(), name) config.set('branch.%s.stgit.parentbranch' % self.get_name(), name) def set_parent(self, remote, localbranch): if localbranch: if remote: self.__set_parent_remote(remote) self.__set_parent_branch(localbranch) # We'll enforce this later # else: # raise StackException, 'Parent branch (%s) should be specified for %s' % localbranch, self.get_name() def __patch_is_current(self, patch): return patch.get_name() == self.get_current() def patch_applied(self, name): """Return true if the patch exists in the applied list """ return name in self.get_applied() def patch_unapplied(self, name): """Return true if the patch exists in the unapplied list """ return name in self.get_unapplied() def patch_hidden(self, name): """Return true if the patch is hidden. """ return name in self.get_hidden() def patch_exists(self, name): """Return true if there is a patch with the given name, false otherwise.""" return self.patch_applied(name) or self.patch_unapplied(name) \ or self.patch_hidden(name) def init(self, create_at=False, parent_remote=None, parent_branch=None): """Initialises the stgit series """ if self.is_initialised(): raise StackException, '%s already initialized' % self.get_name() for d in [self._dir()]: if os.path.exists(d): raise StackException, '%s already exists' % d if (create_at!=False): git.create_branch(self.get_name(), create_at) os.makedirs(self.__patch_dir) self.set_parent(parent_remote, parent_branch) self.create_empty_field('applied') self.create_empty_field('unapplied') config.set(stackupgrade.format_version_key(self.get_name()), str(stackupgrade.FORMAT_VERSION)) def rename(self, to_name): """Renames a series """ to_stack = Series(to_name) if to_stack.is_initialised(): raise StackException, '"%s" already exists' % to_stack.get_name() patches = self.get_applied() + self.get_unapplied() git.rename_branch(self.get_name(), to_name) for patch in patches: git.rename_ref('refs/patches/%s/%s' % (self.get_name(), patch), 'refs/patches/%s/%s' % (to_name, patch)) git.rename_ref('refs/patches/%s/%s.log' % (self.get_name(), patch), 'refs/patches/%s/%s.log' % (to_name, patch)) if os.path.isdir(self._dir()): rename(os.path.join(self._basedir(), 'patches'), self.get_name(), to_stack.get_name()) # Rename the config section for k in ['branch.%s', 'branch.%s.stgit']: config.rename_section(k % self.get_name(), k % to_name) self.__init__(to_name) def clone(self, target_series): """Clones a series """ try: # allow cloning of branches not under StGIT control base = self.get_base() except: base = git.get_head() Series(target_series).init(create_at = base) new_series = Series(target_series) # generate an artificial description file new_series.set_description('clone of "%s"' % self.get_name()) # clone self's entire series as unapplied patches try: # allow cloning of branches not under StGIT control applied = self.get_applied() unapplied = self.get_unapplied() patches = applied + unapplied patches.reverse() except: patches = applied = unapplied = [] for p in patches: patch = self.get_patch(p) newpatch = new_series.new_patch(p, message = patch.get_description(), can_edit = False, unapplied = True, bottom = patch.get_bottom(), top = patch.get_top(), author_name = patch.get_authname(), author_email = patch.get_authemail(), author_date = patch.get_authdate()) if patch.get_log(): out.info('Setting log to %s' % patch.get_log()) newpatch.set_log(patch.get_log()) else: out.info('No log for %s' % p) # fast forward the cloned series to self's top new_series.forward_patches(applied) # Clone parent informations value = config.get('branch.%s.remote' % self.get_name()) if value: config.set('branch.%s.remote' % target_series, value) value = config.get('branch.%s.merge' % self.get_name()) if value: config.set('branch.%s.merge' % target_series, value) value = config.get('branch.%s.stgit.parentbranch' % self.get_name()) if value: config.set('branch.%s.stgit.parentbranch' % target_series, value) def delete(self, force = False, cleanup = False): """Deletes an stgit series """ if self.is_initialised(): patches = self.get_unapplied() + self.get_applied() + \ self.get_hidden(); if not force and patches: raise StackException, \ 'Cannot %s: the series still contains patches' % \ ('delete', 'clean up')[cleanup] for p in patches: self.get_patch(p).delete() # remove the trash directory if any if os.path.exists(self.__trash_dir): for fname in os.listdir(self.__trash_dir): os.remove(os.path.join(self.__trash_dir, fname)) os.rmdir(self.__trash_dir) # FIXME: find a way to get rid of those manual removals # (move functionality to StgitObject ?) if os.path.exists(self.__applied_file): os.remove(self.__applied_file) if os.path.exists(self.__unapplied_file): os.remove(self.__unapplied_file) if os.path.exists(self.__hidden_file): os.remove(self.__hidden_file) if os.path.exists(self._dir()+'/orig-base'): os.remove(self._dir()+'/orig-base') if not os.listdir(self.__patch_dir): os.rmdir(self.__patch_dir) else: out.warn('Patch directory %s is not empty' % self.__patch_dir) try: os.removedirs(self._dir()) except OSError: raise StackException('Series directory %s is not empty' % self._dir()) if not cleanup: try: git.delete_branch(self.get_name()) except git.GitException: out.warn('Could not delete branch "%s"' % self.get_name()) config.remove_section('branch.%s' % self.get_name()) config.remove_section('branch.%s.stgit' % self.get_name()) def refresh_patch(self, files = None, message = None, edit = False, empty = False, show_patch = False, cache_update = True, author_name = None, author_email = None, author_date = None, committer_name = None, committer_email = None, backup = True, sign_str = None, log = 'refresh', notes = None, bottom = None): """Generates a new commit for the topmost patch """ patch = self.get_current_patch() if not patch: raise StackException, 'No patches applied' descr = patch.get_description() if not (message or descr): edit = True descr = '' elif message: descr = message # TODO: move this out of the stgit.stack module, it is really # for higher level commands to handle the user interaction if not message and edit: descr = edit_file(self, descr.rstrip(), \ 'Please edit the description for patch "%s" ' \ 'above.' % patch.get_name(), show_patch) if not author_name: author_name = patch.get_authname() if not author_email: author_email = patch.get_authemail() if not committer_name: committer_name = patch.get_commname() if not committer_email: committer_email = patch.get_commemail() descr = add_sign_line(descr, sign_str, committer_name, committer_email) if not bottom: bottom = patch.get_bottom() if empty: tree_id = git.get_commit(bottom).get_tree() else: tree_id = None commit_id = git.commit(files = files, message = descr, parents = [bottom], cache_update = cache_update, tree_id = tree_id, set_head = True, allowempty = True, author_name = author_name, author_email = author_email, author_date = author_date, committer_name = committer_name, committer_email = committer_email) patch.set_top(commit_id, backup = backup) patch.set_description(descr) patch.set_authname(author_name) patch.set_authemail(author_email) patch.set_authdate(author_date) patch.set_commname(committer_name) patch.set_commemail(committer_email) if log: self.log_patch(patch, log, notes) return commit_id def new_patch(self, name, message = None, can_edit = True, unapplied = False, show_patch = False, top = None, bottom = None, commit = True, author_name = None, author_email = None, author_date = None, committer_name = None, committer_email = None, before_existing = False, sign_str = None): """Creates a new patch, either pointing to an existing commit object, or by creating a new commit object. """ assert commit or (top and bottom) assert not before_existing or (top and bottom) assert not (commit and before_existing) assert (top and bottom) or (not top and not bottom) assert commit or (not top or (bottom == git.get_commit(top).get_parent())) if name != None: self.__patch_name_valid(name) if self.patch_exists(name): raise StackException, 'Patch "%s" already exists' % name # TODO: move this out of the stgit.stack module, it is really # for higher level commands to handle the user interaction def sign(msg): return add_sign_line(msg, sign_str, committer_name or git.committer().name, committer_email or git.committer().email) if not message and can_edit: descr = edit_file( self, sign(''), 'Please enter the description for the patch above.', show_patch) else: descr = sign(message) head = git.get_head() if name == None: name = make_patch_name(descr, self.patch_exists) patch = self.get_patch(name) patch.create() patch.set_description(descr) patch.set_authname(author_name) patch.set_authemail(author_email) patch.set_authdate(author_date) patch.set_commname(committer_name) patch.set_commemail(committer_email) if before_existing: insert_string(self.__applied_file, patch.get_name()) elif unapplied: patches = [patch.get_name()] + self.get_unapplied() write_strings(self.__unapplied_file, patches) set_head = False else: append_string(self.__applied_file, patch.get_name()) set_head = True if commit: if top: top_commit = git.get_commit(top) else: bottom = head top_commit = git.get_commit(head) # create a commit for the patch (may be empty if top == bottom); # only commit on top of the current branch assert(unapplied or bottom == head) commit_id = git.commit(message = descr, parents = [bottom], cache_update = False, tree_id = top_commit.get_tree(), allowempty = True, set_head = set_head, author_name = author_name, author_email = author_email, author_date = author_date, committer_name = committer_name, committer_email = committer_email) # set the patch top to the new commit patch.set_top(commit_id) else: patch.set_top(top) self.log_patch(patch, 'new') return patch def delete_patch(self, name, keep_log = False): """Deletes a patch """ self.__patch_name_valid(name) patch = self.get_patch(name) if self.__patch_is_current(patch): self.pop_patch(name) elif self.patch_applied(name): raise StackException, 'Cannot remove an applied patch, "%s", ' \ 'which is not current' % name elif not name in self.get_unapplied(): raise StackException, 'Unknown patch "%s"' % name # save the commit id to a trash file write_string(os.path.join(self.__trash_dir, name), patch.get_top()) patch.delete(keep_log = keep_log) unapplied = self.get_unapplied() unapplied.remove(name) write_strings(self.__unapplied_file, unapplied) def forward_patches(self, names): """Try to fast-forward an array of patches. On return, patches in names[0:returned_value] have been pushed on the stack. Apply the rest with push_patch """ unapplied = self.get_unapplied() forwarded = 0 top = git.get_head() for name in names: assert(name in unapplied) patch = self.get_patch(name) head = top bottom = patch.get_bottom() top = patch.get_top() # top != bottom always since we have a commit for each patch if head == bottom: # reset the backup information. No logging since the # patch hasn't changed patch.set_top(top, backup = True) else: head_tree = git.get_commit(head).get_tree() bottom_tree = git.get_commit(bottom).get_tree() if head_tree == bottom_tree: # We must just reparent this patch and create a new commit # for it descr = patch.get_description() author_name = patch.get_authname() author_email = patch.get_authemail() author_date = patch.get_authdate() committer_name = patch.get_commname() committer_email = patch.get_commemail() top_tree = git.get_commit(top).get_tree() top = git.commit(message = descr, parents = [head], cache_update = False, tree_id = top_tree, allowempty = True, author_name = author_name, author_email = author_email, author_date = author_date, committer_name = committer_name, committer_email = committer_email) patch.set_top(top, backup = True) self.log_patch(patch, 'push(f)') else: top = head # stop the fast-forwarding, must do a real merge break forwarded+=1 unapplied.remove(name) if forwarded == 0: return 0 git.switch(top) append_strings(self.__applied_file, names[0:forwarded]) write_strings(self.__unapplied_file, unapplied) return forwarded def merged_patches(self, names): """Test which patches were merged upstream by reverse-applying them in reverse order. The function returns the list of patches detected to have been applied. The state of the tree is restored to the original one """ patches = [self.get_patch(name) for name in names] patches.reverse() merged = [] for p in patches: if git.apply_diff(p.get_top(), p.get_bottom()): merged.append(p.get_name()) merged.reverse() git.reset() return merged def push_empty_patch(self, name): """Pushes an empty patch on the stack """ unapplied = self.get_unapplied() assert(name in unapplied) # patch = self.get_patch(name) head = git.get_head() append_string(self.__applied_file, name) unapplied.remove(name) write_strings(self.__unapplied_file, unapplied) self.refresh_patch(bottom = head, cache_update = False, log = 'push(m)') def push_patch(self, name): """Pushes a patch on the stack """ unapplied = self.get_unapplied() assert(name in unapplied) patch = self.get_patch(name) head = git.get_head() bottom = patch.get_bottom() top = patch.get_top() # top != bottom always since we have a commit for each patch if head == bottom: # A fast-forward push. Just reset the backup # information. No need for logging patch.set_top(top, backup = True) git.switch(top) append_string(self.__applied_file, name) unapplied.remove(name) write_strings(self.__unapplied_file, unapplied) return False # Need to create a new commit an merge in the old patch ex = None modified = False # Try the fast applying first. If this fails, fall back to the # three-way merge if not git.apply_diff(bottom, top): # if git.apply_diff() fails, the patch requires a diff3 # merge and can be reported as modified modified = True # merge can fail but the patch needs to be pushed try: git.merge_recursive(bottom, head, top) except git.GitException, ex: out.error('The merge failed during "push".', 'Revert the operation with "stg undo".') append_string(self.__applied_file, name) unapplied.remove(name) write_strings(self.__unapplied_file, unapplied) if not ex: # if the merge was OK and no conflicts, just refresh the patch # The GIT cache was already updated by the merge operation if modified: log = 'push(m)' else: log = 'push' self.refresh_patch(bottom = head, cache_update = False, log = log) else: # we make the patch empty, with the merged state in the # working tree. self.refresh_patch(bottom = head, cache_update = False, empty = True, log = 'push(c)') raise StackException, str(ex) return modified def pop_patch(self, name, keep = False): """Pops the top patch from the stack """ applied = self.get_applied() applied.reverse() assert(name in applied) patch = self.get_patch(name) if git.get_head_file() == self.get_name(): if keep and not git.apply_diff(git.get_head(), patch.get_bottom(), check_index = False): raise StackException( 'Failed to pop patches while preserving the local changes') git.switch(patch.get_bottom(), keep) else: git.set_branch(self.get_name(), patch.get_bottom()) # save the new applied list idx = applied.index(name) + 1 popped = applied[:idx] popped.reverse() unapplied = popped + self.get_unapplied() write_strings(self.__unapplied_file, unapplied) del applied[:idx] applied.reverse() write_strings(self.__applied_file, applied) def empty_patch(self, name): """Returns True if the patch is empty """ self.__patch_name_valid(name) patch = self.get_patch(name) bottom = patch.get_bottom() top = patch.get_top() if bottom == top: return True elif git.get_commit(top).get_tree() \ == git.get_commit(bottom).get_tree(): return True return False def rename_patch(self, oldname, newname): self.__patch_name_valid(newname) applied = self.get_applied() unapplied = self.get_unapplied() if oldname == newname: raise StackException, '"To" name and "from" name are the same' if newname in applied or newname in unapplied: raise StackException, 'Patch "%s" already exists' % newname if oldname in unapplied: self.get_patch(oldname).rename(newname) unapplied[unapplied.index(oldname)] = newname write_strings(self.__unapplied_file, unapplied) elif oldname in applied: self.get_patch(oldname).rename(newname) applied[applied.index(oldname)] = newname write_strings(self.__applied_file, applied) else: raise StackException, 'Unknown patch "%s"' % oldname def log_patch(self, patch, message, notes = None): """Generate a log commit for a patch """ top = git.get_commit(patch.get_top()) old_log = patch.get_log() if message is None: # replace the current log entry if not old_log: raise StackException, \ 'No log entry to annotate for patch "%s"' \ % patch.get_name() replace = True log_commit = git.get_commit(old_log) msg = log_commit.get_log().split('\n')[0] log_parent = log_commit.get_parent() if log_parent: parents = [log_parent] else: parents = [] else: # generate a new log entry replace = False msg = '%s\t%s' % (message, top.get_id_hash()) if old_log: parents = [old_log] else: parents = [] if notes: msg += '\n\n' + notes log = git.commit(message = msg, parents = parents, cache_update = False, tree_id = top.get_tree(), allowempty = True) patch.set_log(log) def hide_patch(self, name): """Add the patch to the hidden list. """ unapplied = self.get_unapplied() if name not in unapplied: # keep the checking order for backward compatibility with # the old hidden patches functionality if self.patch_applied(name): raise StackException, 'Cannot hide applied patch "%s"' % name elif self.patch_hidden(name): raise StackException, 'Patch "%s" already hidden' % name else: raise StackException, 'Unknown patch "%s"' % name if not self.patch_hidden(name): # check needed for backward compatibility with the old # hidden patches functionality append_string(self.__hidden_file, name) unapplied.remove(name) write_strings(self.__unapplied_file, unapplied) def unhide_patch(self, name): """Remove the patch from the hidden list. """ hidden = self.get_hidden() if not name in hidden: if self.patch_applied(name) or self.patch_unapplied(name): raise StackException, 'Patch "%s" not hidden' % name else: raise StackException, 'Unknown patch "%s"' % name hidden.remove(name) write_strings(self.__hidden_file, hidden) if not self.patch_applied(name) and not self.patch_unapplied(name): # check needed for backward compatibility with the old # hidden patches functionality append_string(self.__unapplied_file, name) stgit-0.17.1/stgit/utils.py0000644002002200200220000002302411743516173015745 0ustar cmarinascmarinas"""Common utility functions """ import errno, os, os.path, re, sys from stgit.exception import * from stgit.config import config from stgit.out import * __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ def mkdir_file(filename, mode): """Opens filename with the given mode, creating the directory it's in if it doesn't already exist.""" create_dirs(os.path.dirname(filename)) return file(filename, mode) def read_strings(filename): """Reads the lines from a file """ f = file(filename, 'r') lines = [line.strip() for line in f.readlines()] f.close() return lines def read_string(filename, multiline = False): """Reads the first line from a file """ f = file(filename, 'r') if multiline: result = f.read() else: result = f.readline().strip() f.close() return result def write_strings(filename, lines): """Write 'lines' sequence to file """ f = file(filename, 'w+') f.writelines([line + '\n' for line in lines]) f.close() def write_string(filename, line, multiline = False): """Writes 'line' to file and truncates it """ f = mkdir_file(filename, 'w+') if multiline: f.write(line) else: print >> f, line f.close() def append_strings(filename, lines): """Appends 'lines' sequence to file """ f = mkdir_file(filename, 'a+') for line in lines: print >> f, line f.close() def append_string(filename, line): """Appends 'line' to file """ f = mkdir_file(filename, 'a+') print >> f, line f.close() def insert_string(filename, line): """Inserts 'line' at the beginning of the file """ f = mkdir_file(filename, 'r+') lines = f.readlines() f.seek(0); f.truncate() print >> f, line f.writelines(lines) f.close() def create_empty_file(name): """Creates an empty file """ mkdir_file(name, 'w+').close() def list_files_and_dirs(path): """Return the sets of filenames and directory names in a directory.""" files, dirs = [], [] for fd in os.listdir(path): full_fd = os.path.join(path, fd) if os.path.isfile(full_fd): files.append(fd) elif os.path.isdir(full_fd): dirs.append(fd) return files, dirs def walk_tree(basedir): """Starting in the given directory, iterate through all its subdirectories. For each subdirectory, yield the name of the subdirectory (relative to the base directory), the list of filenames in the subdirectory, and the list of directory names in the subdirectory.""" subdirs = [''] while subdirs: subdir = subdirs.pop() files, dirs = list_files_and_dirs(os.path.join(basedir, subdir)) for d in dirs: subdirs.append(os.path.join(subdir, d)) yield subdir, files, dirs def strip_prefix(prefix, string): """Return string, without the prefix. Blow up if string doesn't start with prefix.""" assert string.startswith(prefix) return string[len(prefix):] def strip_suffix(suffix, string): """Return string, without the suffix. Blow up if string doesn't end with suffix.""" assert string.endswith(suffix) return string[:-len(suffix)] def remove_file_and_dirs(basedir, file): """Remove join(basedir, file), and then remove the directory it was in if empty, and try the same with its parent, until we find a nonempty directory or reach basedir.""" os.remove(os.path.join(basedir, file)) try: os.removedirs(os.path.join(basedir, os.path.dirname(file))) except OSError: # file's parent dir may not be empty after removal pass def create_dirs(directory): """Create the given directory, if the path doesn't already exist.""" if directory and not os.path.isdir(directory): create_dirs(os.path.dirname(directory)) try: os.mkdir(directory) except OSError, e: if e.errno != errno.EEXIST: raise e def rename(basedir, file1, file2): """Rename join(basedir, file1) to join(basedir, file2), not leaving any empty directories behind and creating any directories necessary.""" full_file2 = os.path.join(basedir, file2) create_dirs(os.path.dirname(full_file2)) os.rename(os.path.join(basedir, file1), full_file2) try: os.removedirs(os.path.join(basedir, os.path.dirname(file1))) except OSError: # file1's parent dir may not be empty after move pass class EditorException(StgException): pass def get_editor(): for editor in [os.environ.get('GIT_EDITOR'), config.get('stgit.editor'), # legacy config.get('core.editor'), os.environ.get('VISUAL'), os.environ.get('EDITOR'), 'vi']: if editor: return editor def call_editor(filename): """Run the editor on the specified filename.""" cmd = '%s %s' % (get_editor(), filename) out.start('Invoking the editor: "%s"' % cmd) err = os.system(cmd) if err: raise EditorException, 'editor failed, exit code: %d' % err out.done() def edit_string(s, filename): f = file(filename, 'w') f.write(s) f.close() call_editor(filename) f = file(filename) s = f.read() f.close() os.remove(filename) return s def append_comment(s, comment, separator = '---'): return ('%s\n\n%s\nEverything following the line with "%s" will be' ' ignored\n\n%s' % (s, separator, separator, comment)) def strip_comment(s, separator = '---'): try: return s[:s.index('\n%s\n' % separator)] except ValueError: return s def find_patch_name(patchname, unacceptable): """Find a patch name which is acceptable.""" if unacceptable(patchname): suffix = 0 while unacceptable('%s-%d' % (patchname, suffix)): suffix += 1 patchname = '%s-%d' % (patchname, suffix) return patchname def patch_name_from_msg(msg): """Return a string to be used as a patch name. This is generated from the top line of the string passed as argument.""" if not msg: return None name_len = config.getint('stgit.namelength') if not name_len: name_len = 30 subject_line = msg.split('\n', 1)[0].lstrip().lower() words = re.sub('[\W]+', ' ', subject_line).split() # use loop to avoid truncating the last name name = words and words[0] or 'unknown' for word in words[1:]: new = name + '-' + word if len(new) > name_len: break name = new return name def make_patch_name(msg, unacceptable, default_name = 'patch'): """Return a patch name generated from the given commit message, guaranteed to make unacceptable(name) be false. If the commit message is empty, base the name on default_name instead.""" patchname = patch_name_from_msg(msg) if not patchname: patchname = default_name return find_patch_name(patchname, unacceptable) # any and all functions are builtin in Python 2.5 and higher, but not # in 2.4. if not 'any' in dir(__builtins__): def any(bools): for b in bools: if b: return True return False if not 'all' in dir(__builtins__): def all(bools): for b in bools: if not b: return False return True def add_sign_line(desc, sign_str, name, email): if not sign_str: return desc sign_str = '%s: %s <%s>' % (sign_str, name, email) if sign_str in desc: return desc desc = desc.rstrip() if not any(s in desc for s in ['\nSigned-off-by:', '\nAcked-by:']): desc = desc + '\n' return '%s\n%s\n' % (desc, sign_str) def parse_name_email(address): """Return a tuple consisting of the name and email parsed from a standard 'name ' or 'email (name)' string.""" address = re.sub(r'[\\"]', r'\\\g<0>', address) str_list = re.findall(r'^(.*)\s*<(.*)>\s*$', address) if not str_list: str_list = re.findall(r'^(.*)\s*\((.*)\)\s*$', address) if not str_list: return None return (str_list[0][1], str_list[0][0]) return str_list[0] def parse_name_email_date(address): """Return a tuple consisting of the name, email and date parsed from a 'name date' string.""" address = re.sub(r'[\\"]', r'\\\g<0>', address) str_list = re.findall('^(.*)\s*<(.*)>\s*(.*)\s*$', address) if not str_list: return None return str_list[0] # Exit codes. STGIT_SUCCESS = 0 # everything's OK STGIT_GENERAL_ERROR = 1 # seems to be non-command-specific error STGIT_COMMAND_ERROR = 2 # seems to be a command that failed STGIT_CONFLICT = 3 # merge conflict, otherwise OK STGIT_BUG_ERROR = 4 # a bug in StGit def add_dict(d1, d2): """Return a new dict with the contents of both d1 and d2. In case of conflicting mappings, d2 takes precedence.""" d = dict(d1) d.update(d2) return d stgit-0.17.1/stgit/basedir.py0000644002002200200220000000245610732722750016221 0ustar cmarinascmarinas"""Access to the GIT base directory """ __copyright__ = """ Copyright (C) 2006, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import os from stgit.run import * # GIT_DIR value cached __base_dir = None def get(): """Return the .git directory location """ global __base_dir if not __base_dir: if 'GIT_DIR' in os.environ: __base_dir = os.environ['GIT_DIR'] else: try: __base_dir = Run('git', 'rev-parse', '--git-dir').output_one_line() except RunException: __base_dir = '' return __base_dir def clear_cache(): """Clear the cached location of .git """ global __base_dir __base_dir = None stgit-0.17.1/stgit/lib/0000755002002200200220000000000012222320003014752 5ustar cmarinascmarinasstgit-0.17.1/stgit/lib/edit.py0000644002002200200220000001024311271344641016272 0ustar cmarinascmarinas"""This module contains utility functions for patch editing.""" from stgit import utils from stgit.commands import common from stgit.lib import git def update_patch_description(repo, cd, text, contains_diff): """Update the given L{CommitData} with the given text description, which may contain author name and time stamp in addition to a new commit message. If C{contains_diff} is true, it may also contain a replacement diff. Return a pair: the new L{CommitData}; and the diff text if it didn't apply, or C{None} otherwise.""" (message, authname, authemail, authdate, diff ) = common.parse_patch(text, contains_diff) a = cd.author for val, setter in [(authname, 'set_name'), (authemail, 'set_email'), (git.Date.maybe(authdate), 'set_date')]: if val != None: a = getattr(a, setter)(val) cd = cd.set_message(message).set_author(a) failed_diff = None if diff: tree = repo.apply(cd.parent.data.tree, diff, quiet = False) if tree == None: failed_diff = diff else: cd = cd.set_tree(tree) return cd, failed_diff def patch_desc(repo, cd, append_diff, diff_flags, replacement_diff): """Return a description text for the patch, suitable for editing and/or reimporting with L{update_patch_description()}. @param cd: The L{CommitData} to generate a description of @param append_diff: Whether to append the patch diff to the description @type append_diff: C{bool} @param diff_flags: Extra parameters to pass to C{git diff} @param replacement_diff: Diff text to use; or C{None} if it should be computed from C{cd} @type replacement_diff: C{str} or C{None}""" desc = ['From: %s <%s>' % (cd.author.name, cd.author.email), 'Date: %s' % cd.author.date.isoformat(), '', cd.message] if append_diff: if replacement_diff: diff = replacement_diff else: just_diff = repo.diff_tree(cd.parent.data.tree, cd.tree, diff_flags) diff = '\n'.join([git.diffstat(just_diff), just_diff]) desc += ['---', '', diff] return '\n'.join(desc) def interactive_edit_patch(repo, cd, edit_diff, diff_flags, replacement_diff): """Edit the patch interactively. If C{edit_diff} is true, edit the diff as well. If C{replacement_diff} is not C{None}, it contains a diff to edit instead of the patch's real diff. Return a pair: the new L{CommitData}; and the diff text if it didn't apply, or C{None} otherwise.""" return update_patch_description( repo, cd, utils.edit_string( patch_desc(repo, cd, edit_diff, diff_flags, replacement_diff), '.stgit-edit.' + ['txt', 'patch'][bool(edit_diff)]), edit_diff) def auto_edit_patch(repo, cd, msg, contains_diff, author, committer, sign_str): """Edit the patch noninteractively in a couple of ways: - If C{msg} is not C{None}, parse it to find a replacement message, and possibly also replacement author and timestamp. If C{contains_diff} is true, also look for a replacement diff. - C{author} and C{committer} are two functions that take the original L{Person} value as argument, and return the new one. - C{sign_str}, if not C{None}, is a sign string to append to the message. Return a pair: the new L{CommitData}; and the diff text if it didn't apply, or C{None} otherwise.""" if msg == None: failed_diff = None else: cd, failed_diff = update_patch_description(repo, cd, msg, contains_diff) a, c = author(cd.author), committer(cd.committer) if (a, c) != (cd.author, cd.committer): cd = cd.set_author(a).set_committer(c) if sign_str != None: cd = cd.set_message(utils.add_sign_line( cd.message, sign_str, git.Person.committer().name, git.Person.committer().email)) return cd, failed_diff stgit-0.17.1/stgit/lib/git.py0000644002002200200220000012304712221306627016135 0ustar cmarinascmarinas"""A Python class hierarchy wrapping a git repository and its contents.""" import atexit, os, os.path, re, signal from datetime import datetime, timedelta, tzinfo from stgit import exception, run, utils from stgit.config import config class Immutable(object): """I{Immutable} objects cannot be modified once created. Any modification methods will return a new object, leaving the original object as it was. The reason for this is that we want to be able to represent git objects, which are immutable, and want to be able to create new git objects that are just slight modifications of other git objects. (Such as, for example, modifying the commit message of a commit object while leaving the rest of it intact. This involves creating a whole new commit object that's exactly like the old one except for the commit message.) The L{Immutable} class doesn't actually enforce immutability -- that is up to the individual immutable subclasses. It just serves as documentation.""" class RepositoryException(exception.StgException): """Base class for all exceptions due to failed L{Repository} operations.""" class BranchException(exception.StgException): """Exception raised by failed L{Branch} operations.""" class DateException(exception.StgException): """Exception raised when a date+time string could not be parsed.""" def __init__(self, string, type): exception.StgException.__init__( self, '"%s" is not a valid %s' % (string, type)) class DetachedHeadException(RepositoryException): """Exception raised when HEAD is detached (that is, there is no current branch).""" def __init__(self): RepositoryException.__init__(self, 'Not on any branch') class Repr(object): """Utility class that defines C{__reps__} in terms of C{__str__}.""" def __repr__(self): return str(self) class NoValue(object): """A handy default value that is guaranteed to be distinct from any real argument value.""" pass def make_defaults(defaults): def d(val, attr, default_fun = lambda: None): if val != NoValue: return val elif defaults != NoValue: return getattr(defaults, attr) else: return default_fun() return d class TimeZone(tzinfo, Repr): """A simple time zone class for static offsets from UTC. (We have to define our own since Python's standard library doesn't define any time zone classes.)""" def __init__(self, tzstring): m = re.match(r'^([+-])(\d{2}):?(\d{2})$', tzstring) if not m: raise DateException(tzstring, 'time zone') sign = int(m.group(1) + '1') try: self.__offset = timedelta(hours = sign*int(m.group(2)), minutes = sign*int(m.group(3))) except OverflowError: raise DateException(tzstring, 'time zone') self.__name = tzstring def utcoffset(self, dt): return self.__offset def tzname(self, dt): return self.__name def dst(self, dt): return timedelta(0) def __str__(self): return self.__name def system_date(datestring): m = re.match(r"^(.+)([+-]\d\d:?\d\d)$", datestring) if m: # Time zone included; we parse it ourselves, since "date" # would convert it to the local time zone. (ds, z) = m.groups() try: t = run.Run("date", "+%Y-%m-%d-%H-%M-%S", "-d", ds ).output_one_line() except run.RunException: return None else: # Time zone not included; we ask "date" to provide it for us. try: d = run.Run("date", "+%Y-%m-%d-%H-%M-%S_%z", "-d", datestring ).output_one_line() except run.RunException: return None (t, z) = d.split("_") try: return datetime(*[int(x) for x in t.split("-")], tzinfo=TimeZone(z)) except ValueError: raise DateException(datestring, "date") class Date(Immutable, Repr): """Represents a timestamp used in git commits.""" def __init__(self, datestring): # Try git-formatted date. m = re.match(r'^(\d+)\s+([+-]\d\d:?\d\d)$', datestring) if m: try: self.__time = datetime.fromtimestamp(int(m.group(1)), TimeZone(m.group(2))) except ValueError: raise DateException(datestring, 'date') return # Try iso-formatted date. m = re.match(r'^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})\s+' + r'([+-]\d\d:?\d\d)$', datestring) if m: try: self.__time = datetime( *[int(m.group(i + 1)) for i in xrange(6)], **{'tzinfo': TimeZone(m.group(7))}) except ValueError: raise DateException(datestring, 'date') return # Try parsing with the system's "date" command. sd = system_date(datestring) if sd: self.__time = sd return raise DateException(datestring, 'date') def __str__(self): return self.isoformat() def isoformat(self): """Human-friendly ISO 8601 format.""" return '%s %s' % (self.__time.replace(tzinfo = None).isoformat(' '), self.__time.tzinfo) @classmethod def maybe(cls, datestring): """Return a new object initialized with the argument if it contains a value (otherwise, just return the argument).""" if datestring in [None, NoValue]: return datestring return cls(datestring) class Person(Immutable, Repr): """Represents an author or committer in a git commit object. Contains name, email and timestamp.""" def __init__(self, name = NoValue, email = NoValue, date = NoValue, defaults = NoValue): d = make_defaults(defaults) self.__name = d(name, 'name') self.__email = d(email, 'email') self.__date = d(date, 'date') assert isinstance(self.__date, Date) or self.__date in [None, NoValue] name = property(lambda self: self.__name) email = property(lambda self: self.__email) name_email = property(lambda self: '%s <%s>' % (self.name, self.email)) date = property(lambda self: self.__date) def set_name(self, name): return type(self)(name = name, defaults = self) def set_email(self, email): return type(self)(email = email, defaults = self) def set_date(self, date): return type(self)(date = date, defaults = self) def __str__(self): return '%s %s' % (self.name_email, self.date) @classmethod def parse(cls, s): m = re.match(r'^([^<]*)<([^>]*)>\s+(\d+\s+[+-]\d{4})$', s) assert m name = m.group(1).strip() email = m.group(2) date = Date(m.group(3)) return cls(name, email, date) @classmethod def user(cls): if not hasattr(cls, '__user'): cls.__user = cls(name = config.get('user.name'), email = config.get('user.email')) return cls.__user @classmethod def author(cls): if not hasattr(cls, '__author'): cls.__author = cls( name = os.environ.get('GIT_AUTHOR_NAME', NoValue), email = os.environ.get('GIT_AUTHOR_EMAIL', NoValue), date = Date.maybe(os.environ.get('GIT_AUTHOR_DATE', NoValue)), defaults = cls.user()) return cls.__author @classmethod def committer(cls): if not hasattr(cls, '__committer'): cls.__committer = cls( name = os.environ.get('GIT_COMMITTER_NAME', NoValue), email = os.environ.get('GIT_COMMITTER_EMAIL', NoValue), date = Date.maybe( os.environ.get('GIT_COMMITTER_DATE', NoValue)), defaults = cls.user()) return cls.__committer class GitObject(Immutable, Repr): """Base class for all git objects. One git object is represented by at most one C{GitObject}, which makes it possible to compare them using normal Python object comparison; it also ensures we don't waste more memory than necessary.""" class BlobData(Immutable, Repr): """Represents the data contents of a git blob object.""" def __init__(self, string): self.__string = str(string) str = property(lambda self: self.__string) def commit(self, repository): """Commit the blob. @return: The committed blob @rtype: L{Blob}""" sha1 = repository.run(['git', 'hash-object', '-w', '--stdin'] ).raw_input(self.str).output_one_line() return repository.get_blob(sha1) class Blob(GitObject): """Represents a git blob object. All the actual data contents of the blob object is stored in the L{data} member, which is a L{BlobData} object.""" typename = 'blob' default_perm = '100644' def __init__(self, repository, sha1): self.__repository = repository self.__sha1 = sha1 sha1 = property(lambda self: self.__sha1) def __str__(self): return 'Blob<%s>' % self.sha1 @property def data(self): return BlobData(self.__repository.cat_object(self.sha1)) class ImmutableDict(dict): """A dictionary that cannot be modified once it's been created.""" def error(*args, **kwargs): raise TypeError('Cannot modify immutable dict') __delitem__ = error __setitem__ = error clear = error pop = error popitem = error setdefault = error update = error class TreeData(Immutable, Repr): """Represents the data contents of a git tree object.""" @staticmethod def __x(po): if isinstance(po, GitObject): perm, object = po.default_perm, po else: perm, object = po return perm, object def __init__(self, entries): """Create a new L{TreeData} object from the given mapping from names (strings) to either (I{permission}, I{object}) tuples or just objects.""" self.__entries = ImmutableDict((name, self.__x(po)) for (name, po) in entries.iteritems()) entries = property(lambda self: self.__entries) """Map from name to (I{permission}, I{object}) tuple.""" def set_entry(self, name, po): """Create a new L{TreeData} object identical to this one, except that it maps C{name} to C{po}. @param name: Name of the changed mapping @type name: C{str} @param po: Value of the changed mapping @type po: L{Blob} or L{Tree} or (C{str}, L{Blob} or L{Tree}) @return: The new L{TreeData} object @rtype: L{TreeData}""" e = dict(self.entries) e[name] = self.__x(po) return type(self)(e) def del_entry(self, name): """Create a new L{TreeData} object identical to this one, except that it doesn't map C{name} to anything. @param name: Name of the deleted mapping @type name: C{str} @return: The new L{TreeData} object @rtype: L{TreeData}""" e = dict(self.entries) del e[name] return type(self)(e) def commit(self, repository): """Commit the tree. @return: The committed tree @rtype: L{Tree}""" listing = ''.join( '%s %s %s\t%s\0' % (mode, obj.typename, obj.sha1, name) for (name, (mode, obj)) in self.entries.iteritems()) sha1 = repository.run(['git', 'mktree', '-z'] ).raw_input(listing).output_one_line() return repository.get_tree(sha1) @classmethod def parse(cls, repository, s): """Parse a raw git tree description. @return: A new L{TreeData} object @rtype: L{TreeData}""" entries = {} for line in s.split('\0')[:-1]: m = re.match(r'^([0-7]{6}) ([a-z]+) ([0-9a-f]{40})\t(.*)$', line) assert m perm, type, sha1, name = m.groups() entries[name] = (perm, repository.get_object(type, sha1)) return cls(entries) class Tree(GitObject): """Represents a git tree object. All the actual data contents of the tree object is stored in the L{data} member, which is a L{TreeData} object.""" typename = 'tree' default_perm = '040000' def __init__(self, repository, sha1): self.__sha1 = sha1 self.__repository = repository self.__data = None sha1 = property(lambda self: self.__sha1) @property def data(self): if self.__data == None: self.__data = TreeData.parse( self.__repository, self.__repository.run(['git', 'ls-tree', '-z', self.sha1] ).raw_output()) return self.__data def __str__(self): return 'Tree' % self.sha1 class CommitData(Immutable, Repr): """Represents the data contents of a git commit object.""" def __init__(self, tree = NoValue, parents = NoValue, author = NoValue, committer = NoValue, message = NoValue, defaults = NoValue): d = make_defaults(defaults) self.__tree = d(tree, 'tree') self.__parents = d(parents, 'parents') self.__author = d(author, 'author', Person.author) self.__committer = d(committer, 'committer', Person.committer) self.__message = d(message, 'message') tree = property(lambda self: self.__tree) parents = property(lambda self: self.__parents) @property def parent(self): assert len(self.__parents) == 1 return self.__parents[0] author = property(lambda self: self.__author) committer = property(lambda self: self.__committer) message = property(lambda self: self.__message) def set_tree(self, tree): return type(self)(tree = tree, defaults = self) def set_parents(self, parents): return type(self)(parents = parents, defaults = self) def add_parent(self, parent): return type(self)(parents = list(self.parents or []) + [parent], defaults = self) def set_parent(self, parent): return self.set_parents([parent]) def set_author(self, author): return type(self)(author = author, defaults = self) def set_committer(self, committer): return type(self)(committer = committer, defaults = self) def set_message(self, message): return type(self)(message = message, defaults = self) def is_nochange(self): return len(self.parents) == 1 and self.tree == self.parent.data.tree def __str__(self): if self.tree == None: tree = None else: tree = self.tree.sha1 if self.parents == None: parents = None else: parents = [p.sha1 for p in self.parents] return ('CommitData' ) % (tree, parents, self.author, self.committer, self.message) def commit(self, repository): """Commit the commit. @return: The committed commit @rtype: L{Commit}""" c = ['git', 'commit-tree', self.tree.sha1] for p in self.parents: c.append('-p') c.append(p.sha1) env = {} for p, v1 in ((self.author, 'AUTHOR'), (self.committer, 'COMMITTER')): if p != None: for attr, v2 in (('name', 'NAME'), ('email', 'EMAIL'), ('date', 'DATE')): if getattr(p, attr) != None: env['GIT_%s_%s' % (v1, v2)] = str(getattr(p, attr)) sha1 = repository.run(c, env = env).raw_input(self.message ).output_one_line() return repository.get_commit(sha1) @classmethod def parse(cls, repository, s): """Parse a raw git commit description. @return: A new L{CommitData} object @rtype: L{CommitData}""" cd = cls(parents = []) lines = [] raw_lines = s.split('\n') # Collapse multi-line header lines for i, line in enumerate(raw_lines): if not line: cd = cd.set_message('\n'.join(raw_lines[i+1:])) break if line.startswith(' '): # continuation line lines[-1] += '\n' + line[1:] else: lines.append(line) for line in lines: if ' ' in line: key, value = line.split(' ', 1) if key == 'tree': cd = cd.set_tree(repository.get_tree(value)) elif key == 'parent': cd = cd.add_parent(repository.get_commit(value)) elif key == 'author': cd = cd.set_author(Person.parse(value)) elif key == 'committer': cd = cd.set_committer(Person.parse(value)) return cd class Commit(GitObject): """Represents a git commit object. All the actual data contents of the commit object is stored in the L{data} member, which is a L{CommitData} object.""" typename = 'commit' def __init__(self, repository, sha1): self.__sha1 = sha1 self.__repository = repository self.__data = None sha1 = property(lambda self: self.__sha1) @property def data(self): if self.__data == None: self.__data = CommitData.parse( self.__repository, self.__repository.cat_object(self.sha1)) return self.__data def __str__(self): return 'Commit' % (self.sha1, self.__data) class Refs(object): """Accessor for the refs stored in a git repository. Will transparently cache the values of all refs.""" def __init__(self, repository): self.__repository = repository self.__refs = None def __cache_refs(self): """(Re-)Build the cache of all refs in the repository.""" self.__refs = {} runner = self.__repository.run(['git', 'show-ref']) try: lines = runner.output_lines() except run.RunException: # as this happens both in non-git trees and empty git # trees, we silently ignore this error return for line in lines: m = re.match(r'^([0-9a-f]{40})\s+(\S+)$', line) sha1, ref = m.groups() self.__refs[ref] = sha1 def get(self, ref): """Get the Commit the given ref points to. Throws KeyError if ref doesn't exist.""" if self.__refs == None: self.__cache_refs() return self.__repository.get_commit(self.__refs[ref]) def exists(self, ref): """Check if the given ref exists.""" try: self.get(ref) except KeyError: return False else: return True def set(self, ref, commit, msg): """Write the sha1 of the given Commit to the ref. The ref may or may not already exist.""" if self.__refs == None: self.__cache_refs() old_sha1 = self.__refs.get(ref, '0'*40) new_sha1 = commit.sha1 if old_sha1 != new_sha1: self.__repository.run(['git', 'update-ref', '-m', msg, ref, new_sha1, old_sha1]).no_output() self.__refs[ref] = new_sha1 def delete(self, ref): """Delete the given ref. Throws KeyError if ref doesn't exist.""" if self.__refs == None: self.__cache_refs() self.__repository.run(['git', 'update-ref', '-d', ref, self.__refs[ref]]).no_output() del self.__refs[ref] class ObjectCache(object): """Cache for Python objects, for making sure that we create only one Python object per git object. This reduces memory consumption and makes object comparison very cheap.""" def __init__(self, create): self.__objects = {} self.__create = create def __getitem__(self, name): if not name in self.__objects: self.__objects[name] = self.__create(name) return self.__objects[name] def __contains__(self, name): return name in self.__objects def __setitem__(self, name, val): assert not name in self.__objects self.__objects[name] = val class RunWithEnv(object): def run(self, args, env = {}): """Run the given command with an environment given by self.env. @type args: list of strings @param args: Command and argument vector @type env: dict @param env: Extra environment""" return run.Run(*args).env(utils.add_dict(self.env, env)) class RunWithEnvCwd(RunWithEnv): def run(self, args, env = {}): """Run the given command with an environment given by self.env, and current working directory given by self.cwd. @type args: list of strings @param args: Command and argument vector @type env: dict @param env: Extra environment""" return RunWithEnv.run(self, args, env).cwd(self.cwd) def run_in_cwd(self, args): """Run the given command with an environment given by self.env and self.env_in_cwd, without changing the current working directory. @type args: list of strings @param args: Command and argument vector""" return RunWithEnv.run(self, args, self.env_in_cwd) class CatFileProcess(object): def __init__(self, repo): self.__repo = repo self.__proc = None atexit.register(self.__shutdown) def __get_process(self): if not self.__proc: self.__proc = self.__repo.run(['git', 'cat-file', '--batch'] ).run_background() return self.__proc def __shutdown(self): p = self.__proc if p: os.kill(p.pid(), signal.SIGTERM) p.wait() def cat_file(self, sha1): p = self.__get_process() p.stdin.write('%s\n' % sha1) p.stdin.flush() # Read until we have the entire status line. s = '' while not '\n' in s: s += os.read(p.stdout.fileno(), 4096) h, b = s.split('\n', 1) if h == '%s missing' % sha1: raise SomeException() hash, type, length = h.split() assert hash == sha1 length = int(length) # Read until we have the entire object plus the trailing # newline. while len(b) < length + 1: b += os.read(p.stdout.fileno(), 4096) return type, b[:-1] class DiffTreeProcesses(object): def __init__(self, repo): self.__repo = repo self.__procs = {} atexit.register(self.__shutdown) def __get_process(self, args): args = tuple(args) if not args in self.__procs: self.__procs[args] = self.__repo.run( ['git', 'diff-tree', '--stdin'] + list(args)).run_background() return self.__procs[args] def __shutdown(self): for p in self.__procs.values(): os.kill(p.pid(), signal.SIGTERM) p.wait() def diff_trees(self, args, sha1a, sha1b): p = self.__get_process(args) query = '%s %s\n' % (sha1a, sha1b) end = 'EOF\n' # arbitrary string that's not a 40-digit hex number p.stdin.write(query + end) p.stdin.flush() s = '' while not (s.endswith('\n' + end) or s.endswith('\0' + end)): s += os.read(p.stdout.fileno(), 4096) assert s.startswith(query) assert s.endswith(end) return s[len(query):-len(end)] class Repository(RunWithEnv): """Represents a git repository.""" def __init__(self, directory): self.__git_dir = directory self.__refs = Refs(self) self.__blobs = ObjectCache(lambda sha1: Blob(self, sha1)) self.__trees = ObjectCache(lambda sha1: Tree(self, sha1)) self.__commits = ObjectCache(lambda sha1: Commit(self, sha1)) self.__default_index = None self.__default_worktree = None self.__default_iw = None self.__catfile = CatFileProcess(self) self.__difftree = DiffTreeProcesses(self) env = property(lambda self: { 'GIT_DIR': self.__git_dir }) @classmethod def default(cls): """Return the default repository.""" try: return cls(run.Run('git', 'rev-parse', '--git-dir' ).output_one_line()) except run.RunException: raise RepositoryException('Cannot find git repository') @property def current_branch_name(self): """Return the name of the current branch.""" return utils.strip_prefix('refs/heads/', self.head_ref) @property def default_index(self): """An L{Index} object representing the default index file for the repository.""" if self.__default_index == None: self.__default_index = Index( self, (os.environ.get('GIT_INDEX_FILE', None) or os.path.join(self.__git_dir, 'index'))) return self.__default_index def temp_index(self): """Return an L{Index} object representing a new temporary index file for the repository.""" return Index(self, self.__git_dir) @property def default_worktree(self): """A L{Worktree} object representing the default work tree.""" if self.__default_worktree == None: path = os.environ.get('GIT_WORK_TREE', None) if not path: o = run.Run('git', 'rev-parse', '--show-cdup').output_lines() o = o or ['.'] assert len(o) == 1 path = o[0] self.__default_worktree = Worktree(path) return self.__default_worktree @property def default_iw(self): """An L{IndexAndWorktree} object representing the default index and work tree for this repository.""" if self.__default_iw == None: self.__default_iw = IndexAndWorktree(self.default_index, self.default_worktree) return self.__default_iw directory = property(lambda self: self.__git_dir) refs = property(lambda self: self.__refs) def cat_object(self, sha1): return self.__catfile.cat_file(sha1)[1] def rev_parse(self, rev, discard_stderr = False, object_type = 'commit'): assert object_type in ('commit', 'tree', 'blob') getter = getattr(self, 'get_' + object_type) try: return getter(self.run( ['git', 'rev-parse', '%s^{%s}' % (rev, object_type)] ).discard_stderr(discard_stderr).output_one_line()) except run.RunException: raise RepositoryException('%s: No such %s' % (rev, object_type)) def get_blob(self, sha1): return self.__blobs[sha1] def get_tree(self, sha1): return self.__trees[sha1] def get_commit(self, sha1): return self.__commits[sha1] def get_object(self, type, sha1): return { Blob.typename: self.get_blob, Tree.typename: self.get_tree, Commit.typename: self.get_commit }[type](sha1) def commit(self, objectdata): return objectdata.commit(self) @property def head_ref(self): try: return self.run(['git', 'symbolic-ref', '-q', 'HEAD'] ).output_one_line() except run.RunException: raise DetachedHeadException() def set_head_ref(self, ref, msg): self.run(['git', 'symbolic-ref', '-m', msg, 'HEAD', ref]).no_output() def get_merge_bases(self, commit1, commit2): """Return a set of merge bases of two commits.""" sha1_list = self.run(['git', 'merge-base', '--all', commit1.sha1, commit2.sha1]).output_lines() return [self.get_commit(sha1) for sha1 in sha1_list] def describe(self, commit): """Use git describe --all on the given commit.""" return self.run(['git', 'describe', '--all', commit.sha1] ).discard_stderr().discard_exitcode().raw_output() def simple_merge(self, base, ours, theirs): index = self.temp_index() try: result, index_tree = index.merge(base, ours, theirs) finally: index.delete() return result def apply(self, tree, patch_text, quiet): """Given a L{Tree} and a patch, will either return the new L{Tree} that results when the patch is applied, or None if the patch couldn't be applied.""" assert isinstance(tree, Tree) if not patch_text: return tree index = self.temp_index() try: index.read_tree(tree) try: index.apply(patch_text, quiet) return index.write_tree() except MergeException: return None finally: index.delete() def diff_tree(self, t1, t2, diff_opts, binary = True): """Given two L{Tree}s C{t1} and C{t2}, return the patch that takes C{t1} to C{t2}. @type diff_opts: list of strings @param diff_opts: Extra diff options @rtype: String @return: Patch text""" assert isinstance(t1, Tree) assert isinstance(t2, Tree) diff_opts = list(diff_opts) if binary and not '--binary' in diff_opts: diff_opts.append('--binary') return self.__difftree.diff_trees(['-p'] + diff_opts, t1.sha1, t2.sha1) def diff_tree_files(self, t1, t2): """Given two L{Tree}s C{t1} and C{t2}, iterate over all files for which they differ. For each file, yield a tuple with the old file mode, the new file mode, the old blob, the new blob, the status, the old filename, and the new filename. Except in case of a copy or a rename, the old and new filenames are identical.""" assert isinstance(t1, Tree) assert isinstance(t2, Tree) i = iter(self.__difftree.diff_trees( ['-r', '-z'], t1.sha1, t2.sha1).split('\0')) try: while True: x = i.next() if not x: continue omode, nmode, osha1, nsha1, status = x[1:].split(' ') fn1 = i.next() if status[0] in ['C', 'R']: fn2 = i.next() else: fn2 = fn1 yield (omode, nmode, self.get_blob(osha1), self.get_blob(nsha1), status, fn1, fn2) except StopIteration: pass class MergeException(exception.StgException): """Exception raised when a merge fails for some reason.""" class MergeConflictException(MergeException): """Exception raised when a merge fails due to conflicts.""" def __init__(self, conflicts): MergeException.__init__(self) self.conflicts = conflicts class Index(RunWithEnv): """Represents a git index file.""" def __init__(self, repository, filename): self.__repository = repository if os.path.isdir(filename): # Create a temp index in the given directory. self.__filename = os.path.join( filename, 'index.temp-%d-%x' % (os.getpid(), id(self))) self.delete() else: self.__filename = filename env = property(lambda self: utils.add_dict( self.__repository.env, { 'GIT_INDEX_FILE': self.__filename })) def read_tree(self, tree): self.run(['git', 'read-tree', tree.sha1]).no_output() def write_tree(self): """Write the index contents to the repository. @return: The resulting L{Tree} @rtype: L{Tree}""" try: return self.__repository.get_tree( self.run(['git', 'write-tree']).discard_stderr( ).output_one_line()) except run.RunException: raise MergeException('Conflicting merge') def is_clean(self, tree): """Check whether the index is clean relative to the given treeish.""" try: self.run(['git', 'diff-index', '--quiet', '--cached', tree.sha1] ).discard_output() except run.RunException: return False else: return True def apply(self, patch_text, quiet): """In-index patch application, no worktree involved.""" try: r = self.run(['git', 'apply', '--cached']).raw_input(patch_text) if quiet: r = r.discard_stderr() r.no_output() except run.RunException: raise MergeException('Patch does not apply cleanly') def apply_treediff(self, tree1, tree2, quiet): """Apply the diff from C{tree1} to C{tree2} to the index.""" # Passing --full-index here is necessary to support binary # files. It is also sufficient, since the repository already # contains all involved objects; in other words, we don't have # to use --binary. self.apply(self.__repository.diff_tree(tree1, tree2, ['--full-index']), quiet) def merge(self, base, ours, theirs, current = None): """Use the index (and only the index) to do a 3-way merge of the L{Tree}s C{base}, C{ours} and C{theirs}. The merge will either succeed (in which case the first half of the return value is the resulting tree) or fail cleanly (in which case the first half of the return value is C{None}). If C{current} is given (and not C{None}), it is assumed to be the L{Tree} currently stored in the index; this information is used to avoid having to read the right tree (either of C{ours} and C{theirs}) into the index if it's already there. The second half of the return value is the tree now stored in the index, or C{None} if unknown. If the merge succeeded, this is often the merge result.""" assert isinstance(base, Tree) assert isinstance(ours, Tree) assert isinstance(theirs, Tree) assert current == None or isinstance(current, Tree) # Take care of the really trivial cases. if base == ours: return (theirs, current) if base == theirs: return (ours, current) if ours == theirs: return (ours, current) if current == theirs: # Swap the trees. It doesn't matter since merging is # symmetric, and will allow us to avoid the read_tree() # call below. ours, theirs = theirs, ours if current != ours: self.read_tree(ours) try: self.apply_treediff(base, theirs, quiet = True) result = self.write_tree() return (result, result) except MergeException: return (None, ours) def delete(self): if os.path.isfile(self.__filename): os.remove(self.__filename) def conflicts(self): """The set of conflicting paths.""" paths = set() for line in self.run(['git', 'ls-files', '-z', '--unmerged'] ).raw_output().split('\0')[:-1]: stat, path = line.split('\t', 1) paths.add(path) return paths class Worktree(object): """Represents a git worktree (that is, a checked-out file tree).""" def __init__(self, directory): self.__directory = directory env = property(lambda self: { 'GIT_WORK_TREE': '.' }) env_in_cwd = property(lambda self: { 'GIT_WORK_TREE': self.directory }) directory = property(lambda self: self.__directory) class CheckoutException(exception.StgException): """Exception raised when a checkout fails.""" class IndexAndWorktree(RunWithEnvCwd): """Represents a git index and a worktree. Anything that an index or worktree can do on their own are handled by the L{Index} and L{Worktree} classes; this class concerns itself with the operations that require both.""" def __init__(self, index, worktree): self.__index = index self.__worktree = worktree index = property(lambda self: self.__index) env = property(lambda self: utils.add_dict(self.__index.env, self.__worktree.env)) env_in_cwd = property(lambda self: self.__worktree.env_in_cwd) cwd = property(lambda self: self.__worktree.directory) def checkout_hard(self, tree): assert isinstance(tree, Tree) self.run(['git', 'read-tree', '--reset', '-u', tree.sha1] ).discard_output() def checkout(self, old_tree, new_tree): # TODO: Optionally do a 3-way instead of doing nothing when we # have a problem. Or maybe we should stash changes in a patch? assert isinstance(old_tree, Tree) assert isinstance(new_tree, Tree) try: self.run(['git', 'read-tree', '-u', '-m', '--exclude-per-directory=.gitignore', old_tree.sha1, new_tree.sha1] ).discard_output() except run.RunException: raise CheckoutException('Index/workdir dirty') def merge(self, base, ours, theirs, interactive = False): assert isinstance(base, Tree) assert isinstance(ours, Tree) assert isinstance(theirs, Tree) try: r = self.run(['git', 'merge-recursive', base.sha1, '--', ours.sha1, theirs.sha1], env = { 'GITHEAD_%s' % base.sha1: 'ancestor', 'GITHEAD_%s' % ours.sha1: 'current', 'GITHEAD_%s' % theirs.sha1: 'patched'}) r.returns([0, 1]) output = r.output_lines() if r.exitcode: # There were conflicts if interactive: self.mergetool() else: conflicts = [l for l in output if l.startswith('CONFLICT')] raise MergeConflictException(conflicts) except run.RunException, e: raise MergeException('Index/worktree dirty') def mergetool(self, files = ()): """Invoke 'git mergetool' on the current IndexAndWorktree to resolve any outstanding conflicts. If 'not files', all the files in an unmerged state will be processed.""" self.run(['git', 'mergetool'] + list(files)).returns([0, 1]).run() # check for unmerged entries (prepend 'CONFLICT ' for consistency with # merge()) conflicts = ['CONFLICT ' + f for f in self.index.conflicts()] if conflicts: raise MergeConflictException(conflicts) def changed_files(self, tree, pathlimits = []): """Return the set of files in the worktree that have changed with respect to C{tree}. The listing is optionally restricted to those files that match any of the path limiters given. The path limiters are relative to the current working directory; the returned file names are relative to the repository root.""" assert isinstance(tree, Tree) return set(self.run_in_cwd( ['git', 'diff-index', tree.sha1, '--name-only', '-z', '--'] + list(pathlimits)).raw_output().split('\0')[:-1]) def update_index(self, paths): """Update the index with files from the worktree. C{paths} is an iterable of paths relative to the root of the repository.""" cmd = ['git', 'update-index', '--remove'] self.run(cmd + ['-z', '--stdin'] ).input_nulterm(paths).discard_output() def worktree_clean(self): """Check whether the worktree is clean relative to index.""" try: self.run(['git', 'update-index', '--refresh']).discard_output() except run.RunException: return False else: return True class Branch(object): """Represents a Git branch.""" def __init__(self, repository, name): self.__repository = repository self.__name = name try: self.head except KeyError: raise BranchException('%s: no such branch' % name) name = property(lambda self: self.__name) repository = property(lambda self: self.__repository) def __ref(self): return 'refs/heads/%s' % self.__name @property def head(self): return self.__repository.refs.get(self.__ref()) def set_head(self, commit, msg): self.__repository.refs.set(self.__ref(), commit, msg) def set_parent_remote(self, name): value = config.set('branch.%s.remote' % self.__name, name) def set_parent_branch(self, name): if config.get('branch.%s.remote' % self.__name): # Never set merge if remote is not set to avoid # possibly-erroneous lookups into 'origin' config.set('branch.%s.merge' % self.__name, name) @classmethod def create(cls, repository, name, create_at = None): """Create a new Git branch and return the corresponding L{Branch} object.""" try: branch = cls(repository, name) except BranchException: branch = None if branch: raise BranchException('%s: branch already exists' % name) cmd = ['git', 'branch'] if create_at: cmd.append(create_at.sha1) repository.run(['git', 'branch', create_at.sha1]).discard_output() return cls(repository, name) def diffstat(diff): """Return the diffstat of the supplied diff.""" return run.Run('git', 'apply', '--stat', '--summary' ).raw_input(diff).raw_output() def clone(remote, local): """Clone a remote repository using 'git clone'.""" run.Run('git', 'clone', remote, local).run() stgit-0.17.1/stgit/lib/stackupgrade.py0000644002002200200220000001025711271344641020027 0ustar cmarinascmarinasimport os.path from stgit import utils from stgit.out import out from stgit.config import config # The current StGit metadata format version. FORMAT_VERSION = 2 def format_version_key(branch): return 'branch.%s.stgit.stackformatversion' % branch def update_to_current_format_version(repository, branch): """Update a potentially older StGit directory structure to the latest version. Note: This function should depend as little as possible on external functions that may change during a format version bump, since it must remain able to process older formats.""" branch_dir = os.path.join(repository.directory, 'patches', branch) key = format_version_key(branch) old_key = 'branch.%s.stgitformatversion' % branch def get_format_version(): """Return the integer format version number, or None if the branch doesn't have any StGit metadata at all, of any version.""" fv = config.get(key) ofv = config.get(old_key) if fv: # Great, there's an explicitly recorded format version # number, which means that the branch is initialized and # of that exact version. return int(fv) elif ofv: # Old name for the version info: upgrade it. config.set(key, ofv) config.unset(old_key) return int(ofv) elif os.path.isdir(os.path.join(branch_dir, 'patches')): # There's a .git/patches//patches dirctory, which # means this is an initialized version 1 branch. return 1 elif os.path.isdir(branch_dir): # There's a .git/patches/ directory, which means # this is an initialized version 0 branch. return 0 else: # The branch doesn't seem to be initialized at all. return None def set_format_version(v): out.info('Upgraded branch %s to format version %d' % (branch, v)) config.set(key, '%d' % v) def mkdir(d): if not os.path.isdir(d): os.makedirs(d) def rm(f): if os.path.exists(f): os.remove(f) def rm_ref(ref): if repository.refs.exists(ref): repository.refs.delete(ref) # Update 0 -> 1. if get_format_version() == 0: mkdir(os.path.join(branch_dir, 'trash')) patch_dir = os.path.join(branch_dir, 'patches') mkdir(patch_dir) refs_base = 'refs/patches/%s' % branch for patch in (file(os.path.join(branch_dir, 'unapplied')).readlines() + file(os.path.join(branch_dir, 'applied')).readlines()): patch = patch.strip() os.rename(os.path.join(branch_dir, patch), os.path.join(patch_dir, patch)) topfield = os.path.join(patch_dir, patch, 'top') if os.path.isfile(topfield): top = utils.read_string(topfield, False) else: top = None if top: repository.refs.set(refs_base + '/' + patch, repository.get_commit(top), 'StGit upgrade') set_format_version(1) # Update 1 -> 2. if get_format_version() == 1: desc_file = os.path.join(branch_dir, 'description') if os.path.isfile(desc_file): desc = utils.read_string(desc_file) if desc: config.set('branch.%s.description' % branch, desc) rm(desc_file) rm(os.path.join(branch_dir, 'current')) rm_ref('refs/bases/%s' % branch) set_format_version(2) # compatibility with the new infrastructure. The changes here do not # affect the compatibility with the old infrastructure (format version 2) if get_format_version() == 2: hidden_file = os.path.join(branch_dir, 'hidden') if not os.path.isfile(hidden_file): utils.create_empty_file(hidden_file) # Make sure we're at the latest version. fv = get_format_version() if not fv in [None, FORMAT_VERSION]: raise StackException('Branch %s is at format version %d, expected %d' % (branch, fv, FORMAT_VERSION)) return fv != None # true if branch is initialized stgit-0.17.1/stgit/lib/stack.py0000644002002200200220000002427411271344641016463 0ustar cmarinascmarinas"""A Python class hierarchy wrapping the StGit on-disk metadata.""" import os.path from stgit import exception, utils from stgit.lib import git, stackupgrade from stgit.config import config class StackException(exception.StgException): """Exception raised by L{stack} objects.""" class Patch(object): """Represents an StGit patch. This class is mainly concerned with reading and writing the on-disk representation of a patch.""" def __init__(self, stack, name): self.__stack = stack self.__name = name name = property(lambda self: self.__name) @property def __ref(self): return 'refs/patches/%s/%s' % (self.__stack.name, self.__name) @property def __log_ref(self): return self.__ref + '.log' @property def commit(self): return self.__stack.repository.refs.get(self.__ref) @property def old_commit(self): """Return the previous commit for this patch.""" fn = os.path.join(self.__compat_dir, 'top.old') if not os.path.isfile(fn): return None return self.__stack.repository.get_commit(utils.read_string(fn)) @property def __compat_dir(self): return os.path.join(self.__stack.directory, 'patches', self.__name) def __write_compat_files(self, new_commit, msg): """Write files used by the old infrastructure.""" def write(name, val, multiline = False): fn = os.path.join(self.__compat_dir, name) if val: utils.write_string(fn, val, multiline) elif os.path.isfile(fn): os.remove(fn) def write_patchlog(): try: old_log = [self.__stack.repository.refs.get(self.__log_ref)] except KeyError: old_log = [] cd = git.CommitData(tree = new_commit.data.tree, parents = old_log, message = '%s\t%s' % (msg, new_commit.sha1)) c = self.__stack.repository.commit(cd) self.__stack.repository.refs.set(self.__log_ref, c, msg) return c d = new_commit.data write('authname', d.author.name) write('authemail', d.author.email) write('authdate', d.author.date) write('commname', d.committer.name) write('commemail', d.committer.email) write('description', d.message, multiline = True) write('log', write_patchlog().sha1) write('top', new_commit.sha1) write('bottom', d.parent.sha1) try: old_top_sha1 = self.commit.sha1 old_bottom_sha1 = self.commit.data.parent.sha1 except KeyError: old_top_sha1 = None old_bottom_sha1 = None write('top.old', old_top_sha1) write('bottom.old', old_bottom_sha1) def __delete_compat_files(self): if os.path.isdir(self.__compat_dir): for f in os.listdir(self.__compat_dir): os.remove(os.path.join(self.__compat_dir, f)) os.rmdir(self.__compat_dir) try: # this compatibility log ref might not exist self.__stack.repository.refs.delete(self.__log_ref) except KeyError: pass def set_commit(self, commit, msg): self.__write_compat_files(commit, msg) self.__stack.repository.refs.set(self.__ref, commit, msg) def delete(self): self.__delete_compat_files() self.__stack.repository.refs.delete(self.__ref) def is_applied(self): return self.name in self.__stack.patchorder.applied def is_empty(self): return self.commit.data.is_nochange() def files(self): """Return the set of files this patch touches.""" fs = set() for (_, _, _, _, _, oldname, newname ) in self.__stack.repository.diff_tree_files( self.commit.data.tree, self.commit.data.parent.data.tree): fs.add(oldname) fs.add(newname) return fs class PatchOrder(object): """Keeps track of patch order, and which patches are applied. Works with patch names, not actual patches.""" def __init__(self, stack): self.__stack = stack self.__lists = {} def __read_file(self, fn): return tuple(utils.read_strings( os.path.join(self.__stack.directory, fn))) def __write_file(self, fn, val): utils.write_strings(os.path.join(self.__stack.directory, fn), val) def __get_list(self, name): if not name in self.__lists: self.__lists[name] = self.__read_file(name) return self.__lists[name] def __set_list(self, name, val): val = tuple(val) if val != self.__lists.get(name, None): self.__lists[name] = val self.__write_file(name, val) applied = property(lambda self: self.__get_list('applied'), lambda self, val: self.__set_list('applied', val)) unapplied = property(lambda self: self.__get_list('unapplied'), lambda self, val: self.__set_list('unapplied', val)) hidden = property(lambda self: self.__get_list('hidden'), lambda self, val: self.__set_list('hidden', val)) all = property(lambda self: self.applied + self.unapplied + self.hidden) all_visible = property(lambda self: self.applied + self.unapplied) @staticmethod def create(stackdir): """Create the PatchOrder specific files """ utils.create_empty_file(os.path.join(stackdir, 'applied')) utils.create_empty_file(os.path.join(stackdir, 'unapplied')) utils.create_empty_file(os.path.join(stackdir, 'hidden')) class Patches(object): """Creates L{Patch} objects. Makes sure there is only one such object per patch.""" def __init__(self, stack): self.__stack = stack def create_patch(name): p = Patch(self.__stack, name) p.commit # raise exception if the patch doesn't exist return p self.__patches = git.ObjectCache(create_patch) # name -> Patch def exists(self, name): try: self.get(name) return True except KeyError: return False def get(self, name): return self.__patches[name] def new(self, name, commit, msg): assert not name in self.__patches p = Patch(self.__stack, name) p.set_commit(commit, msg) self.__patches[name] = p return p class Stack(git.Branch): """Represents an StGit stack (that is, a git branch with some extra metadata).""" __repo_subdir = 'patches' def __init__(self, repository, name): git.Branch.__init__(self, repository, name) self.__patchorder = PatchOrder(self) self.__patches = Patches(self) if not stackupgrade.update_to_current_format_version(repository, name): raise StackException('%s: branch not initialized' % name) patchorder = property(lambda self: self.__patchorder) patches = property(lambda self: self.__patches) @property def directory(self): return os.path.join(self.repository.directory, self.__repo_subdir, self.name) @property def base(self): if self.patchorder.applied: return self.patches.get(self.patchorder.applied[0] ).commit.data.parent else: return self.head @property def top(self): """Commit of the topmost patch, or the stack base if no patches are applied.""" if self.patchorder.applied: return self.patches.get(self.patchorder.applied[-1]).commit else: # When no patches are applied, base == head. return self.head def head_top_equal(self): if not self.patchorder.applied: return True return self.head == self.patches.get(self.patchorder.applied[-1]).commit def set_parents(self, remote, branch): if remote: self.set_parent_remote(remote) if branch: self.set_parent_branch(branch) @classmethod def initialise(cls, repository, name = None): """Initialise a Git branch to handle patch series. @param repository: The L{Repository} where the L{Stack} will be created @param name: The name of the L{Stack} """ if not name: name = repository.current_branch_name # make sure that the corresponding Git branch exists git.Branch(repository, name) dir = os.path.join(repository.directory, cls.__repo_subdir, name) compat_dir = os.path.join(dir, 'patches') if os.path.exists(dir): raise StackException('%s: branch already initialized' % name) # create the stack directory and files utils.create_dirs(dir) utils.create_dirs(compat_dir) PatchOrder.create(dir) config.set(stackupgrade.format_version_key(name), str(stackupgrade.FORMAT_VERSION)) return repository.get_stack(name) @classmethod def create(cls, repository, name, create_at = None, parent_remote = None, parent_branch = None): """Create and initialise a Git branch returning the L{Stack} object. @param repository: The L{Repository} where the L{Stack} will be created @param name: The name of the L{Stack} @param create_at: The Git id used as the base for the newly created Git branch @param parent_remote: The name of the remote Git branch @param parent_branch: The name of the parent Git branch """ git.Branch.create(repository, name, create_at = create_at) stack = cls.initialise(repository, name) stack.set_parents(parent_remote, parent_branch) return stack class Repository(git.Repository): """A git L{Repository} with some added StGit-specific operations.""" def __init__(self, *args, **kwargs): git.Repository.__init__(self, *args, **kwargs) self.__stacks = {} # name -> Stack @property def current_stack(self): return self.get_stack() def get_stack(self, name = None): if not name: name = self.current_branch_name if not name in self.__stacks: self.__stacks[name] = Stack(self, name) return self.__stacks[name] stgit-0.17.1/stgit/lib/log.py0000644002002200200220000004405511716721007016135 0ustar cmarinascmarinasr"""This module contains functions and classes for manipulating I{patch stack logs} (or just I{stack logs}). A stack log is a git branch. Each commit contains the complete state of the stack at the moment it was written; the most recent commit has the most recent state. For a branch C{I{foo}}, the stack log is stored in C{I{foo}.stgit}. Each log entry makes sure to have proper references to everything it needs, which means that it is safe against garbage collection -- you can even pull it from one repository to another. Stack log format (version 0) ============================ Version 0 was an experimental version of the stack log format; it is no longer supported. Stack log format (version 1) ============================ Commit message -------------- The commit message is mostly for human consumption; in most cases it is just a subject line: the stg subcommand name and possibly some important command-line flag. An exception to this is log commits for undo and redo. Their subject line is "C{undo I{n}}" and "C{redo I{n}}"; the positive integer I{n} says how many steps were undone or redone. Tree ---- - One blob, C{meta}, that contains the log data: - C{Version:} I{n} where I{n} must be 1. (Future versions of StGit might change the log format; when this is done, this version number will be incremented.) - C{Previous:} I{sha1 or C{None}} The commit of the previous log entry, or C{None} if this is the first entry. - C{Head:} I{sha1} The current branch head. - C{Applied:} Marks the start of the list of applied patches. They are listed in order, each on its own line: first one or more spaces, then the patch name, then a colon, space, then the patch's sha1. - C{Unapplied:} Same as C{Applied:}, but for the unapplied patches. - C{Hidden:} Same as C{Applied:}, but for the hidden patches. - One subtree, C{patches}, that contains one blob per patch:: Bottom: Top: Author: Date: --- Following the message is a newline, three dashes, and another newline. Then come, each on its own line, Parents ------- - The first parent is the I{simplified log}, described below. - The rest of the parents are just there to make sure that all the commits referred to in the log entry -- patches, branch head, previous log entry -- are ancestors of the log commit. (This is necessary to make the log safe with regard to garbage collection and pulling.) Simplified log -------------- The simplified log is exactly like the full log, except that its only parent is the (simplified) previous log entry, if any. It's purpose is mainly ease of visualization.""" import re from stgit.lib import git, stack as libstack from stgit import exception, utils from stgit.out import out import StringIO class LogException(exception.StgException): pass class LogParseException(LogException): pass def patch_file(repo, cd): return repo.commit(git.BlobData(''.join(s + '\n' for s in [ 'Bottom: %s' % cd.parent.data.tree.sha1, 'Top: %s' % cd.tree.sha1, 'Author: %s' % cd.author.name_email, 'Date: %s' % cd.author.date, '', cd.message, '', '---', '', repo.diff_tree(cd.parent.data.tree, cd.tree, ['-M'] ).strip()]))) def log_ref(branch): return 'refs/heads/%s.stgit' % branch class LogEntry(object): __separator = '\n---\n' __max_parents = 16 def __init__(self, repo, prev, head, applied, unapplied, hidden, patches, message): self.__repo = repo self.__prev = prev self.__simplified = None self.head = head self.applied = applied self.unapplied = unapplied self.hidden = hidden self.patches = patches self.message = message @property def simplified(self): if not self.__simplified: self.__simplified = self.commit.data.parents[0] return self.__simplified @property def prev(self): if self.__prev != None and not isinstance(self.__prev, LogEntry): self.__prev = self.from_commit(self.__repo, self.__prev) return self.__prev @property def base(self): if self.applied: return self.patches[self.applied[0]].data.parent else: return self.head @property def top(self): if self.applied: return self.patches[self.applied[-1]] else: return self.head all_patches = property(lambda self: (self.applied + self.unapplied + self.hidden)) @classmethod def from_stack(cls, prev, stack, message): return cls( repo = stack.repository, prev = prev, head = stack.head, applied = list(stack.patchorder.applied), unapplied = list(stack.patchorder.unapplied), hidden = list(stack.patchorder.hidden), patches = dict((pn, stack.patches.get(pn).commit) for pn in stack.patchorder.all), message = message) @staticmethod def __parse_metadata(repo, metadata): """Parse a stack log metadata string.""" if not metadata.startswith('Version:'): raise LogParseException('Malformed log metadata') metadata = metadata.splitlines() version_str = utils.strip_prefix('Version:', metadata.pop(0)).strip() try: version = int(version_str) except ValueError: raise LogParseException( 'Malformed version number: %r' % version_str) if version < 1: raise LogException('Log is version %d, which is too old' % version) if version > 1: raise LogException('Log is version %d, which is too new' % version) parsed = {} for line in metadata: if line.startswith(' '): parsed[key].append(line.strip()) else: key, val = [x.strip() for x in line.split(':', 1)] if val: parsed[key] = val else: parsed[key] = [] prev = parsed['Previous'] if prev == 'None': prev = None else: prev = repo.get_commit(prev) head = repo.get_commit(parsed['Head']) lists = { 'Applied': [], 'Unapplied': [], 'Hidden': [] } patches = {} for lst in lists.keys(): for entry in parsed[lst]: pn, sha1 = [x.strip() for x in entry.split(':')] lists[lst].append(pn) patches[pn] = repo.get_commit(sha1) return (prev, head, lists['Applied'], lists['Unapplied'], lists['Hidden'], patches) @classmethod def from_commit(cls, repo, commit): """Parse a (full or simplified) stack log commit.""" message = commit.data.message try: perm, meta = commit.data.tree.data.entries['meta'] except KeyError: raise LogParseException('Not a stack log') (prev, head, applied, unapplied, hidden, patches ) = cls.__parse_metadata(repo, meta.data.str) lg = cls(repo, prev, head, applied, unapplied, hidden, patches, message) lg.commit = commit return lg def __metadata_string(self): e = StringIO.StringIO() e.write('Version: 1\n') if self.prev == None: e.write('Previous: None\n') else: e.write('Previous: %s\n' % self.prev.commit.sha1) e.write('Head: %s\n' % self.head.sha1) for lst, title in [(self.applied, 'Applied'), (self.unapplied, 'Unapplied'), (self.hidden, 'Hidden')]: e.write('%s:\n' % title) for pn in lst: e.write(' %s: %s\n' % (pn, self.patches[pn].sha1)) return e.getvalue() def __parents(self): """Return the set of parents this log entry needs in order to be a descendant of all the commits it refers to.""" xp = set([self.head]) | set(self.patches[pn] for pn in self.unapplied + self.hidden) if self.applied: xp.add(self.patches[self.applied[-1]]) if self.prev != None: xp.add(self.prev.commit) xp -= set(self.prev.patches.values()) return xp def __tree(self, metadata): if self.prev == None: def pf(c): return patch_file(self.__repo, c.data) else: prev_top_tree = self.prev.commit.data.tree perm, prev_patch_tree = prev_top_tree.data.entries['patches'] # Map from Commit object to patch_file() results taken # from the previous log entry. c2b = dict((self.prev.patches[pn], pf) for pn, pf in prev_patch_tree.data.entries.iteritems()) def pf(c): r = c2b.get(c, None) if not r: r = patch_file(self.__repo, c.data) return r patches = dict((pn, pf(c)) for pn, c in self.patches.iteritems()) return self.__repo.commit(git.TreeData({ 'meta': self.__repo.commit(git.BlobData(metadata)), 'patches': self.__repo.commit(git.TreeData(patches)) })) def write_commit(self): metadata = self.__metadata_string() tree = self.__tree(metadata) self.__simplified = self.__repo.commit(git.CommitData( tree = tree, message = self.message, parents = [prev.simplified for prev in [self.prev] if prev != None])) parents = list(self.__parents()) while len(parents) >= self.__max_parents: g = self.__repo.commit(git.CommitData( tree = tree, parents = parents[-self.__max_parents:], message = 'Stack log parent grouping')) parents[-self.__max_parents:] = [g] self.commit = self.__repo.commit(git.CommitData( tree = tree, message = self.message, parents = [self.simplified] + parents)) def get_log_entry(repo, ref, commit): try: return LogEntry.from_commit(repo, commit) except LogException, e: raise LogException('While reading log from %s: %s' % (ref, e)) def same_state(log1, log2): """Check whether two log entries describe the same current state.""" s = [[lg.head, lg.applied, lg.unapplied, lg.hidden, lg.patches] for lg in [log1, log2]] return s[0] == s[1] def log_entry(stack, msg): """Write a new log entry for the stack.""" ref = log_ref(stack.name) try: last_log_commit = stack.repository.refs.get(ref) except KeyError: last_log_commit = None try: if last_log_commit: last_log = get_log_entry(stack.repository, ref, last_log_commit) else: last_log = None new_log = LogEntry.from_stack(last_log, stack, msg) except LogException, e: out.warn(str(e), 'No log entry written.') return if last_log and same_state(last_log, new_log): return new_log.write_commit() stack.repository.refs.set(ref, new_log.commit, msg) class Fakestack(object): """Imitates a real L{Stack}, but with the topmost patch popped.""" def __init__(self, stack): appl = list(stack.patchorder.applied) unappl = list(stack.patchorder.unapplied) hidd = list(stack.patchorder.hidden) class patchorder(object): applied = appl[:-1] unapplied = [appl[-1]] + unappl hidden = hidd all = appl + unappl + hidd self.patchorder = patchorder class patches(object): @staticmethod def get(pn): if pn == appl[-1]: class patch(object): commit = stack.patches.get(pn).old_commit return patch else: return stack.patches.get(pn) self.patches = patches self.head = stack.head.data.parent self.top = stack.top.data.parent self.base = stack.base self.name = stack.name self.repository = stack.repository def compat_log_entry(msg): """Write a new log entry. (Convenience function intended for use by code not yet converted to the new infrastructure.)""" repo = default_repo() try: stack = repo.get_stack(repo.current_branch_name) except libstack.StackException, e: out.warn(str(e), 'Could not write to stack log') else: if repo.default_index.conflicts() and stack.patchorder.applied: log_entry(Fakestack(stack), msg) log_entry(stack, msg + ' (CONFLICT)') else: log_entry(stack, msg) def delete_log(repo, branch): ref = log_ref(branch) if repo.refs.exists(ref): repo.refs.delete(ref) def rename_log(repo, old_branch, new_branch, msg): old_ref = log_ref(old_branch) new_ref = log_ref(new_branch) if repo.refs.exists(old_ref): repo.refs.set(new_ref, repo.refs.get(old_ref), msg) repo.refs.delete(old_ref) def copy_log(repo, src_branch, dst_branch, msg): src_ref = log_ref(src_branch) dst_ref = log_ref(dst_branch) if repo.refs.exists(src_ref): repo.refs.set(dst_ref, repo.refs.get(src_ref), msg) def default_repo(): return libstack.Repository.default() def reset_stack(trans, iw, state): """Reset the stack to a given previous state.""" for pn in trans.all_patches: trans.patches[pn] = None for pn in state.all_patches: trans.patches[pn] = state.patches[pn] trans.applied = state.applied trans.unapplied = state.unapplied trans.hidden = state.hidden trans.base = state.base trans.head = state.head def reset_stack_partially(trans, iw, state, only_patches): """Reset the stack to a given previous state -- but only the given patches, not anything else. @param only_patches: Touch only these patches @type only_patches: iterable""" only_patches = set(only_patches) patches_to_reset = set(state.all_patches) & only_patches existing_patches = set(trans.all_patches) original_applied_order = list(trans.applied) to_delete = (existing_patches - patches_to_reset) & only_patches # In one go, do all the popping we have to in order to pop the # patches we're going to delete or modify. def mod(pn): if not pn in only_patches: return False if pn in to_delete: return True if trans.patches[pn] != state.patches.get(pn, None): return True return False trans.pop_patches(mod) # Delete and modify/create patches. We've previously popped all # patches that we touch in this step. trans.delete_patches(lambda pn: pn in to_delete) for pn in patches_to_reset: if pn in existing_patches: if trans.patches[pn] == state.patches[pn]: continue else: out.info('Resetting %s' % pn) else: if pn in state.hidden: trans.hidden.append(pn) else: trans.unapplied.append(pn) out.info('Resurrecting %s' % pn) trans.patches[pn] = state.patches[pn] # Push all the patches that we've popped, if they still # exist. pushable = set(trans.unapplied + trans.hidden) for pn in original_applied_order: if pn in pushable: trans.push_patch(pn, iw) def undo_state(stack, undo_steps): """Find the log entry C{undo_steps} steps in the past. (Successive undo operations are supposed to "add up", so if we find other undo operations along the way we have to add those undo steps to C{undo_steps}.) If C{undo_steps} is negative, redo instead of undo. @return: The log entry that is the destination of the undo operation @rtype: L{LogEntry}""" ref = log_ref(stack.name) try: commit = stack.repository.refs.get(ref) except KeyError: raise LogException('Log is empty') log = get_log_entry(stack.repository, ref, commit) while undo_steps != 0: msg = log.message.strip() um = re.match(r'^undo\s+(\d+)$', msg) if undo_steps > 0: if um: undo_steps += int(um.group(1)) else: undo_steps -= 1 else: rm = re.match(r'^redo\s+(\d+)$', msg) if um: undo_steps += 1 elif rm: undo_steps -= int(rm.group(1)) else: raise LogException('No more redo information available') if not log.prev: raise LogException('Not enough undo information available') log = log.prev return log def log_external_mods(stack): ref = log_ref(stack.name) try: log_commit = stack.repository.refs.get(ref) except KeyError: # No log exists yet. log_entry(stack, 'start of log') return try: log = get_log_entry(stack.repository, ref, log_commit) except LogException: # Something's wrong with the log, so don't bother. return if log.head == stack.head: # No external modifications. return log_entry(stack, '\n'.join([ 'external modifications', '', 'Modifications by tools other than StGit (e.g. git).'])) def compat_log_external_mods(): try: repo = default_repo() except git.RepositoryException: # No repository, so we couldn't log even if we wanted to. return try: stack = repo.get_stack(repo.current_branch_name) except exception.StgException: # Stack doesn't exist, so we can't log. return log_external_mods(stack) stgit-0.17.1/stgit/lib/transaction.py0000644002002200200220000004174711743517572017720 0ustar cmarinascmarinas"""The L{StackTransaction} class makes it possible to make complex updates to an StGit stack in a safe and convenient way.""" import atexit import itertools as it from stgit import exception, utils from stgit.utils import any, all from stgit.out import * from stgit.lib import git, log from stgit.config import config class TransactionException(exception.StgException): """Exception raised when something goes wrong with a L{StackTransaction}.""" class TransactionHalted(TransactionException): """Exception raised when a L{StackTransaction} stops part-way through. Used to make a non-local jump from the transaction setup to the part of the transaction code where the transaction is run.""" def _print_current_patch(old_applied, new_applied): def now_at(pn): out.info('Now at patch "%s"' % pn) if not old_applied and not new_applied: pass elif not old_applied: now_at(new_applied[-1]) elif not new_applied: out.info('No patch applied') elif old_applied[-1] == new_applied[-1]: pass else: now_at(new_applied[-1]) class _TransPatchMap(dict): """Maps patch names to Commit objects.""" def __init__(self, stack): dict.__init__(self) self.__stack = stack def __getitem__(self, pn): try: return dict.__getitem__(self, pn) except KeyError: return self.__stack.patches.get(pn).commit class StackTransaction(object): """A stack transaction, used for making complex updates to an StGit stack in one single operation that will either succeed or fail cleanly. The basic theory of operation is the following: 1. Create a transaction object. 2. Inside a:: try ... except TransactionHalted: pass block, update the transaction with e.g. methods like L{pop_patches} and L{push_patch}. This may create new git objects such as commits, but will not write any refs; this means that in case of a fatal error we can just walk away, no clean-up required. (Some operations may need to touch your index and working tree, though. But they are cleaned up when needed.) 3. After the C{try} block -- wheher or not the setup ran to completion or halted part-way through by raising a L{TransactionHalted} exception -- call the transaction's L{run} method. This will either succeed in writing the updated state to your refs and index+worktree, or fail without having done anything.""" def __init__(self, stack, msg, discard_changes = False, allow_conflicts = False, allow_bad_head = False, check_clean_iw = None): """Create a new L{StackTransaction}. @param discard_changes: Discard any changes in index+worktree @type discard_changes: bool @param allow_conflicts: Whether to allow pre-existing conflicts @type allow_conflicts: bool or function of L{StackTransaction}""" self.__stack = stack self.__msg = msg self.__patches = _TransPatchMap(stack) self.__applied = list(self.__stack.patchorder.applied) self.__unapplied = list(self.__stack.patchorder.unapplied) self.__hidden = list(self.__stack.patchorder.hidden) self.__error = None self.__current_tree = self.__stack.head.data.tree self.__base = self.__stack.base self.__discard_changes = discard_changes self.__bad_head = None self.__conflicts = None if isinstance(allow_conflicts, bool): self.__allow_conflicts = lambda trans: allow_conflicts else: self.__allow_conflicts = allow_conflicts self.__temp_index = self.temp_index_tree = None if not allow_bad_head: self.__assert_head_top_equal() if check_clean_iw: self.__assert_index_worktree_clean(check_clean_iw) stack = property(lambda self: self.__stack) patches = property(lambda self: self.__patches) def __set_applied(self, val): self.__applied = list(val) applied = property(lambda self: self.__applied, __set_applied) def __set_unapplied(self, val): self.__unapplied = list(val) unapplied = property(lambda self: self.__unapplied, __set_unapplied) def __set_hidden(self, val): self.__hidden = list(val) hidden = property(lambda self: self.__hidden, __set_hidden) all_patches = property(lambda self: (self.__applied + self.__unapplied + self.__hidden)) def __set_base(self, val): assert (not self.__applied or self.patches[self.applied[0]].data.parent == val) self.__base = val base = property(lambda self: self.__base, __set_base) @property def temp_index(self): if not self.__temp_index: self.__temp_index = self.__stack.repository.temp_index() atexit.register(self.__temp_index.delete) return self.__temp_index @property def top(self): if self.__applied: return self.__patches[self.__applied[-1]] else: return self.__base def __get_head(self): if self.__bad_head: return self.__bad_head else: return self.top def __set_head(self, val): self.__bad_head = val head = property(__get_head, __set_head) def __assert_head_top_equal(self): if not self.__stack.head_top_equal(): out.error( 'HEAD and top are not the same.', 'This can happen if you modify a branch with git.', '"stg repair --help" explains more about what to do next.') self.__abort() def __assert_index_worktree_clean(self, iw): if not iw.worktree_clean(): self.__halt('Worktree not clean. Use "refresh" or "reset --hard"') if not iw.index.is_clean(self.stack.head): self.__halt('Index not clean. Use "refresh" or "reset --hard"') def __checkout(self, tree, iw, allow_bad_head): if not allow_bad_head: self.__assert_head_top_equal() if self.__current_tree == tree and not self.__discard_changes: # No tree change, but we still want to make sure that # there are no unresolved conflicts. Conflicts # conceptually "belong" to the topmost patch, and just # carrying them along to another patch is confusing. if (self.__allow_conflicts(self) or iw == None or not iw.index.conflicts()): return out.error('Need to resolve conflicts first') self.__abort() assert iw != None if self.__discard_changes: iw.checkout_hard(tree) else: iw.checkout(self.__current_tree, tree) self.__current_tree = tree @staticmethod def __abort(): raise TransactionException( 'Command aborted (all changes rolled back)') def __check_consistency(self): remaining = set(self.all_patches) for pn, commit in self.__patches.iteritems(): if commit == None: assert self.__stack.patches.exists(pn) else: assert pn in remaining def abort(self, iw = None): # The only state we need to restore is index+worktree. if iw: self.__checkout(self.__stack.head.data.tree, iw, allow_bad_head = True) def run(self, iw = None, set_head = True, allow_bad_head = False, print_current_patch = True): """Execute the transaction. Will either succeed, or fail (with an exception) and do nothing.""" self.__check_consistency() log.log_external_mods(self.__stack) new_head = self.head # Set branch head. if set_head: if iw: try: self.__checkout(new_head.data.tree, iw, allow_bad_head) except git.CheckoutException: # We have to abort the transaction. self.abort(iw) self.__abort() self.__stack.set_head(new_head, self.__msg) if self.__error: if self.__conflicts: out.error(*([self.__error] + self.__conflicts)) else: out.error(self.__error) # Write patches. def write(msg): for pn, commit in self.__patches.iteritems(): if self.__stack.patches.exists(pn): p = self.__stack.patches.get(pn) if commit == None: p.delete() else: p.set_commit(commit, msg) else: self.__stack.patches.new(pn, commit, msg) self.__stack.patchorder.applied = self.__applied self.__stack.patchorder.unapplied = self.__unapplied self.__stack.patchorder.hidden = self.__hidden log.log_entry(self.__stack, msg) old_applied = self.__stack.patchorder.applied if not self.__conflicts: write(self.__msg) else: write(self.__msg + ' (CONFLICT)') if print_current_patch: _print_current_patch(old_applied, self.__applied) if self.__error: return utils.STGIT_CONFLICT else: return utils.STGIT_SUCCESS def __halt(self, msg): self.__error = msg raise TransactionHalted(msg) @staticmethod def __print_popped(popped): if len(popped) == 0: pass elif len(popped) == 1: out.info('Popped %s' % popped[0]) else: out.info('Popped %s -- %s' % (popped[-1], popped[0])) def pop_patches(self, p): """Pop all patches pn for which p(pn) is true. Return the list of other patches that had to be popped to accomplish this. Always succeeds.""" popped = [] for i in xrange(len(self.applied)): if p(self.applied[i]): popped = self.applied[i:] del self.applied[i:] break popped1 = [pn for pn in popped if not p(pn)] popped2 = [pn for pn in popped if p(pn)] self.unapplied = popped1 + popped2 + self.unapplied self.__print_popped(popped) return popped1 def delete_patches(self, p, quiet = False): """Delete all patches pn for which p(pn) is true. Return the list of other patches that had to be popped to accomplish this. Always succeeds.""" popped = [] all_patches = self.applied + self.unapplied + self.hidden for i in xrange(len(self.applied)): if p(self.applied[i]): popped = self.applied[i:] del self.applied[i:] break popped = [pn for pn in popped if not p(pn)] self.unapplied = popped + [pn for pn in self.unapplied if not p(pn)] self.hidden = [pn for pn in self.hidden if not p(pn)] self.__print_popped(popped) for pn in all_patches: if p(pn): s = ['', ' (empty)'][self.patches[pn].data.is_nochange()] self.patches[pn] = None if not quiet: out.info('Deleted %s%s' % (pn, s)) return popped def push_patch(self, pn, iw = None, allow_interactive = False, already_merged = False): """Attempt to push the named patch. If this results in conflicts, halts the transaction. If index+worktree are given, spill any conflicts to them.""" out.start('Pushing patch "%s"' % pn) orig_cd = self.patches[pn].data cd = orig_cd.set_committer(None) oldparent = cd.parent cd = cd.set_parent(self.top) if already_merged: # the resulting patch is empty tree = cd.parent.data.tree else: base = oldparent.data.tree ours = cd.parent.data.tree theirs = cd.tree tree, self.temp_index_tree = self.temp_index.merge( base, ours, theirs, self.temp_index_tree) s = '' merge_conflict = False if not tree: if iw == None: self.__halt('%s does not apply cleanly' % pn) try: self.__checkout(ours, iw, allow_bad_head = False) except git.CheckoutException: self.__halt('Index/worktree dirty') try: interactive = (allow_interactive and config.get('stgit.autoimerge') == 'yes') iw.merge(base, ours, theirs, interactive = interactive) tree = iw.index.write_tree() self.__current_tree = tree s = 'modified' except git.MergeConflictException, e: tree = ours merge_conflict = True self.__conflicts = e.conflicts s = 'conflict' except git.MergeException, e: self.__halt(str(e)) cd = cd.set_tree(tree) if any(getattr(cd, a) != getattr(orig_cd, a) for a in ['parent', 'tree', 'author', 'message']): comm = self.__stack.repository.commit(cd) if merge_conflict: # When we produce a conflict, we'll run the update() # function defined below _after_ having done the # checkout in run(). To make sure that we check out # the real stack top (as it will look after update() # has been run), set it hard here. self.head = comm else: comm = None s = 'unmodified' if already_merged: s = 'merged' elif not merge_conflict and cd.is_nochange(): s = 'empty' out.done(s) if merge_conflict: # We've just caused conflicts, so we must allow them in # the final checkout. self.__allow_conflicts = lambda trans: True # Update the stack state if comm: self.patches[pn] = comm if pn in self.hidden: x = self.hidden else: x = self.unapplied del x[x.index(pn)] self.applied.append(pn) if merge_conflict: self.__halt("%d merge conflict(s)" % len(self.__conflicts)) def push_tree(self, pn): """Push the named patch without updating its tree.""" orig_cd = self.patches[pn].data cd = orig_cd.set_committer(None).set_parent(self.top) s = '' if any(getattr(cd, a) != getattr(orig_cd, a) for a in ['parent', 'tree', 'author', 'message']): self.patches[pn] = self.__stack.repository.commit(cd) else: s = ' (unmodified)' if cd.is_nochange(): s = ' (empty)' out.info('Pushed %s%s' % (pn, s)) if pn in self.hidden: x = self.hidden else: x = self.unapplied del x[x.index(pn)] self.applied.append(pn) def reorder_patches(self, applied, unapplied, hidden = None, iw = None, allow_interactive = False): """Push and pop patches to attain the given ordering.""" if hidden is None: hidden = self.hidden common = len(list(it.takewhile(lambda (a, b): a == b, zip(self.applied, applied)))) to_pop = set(self.applied[common:]) self.pop_patches(lambda pn: pn in to_pop) for pn in applied[common:]: self.push_patch(pn, iw, allow_interactive = allow_interactive) # We only get here if all the pushes succeeded. assert self.applied == applied assert set(self.unapplied + self.hidden) == set(unapplied + hidden) self.unapplied = unapplied self.hidden = hidden def check_merged(self, patches, tree = None, quiet = False): """Return a subset of patches already merged.""" if not quiet: out.start('Checking for patches merged upstream') merged = [] if tree: self.temp_index.read_tree(tree) self.temp_index_tree = tree elif self.temp_index_tree != self.stack.head.data.tree: self.temp_index.read_tree(self.stack.head.data.tree) self.temp_index_tree = self.stack.head.data.tree for pn in reversed(patches): # check whether patch changes can be reversed in the current index cd = self.patches[pn].data if cd.is_nochange(): continue try: self.temp_index.apply_treediff(cd.tree, cd.parent.data.tree, quiet = True) merged.append(pn) # The self.temp_index was modified by apply_treediff() so # force read_tree() the next time merge() is used. self.temp_index_tree = None except git.MergeException: pass if not quiet: out.done('%d found' % len(merged)) return merged stgit-0.17.1/stgit/lib/__init__.py0000644002002200200220000000131611271344641017105 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2007, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ stgit-0.17.1/stgit/templates.py0000644002002200200220000000247310454666642016615 0ustar cmarinascmarinas"""Template files look-up """ __copyright__ = """ Copyright (C) 2006, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from stgit import basedir def get_template(tfile): """Return the string in the template file passed as argument or None if the file wasn't found. """ tmpl_list = [ os.path.join(basedir.get(), tfile), os.path.join(os.path.expanduser('~'), '.stgit', 'templates', tfile), os.path.join(sys.prefix, 'share', 'stgit', 'templates', tfile) ] tmpl = None for t in tmpl_list: if os.path.isfile(t): tmpl = file(t).read() break return tmpl stgit-0.17.1/stgit/out.py0000644002002200200220000001071611271344641015413 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2007, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, textwrap class MessagePrinter(object): def __init__(self, file = None): class Output(object): def __init__(self, write, flush): self.write = write self.flush = flush self.at_start_of_line = True self.level = 0 def new_line(self): """Ensure that we're at the beginning of a line.""" if not self.at_start_of_line: self.write('\n') self.at_start_of_line = True def single_line(self, msg, print_newline = True, need_newline = True): """Write a single line. Newline before and after are separately configurable.""" if need_newline: self.new_line() if self.at_start_of_line: self.write(' '*self.level) self.write(msg) if print_newline: self.write('\n') self.at_start_of_line = True else: self.flush() self.at_start_of_line = False def tagged_lines(self, tag, lines): tag += ': ' width = 79 - 2*self.level - len(tag) lines = [wl for line in lines for wl in textwrap.wrap(line, width, break_long_words = False)] for line in lines: self.single_line(tag + line) tag = ' '*len(tag) def write_line(self, line): """Write one line of text on a lines of its own, not indented.""" self.new_line() self.write('%s\n' % line) self.at_start_of_line = True def write_raw(self, string): """Write an arbitrary string, possibly containing newlines.""" self.new_line() self.write(string) self.at_start_of_line = string.endswith('\n') if file: self.__stdout = self.__stderr = Output(file.write, file.flush) else: self.__stdout = Output(sys.stdout.write, sys.stdout.flush) self.__stderr = Output(sys.stderr.write, sys.stderr.flush) if file or sys.stdout.isatty(): self.__out = self.__stdout else: self.__out = Output(lambda msg: None, lambda: None) self.__err = self.__stderr def stdout(self, line): """Write a line to stdout.""" self.__stdout.write_line(line) def stdout_raw(self, string): """Write a string possibly containing newlines to stdout.""" self.__stdout.write_raw(string) def err_raw(self, string): """Write a string possibly containing newlines to the error output.""" self.__err.write_raw(string) def info(self, *msgs): for msg in msgs: self.__out.single_line(msg) def note(self, *msgs, **kw): self.__out.tagged_lines(kw.get('title', 'Notice'), msgs) def warn(self, *msgs, **kw): self.__err.tagged_lines(kw.get('title', 'Warning'), msgs) def error(self, *msgs, **kw): self.__err.tagged_lines(kw.get('title', 'Error'), msgs) def start(self, msg): """Start a long-running operation.""" self.__out.single_line('%s ... ' % msg, print_newline = False) self.__out.level += 1 def done(self, extramsg = None): """Finish long-running operation.""" self.__out.level -= 1 if extramsg: msg = 'done (%s)' % extramsg else: msg = 'done' self.__out.single_line(msg, need_newline = False) out = MessagePrinter() stgit-0.17.1/stgit/main.py0000644002002200200220000001541212221306627015524 0ustar cmarinascmarinas"""Basic quilt-like functionality """ __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os, traceback import stgit.commands from stgit.out import * from stgit import argparse, run, utils from stgit.config import config class CommandAlias(object): def __init__(self, name, command): self.__command = command self.__name__ = name self.usage = [''] self.help = 'Alias for "%s ".' % self.__command self.options = [] def func(self, args): cmd = self.__command.split() + args p = run.Run(*cmd) p.discard_exitcode().run() return p.exitcode def is_cmd_alias(cmd): return isinstance(cmd, CommandAlias) def append_alias_commands(cmd_list): for (name, command) in config.getstartswith('stgit.alias.'): name = utils.strip_prefix('stgit.alias.', name) cmd_list[name] = (CommandAlias(name, command), 'Alias commands', command) # # The commands map # class Commands(dict): """Commands class. It performs on-demand module loading """ def canonical_cmd(self, key): """Return the canonical name for a possibly-shortenned command name. """ candidates = [cmd for cmd in self.keys() if cmd.startswith(key)] if not candidates: out.error('Unknown command: %s' % key, 'Try "%s help" for a list of supported commands' % prog) sys.exit(utils.STGIT_GENERAL_ERROR) elif len(candidates) > 1: out.error('Ambiguous command: %s' % key, 'Candidates are: %s' % ', '.join(candidates)) sys.exit(utils.STGIT_GENERAL_ERROR) return candidates[0] def __getitem__(self, key): cmd_mod = self.get(key) or self.get(self.canonical_cmd(key)) if is_cmd_alias(cmd_mod): return cmd_mod else: return stgit.commands.get_command(cmd_mod) cmd_list = stgit.commands.get_commands() append_alias_commands(cmd_list) commands = Commands((cmd, mod) for cmd, (mod, kind, help) in cmd_list.iteritems()) def print_help(): print 'usage: %s [options]' % os.path.basename(sys.argv[0]) print print 'Generic commands:' print ' help print the detailed command usage' print ' version display version information' print ' copyright display copyright information' print stgit.commands.pretty_command_list(cmd_list, sys.stdout) # # The main function (command dispatcher) # def _main(): """The main function """ global prog prog = os.path.basename(sys.argv[0]) if len(sys.argv) < 2: print >> sys.stderr, 'usage: %s ' % prog print >> sys.stderr, \ ' Try "%s --help" for a list of supported commands' % prog sys.exit(utils.STGIT_GENERAL_ERROR) cmd = sys.argv[1] if cmd in ['-h', '--help']: if len(sys.argv) >= 3: cmd = commands.canonical_cmd(sys.argv[2]) sys.argv[2] = '--help' else: print_help() sys.exit(utils.STGIT_SUCCESS) if cmd == 'help': if len(sys.argv) == 3 and not sys.argv[2] in ['-h', '--help']: cmd = commands.canonical_cmd(sys.argv[2]) if not cmd in commands: out.error('%s help: "%s" command unknown' % (prog, cmd)) sys.exit(utils.STGIT_GENERAL_ERROR) sys.argv[0] += ' %s' % cmd command = commands[cmd] parser = argparse.make_option_parser(command) if is_cmd_alias(command): parser.remove_option('-h') from pydoc import pager pager(parser.format_help()) else: print_help() sys.exit(utils.STGIT_SUCCESS) if cmd in ['-v', '--version', 'version']: from stgit.version import version print 'Stacked GIT %s' % version os.system('git --version') print 'Python version %s' % sys.version sys.exit(utils.STGIT_SUCCESS) if cmd in ['copyright']: print __copyright__ sys.exit(utils.STGIT_SUCCESS) # re-build the command line arguments cmd = commands.canonical_cmd(cmd) sys.argv[0] += ' %s' % cmd del(sys.argv[1]) command = commands[cmd] if is_cmd_alias(command): sys.exit(command.func(sys.argv[1:])) parser = argparse.make_option_parser(command) directory = command.directory # These modules are only used from this point onwards and do not # need to be imported earlier from stgit.exception import StgException from stgit.config import config_setup from ConfigParser import ParsingError, NoSectionError from stgit.stack import Series try: debug_level = int(os.environ.get('STGIT_DEBUG_LEVEL', 0)) except ValueError: out.error('Invalid STGIT_DEBUG_LEVEL environment variable') sys.exit(utils.STGIT_GENERAL_ERROR) try: (options, args) = parser.parse_args() directory.setup() config_setup() # Some commands don't (always) need an initialized series. if directory.needs_current_series: if hasattr(options, 'branch') and options.branch: command.crt_series = Series(options.branch) else: command.crt_series = Series() ret = command.func(parser, options, args) except (StgException, IOError, ParsingError, NoSectionError), err: directory.write_log(cmd) if debug_level > 0: traceback.print_exc(file=sys.stderr) out.error(str(err), title = '%s %s' % (prog, cmd)) sys.exit(utils.STGIT_COMMAND_ERROR) except SystemExit: # Triggered by the option parser when it finds bad commandline # parameters. sys.exit(utils.STGIT_COMMAND_ERROR) except KeyboardInterrupt: sys.exit(utils.STGIT_GENERAL_ERROR) except: out.error('Unhandled exception:') traceback.print_exc(file=sys.stderr) sys.exit(utils.STGIT_BUG_ERROR) directory.write_log(cmd) sys.exit(ret or utils.STGIT_SUCCESS) def main(): try: _main() finally: run.finish_logging() stgit-0.17.1/stgit/version.py0000644002002200200220000000310511271344641016263 0ustar cmarinascmarinasfrom stgit.exception import StgException from stgit import run, utils import os, os.path, re, sys class VersionUnavailable(StgException): pass def git_describe_version(): path = sys.path[0] try: v = run.Run('git', 'describe', '--tags', '--abbrev=4' ).cwd(path).output_one_line() except run.RunException, e: raise VersionUnavailable(str(e)) if not re.match(r'^v[0-9]', v): raise VersionUnavailable('%s: bad version' % v) try: dirty = run.Run('git', 'diff-index', '--name-only', 'HEAD' ).cwd(path).raw_output() except run.RunException, e: raise VersionUnavailable(str(e)) if dirty: v += '-dirty' return utils.strip_prefix('v', v) def builtin_version(): try: import builtin_version as bv except ImportError: raise VersionUnavailable() else: return bv.version def _builtin_version_file(ext = 'py'): return os.path.join(sys.path[0], 'stgit', 'builtin_version.%s' % ext) def write_builtin_version(): try: v = git_describe_version() except VersionUnavailable: return f = file(_builtin_version_file(), 'w') f.write('# This file was generated automatically. Do not edit by hand.\n' 'version = %r\n' % v) def get_version(): for v in [builtin_version, git_describe_version]: try: return v() except VersionUnavailable: pass return 'unknown-version' version = get_version() # minimum version requirements git_min_ver = '1.5.2' python_min_ver = '2.4' stgit-0.17.1/stgit/completion.py0000644002002200200220000001306111271344641016751 0ustar cmarinascmarinasimport textwrap import stgit.commands from stgit import argparse import itertools 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( itertools.chain( ('--help',), (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 "help version copyright $_stg_commands" --' ' "${COMP_WORDS[COMP_CWORD]}"))'), 'return'], 'fi', '', '# Complete arguments to subcommands.', 'case "$command" in', [ 'help) ', [ ('COMPREPLY=($(compgen -W "$_stg_commands" --' ' "${COMP_WORDS[COMP_CWORD]}"))'), 'return ;;'], 'version) return ;;', 'copyright) return ;;'], [ '%s) _stg_%s ;;' % (cmd, cmd) for cmd in sorted(commands.iterkeys())], 'esac') def install(): return ['complete -o bashdefault -o default -F _stg stg 2>/dev/null \\', [ '|| 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, '')) stgit-0.17.1/stgit/exception.py0000644002002200200220000000012710732722750016577 0ustar cmarinascmarinasclass StgException(Exception): """Base class for all StGit exceptions.""" pass stgit-0.17.1/stgit/config.py0000644002002200200220000001257611743516173016064 0ustar cmarinascmarinas"""Handles the Stacked GIT configuration files """ __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import os, re from stgit import basedir from stgit.exception import * from stgit.run import * class GitConfigException(StgException): pass class GitConfig: __defaults={ 'stgit.smtpserver': ['localhost:25'], 'stgit.smtpdelay': ['5'], 'stgit.pullcmd': ['git pull'], 'stgit.fetchcmd': ['git fetch'], 'stgit.pull-policy': ['pull'], 'stgit.autoimerge': ['no'], 'stgit.keepoptimized': ['no'], 'stgit.shortnr': ['5'], 'stgit.pager': ['less'], 'stgit.alias.add': ['git add'], 'stgit.alias.rm': ['git rm'], 'stgit.alias.mv': ['git mv'], 'stgit.alias.resolved': ['git add'], 'stgit.alias.status': ['git status -s'] } __cache = None def load(self): """Load the whole configuration in __cache unless it has been done already.""" if self.__cache is not None: return self.__cache = self.__defaults lines = Run('git', 'config', '--null', '--list' ).discard_exitcode().raw_output() for line in filter(None, lines.split('\0')): key, value = line.split('\n', 1) self.__cache.setdefault(key, []).append(value) def get(self, name): self.load() try: return self.__cache[name][-1] except KeyError: return None def getall(self, name): self.load() try: return self.__cache[name] except KeyError: return [] def getint(self, name): value = self.get(name) if value == None: return None elif value.isdigit(): return int(value) else: raise GitConfigException, 'Value for "%s" is not an integer: "%s"' % (name, value) def getstartswith(self, name): self.load() return ((n, v[-1]) for (n, v) in self.__cache.iteritems() if n.startswith(name)) def rename_section(self, from_name, to_name): """Rename a section in the config file. Silently do nothing if the section doesn't exist.""" Run('git', 'config', '--rename-section', from_name, to_name ).returns([0, 1, 128]).run() self.__cache.clear() def remove_section(self, name): """Remove a section in the config file. Silently do nothing if the section doesn't exist.""" Run('git', 'config', '--remove-section', name ).returns([0, 1, 128]).discard_stderr().discard_output() self.__cache.clear() def set(self, name, value): Run('git', 'config', name, value).run() self.__cache[name] = value def unset(self, name): Run('git', 'config', '--unset', name).run() self.__cache[name] = [None] def sections_matching(self, regexp): """Takes a regexp with a single group, matches it against all config variables, and returns a list whose members are the group contents, for all variable names matching the regexp. """ result = [] for line in Run('git', 'config', '--get-regexp', '"^%s$"' % regexp ).returns([0, 1]).output_lines(): m = re.match('^%s ' % regexp, line) if m: result.append(m.group(1)) return result def get_colorbool(self, name, stdout_is_tty): """Invoke 'git config --get-colorbool' and return the result.""" return Run('git', 'config', '--get-colorbool', name, stdout_is_tty).output_one_line() config=GitConfig() def config_setup(): global config os.environ.setdefault('PAGER', config.get('stgit.pager')) os.environ.setdefault('LESS', '-FRSX') # FIXME: handle EDITOR the same way ? class ConfigOption: """Delayed cached reading of a configuration option. """ def __init__(self, section, option): self.__section = section self.__option = option self.__value = None def __str__(self): if not self.__value: self.__value = config.get(self.__section + '.' + self.__option) return self.__value # cached extensions __extensions = None def file_extensions(): """Returns a dictionary with the conflict file extensions """ global __extensions if not __extensions: cfg_ext = config.get('stgit.extensions').split() if len(cfg_ext) != 3: raise CmdException, '"extensions" configuration error' __extensions = { 'ancestor': cfg_ext[0], 'current': cfg_ext[1], 'patched': cfg_ext[2] } return __extensions stgit-0.17.1/stgit/commands/0000755002002200200220000000000012222320003016005 5ustar cmarinascmarinasstgit-0.17.1/stgit/commands/files.py0000644002002200200220000000477611743516173017525 0ustar cmarinascmarinas __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import argparse, stack, git from stgit.lib import git as gitlib help = 'Show the files modified by a patch (or the current patch)' kind = 'patch' usage = ['[options] [--] [[:]]'] description = """ List the files modified by the given patch (defaulting to the current one). Passing the '--stat' option shows the diff statistics for the 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'), opt('--bare', action = 'store_true', short = 'Bare file names (useful for scripting)'), ] + argparse.diff_opts_option() directory = DirectoryHasRepository(log = False) def func(parser, options, args): """Show the files modified by a patch (or the current patch) """ if len(args) == 0: patch = 'HEAD' elif len(args) == 1: patch = args[0] else: parser.error('incorrect number of arguments') rev1 = git_id(crt_series, '%s^' % patch) rev2 = git_id(crt_series, '%s' % patch) if options.stat: output = gitlib.diffstat(git.diff(rev1 = rev1, rev2 = rev2, diff_flags = options.diff_flags)) elif options.bare: output = git.barefiles(rev1, rev2) else: output = git.files(rev1, rev2, diff_flags = options.diff_flags) if output: if not output.endswith('\n'): output += '\n' out.stdout_raw(output) stgit-0.17.1/stgit/commands/imprt.py0000644002002200200220000002631711743516173017551 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os, re, email, tarfile from mailbox import UnixMailbox from StringIO import StringIO from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import argparse, stack, git name = 'import' help = 'Import a GNU diff file as a new patch' kind = 'patch' usage = ['[options] [--] [|]'] description = """ Create a new patch and apply the given GNU diff file (or the standard input). By default, the file name is used as the patch name but this can be overridden with the '--name' option. The patch can either be a normal file with the description at the top or it can have standard mail format, the Subject, From and Date headers being used for generating the patch information. The command can also read series and mbox files. If a patch does not apply cleanly, the failed diff is written to the .stgit-failed.patch file and an empty StGIT patch is added to the 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'), opt('-M', '--mbox', action = 'store_true', short = 'Import a series of patches from an mbox file'), opt('-s', '--series', action = 'store_true', short = 'Import a series of patches', long = """ Import a series of patches from a series file or a tar archive."""), opt('-u', '--url', action = 'store_true', short = 'Import a patch from a URL'), opt('-n', '--name', short = 'Use NAME as the patch name'), opt('-p', '--strip', type = 'int', metavar = 'N', short = 'Remove N leading slashes from diff paths (default 1)'), opt('-t', '--stripname', action = 'store_true', short = 'Strip numbering and extension from patch name'), opt('-i', '--ignore', action = 'store_true', short = 'Ignore the applied patches in the series'), opt('--replace', action = 'store_true', short = 'Replace the unapplied patches in the series'), opt('-b', '--base', args = [argparse.commit], short = 'Use BASE instead of HEAD for file importing'), opt('--reject', action = 'store_true', short = 'Leave the rejected hunks in corresponding *.rej files'), opt('-e', '--edit', action = 'store_true', short = 'Invoke an editor for the patch description'), opt('-d', '--showdiff', action = 'store_true', short = 'Show the patch content in the editor buffer'), opt('-a', '--author', metavar = '"NAME "', short = 'Use "NAME " as the author details'), opt('--authname', short = 'Use AUTHNAME as the author name'), opt('--authemail', short = 'Use AUTHEMAIL as the author e-mail'), opt('--authdate', short = 'Use AUTHDATE as the author date'), ] + argparse.sign_options() directory = DirectoryHasRepository(log = True) def __strip_patch_name(name): stripped = re.sub('^[0-9]+-(.*)$', '\g<1>', name) stripped = re.sub('^(.*)\.(diff|patch)$', '\g<1>', stripped) return stripped def __replace_slashes_with_dashes(name): stripped = name.replace('/', '-') return stripped def __create_patch(filename, message, author_name, author_email, author_date, diff, options): """Create a new patch on the stack """ if options.name: patch = options.name elif filename: patch = os.path.basename(filename) else: patch = '' if options.stripname: patch = __strip_patch_name(patch) if not patch: if options.ignore or options.replace: unacceptable_name = lambda name: False else: unacceptable_name = crt_series.patch_exists patch = make_patch_name(message, unacceptable_name) else: # fix possible invalid characters in the patch name patch = re.sub('[^\w.]+', '-', patch).strip('-') if options.ignore and patch in crt_series.get_applied(): out.info('Ignoring already applied patch "%s"' % patch) return if options.replace and patch in crt_series.get_unapplied(): crt_series.delete_patch(patch, keep_log = True) # refresh_patch() will invoke the editor in this case, with correct # patch content if not message: can_edit = False if options.author: options.authname, options.authemail = name_email(options.author) # override the automatically parsed settings if options.authname: author_name = options.authname if options.authemail: author_email = options.authemail if options.authdate: author_date = options.authdate sign_str = options.sign_str if not options.sign_str: sign_str = config.get('stgit.autosign') crt_series.new_patch(patch, message = message, can_edit = False, author_name = author_name, author_email = author_email, author_date = author_date, sign_str = sign_str) if not diff: out.warn('No diff found, creating empty patch') else: out.start('Importing patch "%s"' % patch) if options.base: base = git_id(crt_series, options.base) else: base = None try: git.apply_patch(diff = diff, base = base, reject = options.reject, strip = options.strip) except git.GitException: if not options.reject: crt_series.delete_patch(patch) raise crt_series.refresh_patch(edit = options.edit, show_patch = options.showdiff, author_date = author_date, backup = False) out.done() def __mkpatchname(name, suffix): if name.lower().endswith(suffix.lower()): return name[:-len(suffix)] return name def __get_handle_and_name(filename): """Return a file object and a patch name derived from filename """ # see if it's a gzip'ed or bzip2'ed patch import bz2, gzip for copen, ext in [(gzip.open, '.gz'), (bz2.BZ2File, '.bz2')]: try: f = copen(filename) f.read(1) f.seek(0) return (f, __mkpatchname(filename, ext)) except IOError, e: pass # plain old file... return (open(filename), filename) def __import_file(filename, options, patch = None): """Import a patch from a file or standard input """ pname = None if filename: (f, pname) = __get_handle_and_name(filename) else: f = sys.stdin if patch: pname = patch elif not pname: pname = filename if options.mail: try: msg = email.message_from_file(f) except Exception, ex: raise CmdException, 'error parsing the e-mail file: %s' % str(ex) message, author_name, author_email, author_date, diff = \ parse_mail(msg) else: message, author_name, author_email, author_date, diff = \ parse_patch(f.read(), contains_diff = True) if filename: f.close() __create_patch(pname, message, author_name, author_email, author_date, diff, options) def __import_series(filename, options): """Import a series of patches """ applied = crt_series.get_applied() if filename: if tarfile.is_tarfile(filename): __import_tarfile(filename, options) return f = file(filename) patchdir = os.path.dirname(filename) else: f = sys.stdin patchdir = '' for line in f: patch = re.sub('#.*$', '', line).strip() if not patch: continue patchfile = os.path.join(patchdir, patch) patch = __replace_slashes_with_dashes(patch); __import_file(patchfile, options, patch) if filename: f.close() def __import_mbox(filename, options): """Import a series from an mbox file """ if filename: f = file(filename, 'rb') else: f = StringIO(sys.stdin.read()) try: mbox = UnixMailbox(f, email.message_from_file) except Exception, ex: raise CmdException, 'error parsing the mbox file: %s' % str(ex) for msg in mbox: message, author_name, author_email, author_date, diff = \ parse_mail(msg) __create_patch(None, message, author_name, author_email, author_date, diff, options) f.close() def __import_url(url, options): """Import a patch from a URL """ import urllib import tempfile if not url: raise CmdException('URL argument required') patch = os.path.basename(urllib.unquote(url)) filename = os.path.join(tempfile.gettempdir(), patch) urllib.urlretrieve(url, filename) __import_file(filename, options) def __import_tarfile(tar, options): """Import patch series from a tar archive """ import tempfile import shutil if not tarfile.is_tarfile(tar): raise CmdException, "%s is not a tarfile!" % tar t = tarfile.open(tar, 'r') names = t.getnames() # verify paths in the tarfile are safe for n in names: if n.startswith('/'): raise CmdException, "Absolute path found in %s" % tar if n.find("..") > -1: raise CmdException, "Relative path found in %s" % tar # find the series file seriesfile = ''; for m in names: if m.endswith('/series') or m == 'series': seriesfile = m break if seriesfile == '': raise CmdException, "no 'series' file found in %s" % tar # unpack into a tmp dir tmpdir = tempfile.mkdtemp('.stg') t.extractall(tmpdir) # apply the series __import_series(os.path.join(tmpdir, seriesfile), options) # cleanup the tmpdir shutil.rmtree(tmpdir) def func(parser, options, args): """Import a GNU diff file as a new patch """ if len(args) > 1: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) if len(args) == 1: filename = args[0] else: filename = None if not options.url and filename: filename = os.path.abspath(filename) directory.cd_to_topdir() if options.series: __import_series(filename, options) elif options.mbox: __import_mbox(filename, options) elif options.url: __import_url(filename, options) else: __import_file(filename, options) print_crt_patch(crt_series) stgit-0.17.1/stgit/commands/edit.py0000644002002200200220000001336611743516173017343 0ustar cmarinascmarinas"""Patch editing command """ __copyright__ = """ Copyright (C) 2007, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from stgit.argparse import opt from stgit import argparse, git, utils from stgit.commands import common from stgit.lib import git as gitlib, transaction, edit from stgit.out import * help = 'Edit a patch description or diff' kind = 'patch' usage = ['[options] [--] []'] description = """ Edit the description and author information of the given patch (or the current patch if no patch name was given). With --diff, also edit the diff. The editor is invoked with the following contents: From: A U Thor Date: creation date Patch description If --diff was specified, the diff appears at the bottom, after a separator: --- Diff text Command-line options can be used to modify specific information without invoking the editor. (With the --edit option, the editor is invoked even if such command-line options are given.) 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. With --set-tree you set the git tree of the patch to the specified TREE-ISH without changing the tree of any other patches. When used on the top patch, the index and work tree will be updated to match the tree. This low-level option is primarily meant to be used by tools built on top of StGit, such as the Emacs mode. See also the --set-tree flag of stg push.""" args = [argparse.applied_patches, argparse.unapplied_patches, argparse.hidden_patches] options = ( [ opt('-d', '--diff', action = 'store_true', short = 'Edit the patch diff'), opt('-e', '--edit', action = 'store_true', short = 'Invoke interactive editor') ] + argparse.sign_options() + argparse.message_options(save_template = True) + argparse.author_options() + argparse.diff_opts_option() + [ opt('-t', '--set-tree', action = 'store', metavar = 'TREE-ISH', short = 'Set the git tree of the patch to TREE-ISH') ]) directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Edit the given patch or the current one. """ stack = directory.repository.current_stack if len(args) == 0: if not stack.patchorder.applied: raise common.CmdException( 'Cannot edit top patch, because no patches are applied') patchname = stack.patchorder.applied[-1] elif len(args) == 1: [patchname] = args if not stack.patches.exists(patchname): raise common.CmdException('%s: no such patch' % patchname) else: parser.error('Cannot edit more than one patch') cd = orig_cd = stack.patches.get(patchname).commit.data if options.set_tree: cd = cd.set_tree(stack.repository.rev_parse( options.set_tree, discard_stderr = True, object_type = 'tree')) cd, failed_diff = edit.auto_edit_patch( stack.repository, cd, msg = options.message, contains_diff = True, author = options.author, committer = lambda p: p, sign_str = options.sign_str) if options.save_template: options.save_template( edit.patch_desc(stack.repository, cd, options.diff, options.diff_flags, failed_diff)) return utils.STGIT_SUCCESS if cd == orig_cd or options.edit: cd, failed_diff = edit.interactive_edit_patch( stack.repository, cd, options.diff, options.diff_flags, failed_diff) def failed(): fn = '.stgit-failed.patch' f = file(fn, 'w') f.write(edit.patch_desc(stack.repository, cd, options.diff, options.diff_flags, failed_diff)) f.close() out.error('Edited patch did not apply.', 'It has been saved to "%s".' % fn) return utils.STGIT_COMMAND_ERROR # If we couldn't apply the patch, fail without even trying to # effect any of the changes. if failed_diff: return failed() # The patch applied, so now we have to rewrite the StGit patch # (and any patches on top of it). iw = stack.repository.default_iw trans = transaction.StackTransaction(stack, 'edit', allow_conflicts = True) if patchname in trans.applied: popped = trans.applied[trans.applied.index(patchname)+1:] assert not trans.pop_patches(lambda pn: pn in popped) else: popped = [] trans.patches[patchname] = stack.repository.commit(cd) try: for pn in popped: if options.set_tree: trans.push_tree(pn) else: trans.push_patch(pn, iw, allow_interactive = True) except transaction.TransactionHalted: pass try: # Either a complete success, or a conflict during push. But in # either case, we've successfully effected the edits the user # asked us for. return trans.run(iw) except transaction.TransactionException: # Transaction aborted -- we couldn't check out files due to # dirty index/worktree. The edits were not carried out. return failed() stgit-0.17.1/stgit/commands/show.py0000644002002200200220000000555211743516173017374 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2006, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from pydoc import pager from stgit.argparse import opt from stgit.commands.common import * from stgit import argparse, git from stgit.lib import git as gitlib help = 'Show the commit corresponding to a patch' kind = 'patch' usage = ['[options] [--] [] [] [..]'] 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', args = [argparse.stg_branches], short = 'Use BRANCH instead of the default branch'), opt('-a', '--applied', action = 'store_true', short = 'Show the applied patches'), opt('-u', '--unapplied', action = 'store_true', short = 'Show the unapplied patches'), opt('-s', '--stat', action = 'store_true', short = 'Show a diffstat summary of the specified patches'), ] + argparse.diff_opts_option() directory = DirectoryHasRepository(log = False) def func(parser, options, args): """Show commit log and diff """ if options.applied: patches = crt_series.get_applied() elif options.unapplied: patches = crt_series.get_unapplied() elif len(args) == 0: patches = ['HEAD'] elif '..' in ' '.join(args): # patch ranges applied = crt_series.get_applied() unapplied = crt_series.get_unapplied() patches = parse_patches(args, applied + unapplied + \ crt_series.get_hidden(), len(applied)) else: # individual patches or commit ids patches = args if not options.stat: options.diff_flags.extend(color_diff_flags()) commit_ids = [git_id(crt_series, patch) for patch in patches] commit_str = '\n'.join([git.pretty_commit(commit_id, flags = options.diff_flags) for commit_id in commit_ids]) if options.stat: commit_str = gitlib.diffstat(commit_str) if commit_str: pager(commit_str) stgit-0.17.1/stgit/commands/clone.py0000644002002200200220000000402511271344641017501 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2009, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import os from stgit.commands import common from stgit.lib import git, stack from stgit import argparse from stgit.out import out help = 'Make a local clone of a remote repository' kind = 'repo' usage = [' '] description = """ Clone a git repository into the local directory (using linkstg:clone[]) and initialise the local branch "master". This operation is for example suitable to start working using the "tracking branch" workflow (see link:stg[1]). Other means to setup an StGit stack are linkstg:init[] and the '--create' and '--clone' commands of linkstg:branch[]. The target directory will be created by this command, and must not already exist.""" args = [argparse.repo, argparse.dir] options = [] directory = common.DirectoryAnywhere(needs_current_series = False, log = False) def func(parser, options, args): """Clone the into the local and initialises the stack """ if len(args) != 2: parser.error('incorrect number of arguments') repository = args[0] local_dir = args[1] if os.path.exists(local_dir): raise common.CmdException('"%s" exists. Remove it first' % local_dir) git.clone(repository, local_dir) os.chdir(local_dir) directory = common.DirectoryHasRepositoryLib() directory.setup() stack.Stack.initialise(directory.repository) stgit-0.17.1/stgit/commands/pop.py0000644002002200200220000000614111743516173017205 0ustar cmarinascmarinas __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 from stgit.argparse import opt help = 'Pop one or more patches from the stack' kind = 'stack' usage = ['[options] [--] [] [] [..]'] description = """ Pop the topmost patch or a range of patches from the stack. The command fails if there are conflicts or local changes (and --keep was not specified). A series of pop and push operations are performed so that only the 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'), opt('-n', '--number', type = 'int', short = 'Pop the specified number of patches', long = ''' Pop the specified number of patches. With a negative number, pop all but that many patches.'''), ] + argparse.keep_option() directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Pop the given patches or the topmost one from the stack.""" stack = directory.repository.current_stack iw = stack.repository.default_iw clean_iw = (not options.keep and iw) or None trans = transaction.StackTransaction(stack, 'pop', check_clean_iw = clean_iw) if options.number == 0: # explicitly allow this without any warning/error message return if not trans.applied: raise common.CmdException('No patches applied') if options.all: patches = trans.applied elif options.number is not None: # reverse it twice to also work with negative or bigger than # the length numbers patches = trans.applied[::-1][:options.number][::-1] elif not args: patches = [trans.applied[-1]] else: patches = common.parse_patches(args, trans.applied, ordered = True) if not patches: raise common.CmdException('No patches to pop') applied = [p for p in trans.applied if not p in set(patches)] unapplied = patches + trans.unapplied try: trans.reorder_patches(applied, unapplied, iw = iw, allow_interactive = True) except transaction.TransactionException: pass return trans.run(iw) stgit-0.17.1/stgit/commands/prev.py0000644002002200200220000000301611271344641017354 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 previous patch' kind = 'stack' usage = [''] description = """ Print the name of the previous patch.""" args = [] options = [ opt('-b', '--branch', args = [argparse.stg_branches], short = 'Use BRANCH instead of the default branch')] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Show the name of the previous patch """ if len(args) != 0: parser.error('incorrect number of arguments') stack = directory.repository.get_stack(options.branch) applied = stack.patchorder.applied if applied and len(applied) >= 2: out.stdout(applied[-2]) else: raise common.CmdException, 'Not enough applied patches' stgit-0.17.1/stgit/commands/hide.py0000644002002200200220000000403311743516173017316 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2009, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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, out from stgit.argparse import opt help = 'Hide a patch in the series' kind = 'stack' usage = ['[options] [--] '] 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', args = [argparse.stg_branches], short = 'Use BRANCH instead of the default branch')] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Hide a range of patch in the series.""" stack = directory.repository.current_stack trans = transaction.StackTransaction(stack, 'hide') if not args: parser.error('No patches specified') patches = common.parse_patches(args, trans.all_patches) for p in patches: if p in trans.hidden: out.warn('Patch "%s" already hidden' % p) patches = [p for p in patches if not p in set(trans.hidden)] applied = [p for p in trans.applied if not p in set(patches)] unapplied = [p for p in trans.unapplied if not p in set(patches)] hidden = patches + trans.hidden trans.reorder_patches(applied, unapplied, hidden) return trans.run() stgit-0.17.1/stgit/commands/pull.py0000644002002200200220000000745511743516173017374 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit.config import GitConfigException from stgit import argparse, stack, git help = 'Pull changes from a remote repository' kind = 'stack' usage = ['[options] [--] []'] description = """ Pull the latest changes from the given remote repository (defaulting to branch..remote, or 'origin' if not set). This command works by popping all the patches from the stack, pulling the changes in the parent repository, setting the base of the stack to the latest parent HEAD and pushing the patches back (unless '--nopush' is specified). The 'push' operation can fail if there are conflicts. They need to be resolved and the patch pushed again. Check the 'git fetch' documentation for the format.""" args = [argparse.repo] options = [ opt('-n', '--nopush', action = 'store_true', short = 'Do not push the patches back after pulling'), opt('-m', '--merged', action = 'store_true', short = 'Check for patches merged upstream')] directory = DirectoryGotoToplevel(log = True) def func(parser, options, args): """Pull the changes from a remote repository """ policy = config.get('branch.%s.stgit.pull-policy' % crt_series.get_name()) or \ config.get('stgit.pull-policy') if policy == 'rebase': # parent is local if len(args) == 1: parser.error('specifying a repository is meaningless for policy="%s"' % policy) if len(args) > 0: parser.error('incorrect number of arguments') else: # parent is remote if len(args) > 1: parser.error('incorrect number of arguments') if len(args) >= 1: repository = args[0] else: repository = crt_series.get_parent_remote() if crt_series.get_protected(): raise CmdException, 'This branch is protected. Pulls are not permitted' check_local_changes() check_conflicts() check_head_top_equal(crt_series) if policy not in ['pull', 'fetch-rebase', 'rebase']: raise GitConfigException, 'Unsupported pull-policy "%s"' % policy applied = prepare_rebase(crt_series) # pull the remote changes if policy == 'pull': out.info('Pulling from "%s"' % repository) git.pull(repository) elif policy == 'fetch-rebase': out.info('Fetching from "%s"' % repository) git.fetch(repository) try: target = git.fetch_head() except git.GitException: out.error('Could not find the remote head to rebase onto - fix branch.%s.merge in .git/config' % crt_series.get_name()) out.error('Pushing any patches back...') post_rebase(crt_series, applied, False, False) raise rebase(crt_series, target) elif policy == 'rebase': rebase(crt_series, crt_series.get_parent_branch()) post_rebase(crt_series, applied, options.nopush, options.merged) # maybe tidy up if config.get('stgit.keepoptimized') == 'yes': git.repack() print_crt_patch(crt_series) stgit-0.17.1/stgit/commands/clean.py0000644002002200200220000000516011271344641017464 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from stgit.argparse import opt from stgit.out import * from stgit.commands import common from stgit.lib import transaction help = 'Delete the empty patches in the series' kind = 'stack' usage = [''] description = """ 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'), opt('-u', '--unapplied', action = 'store_true', short = 'Delete the empty unapplied patches')] directory = common.DirectoryHasRepositoryLib() def _clean(stack, clean_applied, clean_unapplied): trans = transaction.StackTransaction(stack, 'clean', allow_conflicts = True) def del_patch(pn): if pn in stack.patchorder.applied: if pn == stack.patchorder.applied[-1]: # We're about to clean away the topmost patch. Don't # do that if we have conflicts, since that means the # patch is only empty because the conflicts have made # us dump its contents into the index and worktree. if stack.repository.default_index.conflicts(): return False return clean_applied and trans.patches[pn].data.is_nochange() elif pn in stack.patchorder.unapplied: return clean_unapplied and trans.patches[pn].data.is_nochange() for pn in trans.delete_patches(del_patch): trans.push_patch(pn) trans.run() def func(parser, options, args): """Delete the empty patches in the series """ if len(args) != 0: parser.error('incorrect number of arguments') if not (options.applied or options.unapplied): options.applied = options.unapplied = True _clean(directory.repository.current_stack, options.applied, options.unapplied) stgit-0.17.1/stgit/commands/cmdlist.py0000644002002200200220000000727012222320003020024 0ustar cmarinascmarinascommand_list = { 'branch': ('branch', 'Stack (branch) commands', 'Branch operations: switch, list, create, rename, delete, ...'), 'clean': ('clean', 'Stack (branch) commands', 'Delete the empty patches in the series'), 'clone': ('clone', 'Repository commands', 'Make a local clone of a remote repository'), 'commit': ('commit', 'Stack (branch) commands', 'Permanently store the applied patches into the stack base'), 'delete': ('delete', 'Patch commands', 'Delete patches'), 'diff': ('diff', 'Index/worktree commands', 'Show the tree diff'), 'edit': ('edit', 'Patch commands', 'Edit a patch description or diff'), 'export': ('export', 'Patch commands', 'Export patches to a directory'), 'files': ('files', 'Patch commands', 'Show the files modified by a patch (or the current patch)'), 'float': ('float', 'Stack (branch) commands', 'Push patches to the top, even if applied'), 'fold': ('fold', 'Patch commands', 'Integrate a GNU diff patch into the current patch'), 'goto': ('goto', 'Stack (branch) commands', 'Push or pop patches to the given one'), 'hide': ('hide', 'Stack (branch) commands', 'Hide a patch in the series'), 'id': ('id', 'Repository commands', 'Print the git hash value of a StGit reference'), 'import': ('imprt', 'Patch commands', 'Import a GNU diff file as a new patch'), 'init': ('init', 'Stack (branch) commands', 'Initialise the current branch for use with StGIT'), 'log': ('log', 'Stack (branch) commands', 'Display the patch changelog'), 'mail': ('mail', 'Patch commands', 'Send a patch or series of patches by e-mail'), 'new': ('new', 'Patch commands', 'Create a new, empty patch'), 'next': ('next', 'Stack (branch) commands', 'Print the name of the next patch'), 'patches': ('patches', 'Stack (branch) commands', 'Show the applied patches modifying a file'), 'pick': ('pick', 'Patch commands', 'Import a patch from a different branch or a commit object'), 'pop': ('pop', 'Stack (branch) commands', 'Pop one or more patches from the stack'), 'prev': ('prev', 'Stack (branch) commands', 'Print the name of the previous patch'), 'publish': ('publish', 'Stack (branch) commands', 'Push the stack changes to a merge-friendly branch'), 'pull': ('pull', 'Stack (branch) commands', 'Pull changes from a remote repository'), 'push': ('push', 'Stack (branch) commands', 'Push one or more patches onto the stack'), 'rebase': ('rebase', 'Stack (branch) commands', 'Move the stack base to another point in history'), 'redo': ('redo', 'Stack (branch) commands', 'Undo the last undo operation'), 'refresh': ('refresh', 'Patch commands', 'Generate a new commit for the current patch'), 'rename': ('rename', 'Patch commands', 'Rename a patch'), 'repair': ('repair', 'Stack (branch) commands', 'Fix StGit metadata if branch was modified with git commands'), 'reset': ('reset', 'Stack (branch) commands', 'Reset the patch stack to an earlier state'), 'series': ('series', 'Stack (branch) commands', 'Print the patch series'), 'show': ('show', 'Patch commands', 'Show the commit corresponding to a patch'), 'sink': ('sink', 'Stack (branch) commands', 'Send patches deeper down the stack'), 'squash': ('squash', 'Stack (branch) commands', 'Squash two or more patches into one'), 'sync': ('sync', 'Patch commands', 'Synchronise patches with a branch or a series'), 'top': ('top', 'Stack (branch) commands', 'Print the name of the top patch'), 'uncommit': ('uncommit', 'Stack (branch) commands', 'Turn regular git commits into StGit patches'), 'undo': ('undo', 'Stack (branch) commands', 'Undo the last operation'), 'unhide': ('unhide', 'Stack (branch) commands', 'Unhide a hidden patch'), } stgit-0.17.1/stgit/commands/patches.py0000644002002200200220000000613311743516173020037 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from pydoc import pager from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import argparse, stack, git help = 'Show the applied patches modifying a file' kind = 'stack' usage = ['[options] [--] []'] description = """ Show the applied patches modifying the given files. Without arguments, 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', args = [argparse.stg_branches], short = 'Use BRANCH instead of the default branch')] directory = DirectoryHasRepository(log = False) diff_tmpl = \ '-------------------------------------------------------------------------------\n' \ '%s\n' \ '-------------------------------------------------------------------------------\n' \ '%s' \ '---\n\n' \ '%s' def func(parser, options, args): """Show the patches modifying a file """ if not args: files = [path for (stat,path) in git.tree_status(verbose = True)] # git.tree_status returns absolute paths else: files = git.ls_files(args) directory.cd_to_topdir() if not files: raise CmdException, 'No files specified or no local changes' applied = crt_series.get_applied() if not applied: raise CmdException, 'No patches applied' revs = git.modifying_revs(files, crt_series.get_base(), crt_series.get_head()) revs.reverse() # build the patch/revision mapping rev_patch = dict() for name in applied: patch = crt_series.get_patch(name) rev_patch[patch.get_top()] = patch # print the patch names diff_output = '' for rev in revs: if rev in rev_patch: patch = rev_patch[rev] if options.diff: diff_output += diff_tmpl \ % (patch.get_name(), patch.get_description(), git.diff(files, patch.get_bottom(), patch.get_top())) else: out.stdout(patch.get_name()) if options.diff: pager(diff_output) stgit-0.17.1/stgit/commands/top.py0000644002002200200220000000276411271344641017213 0ustar cmarinascmarinas __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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' usage = [''] description = """ Print the name of the current (topmost) patch.""" args = [] options = [ opt('-b', '--branch', args = [argparse.stg_branches], short = 'Use BRANCH instead of the default branch')] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Show the name of the topmost patch """ if len(args) != 0: parser.error('incorrect number of arguments') stack = directory.repository.get_stack(options.branch) applied = stack.patchorder.applied if applied: out.stdout(applied[-1]) else: raise common.CmdException, 'No patches applied' stgit-0.17.1/stgit/commands/new.py0000644002002200200220000000621711743516173017204 0ustar cmarinascmarinas __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from stgit import argparse, utils from stgit.commands import common from stgit.lib import git as gitlib, transaction from stgit.config import config help = 'Create a new, empty patch' kind = 'patch' usage = ['[options] [--] []'] description = """ Create a new, empty patch on the current stack. The new patch is created on top of the currently applied patches, and is made the new top of the stack. Uncommitted changes in the work tree are not included in the patch -- that is handled by linkstg:refresh[]. The given name must be unique in the stack, and may only contain alphanumeric characters, dashes and underscores. If no name is given, one is generated from the first line of the patch's commit message. An editor will be launched to edit the commit message to be used for 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_options() + argparse.message_options(save_template = True) + argparse.sign_options()) directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Create a new patch.""" stack = directory.repository.current_stack if stack.repository.default_index.conflicts(): raise common.CmdException( 'Cannot create a new patch -- resolve conflicts first') # Choose a name for the new patch -- or None, which means make one # up later when we've gotten hold of the commit message. if len(args) == 0: name = None elif len(args) == 1: name = args[0] if stack.patches.exists(name): raise common.CmdException('%s: patch already exists' % name) else: parser.error('incorrect number of arguments') cd = gitlib.CommitData( tree = stack.head.data.tree, parents = [stack.head], message = '', author = gitlib.Person.author(), committer = gitlib.Person.committer()) cd = common.update_commit_data(cd, options) if options.save_template: options.save_template(cd.message) return utils.STGIT_SUCCESS if name == None: name = utils.make_patch_name(cd.message, lambda name: stack.patches.exists(name)) # Write the new patch. iw = stack.repository.default_iw trans = transaction.StackTransaction(stack, 'new') trans.patches[name] = stack.repository.commit(cd) trans.applied.append(name) return trans.run() stgit-0.17.1/stgit/commands/mail.py0000644002002200200220000006326412152142021017320 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os, re, time, datetime, socket, smtplib, getpass import email, email.Utils, email.Header from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import argparse, stack, git, version, templates from stgit.config import config from stgit.run import Run from stgit.lib import git as gitlib help = 'Send a patch or series of patches by e-mail' kind = 'patch' usage = [' [options] [--] [] [] [..]'] description = r""" Send a patch or a range of patches by e-mail using the SMTP server specified by the 'stgit.smtpserver' configuration option, or the '--smtp-server' command line option. This option can also be an absolute path to 'sendmail' followed by command line arguments. The From address and the e-mail format are generated from the template file passed as argument to '--template' (defaulting to '.git/patchmail.tmpl' or '~/.stgit/templates/patchmail.tmpl' or '/usr/share/stgit/templates/patchmail.tmpl'). A patch can be sent as attachment using the --attach option in which case the 'mailattch.tmpl' template will be used instead of 'patchmail.tmpl'. The To/Cc/Bcc addresses can either be added to the template file or passed via the corresponding command line options. They can be e-mail addresses or aliases which are automatically expanded to the values stored in the [mail "alias"] section of GIT configuration files. A preamble e-mail can be sent using the '--cover' and/or '--edit-cover' options. The first allows the user to specify a file to be used as a template. The latter option will invoke the editor on the specified file (defaulting to '.git/covermail.tmpl' or '~/.stgit/templates/covermail.tmpl' or '/usr/share/stgit/templates/covermail.tmpl'). All the subsequent e-mails appear as replies to the first e-mail sent (either the preamble or the first patch). E-mails can be seen as replies to a different e-mail by using the '--in-reply-to' option. SMTP authentication is also possible with '--smtp-user' and '--smtp-password' options, also available as configuration settings: 'smtpuser' and 'smtppassword'. TLS encryption can be enabled by '--smtp-tls' option and 'smtptls' setting. The following variables are accepted by both the preamble and the patch e-mail templates: %(diffstat)s - diff statistics %(number)s - empty if only one patch is sent or 'patchnr/totalnr' %(snumber)s - stripped version of '%(number)s' %(nspace)s - ' ' if %(number)s is non-empty, otherwise empty string %(patchnr)s - patch number %(sender)s - 'sender' or 'authname ' as per the config file %(totalnr)s - total number of patches to be sent %(version)s - 'version' string passed on the command line (or empty) %(vspace)s - ' ' if %(version)s is non-empty, otherwise empty string In addition to the common variables, the preamble e-mail template accepts the following: %(shortlog)s - first line of each patch description, listed by author In addition to the common variables, the patch e-mail template accepts the following: %(authdate)s - patch creation date %(authemail)s - author's email %(authname)s - author's name %(commemail)s - committer's e-mail %(commname)s - committer's name %(diff)s - unified diff of the patch %(fromauth)s - 'From: author\n\n' if different from sender %(longdescr)s - the rest of the patch description, after the first line %(patch)s - patch name %(prefix)s - 'prefix' string passed on the command line %(pspace)s - ' ' if %(prefix)s is non-empty, otherwise empty string %(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'), opt('--to', action = 'append', short = 'Add TO to the To: list'), opt('--cc', action = 'append', short = 'Add CC to the Cc: list'), opt('--bcc', action = 'append', short = 'Add BCC to the Bcc: list'), opt('--auto', action = 'store_true', short = 'Automatically cc the patch signers'), opt('--no-thread', action = 'store_true', short = 'Do not send subsequent messages as replies'), opt('--unrelated', action = 'store_true', short = 'Send patches without sequence numbering'), opt('--attach', action = 'store_true', short = 'Send a patch as attachment'), opt('--attach-inline', action = 'store_true', short = 'Send a patch inline and as an attachment'), opt('-v', '--version', metavar = 'VERSION', short = 'Add VERSION to the [PATCH ...] prefix'), opt('--prefix', metavar = 'PREFIX', short = 'Add PREFIX to the [... PATCH ...] prefix'), opt('-t', '--template', metavar = 'FILE', short = 'Use FILE as the message template'), opt('-c', '--cover', metavar = 'FILE', short = 'Send FILE as the cover message'), opt('-e', '--edit-cover', action = 'store_true', short = 'Edit the cover message before sending'), opt('-E', '--edit-patches', action = 'store_true', short = 'Edit each patch before sending'), opt('-s', '--sleep', type = 'int', metavar = 'SECONDS', short = 'Sleep for SECONDS between e-mails sending'), opt('--in-reply-to', metavar = 'REFID', short = 'Use REFID as the reference id'), opt('--smtp-server', metavar = 'HOST[:PORT] or "/path/to/sendmail -t -i"', short = 'SMTP server or command to use for sending mail'), opt('-u', '--smtp-user', metavar = 'USER', short = 'Username for SMTP authentication'), opt('-p', '--smtp-password', metavar = 'PASSWORD', short = 'Password for SMTP authentication'), opt('-T', '--smtp-tls', action = 'store_true', short = 'Use SMTP with TLS encryption'), 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'), opt('--git', action = 'store_true', short = 'Use git send-email (EXPERIMENTAL)') ] + argparse.diff_opts_option() directory = DirectoryHasRepository(log = False) def __get_sender(): """Return the 'authname ' string as read from the configuration file """ sender=config.get('stgit.sender') if not sender: try: sender = str(git.user()) except git.GitException: try: sender = str(git.author()) except git.GitException: pass if not sender: raise CmdException, ('Unknown sender name and e-mail; you should' ' for example set git config user.name and' ' user.email') sender = email.Utils.parseaddr(sender) return email.Utils.formataddr(address_or_alias(sender)) def __addr_list(msg, header): return [addr for name, addr in email.Utils.getaddresses(msg.get_all(header, []))] def __parse_addresses(msg): """Return a two elements tuple: (from, [to]) """ from_addr_list = __addr_list(msg, 'From') if len(from_addr_list) == 0: raise CmdException, 'No "From" address' to_addr_list = __addr_list(msg, 'To') + __addr_list(msg, 'Cc') \ + __addr_list(msg, 'Bcc') if len(to_addr_list) == 0: raise CmdException, 'No "To/Cc/Bcc" addresses' return (from_addr_list[0], set(to_addr_list)) def __send_message_sendmail(sendmail, msg): """Send the message using the sendmail command. """ cmd = sendmail.split() Run(*cmd).raw_input(msg).discard_output() __smtp_credentials = None def __set_smtp_credentials(options): """Set the (smtpuser, smtppassword, smtpusetls) credentials if the method of sending is SMTP. """ global __smtp_credentials smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.mbox or options.git or smtpserver.startswith('/'): return smtppassword = options.smtp_password or config.get('stgit.smtppassword') smtpuser = options.smtp_user or config.get('stgit.smtpuser') smtpusetls = options.smtp_tls or config.get('stgit.smtptls') == 'yes' if (smtppassword and not smtpuser): raise CmdException('SMTP password supplied, username needed') if (smtpusetls and not smtpuser): raise CmdException('SMTP over TLS requested, username needed') if (smtpuser and not smtppassword): smtppassword = getpass.getpass("Please enter SMTP password: ") __smtp_credentials = (smtpuser, smtppassword, smtpusetls) def __send_message_smtp(smtpserver, from_addr, to_addr_list, msg, options): """Send the message using the given SMTP server """ smtpuser, smtppassword, smtpusetls = __smtp_credentials try: s = smtplib.SMTP(smtpserver) except Exception, err: raise CmdException, str(err) s.set_debuglevel(0) try: if smtpuser and smtppassword: s.ehlo() if smtpusetls: if not hasattr(socket, 'ssl'): raise CmdException, "cannot use TLS - no SSL support in Python" s.starttls() s.ehlo() s.login(smtpuser, smtppassword) result = s.sendmail(from_addr, to_addr_list, msg) if len(result): print "mail server refused delivery for the following recipients: %s" % result except Exception, err: raise CmdException, str(err) s.quit() def __send_message_git(msg, options): """Send the message using git send-email """ from subprocess import call from tempfile import mkstemp cmd = ["git", "send-email", "--from=%s" % msg['From']] cmd.append("--quiet") cmd.append("--suppress-cc=self") if not options.auto: cmd.append("--suppress-cc=body") if options.in_reply_to: cmd.extend(["--in-reply-to", options.in_reply_to]) if options.no_thread: cmd.append("--no-thread") # We only support To/Cc/Bcc in git send-email for now. for x in ['to', 'cc', 'bcc']: if getattr(options, x): cmd.extend('--%s=%s' % (x, a) for a in getattr(options, x)) (fd, path) = mkstemp() os.write(fd, msg.as_string(options.mbox)) os.close(fd) try: try: cmd.append(path) call(cmd) except Exception, err: raise CmdException, str(err) finally: os.unlink(path) def __send_message(type, tmpl, options, *args): """Message sending dispatcher. """ (build, outstr) = {'cover': (__build_cover, 'the cover message'), 'patch': (__build_message, 'patch "%s"' % args[0])}[type] if type == 'patch': (patch_nr, total_nr) = (args[1], args[2]) msg_id = email.Utils.make_msgid('stgit') msg = build(tmpl, msg_id, options, *args) msg_str = msg.as_string(options.mbox) if options.mbox: out.stdout_raw(msg_str + '\n') return msg_id if not options.git: from_addr, to_addrs = __parse_addresses(msg) out.start('Sending ' + outstr) smtpserver = options.smtp_server or config.get('stgit.smtpserver') if options.git: __send_message_git(msg, options) elif smtpserver.startswith('/'): # Use the sendmail tool __send_message_sendmail(smtpserver, msg_str) else: # Use the SMTP server (we have host and port information) __send_message_smtp(smtpserver, from_addr, to_addrs, msg_str, options) # give recipients a chance of receiving related patches in correct order if type == 'cover' or (type == 'patch' and patch_nr < total_nr): sleep = options.sleep or config.getint('stgit.smtpdelay') time.sleep(sleep) if not options.git: out.done() return msg_id def __update_header(msg, header, addr = '', ignore = ()): def __addr_pairs(msg, header, extra): pairs = email.Utils.getaddresses(msg.get_all(header, []) + extra) # remove pairs without an address and resolve the aliases return [address_or_alias(p) for p in pairs if p[1]] addr_pairs = __addr_pairs(msg, header, [addr]) del msg[header] # remove the duplicates and filter the addresses addr_dict = dict((addr, email.Utils.formataddr((name, addr))) for name, addr in addr_pairs if addr not in ignore) if addr_dict: msg[header] = ', '.join(addr_dict.itervalues()) return set(addr_dict.iterkeys()) def __build_address_headers(msg, options, extra_cc = []): """Build the address headers and check existing headers in the template. """ to_addr = '' cc_addr = '' extra_cc_addr = '' bcc_addr = '' autobcc = config.get('stgit.autobcc') or '' if options.to: to_addr = ', '.join(options.to) if options.cc: cc_addr = ', '.join(options.cc) if extra_cc: extra_cc_addr = ', '.join(extra_cc) if options.bcc: bcc_addr = ', '.join(options.bcc + [autobcc]) elif autobcc: bcc_addr = autobcc # if an address is on a header, ignore it from the rest from_set = __update_header(msg, 'From') to_set = __update_header(msg, 'To', to_addr) # --auto generated addresses, don't include the sender __update_header(msg, 'Cc', extra_cc_addr, from_set) cc_set = __update_header(msg, 'Cc', cc_addr, to_set) bcc_set = __update_header(msg, 'Bcc', bcc_addr, to_set.union(cc_set)) def __get_signers_list(msg): """Return the address list generated from signed-off-by and acked-by lines in the message. """ addr_list = [] tags = '%s|%s|%s|%s|%s|%s|%s' % ( 'signed-off-by', 'acked-by', 'cc', 'reviewed-by', 'reported-by', 'tested-by', 'reported-and-tested-by') regex = '^(%s):\s+(.+)$' % tags r = re.compile(regex, re.I) for line in msg.split('\n'): m = r.match(line) if m: addr_list.append(m.expand('\g<2>')) return addr_list def __build_extra_headers(msg, msg_id, ref_id = None): """Build extra email headers and encoding """ del msg['Date'] msg['Date'] = email.Utils.formatdate(localtime = True) msg['Message-ID'] = msg_id if ref_id: # make sure the ref id has the angle brackets ref_id = '<%s>' % ref_id.strip(' \t\n<>') msg['In-Reply-To'] = ref_id msg['References'] = ref_id msg['User-Agent'] = 'StGit/%s' % version.version # update other address headers __update_header(msg, 'Reply-To') __update_header(msg, 'Mail-Reply-To') __update_header(msg, 'Mail-Followup-To') def __encode_message(msg): # 7 or 8 bit encoding charset = email.Charset.Charset('utf-8') charset.body_encoding = None # encode headers for header, value in msg.items(): words = [] for word in value.split(' '): try: uword = unicode(word, 'utf-8') except UnicodeDecodeError: # maybe we should try a different encoding or report # the error. At the moment, we just ignore it pass words.append(email.Header.Header(uword).encode()) new_val = ' '.join(words) msg.replace_header(header, new_val) # replace the Subject string with a Header() object otherwise the long # line folding is done using "\n\t" rather than "\n ", causing issues with # some e-mail clients subject = msg.get('subject', '') msg.replace_header('subject', email.Header.Header(subject, header_name = 'subject')) # encode the body and set the MIME and encoding headers if msg.is_multipart(): for p in msg.get_payload(): p.set_charset(charset) else: msg.set_charset(charset) def __edit_message(msg): fname = '.stgitmail.txt' # create the initial file f = file(fname, 'w') f.write(msg) f.close() call_editor(fname) # read the message back f = file(fname) msg = f.read() f.close() return msg def __build_cover(tmpl, msg_id, options, patches): """Build the cover message (series description) to be sent via SMTP """ sender = __get_sender() if options.version: version_str = '%s' % options.version version_space = ' ' else: version_str = '' version_space = '' if options.prefix: prefix_str = options.prefix else: prefix_str = config.get('stgit.mail.prefix') if prefix_str: prefix_space = ' ' else: prefix_str = '' prefix_space = '' total_nr_str = str(len(patches)) patch_nr_str = '0'.zfill(len(total_nr_str)) if len(patches) > 1: number_str = '%s/%s' % (patch_nr_str, total_nr_str) number_space = ' ' else: number_str = '' number_space = '' tmpl_dict = {'sender': sender, # for backward template compatibility 'maintainer': sender, # for backward template compatibility 'endofheaders': '', # for backward template compatibility 'date': '', 'version': version_str, 'vspace': version_space, 'prefix': prefix_str, 'pspace': prefix_space, 'patchnr': patch_nr_str, 'totalnr': total_nr_str, 'number': number_str, 'nspace': number_space, 'snumber': number_str.strip(), 'shortlog': stack.shortlog(crt_series.get_patch(p) for p in reversed(patches)), 'diffstat': gitlib.diffstat(git.diff( rev1 = git_id(crt_series, '%s^' % patches[0]), rev2 = git_id(crt_series, '%s' % patches[-1]), diff_flags = options.diff_flags))} try: msg_string = tmpl % tmpl_dict except KeyError, err: raise CmdException, 'Unknown patch template variable: %s' \ % err except TypeError: raise CmdException, 'Only "%(name)s" variables are ' \ 'supported in the patch template' if options.edit_cover: msg_string = __edit_message(msg_string) # The Python email message try: msg = email.message_from_string(msg_string) except Exception, ex: raise CmdException, 'template parsing error: %s' % str(ex) if not options.git: __build_address_headers(msg, options) __build_extra_headers(msg, msg_id, options.in_reply_to) __encode_message(msg) return msg def __build_message(tmpl, msg_id, options, patch, patch_nr, total_nr, ref_id): """Build the message to be sent via SMTP """ p = crt_series.get_patch(patch) if p.get_description(): descr = p.get_description().strip() else: # provide a place holder and force the edit message option on descr = '' options.edit_patches = True descr_lines = descr.split('\n') short_descr = descr_lines[0].strip() long_descr = '\n'.join(l.rstrip() for l in descr_lines[1:]).lstrip('\n') authname = p.get_authname(); authemail = p.get_authemail(); commname = p.get_commname(); commemail = p.get_commemail(); sender = __get_sender() fromauth = '%s <%s>' % (authname, authemail) if fromauth != sender: fromauth = 'From: %s\n\n' % fromauth else: fromauth = '' if options.version: version_str = '%s' % options.version version_space = ' ' else: version_str = '' version_space = '' if options.prefix: prefix_str = options.prefix else: prefix_str = config.get('stgit.mail.prefix') if prefix_str: prefix_space = ' ' else: prefix_str = '' prefix_space = '' total_nr_str = str(total_nr) patch_nr_str = str(patch_nr).zfill(len(total_nr_str)) if not options.unrelated and total_nr > 1: number_str = '%s/%s' % (patch_nr_str, total_nr_str) number_space = ' ' else: number_str = '' number_space = '' diff = git.diff(rev1 = git_id(crt_series, '%s^' % patch), rev2 = git_id(crt_series, '%s' % patch), diff_flags = options.diff_flags) tmpl_dict = {'patch': patch, 'sender': sender, # for backward template compatibility 'maintainer': sender, 'shortdescr': short_descr, 'longdescr': long_descr, # for backward template compatibility 'endofheaders': '', 'diff': diff, 'diffstat': gitlib.diffstat(diff), # for backward template compatibility 'date': '', 'version': version_str, 'vspace': version_space, 'prefix': prefix_str, 'pspace': prefix_space, 'patchnr': patch_nr_str, 'totalnr': total_nr_str, 'number': number_str, 'nspace': number_space, 'snumber': number_str.strip(), 'fromauth': fromauth, 'authname': authname, 'authemail': authemail, 'authdate': p.get_authdate(), 'commname': commname, 'commemail': commemail} # change None to '' for key in tmpl_dict: if not tmpl_dict[key]: tmpl_dict[key] = '' try: msg_string = tmpl % tmpl_dict except KeyError, err: raise CmdException, 'Unknown patch template variable: %s' \ % err except TypeError: raise CmdException, 'Only "%(name)s" variables are ' \ 'supported in the patch template' if options.edit_patches: msg_string = __edit_message(msg_string) # The Python email message try: msg = email.message_from_string(msg_string) except Exception, ex: raise CmdException, 'template parsing error: %s' % str(ex) if options.auto: extra_cc = __get_signers_list(descr) else: extra_cc = [] if not options.git: __build_address_headers(msg, options, extra_cc) __build_extra_headers(msg, msg_id, ref_id) __encode_message(msg) return msg def func(parser, options, args): """Send the patches by e-mail using the patchmail.tmpl file as a template """ applied = crt_series.get_applied() if options.all: patches = applied elif len(args) >= 1: unapplied = crt_series.get_unapplied() patches = parse_patches(args, applied + unapplied, len(applied)) else: raise CmdException, 'Incorrect options. Unknown patches to send' # early test for sender identity __get_sender() out.start('Checking the validity of the patches') for p in patches: if crt_series.empty_patch(p): raise CmdException, 'Cannot send empty patch "%s"' % p out.done() total_nr = len(patches) if total_nr == 0: raise CmdException, 'No patches to send' if options.in_reply_to: if options.no_thread or options.unrelated: raise CmdException, \ '--in-reply-to option not allowed with --no-thread or --unrelated' ref_id = options.in_reply_to else: ref_id = None # get username/password if sending by SMTP __set_smtp_credentials(options) # send the cover message (if any) if options.cover or options.edit_cover: if options.unrelated: raise CmdException, 'cover sending not allowed with --unrelated' # find the template file if options.cover: tmpl = file(options.cover).read() else: tmpl = templates.get_template('covermail.tmpl') if not tmpl: raise CmdException, 'No cover message template file found' msg_id = __send_message('cover', tmpl, options, patches) # subsequent e-mails are seen as replies to the first one if not options.no_thread: ref_id = msg_id # send the patches if options.template: tmpl = file(options.template).read() else: if options.attach: tmpl = templates.get_template('mailattch.tmpl') elif options.attach_inline: tmpl = templates.get_template('patchandattch.tmpl') else: tmpl = templates.get_template('patchmail.tmpl') if not tmpl: raise CmdException, 'No e-mail template file found' for (p, n) in zip(patches, range(1, total_nr + 1)): msg_id = __send_message('patch', tmpl, options, p, n, total_nr, ref_id) # subsequent e-mails are seen as replies to the first one if not options.no_thread and not options.unrelated and not ref_id: ref_id = msg_id stgit-0.17.1/stgit/commands/squash.py0000644002002200200220000001240711743516173017715 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2007, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from stgit.argparse import opt from stgit.out import * from stgit import argparse, utils from stgit.commands import common from stgit.lib import git, transaction help = 'Squash two or more patches into one' kind = 'stack' usage = ['[options] [--] '] description = """ Squash two or more patches, creating one big patch that contains all their changes. In more detail: 1. Pop all the given patches, plus any other patches on top of them. 2. Push the given patches in the order they were given on the command line. 3. Squash the given patches into one big patch. 4. Allow the user to edit the commit message of the new patch interactively. 5. Push the other patches that were popped in step (1). Conflicts can occur whenever we push a patch; that is, in step (2) and (5). If there are conflicts, the command will stop so that you can resolve them.""" args = [argparse.patch_range(argparse.applied_patches, argparse.unapplied_patches)] options = [opt('-n', '--name', short = 'Name of squashed patch') ] + argparse.message_options(save_template = True) directory = common.DirectoryHasRepositoryLib() class SaveTemplateDone(Exception): pass def _squash_patches(trans, patches, msg, save_template): cd = trans.patches[patches[0]].data cd = git.CommitData(tree = cd.tree, parents = cd.parents) for pn in patches[1:]: c = trans.patches[pn] tree = trans.stack.repository.simple_merge( base = c.data.parent.data.tree, ours = cd.tree, theirs = c.data.tree) if not tree: return None cd = cd.set_tree(tree) if msg == None: msg = utils.append_comment( trans.patches[patches[0]].data.message, '\n\n'.join('%s\n\n%s' % (pn.ljust(70, '-'), trans.patches[pn].data.message) for pn in patches[1:])) if save_template: save_template(msg) raise SaveTemplateDone() else: msg = utils.edit_string(msg, '.stgit-squash.txt') msg = utils.strip_comment(msg).strip() cd = cd.set_message(msg) return cd def _squash(stack, iw, name, msg, save_template, patches): # If a name was supplied on the command line, make sure it's OK. def bad_name(pn): return pn not in patches and stack.patches.exists(pn) def get_name(cd): return name or utils.make_patch_name(cd.message, bad_name) if name and bad_name(name): raise common.CmdException('Patch name "%s" already taken') def make_squashed_patch(trans, new_commit_data): name = get_name(new_commit_data) trans.patches[name] = stack.repository.commit(new_commit_data) trans.unapplied.insert(0, name) trans = transaction.StackTransaction(stack, 'squash', allow_conflicts = True) push_new_patch = bool(set(patches) & set(trans.applied)) try: new_commit_data = _squash_patches(trans, patches, msg, save_template) if new_commit_data: # We were able to construct the squashed commit # automatically. So just delete its constituent patches. to_push = trans.delete_patches(lambda pn: pn in patches) else: # Automatic construction failed. So push the patches # consecutively, so that a second construction attempt is # guaranteed to work. to_push = trans.pop_patches(lambda pn: pn in patches) for pn in patches: trans.push_patch(pn, iw) new_commit_data = _squash_patches(trans, patches, msg, save_template) assert not trans.delete_patches(lambda pn: pn in patches) make_squashed_patch(trans, new_commit_data) # Push the new patch if necessary, and any unrelated patches we've # had to pop out of the way. if push_new_patch: trans.push_patch(get_name(new_commit_data), iw) for pn in to_push: trans.push_patch(pn, iw) except SaveTemplateDone: trans.abort(iw) return except transaction.TransactionHalted: pass return trans.run(iw) def func(parser, options, args): stack = directory.repository.current_stack patches = common.parse_patches(args, list(stack.patchorder.all)) if len(patches) < 2: raise common.CmdException('Need at least two patches') return _squash(stack, stack.repository.default_iw, options.name, options.message, options.save_template, patches) stgit-0.17.1/stgit/commands/branch.py0000644002002200200220000003211211743516513017637 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Chuck Lever This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os, time, re from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import argparse, stack, git, basedir from stgit.lib import log help = 'Branch operations: switch, list, create, rename, delete, ...' kind = 'stack' usage = ['', '[--merge] [--] ', '--list', '--create [--] []', '--clone [--] []', '--rename [--] ', '--protect [--] []', '--unprotect [--] []', '--delete [--force] [--] ', '--cleanup [--force] [--] []', '--description= [--] []'] description = """ Create, clone, switch between, rename, or delete development branches within a git repository. 'stg branch':: Display the name of the current branch. 'stg 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 = """ List each branch in the current repository, followed by its branch description (if any). The current branch is prefixed with '>'. Branches that have been initialized for StGit (with linkstg:init[]) are prefixed with 's'. Protected branches are prefixed with 'p'."""), opt('-c', '--create', action = 'store_true', short = 'Create (and switch to) a new branch', long = """ Create (and switch to) a new branch. The new branch is already initialized as an StGit patch stack, so you do not have to run linkstg:init[] manually. If you give a committish argument, the new branch is based there; otherwise, it is based at the current HEAD. StGit will try to detect the branch off of which the new branch is forked, as well as the remote repository from which that parent branch is taken (if any), so that running linkstg:pull[] will automatically pull new commits from the correct branch. It will warn if it cannot guess the parent branch (e.g. if you do not specify a branch name as committish)."""), opt('--clone', action = 'store_true', short = 'Clone the contents of the current branch', long = """ Clone the current branch, under the name if specified, or using the current branch's name plus a timestamp. The description of the new branch is set to tell it is a clone of the current branch. The parent information of the new branch is copied from the current branch."""), opt('-r', '--rename', action = 'store_true', short = 'Rename an existing branch'), opt('-p', '--protect', action = 'store_true', short = 'Prevent StGit from modifying a branch', long = """ Prevent StGit from modifying a branch -- either the current one, or one named on the command line."""), opt('-u', '--unprotect', action = 'store_true', short = 'Allow StGit to modify a branch', long = """ Allow StGit to modify a branch -- either the current one, or one named on the command line. This undoes the effect of an earlier 'stg branch --protect' command."""), opt('--delete', action = 'store_true', short = 'Delete a branch', long = """ Delete the named branch. If there are any patches left in the branch, StGit will refuse to delete it unless you give the '--force' flag. A protected branch cannot be deleted; it must be unprotected first (see '--unprotect' above). If you delete the current branch, you are switched to the "master" branch, if it exists."""), opt('--cleanup', action = 'store_true', short = 'Clean up the StGit metadata for a branch', long = """ Remove the StGit information for the current or given branch. If there are patches left in the branch, StGit refuses the operation unless '--force' is given. A protected branch cannot be cleaned up; it must be unprotected first (see '--unprotect' above). A cleaned up branch can be re-initialised using the 'stg init' command."""), opt('-d', '--description', short = 'Set the branch description'), opt('--merge', action = 'store_true', short = 'Merge work tree changes into the other branch'), opt('--force', action = 'store_true', short = 'Force a delete when the series is not empty')] directory = DirectoryGotoToplevel(log = False) def __is_current_branch(branch_name): return crt_series.get_name() == branch_name def __print_branch(branch_name, length): initialized = ' ' current = ' ' protected = ' ' branch = stack.Series(branch_name) if branch.is_initialised(): initialized = 's' if __is_current_branch(branch_name): current = '>' if branch.get_protected(): protected = 'p' out.stdout(current + ' ' + initialized + protected + '\t' + branch_name.ljust(length) + ' | ' + branch.get_description()) def __delete_branch(doomed_name, force = False): doomed = stack.Series(doomed_name) if __is_current_branch(doomed_name): raise CmdException('Cannot delete the current branch') if doomed.get_protected(): raise CmdException, 'This branch is protected. Delete is not permitted' out.start('Deleting branch "%s"' % doomed_name) doomed.delete(force) out.done() def __cleanup_branch(name, force = False): branch = stack.Series(name) if branch.get_protected(): raise CmdExcpetion('This branch is protected. Clean up is not permitted') out.start('Cleaning up branch "%s"' % name) branch.delete(force = force, cleanup = True) out.done() def func(parser, options, args): if options.create: if len(args) == 0 or len(args) > 2: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) tree_id = None if len(args) >= 2: parentbranch = None try: branchpoint = git.rev_parse(args[1]) # parent branch? head_re = re.compile('refs/(heads|remotes)/') ref_re = re.compile(args[1] + '$') for ref in git.all_refs(): if head_re.match(ref) and ref_re.search(ref): # args[1] is a valid ref from the branchpoint # setting above parentbranch = args[1] break; except git.GitException: # should use a more specific exception to catch only # non-git refs ? out.info('Don\'t know how to determine parent branch' ' from "%s"' % args[1]) # exception in branch = rev_parse() leaves branchpoint unbound branchpoint = None tree_id = git_id(crt_series, branchpoint or args[1]) if parentbranch: out.info('Recording "%s" as parent branch' % parentbranch) else: out.info('Don\'t know how to determine parent branch' ' from "%s"' % args[1]) else: # branch stack off current branch parentbranch = git.get_head_file() if parentbranch: parentremote = git.identify_remote(parentbranch) if parentremote: out.info('Using remote "%s" to pull parent from' % parentremote) else: out.info('Recording as a local branch') else: # no known parent branch, can't guess the remote parentremote = None stack.Series(args[0]).init(create_at = tree_id, parent_remote = parentremote, parent_branch = parentbranch) out.info('Branch "%s" created' % args[0]) log.compat_log_entry('branch --create') return elif options.clone: if len(args) == 0: clone = crt_series.get_name() + \ time.strftime('-%C%y%m%d-%H%M%S') elif len(args) == 1: clone = args[0] else: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) out.start('Cloning current branch to "%s"' % clone) crt_series.clone(clone) out.done() log.copy_log(log.default_repo(), crt_series.get_name(), clone, 'branch --clone') return elif options.delete: if len(args) != 1: parser.error('incorrect number of arguments') __delete_branch(args[0], options.force) log.delete_log(log.default_repo(), args[0]) return elif options.cleanup: if not args: name = crt_series.get_name() elif len(args) == 1: name = args[0] else: parser.error('incorrect number of arguments') __cleanup_branch(name, options.force) log.delete_log(log.default_repo(), name) return elif options.list: if len(args) != 0: parser.error('incorrect number of arguments') branches = set(git.get_heads()) for br in set(branches): m = re.match(r'^(.*)\.stgit$', br) if m and m.group(1) in branches: branches.remove(br) if branches: out.info('Available branches:') max_len = max([len(i) for i in branches]) for i in sorted(branches): __print_branch(i, max_len) else: out.info('No branches') return elif options.protect: if len(args) == 0: branch_name = crt_series.get_name() elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') branch = stack.Series(branch_name) if not branch.is_initialised(): raise CmdException, 'Branch "%s" is not controlled by StGIT' \ % branch_name out.start('Protecting branch "%s"' % branch_name) branch.protect() out.done() return elif options.rename: if len(args) != 2: parser.error('incorrect number of arguments') if __is_current_branch(args[0]): raise CmdException, 'Renaming the current branch is not supported' stack.Series(args[0]).rename(args[1]) out.info('Renamed branch "%s" to "%s"' % (args[0], args[1])) log.rename_log(log.default_repo(), args[0], args[1], 'branch --rename') return elif options.unprotect: if len(args) == 0: branch_name = crt_series.get_name() elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') branch = stack.Series(branch_name) if not branch.is_initialised(): raise CmdException, 'Branch "%s" is not controlled by StGIT' \ % branch_name out.info('Unprotecting branch "%s"' % branch_name) branch.unprotect() out.done() return elif options.description is not None: if len(args) == 0: branch_name = crt_series.get_name() elif len(args) == 1: branch_name = args[0] else: parser.error('incorrect number of arguments') branch = stack.Series(branch_name) if not branch.is_initialised(): raise CmdException, 'Branch "%s" is not controlled by StGIT' \ % branch_name branch.set_description(options.description) return elif len(args) == 1: if __is_current_branch(args[0]): raise CmdException, 'Branch "%s" is already the current branch' \ % args[0] if not options.merge: check_local_changes() check_conflicts() check_head_top_equal(crt_series) out.start('Switching to branch "%s"' % args[0]) git.switch_branch(args[0]) out.done() return # default action: print the current branch if len(args) != 0: parser.error('incorrect number of arguments') print crt_series.get_name() stgit-0.17.1/stgit/commands/rebase.py0000644002002200200220000000455411743516173017656 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit import argparse, stack, git help = 'Move the stack base to another point in history' kind = 'stack' usage = ['[options] [--] '] description = """ Pop all patches from current stack, move the stack base to the given and push the patches back. If you experience merge conflicts, resolve the problem and continue the rebase by executing the following sequence: $ git add --update $ stg refresh $ stg goto top-patch 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'), opt('-m', '--merged', action = 'store_true', short = 'Check for patches merged upstream')] directory = DirectoryGotoToplevel(log = True) def func(parser, options, args): """Rebase the current stack """ if len(args) != 1: parser.error('incorrect number of arguments') if crt_series.get_protected(): raise CmdException, 'This branch is protected. Rebase is not permitted' check_local_changes() check_conflicts() check_head_top_equal(crt_series) # ensure an exception is raised before popping on non-existent target if git_id(crt_series, args[0]) == None: raise GitException, 'Unknown revision: %s' % args[0] applied = prepare_rebase(crt_series) rebase(crt_series, args[0]) post_rebase(crt_series, applied, options.nopush, options.merged) print_crt_patch(crt_series) stgit-0.17.1/stgit/commands/diff.py0000644002002200200220000000536311743516173017324 0ustar cmarinascmarinas __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from pydoc import pager from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import argparse, stack, git from stgit.lib import git as gitlib help = 'Show the tree diff' kind = 'wc' usage = ['[options] [--] []'] description = """ Show the diff (default) or diffstat between the current working copy or a tree-ish object and another tree-ish object (defaulting to HEAD). File names can also be given to restrict the diff output. The tree-ish object has the format accepted by the linkstg:id[] command.""" 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'), ] + argparse.diff_opts_option() directory = DirectoryHasRepository(log = False) def func(parser, options, args): """Show the tree diff """ args = git.ls_files(args) directory.cd_to_topdir() if options.revs: rev_list = options.revs.split('..') rev_list_len = len(rev_list) if rev_list_len == 1: rev1 = rev_list[0] rev2 = None elif rev_list_len == 2: rev1 = rev_list[0] rev2 = rev_list[1] else: parser.error('incorrect parameters to -r') else: rev1 = 'HEAD' rev2 = None if not options.stat: options.diff_flags.extend(color_diff_flags()) diff_str = git.diff(args, rev1 and git_id(crt_series, rev1), rev2 and git_id(crt_series, rev2), diff_flags = options.diff_flags) if options.stat: out.stdout_raw(gitlib.diffstat(diff_str) + '\n') else: if diff_str: pager(diff_str) stgit-0.17.1/stgit/commands/refresh.py0000644002002200200220000002732212222315415020036 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2005, Catalin Marinas Copyright (C) 2008, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 git, transaction, edit from stgit.out import out from stgit import argparse, utils help = 'Generate a new commit for the current patch' kind = 'patch' usage = ['[options] [--] []'] description = """ Include the latest work tree and index changes in the current patch. This command generates a new git commit object for the patch; the old commit is no longer visible. Refresh will warn if the index is dirty, and require use of either the '--index' or '--force' options to override this check. This is to prevent accidental full refresh when only some changes were staged using git add interative mode. You may optionally list one or more files or directories relative to the current working directory; if you do, only matching files will be updated. Behind the scenes, stg refresh first creates a new temporary patch with your updates, and then merges that patch into the patch you asked to have refreshed. If you asked to refresh a patch other than the topmost patch, there can be conflicts; in that case, the temporary patch will be left for you to take care of, for example with stg squash. The creation of the temporary patch is recorded in a separate entry in 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'), opt('-i', '--index', action = 'store_true', 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('-F', '--force', action = 'store_true', short = 'Force refresh even if index is dirty', long = """ Instead of warning the user when some work has already been staged (such as with git add interactive mode) force a full refresh."""), 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'), opt('-a', '--annotate', metavar = 'NOTE', short = 'Annotate the patch log entry') ] + (argparse.message_options(save_template = False) + argparse.sign_options() + argparse.author_options()) directory = common.DirectoryHasRepositoryLib() def get_patch(stack, given_patch): """Get the name of the patch we are to refresh.""" if given_patch: patch_name = given_patch if not stack.patches.exists(patch_name): raise common.CmdException('%s: no such patch' % patch_name) return patch_name else: if not stack.patchorder.applied: raise common.CmdException( 'Cannot refresh top patch, because no patches are applied') return stack.patchorder.applied[-1] def list_files(stack, patch_name, args, index, update): """Figure out which files to update.""" if index: # --index: Don't update the index. return set() paths = stack.repository.default_iw.changed_files( stack.head.data.tree, args or []) if update: # --update: Restrict update to the paths that were already # part of the patch. paths &= stack.patches.get(patch_name).files() return paths def write_tree(stack, paths, temp_index): """Possibly update the index, and then write its tree. @return: The written tree. @rtype: L{Tree}""" def go(index): if paths: iw = git.IndexAndWorktree(index, stack.repository.default_worktree) iw.update_index(paths) return index.write_tree() if temp_index: index = stack.repository.temp_index() try: index.read_tree(stack.head) return go(index) finally: index.delete() stack.repository.default_iw.update_index(paths) else: return go(stack.repository.default_index) def make_temp_patch(stack, patch_name, paths, temp_index): """Commit index to temp patch, in a complete transaction. If any path limiting is in effect, use a temp index.""" tree = write_tree(stack, paths, temp_index) commit = stack.repository.commit(git.CommitData( tree = tree, parents = [stack.head], message = 'Refresh of %s' % patch_name)) temp_name = utils.make_patch_name('refresh-temp', stack.patches.exists) trans = transaction.StackTransaction(stack, 'refresh (create temporary patch)') trans.patches[temp_name] = commit trans.applied.append(temp_name) return trans.run(stack.repository.default_iw, print_current_patch = False), temp_name def absorb_applied(trans, iw, patch_name, temp_name, edit_fun): """Absorb the temp patch (C{temp_name}) into the given patch (C{patch_name}), which must be applied. If the absorption succeeds, call C{edit_fun} on the resulting L{CommitData} before committing it and commit the return value. @return: C{True} if we managed to absorb the temp patch, C{False} if we had to leave it for the user to deal with.""" temp_absorbed = False try: # Pop any patch on top of the patch we're refreshing. to_pop = trans.applied[trans.applied.index(patch_name)+1:] if len(to_pop) > 1: popped = trans.pop_patches(lambda pn: pn in to_pop) assert not popped # no other patches were popped trans.push_patch(temp_name, iw) assert to_pop.pop() == temp_name # Absorb the temp patch. temp_cd = trans.patches[temp_name].data assert trans.patches[patch_name] == temp_cd.parent trans.patches[patch_name] = trans.stack.repository.commit( edit_fun(trans.patches[patch_name].data.set_tree(temp_cd.tree))) popped = trans.delete_patches(lambda pn: pn == temp_name, quiet = True) assert not popped # the temp patch was topmost temp_absorbed = True # Push back any patch we were forced to pop earlier. for pn in to_pop: trans.push_patch(pn, iw) except transaction.TransactionHalted: pass return temp_absorbed def absorb_unapplied(trans, iw, patch_name, temp_name, edit_fun): """Absorb the temp patch (C{temp_name}) into the given patch (C{patch_name}), which must be unapplied. If the absorption succeeds, call C{edit_fun} on the resulting L{CommitData} before committing it and commit the return value. @param iw: Not used. @return: C{True} if we managed to absorb the temp patch, C{False} if we had to leave it for the user to deal with.""" # Pop the temp patch. popped = trans.pop_patches(lambda pn: pn == temp_name) assert not popped # the temp patch was topmost # Try to create the new tree of the refreshed patch. (This is the # same operation as pushing the temp patch onto the patch we're # trying to refresh -- but we don't have a worktree to spill # conflicts to, so if the simple merge doesn't succeed, we have to # give up.) patch_cd = trans.patches[patch_name].data temp_cd = trans.patches[temp_name].data new_tree = trans.stack.repository.simple_merge( base = temp_cd.parent.data.tree, ours = patch_cd.tree, theirs = temp_cd.tree) if new_tree: # It worked. Refresh the patch with the new tree, and delete # the temp patch. trans.patches[patch_name] = trans.stack.repository.commit( edit_fun(patch_cd.set_tree(new_tree))) popped = trans.delete_patches(lambda pn: pn == temp_name, quiet = True) assert not popped # the temp patch was not applied return True else: # Nope, we couldn't create the new tree, so we'll just have to # leave the temp patch for the user. return False def absorb(stack, patch_name, temp_name, edit_fun, annotate = None): """Absorb the temp patch into the target patch.""" if annotate: log_msg = 'refresh\n\n' + annotate else: log_msg = 'refresh' trans = transaction.StackTransaction(stack, log_msg) iw = stack.repository.default_iw f = { True: absorb_applied, False: absorb_unapplied }[patch_name in trans.applied] if f(trans, iw, patch_name, temp_name, edit_fun): def info_msg(): pass else: def info_msg(): out.warn('The new changes did not apply cleanly to %s.' % patch_name, 'They were saved in %s.' % temp_name) r = trans.run(iw) info_msg() return r def func(parser, options, args): """Generate a new commit for the current or given patch.""" # Catch illegal argument combinations. path_limiting = bool(args or options.update) if options.index and path_limiting: raise common.CmdException( 'Only full refresh is available with the --index option') if options.index and options.force: raise common.CmdException( 'You cannot --force a full refresh when using --index mode') stack = directory.repository.current_stack patch_name = get_patch(stack, options.patch) paths = list_files(stack, patch_name, args, options.index, options.update) # Make sure there are no conflicts in the files we want to # refresh. if stack.repository.default_index.conflicts() & paths: raise common.CmdException( 'Cannot refresh -- resolve conflicts first') # Make sure the index is clean before performing a full refresh if not options.index and not options.force: if not (stack.repository.default_index.is_clean(stack.head) or stack.repository.default_iw.worktree_clean()): raise common.CmdException( 'The index is dirty. Did you mean --index? To force a full refresh use --force.') # Commit index to temp patch, and absorb it into the target patch. retval, temp_name = make_temp_patch( stack, patch_name, paths, temp_index = path_limiting) if retval != utils.STGIT_SUCCESS: return retval def edit_fun(cd): cd, failed_diff = edit.auto_edit_patch( stack.repository, cd, msg = options.message, contains_diff = False, author = options.author, committer = lambda p: p, sign_str = options.sign_str) assert not failed_diff if options.edit: cd, failed_diff = edit.interactive_edit_patch( stack.repository, cd, edit_diff = False, diff_flags = [], replacement_diff = None) assert not failed_diff return cd return absorb(stack, patch_name, temp_name, edit_fun, annotate = options.annotate) stgit-0.17.1/stgit/commands/log.py0000644002002200200220000000706011743516173017171 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2006, Catalin Marinas Copyright (C) 2008, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import os.path from optparse import make_option from stgit import argparse, run from stgit.argparse import opt from stgit.commands import common from stgit.lib import log from stgit.out import out help = 'Display the patch changelog' kind = 'stack' usage = ['[options] [--] []'] description = """ List the history of the patch stack: the stack log. If one or more patch names are given, limit the list to the log entries that touch 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', args = [argparse.stg_branches], short = 'Use BRANCH instead of the default one'), opt('-d', '--diff', action = 'store_true', short = 'Show the refresh diffs'), opt('-n', '--number', type = 'int', short = 'Limit the output to NUMBER commits'), opt('-f', '--full', action = 'store_true', short = 'Show the full commit ids'), opt('-g', '--graphical', action = 'store_true', short = 'Run gitk instead of printing'), opt('--clear', action = 'store_true', short = 'Clear the log history')] directory = common.DirectoryHasRepositoryLib() def show_log(stacklog, pathlim, num, full, show_diff): cmd = ['git', 'log'] if num != None and num > 0: cmd.append('-%d' % num) if show_diff: cmd.append('-p') elif not full: cmd.append('--pretty=format:%h %aD %s') run.Run(*(cmd + [stacklog.sha1, '--'] + pathlim)).run() def func(parser, options, args): if options.branch: stack = directory.repository.get_stack(options.branch) else: stack = directory.repository.current_stack patches = common.parse_patches(args, list(stack.patchorder.all)) logref = log.log_ref(stack.name) try: logcommit = stack.repository.refs.get(logref) except KeyError: out.info('Log is empty') return if options.clear: log.delete_log(stack.repository, stack.name) return stacklog = log.get_log_entry(stack.repository, logref, logcommit) pathlim = [os.path.join('patches', pn) for pn in patches] if options.graphical: for o in ['diff', 'number', 'full']: if getattr(options, o): parser.error('cannot combine --graphical and --%s' % o) # Discard the exit codes generated by SIGINT, SIGKILL, and SIGTERM. run.Run(*(['gitk', stacklog.simplified.sha1, '--'] + pathlim) ).returns([0, -2, -9, -15]).run() else: show_log(stacklog.simplified, pathlim, options.number, options.full, options.diff) stgit-0.17.1/stgit/commands/undo.py0000644002002200200220000000370511271344641017352 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2008, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 git, log, transaction from stgit.out import out help = 'Undo the last operation' kind = 'stack' usage = [''] 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'), opt('--hard', action = 'store_true', short = 'Discard changes in your index/worktree')] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): stack = directory.repository.current_stack if options.number < 1: raise common.CmdException('Bad number of commands to undo') state = log.undo_state(stack, options.number) trans = transaction.StackTransaction(stack, 'undo %d' % options.number, discard_changes = options.hard, allow_bad_head = True) try: log.reset_stack(trans, stack.repository.default_iw, state) except transaction.TransactionHalted: pass return trans.run(stack.repository.default_iw, allow_bad_head = True) stgit-0.17.1/stgit/commands/sink.py0000644002002200200220000000735411747465264017372 0ustar cmarinascmarinas __copyright__ = """ Copyright (C) 2007, Yann Dirson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 = 'Send patches deeper down the stack' kind = 'stack' usage = ['[-t ] [-n] [--] []'] description = """ This is the opposite operation of linkstg:float[]: move the specified patches down the stack. It is for example useful to group stable patches near the bottom of the stack, where they are less likely to be impacted by the push of another patch, and from where they can be more easily committed or pushed. If no patch is specified on command-line, the current patch gets sunk. By default patches are sunk to the bottom of the stack, but the '--to' option allows one to place them under any applied patch. Sinking internally involves popping all patches (or all patches including ), 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', 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.""") ] + argparse.keep_option() directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Sink patches down the stack. """ stack = directory.repository.current_stack if options.to and not options.to in stack.patchorder.applied: raise common.CmdException('Cannot sink below %s since it is not applied' % options.to) if len(args) > 0: patches = common.parse_patches(args, stack.patchorder.all) else: # current patch patches = list(stack.patchorder.applied[-1:]) if not patches: raise common.CmdException('No patches to sink') if options.to and options.to in patches: raise common.CmdException('Cannot have a sinked patch as target') applied = [p for p in stack.patchorder.applied if p not in patches] if options.to: insert_idx = applied.index(options.to) else: insert_idx = 0 applied = applied[:insert_idx] + patches + applied[insert_idx:] unapplied = [p for p in stack.patchorder.unapplied if p not in patches] iw = stack.repository.default_iw clean_iw = (not options.keep and iw) or None trans = transaction.StackTransaction(stack, 'sink', check_clean_iw = clean_iw) try: trans.reorder_patches(applied, unapplied, iw = iw, allow_interactive = True) except transaction.TransactionHalted: pass return trans.run(iw) stgit-0.17.1/stgit/commands/fold.py0000644002002200200220000000623311743516173017335 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import argparse, stack, git help = 'Integrate a GNU diff patch into the current patch' kind = 'patch' usage = ['[options] [--] []'] description = """ Apply the given GNU diff file (or the standard input) onto the top of the current patch. With the '--threeway' option, the patch is applied onto the bottom of the current patch and a three-way merge is 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', args = [argparse.commit], short = 'Use BASE instead of HEAD when applying the patch'), opt('-p', '--strip', type = 'int', metavar = 'N', short = 'Remove N leading slashes from diff paths (default 1)'), opt('--reject', action = 'store_true', short = 'Leave the rejected hunks in corresponding *.rej files')] directory = DirectoryHasRepository(log = True) def func(parser, options, args): """Integrate a GNU diff patch into the current patch """ if len(args) > 1: parser.error('incorrect number of arguments') check_local_changes() check_conflicts() check_head_top_equal(crt_series) if len(args) == 1: filename = args[0] else: filename = None current = crt_series.get_current() if not current: raise CmdException, 'No patches applied' if filename: if os.path.exists(filename): out.start('Folding patch "%s"' % filename) else: raise CmdException, 'No such file: %s' % filename else: out.start('Folding patch from stdin') if options.threeway: crt_patch = crt_series.get_patch(current) bottom = crt_patch.get_bottom() git.apply_patch(filename = filename, base = bottom, strip = options.strip, reject = options.reject) elif options.base: git.apply_patch(filename = filename, reject = options.reject, strip = options.strip, base = git_id(crt_series, options.base)) else: git.apply_patch(filename = filename, strip = options.strip, reject = options.reject) out.done() stgit-0.17.1/stgit/commands/next.py0000644002002200200220000000275511271344641017367 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 next patch' kind = 'stack' usage = [''] description = """ Print the name of the next patch.""" args = [] options = [ opt('-b', '--branch', args = [argparse.stg_branches], short = 'Use BRANCH instead of the default branch')] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Show the name of the next patch """ if len(args) != 0: parser.error('incorrect number of arguments') stack = directory.repository.get_stack(options.branch) unapplied = stack.patchorder.unapplied if unapplied: out.stdout(unapplied[0]) else: raise common.CmdException, 'No unapplied patches' stgit-0.17.1/stgit/commands/common.py0000644002002200200220000004500411743517572017704 0ustar cmarinascmarinas"""Function/variables common to all the commands """ __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os, os.path, re, email.Utils from stgit.exception import * from stgit.utils import * from stgit.out import * from stgit.run import * from stgit import stack, git, basedir from stgit.config import config, file_extensions from stgit.lib import stack as libstack from stgit.lib import git as libgit from stgit.lib import log # Command exception class class CmdException(StgException): pass # Utility functions def parse_rev(rev): """Parse a revision specification into its branch:patch parts. """ try: branch, patch = rev.split(':', 1) except ValueError: branch = None patch = rev return (branch, patch) def git_id(crt_series, rev): """Return the GIT id """ # TODO: remove this function once all the occurrences were converted # to git_commit() repository = libstack.Repository.default() return git_commit(rev, repository, crt_series.get_name()).sha1 def get_public_ref(branch_name): """Return the public ref of the branch.""" public_ref = config.get('branch.%s.public' % branch_name) if not public_ref: public_ref = 'refs/heads/%s.public' % branch_name return public_ref def git_commit(name, repository, branch_name = None): """Return the a Commit object if 'name' is a patch name or Git commit. The patch names allowed are in the form ':' and can be followed by standard symbols used by git rev-parse. If is '{base}', it represents the bottom of the stack. If is {public}, it represents the public branch corresponding to the stack as described in the 'publish' command. """ # Try a [branch:]patch name first branch, patch = parse_rev(name) if not branch: branch = branch_name or repository.current_branch_name # The stack base if patch.startswith('{base}'): base_id = repository.get_stack(branch).base.sha1 return repository.rev_parse(base_id + strip_prefix('{base}', patch)) elif patch.startswith('{public}'): public_ref = get_public_ref(branch) return repository.rev_parse(public_ref + strip_prefix('{public}', patch), discard_stderr = True) # Other combination of branch and patch try: return repository.rev_parse('patches/%s/%s' % (branch, patch), discard_stderr = True) except libgit.RepositoryException: pass # Try a Git commit try: return repository.rev_parse(name, discard_stderr = True) except libgit.RepositoryException: raise CmdException('%s: Unknown patch or revision name' % name) def color_diff_flags(): """Return the git flags for coloured diff output if the configuration and stdout allows.""" stdout_is_tty = (sys.stdout.isatty() and 'true') or 'false' if config.get_colorbool('color.diff', stdout_is_tty) == 'true': return ['--color'] else: return [] def check_local_changes(): if git.local_changes(): raise CmdException('local changes in the tree. Use "refresh" or' ' "reset --hard"') def check_head_top_equal(crt_series): if not crt_series.head_top_equal(): raise CmdException('HEAD and top are not the same. This can happen' ' if you modify a branch with git. "stg repair' ' --help" explains more about what to do next.') def check_conflicts(): if git.get_conflicts(): raise CmdException('Unsolved conflicts. Please fix the conflicts' ' then use "git add --update " or revert the' ' changes with "reset --hard".') def print_crt_patch(crt_series, branch = None): if not branch: patch = crt_series.get_current() else: patch = stack.Series(branch).get_current() if patch: out.info('Now at patch "%s"' % patch) else: out.info('No patches applied') def resolved_all(reset = None): conflicts = git.get_conflicts() git.resolved(conflicts, reset) def push_patches(crt_series, patches, check_merged = False): """Push multiple patches onto the stack. This function is shared between the push and pull commands """ forwarded = crt_series.forward_patches(patches) if forwarded > 1: out.info('Fast-forwarded patches "%s" - "%s"' % (patches[0], patches[forwarded - 1])) elif forwarded == 1: out.info('Fast-forwarded patch "%s"' % patches[0]) names = patches[forwarded:] # check for patches merged upstream if names and check_merged: out.start('Checking for patches merged upstream') merged = crt_series.merged_patches(names) out.done('%d found' % len(merged)) else: merged = [] for p in names: out.start('Pushing patch "%s"' % p) if p in merged: crt_series.push_empty_patch(p) out.done('merged upstream') else: modified = crt_series.push_patch(p) if crt_series.empty_patch(p): out.done('empty patch') elif modified: out.done('modified') else: out.done() def pop_patches(crt_series, patches, keep = False): """Pop the patches in the list from the stack. It is assumed that the patches are listed in the stack reverse order. """ if len(patches) == 0: out.info('Nothing to push/pop') else: p = patches[-1] if len(patches) == 1: out.start('Popping patch "%s"' % p) else: out.start('Popping patches "%s" - "%s"' % (patches[0], p)) crt_series.pop_patch(p, keep) out.done() def get_patch_from_list(part_name, patch_list): candidates = [full for full in patch_list if str.find(full, part_name) != -1] if len(candidates) >= 2: out.info('Possible patches:\n %s' % '\n '.join(candidates)) raise CmdException, 'Ambiguous patch name "%s"' % part_name elif len(candidates) == 1: return candidates[0] else: return None def parse_patches(patch_args, patch_list, boundary = 0, ordered = False): """Parse patch_args list for patch names in patch_list and return a list. The names can be individual patches and/or in the patch1..patch2 format. """ # in case it receives a tuple patch_list = list(patch_list) patches = [] for name in patch_args: pair = name.split('..') for p in pair: if p and not p in patch_list: raise CmdException, 'Unknown patch name: %s' % p if len(pair) == 1: # single patch name pl = pair elif len(pair) == 2: # patch range [p1]..[p2] # inclusive boundary if pair[0]: first = patch_list.index(pair[0]) else: first = -1 # exclusive boundary if pair[1]: last = patch_list.index(pair[1]) + 1 else: last = -1 # only cross the boundary if explicitly asked if not boundary: boundary = len(patch_list) if first < 0: if last <= boundary: first = 0 else: first = boundary if last < 0: if first < boundary: last = boundary else: last = len(patch_list) if last > first: pl = patch_list[first:last] else: pl = patch_list[(last - 1):(first + 1)] pl.reverse() else: raise CmdException, 'Malformed patch name: %s' % name for p in pl: if p in patches: raise CmdException, 'Duplicate patch name: %s' % p patches += pl if ordered: patches = [p for p in patch_list if p in patches] return patches def name_email(address): p = email.Utils.parseaddr(address) if p[1]: return p else: raise CmdException('Incorrect "name "/"email (name)" string: %s' % address) def name_email_date(address): p = parse_name_email_date(address) if p: return p else: raise CmdException('Incorrect "name date" string: %s' % address) def address_or_alias(addr_pair): """Return a name-email tuple the e-mail address is valid or look up the aliases in the config files. """ addr = addr_pair[1] if '@' in addr: # it's an e-mail address return addr_pair alias = config.get('mail.alias.' + addr) if alias: # it's an alias return name_email(alias) raise CmdException, 'unknown e-mail alias: %s' % addr def prepare_rebase(crt_series): # pop all patches applied = crt_series.get_applied() if len(applied) > 0: out.start('Popping all applied patches') crt_series.pop_patch(applied[0]) out.done() return applied def rebase(crt_series, target): try: tree_id = git_id(crt_series, target) except: # it might be that we use a custom rebase command with its own # target type tree_id = target if target: out.start('Rebasing to "%s"' % target) else: out.start('Rebasing to the default target') git.rebase(tree_id = tree_id) out.done() def post_rebase(crt_series, applied, nopush, merged): # memorize that we rebased to here crt_series._set_field('orig-base', git.get_head()) # push the patches back if not nopush: push_patches(crt_series, applied, merged) # # Patch description/e-mail/diff parsing # def __end_descr(line): return re.match('---\s*$', line) or re.match('diff -', line) or \ re.match('Index: ', line) or re.match('--- \w', line) def __split_descr_diff(string): """Return the description and the diff from the given string """ descr = diff = '' top = True for line in string.split('\n'): if top: if not __end_descr(line): descr += line + '\n' continue else: top = False diff += line + '\n' return (descr.rstrip(), diff) def __parse_description(descr): """Parse the patch description and return the new description and author information (if any). """ subject = body = '' authname = authemail = authdate = None descr_lines = [line.rstrip() for line in descr.split('\n')] if not descr_lines: raise CmdException, "Empty patch description" lasthdr = 0 end = len(descr_lines) descr_strip = 0 # Parse the patch header for pos in range(0, end): if not descr_lines[pos]: continue # check for a "From|Author:" line if re.match('\s*(?:from|author):\s+', descr_lines[pos], re.I): auth = re.findall('^.*?:\s+(.*)$', descr_lines[pos])[0] authname, authemail = name_email(auth) lasthdr = pos + 1 continue # check for a "Date:" line if re.match('\s*date:\s+', descr_lines[pos], re.I): authdate = re.findall('^.*?:\s+(.*)$', descr_lines[pos])[0] lasthdr = pos + 1 continue if subject: break # get the subject subject = descr_lines[pos][descr_strip:] if re.match('commit [\da-f]{40}$', subject): # 'git show' output, look for the real subject subject = '' descr_strip = 4 lasthdr = pos + 1 # get the body if lasthdr < end: body = '\n' + '\n'.join(l[descr_strip:] for l in descr_lines[lasthdr:]) return (subject + body, authname, authemail, authdate) def parse_mail(msg): """Parse the message object and return (description, authname, authemail, authdate, diff) """ from email.Header import decode_header, make_header def __decode_header(header): """Decode a qp-encoded e-mail header as per rfc2047""" try: words_enc = decode_header(header) hobj = make_header(words_enc) except Exception, ex: raise CmdException, 'header decoding error: %s' % str(ex) return unicode(hobj).encode('utf-8') # parse the headers if msg.has_key('from'): authname, authemail = name_email(__decode_header(msg['from'])) else: authname = authemail = None # '\n\t' can be found on multi-line headers descr = __decode_header(msg['subject']) descr = re.sub('\n[ \t]*', ' ', descr) authdate = msg['date'] # remove the '[*PATCH*]' expression in the subject if descr: descr = re.findall('^(\[.*?[Pp][Aa][Tt][Cc][Hh].*?\])?\s*(.*)$', descr)[0][1] else: raise CmdException, 'Subject: line not found' # the rest of the message msg_text = '' for part in msg.walk(): if part.get_content_type() in ['text/plain', 'application/octet-stream']: msg_text += part.get_payload(decode = True) rem_descr, diff = __split_descr_diff(msg_text) if rem_descr: descr += '\n\n' + rem_descr # parse the description for author information descr, descr_authname, descr_authemail, descr_authdate = \ __parse_description(descr) if descr_authname: authname = descr_authname if descr_authemail: authemail = descr_authemail if descr_authdate: authdate = descr_authdate return (descr, authname, authemail, authdate, diff) def parse_patch(text, contains_diff): """Parse the input text and return (description, authname, authemail, authdate, diff) """ if contains_diff: (text, diff) = __split_descr_diff(text) else: diff = None (descr, authname, authemail, authdate) = __parse_description(text) # we don't yet have an agreed place for the creation date. # Just return None return (descr, authname, authemail, authdate, diff) def readonly_constant_property(f): """Decorator that converts a function that computes a value to an attribute that returns the value. The value is computed only once, the first time it is accessed.""" def new_f(self): n = '__' + f.__name__ if not hasattr(self, n): setattr(self, n, f(self)) return getattr(self, n) return property(new_f) def update_commit_data(cd, options): """Return a new CommitData object updated according to the command line options.""" # Set the commit message from commandline. if options.message is not None: cd = cd.set_message(options.message) # Modify author data. cd = cd.set_author(options.author(cd.author)) # Add Signed-off-by: or similar. if options.sign_str != None: sign_str = options.sign_str else: sign_str = config.get("stgit.autosign") if sign_str != None: cd = cd.set_message( add_sign_line(cd.message, sign_str, cd.committer.name, cd.committer.email)) # Let user edit the commit message manually, unless # --save-template or --message was specified. if not getattr(options, 'save_template', None) and options.message is None: cd = cd.set_message(edit_string(cd.message, '.stgit-new.txt')) return cd class DirectoryException(StgException): pass class _Directory(object): def __init__(self, needs_current_series = True, log = True): self.needs_current_series = needs_current_series self.log = log @readonly_constant_property def git_dir(self): try: return Run('git', 'rev-parse', '--git-dir' ).discard_stderr().output_one_line() except RunException: raise DirectoryException('No git repository found') @readonly_constant_property def __topdir_path(self): try: lines = Run('git', 'rev-parse', '--show-cdup' ).discard_stderr().output_lines() if len(lines) == 0: return '.' elif len(lines) == 1: return lines[0] else: raise RunException('Too much output') except RunException: raise DirectoryException('No git repository found') @readonly_constant_property def is_inside_git_dir(self): return { 'true': True, 'false': False }[Run('git', 'rev-parse', '--is-inside-git-dir' ).output_one_line()] @readonly_constant_property def is_inside_worktree(self): return { 'true': True, 'false': False }[Run('git', 'rev-parse', '--is-inside-work-tree' ).output_one_line()] def cd_to_topdir(self): os.chdir(self.__topdir_path) def write_log(self, msg): if self.log: log.compat_log_entry(msg) class DirectoryAnywhere(_Directory): def setup(self): pass class DirectoryHasRepository(_Directory): def setup(self): self.git_dir # might throw an exception log.compat_log_external_mods() class DirectoryInWorktree(DirectoryHasRepository): def setup(self): DirectoryHasRepository.setup(self) if not self.is_inside_worktree: raise DirectoryException('Not inside a git worktree') class DirectoryGotoToplevel(DirectoryInWorktree): def setup(self): DirectoryInWorktree.setup(self) self.cd_to_topdir() class DirectoryHasRepositoryLib(_Directory): """For commands that use the new infrastructure in stgit.lib.*.""" def __init__(self): self.needs_current_series = False self.log = False # stgit.lib.transaction handles logging def setup(self): # This will throw an exception if we don't have a repository. self.repository = libstack.Repository.default() stgit-0.17.1/stgit/commands/redo.py0000644002002200200220000000406211271344641017333 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2008, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 log, transaction help = 'Undo the last undo operation' kind = 'stack' usage = [''] description = """ If the last command was an undo, reset the patch stack to the state it had before the undo. Consecutive invocations of "stg redo" will undo 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'), opt('--hard', action = 'store_true', short = 'Discard changes in your index/worktree')] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): stack = directory.repository.current_stack if options.number < 1: raise common.CmdException('Bad number of undos to redo') state = log.undo_state(stack, -options.number) trans = transaction.StackTransaction(stack, 'redo %d' % options.number, discard_changes = options.hard, allow_bad_head = True) try: log.reset_stack(trans, stack.repository.default_iw, state) except transaction.TransactionHalted: pass return trans.run(stack.repository.default_iw, allow_bad_head = True) stgit-0.17.1/stgit/commands/series.py0000644002002200200220000002031011743516173017673 0ustar cmarinascmarinas __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from stgit.argparse import opt 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' usage = ['[options] [--] []'] description = """ Show all the patches in the series, or just those in the given range, ordered from top to bottom. The applied patches are prefixed with a +++ (except the current patch, which is prefixed with a +>+), the unapplied patches with a +-+, and the hidden patches 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', 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'), opt('-A', '--applied', action = 'store_true', short = 'Show the applied patches only'), opt('-U', '--unapplied', action = 'store_true', short = 'Show the unapplied patches only'), opt('-H', '--hidden', action = 'store_true', short = 'Show the hidden patches only'), 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'), opt('-d', '--description', action = 'store_true', short = 'Show a short description for each patch'), opt('--author', action = 'store_true', short = 'Show the author name for each patch'), opt('-e', '--empty', action = 'store_true', short = 'Check whether patches are empty', long = """ Before the +++, +>+, +-+, and +!+ prefixes, print a column that contains either +0+ (for empty patches) or a space (for non-empty patches)."""), opt('--showbranch', action = 'store_true', short = 'Append the branch name to the listed patches'), opt('--noprefix', action = 'store_true', short = 'Do not show the patch status prefix'), opt('-s', '--short', action = 'store_true', short = 'List just the patches around the topmost patch')] directory = common.DirectoryHasRepositoryLib() def __get_description(stack, patch): """Extract and return a patch's short description """ cd = stack.patches.get(patch).commit.data descr = cd.message.strip() descr_lines = descr.split('\n') return descr_lines[0].rstrip() def __get_author(stack, patch): """Extract and return a patch's short description """ cd = stack.patches.get(patch).commit.data return cd.author.name def __render_text(text, effects): _effects = { 'none' : 0, 'bright' : 1, 'dim' : 2, 'black_foreground' : 30, 'red_foreground' : 31, 'green_foreground' : 32, 'yellow_foreground' : 33, 'blue_foreground' : 34, 'magenta_foreground' : 35, 'cyan_foreground' : 36, 'white_foreground' : 37, 'black_background' : 40, 'red_background' : 41, 'green_background' : 42, 'yellow_background' : 44, 'blue_background' : 44, 'magenta_background' : 45, 'cyan_background' : 46, 'white_background' : 47 } start = [str(_effects[e]) for e in effects.split() if e in _effects] start = '\033[' + ';'.join(start) + 'm' stop = '\033[' + str(_effects['none']) + 'm' return ''.join([start, text, stop]) def __print_patch(stack, patch, branch_str, prefix, length, options, effects): """Print a patch name, description and various markers. """ if options.noprefix: prefix = '' elif options.empty: if stack.patches.get(patch).is_empty(): prefix = '0' + prefix else: prefix = ' ' + prefix patch_str = branch_str + patch if options.description or options.author: patch_str = patch_str.ljust(length) if options.description: output = prefix + patch_str + ' # ' + __get_description(stack, patch) elif options.author: output = prefix + patch_str + ' # ' + __get_author(stack, patch) else: output = prefix + patch_str if not effects: out.stdout(output) else: out.stdout(__render_text(output, effects)) def func(parser, options, args): """Show the patch series """ if options.all and options.short: raise common.CmdException, 'combining --all and --short is meaningless' stack = directory.repository.get_stack(options.branch) if options.missing: cmp_stack = stack stack = directory.repository.get_stack(options.missing) # current series patches applied = unapplied = hidden = () if options.applied or options.unapplied or options.hidden: if options.all: raise common.CmdException, \ '--all cannot be used with --applied/unapplied/hidden' if options.applied: applied = stack.patchorder.applied if options.unapplied: unapplied = stack.patchorder.unapplied if options.hidden: hidden = stack.patchorder.hidden elif options.all: applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied hidden = stack.patchorder.hidden else: applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied if options.missing: cmp_patches = cmp_stack.patchorder.all else: cmp_patches = () # the filtering range covers the whole series if args: show_patches = parse_patches(args, applied + unapplied + hidden, len(applied)) else: show_patches = applied + unapplied + hidden # missing filtering show_patches = [p for p in show_patches if p not in cmp_patches] # filter the patches applied = [p for p in applied if p in show_patches] unapplied = [p for p in unapplied if p in show_patches] hidden = [p for p in hidden if p in show_patches] if options.short: nr = int(config.get('stgit.shortnr')) if len(applied) > nr: applied = applied[-(nr+1):] n = len(unapplied) if n > nr: unapplied = unapplied[:nr] elif n < nr: hidden = hidden[:nr-n] patches = applied + unapplied + hidden if options.count: out.stdout(len(patches)) return if not patches: return if options.showbranch: branch_str = stack.name + ':' else: branch_str = '' max_len = 0 if len(patches) > 0: max_len = max([len(i + branch_str) for i in patches]) if applied: for p in applied[:-1]: __print_patch(stack, p, branch_str, '+ ', max_len, options, config.get("stgit.color.applied")) __print_patch(stack, applied[-1], branch_str, '> ', max_len, options, config.get("stgit.color.current")) for p in unapplied: __print_patch(stack, p, branch_str, '- ', max_len, options, config.get("stgit.color.unapplied")) for p in hidden: __print_patch(stack, p, branch_str, '! ', max_len, options, config.get("stgit.color.hidden")) stgit-0.17.1/stgit/commands/unhide.py0000644002002200200220000000360411743516173017664 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2009, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 from stgit.argparse import opt help = 'Unhide a hidden patch' kind = 'stack' usage = ['[options] [--] '] 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', args = [argparse.stg_branches], short = 'Use BRANCH instead of the default branch')] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Unhide a range of patch in the series.""" stack = directory.repository.current_stack trans = transaction.StackTransaction(stack, 'unhide') if not args: parser.error('No patches specified') patches = common.parse_patches(args, trans.all_patches) for p in patches: if not p in trans.hidden: raise common.CmdException('Patch "%s" not hidden' % p) applied = list(trans.applied) unapplied = trans.unapplied + patches hidden = [p for p in trans.hidden if not p in set(patches)] trans.reorder_patches(applied, unapplied, hidden) return trans.run() stgit-0.17.1/stgit/commands/rename.py0000644002002200200220000000347511743516173017665 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import argparse, stack, git help = 'Rename a patch' kind = 'patch' usage = ['[options] [--] [oldpatch] '] description = """ Rename into in a series. If is not given, the top-most patch will be renamed.""" args = [argparse.applied_patches, argparse.unapplied_patches, argparse.hidden_patches] options = [ opt('-b', '--branch', args = [argparse.stg_branches], short = 'use BRANCH instead of the default one')] directory = DirectoryHasRepository(log = True) def func(parser, options, args): """Rename a patch in the series """ crt = crt_series.get_current() if len(args) == 2: old, new = args elif len(args) == 1: if not crt: raise CmdException, "No applied top patch to rename exists." old, [new] = crt, args else: parser.error('incorrect number of arguments') out.start('Renaming patch "%s" to "%s"' % (old, new)) crt_series.rename_patch(old, new) out.done() stgit-0.17.1/stgit/commands/uncommit.py0000644002002200200220000001312412101713074020225 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2006, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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.out import * from stgit import argparse, utils help = 'Turn regular git commits into StGit patches' kind = 'stack' usage = ['[--] [ ...]', '-n NUM [--] []', '-t [-x]'] description = """ Take one or more git commits at the base of the current stack and turn them into StGIT patches. The new patches are created as applied patches at the bottom of the stack. This is the opposite of 'stg commit'. By default, the number of patches to uncommit is determined by the number of patch names provided on the command line. First name is used for the first patch to uncommit, i.e. for the newest patch. The -n/--number option specifies the number of patches to uncommit. In this case, at most one patch name may be specified. It is used as prefix to which the patch number is appended. If no patch names are provided on the command line, StGIT automatically generates them based on the first line of the patch description. The -t/--to option specifies that all commits up to and including the 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', args = [argparse.commit], short = 'Uncommit to the specified commit'), opt('-x', '--exclusive', action = 'store_true', short = 'Exclude the commit specified by the --to option')] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Uncommit a number of patches. """ stack = directory.repository.current_stack if options.to: if options.number: parser.error('cannot give both --to and --number') if len(args) != 0: parser.error('cannot specify patch name with --to') patch_nr = patchnames = None to_commit = stack.repository.rev_parse(options.to) # check whether the --to commit is on a different branch merge_bases = directory.repository.get_merge_bases(to_commit, stack.base) if not to_commit in merge_bases: to_commit = merge_bases[0] options.exclusive = True elif options.number: if options.number <= 0: parser.error('invalid value passed to --number') patch_nr = options.number if len(args) == 0: patchnames = None elif len(args) == 1: # prefix specified patchnames = ['%s%d' % (args[0], i) for i in xrange(patch_nr, 0, -1)] else: parser.error('when using --number, specify at most one patch name') elif len(args) == 0: patchnames = None patch_nr = 1 else: patchnames = args patch_nr = len(patchnames) def check_and_append(c, n): next = n.data.parents; try: [next] = next except ValueError: out.done() raise common.CmdException( 'Trying to uncommit %s, which does not have exactly one parent' % n.sha1) return c.append(n) commits = [] next_commit = stack.base if patch_nr: out.start('Uncommitting %d patches' % patch_nr) for i in xrange(patch_nr): check_and_append(commits, next_commit) next_commit = next_commit.data.parent else: if options.exclusive: out.start('Uncommitting to %s (exclusive)' % to_commit.sha1) else: out.start('Uncommitting to %s' % to_commit.sha1) while True: if next_commit == to_commit: if not options.exclusive: check_and_append(commits, next_commit) break check_and_append(commits, next_commit) next_commit = next_commit.data.parent patch_nr = len(commits) taken_names = set(stack.patchorder.all) if patchnames: for pn in patchnames: if pn in taken_names: raise common.CmdException('Patch name "%s" already taken' % pn) taken_names.add(pn) else: patchnames = [] for c in reversed(commits): pn = utils.make_patch_name(c.data.message, lambda pn: pn in taken_names) patchnames.append(pn) taken_names.add(pn) patchnames.reverse() trans = transaction.StackTransaction(stack, 'uncommit', allow_conflicts = True, allow_bad_head = True) for commit, pn in zip(commits, patchnames): trans.patches[pn] = commit trans.applied = list(reversed(patchnames)) + trans.applied trans.run(set_head = False) out.done() stgit-0.17.1/stgit/commands/repair.py0000644002002200200220000002035611747465264017705 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2006, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit.run import * from stgit import stack, git help = 'Fix StGit metadata if branch was modified with git commands' kind = 'stack' usage = [''] description = """ If you modify an StGit stack (branch) with some git commands -- such as commit, pull, merge, and rebase -- you will leave the StGit metadata in an inconsistent state. In that situation, you have two options: 1. Use "stg undo" to undo the effect of the git commands. (If you know what you are doing and want more control, "git reset" or similar will work too.) 2. Use "stg repair". This will fix up the StGit metadata to accommodate the modifications to the branch. Specifically, it will do the following: * If you have made regular git commits on top of your stack of StGit patches, "stg repair" makes new StGit patches out of them, preserving their contents. * However, merge commits cannot become patches; if you have committed a merge on top of your stack, "repair" will simply mark all patches below the merge unapplied, since they are no longer reachable. If this is not what you want, use "stg undo" to get rid of the merge and run "stg repair" again. * The applied patches are supposed to be precisely those that are reachable from the branch head. If you have used e.g. "git reset" to move the head, some applied patches may no longer be reachable, and some unapplied patches may have become reachable. "stg repair" will correct the appliedness of such patches. "stg repair" will fix these inconsistencies reliably, so as long as you like what it does, you have no reason to avoid causing them in the first place. For example, you might find it convenient to make commits with a graphical tool and then have "stg repair" make proper patches of the commits. 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) class Commit(object): def __init__(self, id): self.id = id self.parents = set() self.children = set() self.patch = None self.__commit = None def __get_commit(self): if not self.__commit: self.__commit = git.get_commit(self.id) return self.__commit commit = property(__get_commit) def __str__(self): if self.patch: return '%s (%s)' % (self.id, self.patch) else: return self.id def __repr__(self): return '<%s>' % str(self) def read_commit_dag(branch): out.start('Reading commit DAG') commits = {} patches = set() for line in Run('git', 'rev-list', '--parents', '--all').output_lines(): cs = line.split() for id in cs: if not id in commits: commits[id] = Commit(id) for id in cs[1:]: commits[cs[0]].parents.add(commits[id]) commits[id].children.add(commits[cs[0]]) for line in Run('git', 'show-ref').output_lines(): id, ref = line.split() m = re.match(r'^refs/patches/%s/(.+)$' % re.escape(branch), ref) if m and not m.group(1).endswith('.log'): c = commits[id] c.patch = m.group(1) patches.add(c) out.done() return commits, patches def func(parser, options, args): """Repair inconsistencies in StGit metadata.""" orig_applied = crt_series.get_applied() orig_unapplied = crt_series.get_unapplied() orig_hidden = crt_series.get_hidden() if crt_series.get_protected(): raise CmdException( 'This branch is protected. Modification is not permitted.') # Find commits that aren't patches, and applied patches. head = git.get_commit(git.get_head()).get_id_hash() commits, patches = read_commit_dag(crt_series.get_name()) c = commits[head] patchify = [] # commits to definitely patchify maybe_patchify = [] # commits to patchify if we find a patch below them applied = [] while len(c.parents) == 1: parent, = c.parents if c.patch: applied.append(c) patchify.extend(maybe_patchify) maybe_patchify = [] else: maybe_patchify.append(c) c = parent applied.reverse() patchify.reverse() # Find patches hidden behind a merge. merge = c todo = set([c]) seen = set() hidden = set() while todo: c = todo.pop() seen.add(c) todo |= c.parents - seen if c.patch: hidden.add(c) if hidden: out.warn(('%d patch%s are hidden below the merge commit' % (len(hidden), ['es', ''][len(hidden) == 1])), '%s,' % merge.id, 'and will be considered unapplied.') # Make patches of any linear sequence of commits on top of a patch. names = set(p.patch for p in patches) def name_taken(name): return name in names if applied and patchify: out.start('Creating %d new patch%s' % (len(patchify), ['es', ''][len(patchify) == 1])) for p in patchify: name = make_patch_name(p.commit.get_log(), name_taken) out.info('Creating patch %s from commit %s' % (name, p.id)) aname, amail, adate = name_email_date(p.commit.get_author()) cname, cmail, cdate = name_email_date(p.commit.get_committer()) parent, = p.parents crt_series.new_patch( name, can_edit = False, commit = False, top = p.id, bottom = parent.id, message = p.commit.get_log(), author_name = aname, author_email = amail, author_date = adate, committer_name = cname, committer_email = cmail) p.patch = name applied.append(p) names.add(name) out.done() # Figure out hidden orig_patches = orig_applied + orig_unapplied + orig_hidden orig_applied_name_set = set(orig_applied) orig_unapplied_name_set = set(orig_unapplied) orig_hidden_name_set = set(orig_hidden) orig_patches_name_set = set(orig_patches) hidden = [p for p in patches if p.patch in orig_hidden_name_set] # Write the applied/unapplied files. out.start('Checking patch appliedness') unapplied = patches - set(applied) - set(hidden) applied_name_set = set(p.patch for p in applied) unapplied_name_set = set(p.patch for p in unapplied) hidden_name_set = set(p.patch for p in hidden) patches_name_set = set(p.patch for p in patches) for name in orig_patches_name_set - patches_name_set: out.info('%s is gone' % name) for name in applied_name_set - orig_applied_name_set: out.info('%s is now applied' % name) for name in unapplied_name_set - orig_unapplied_name_set: out.info('%s is now unapplied' % name) for name in hidden_name_set - orig_hidden_name_set: out.info('%s is now hidden' % name) orig_order = dict(zip(orig_patches, xrange(len(orig_patches)))) def patchname_cmp(p1, p2): i1 = orig_order.get(p1, len(orig_order)) i2 = orig_order.get(p2, len(orig_order)) return cmp((i1, p1), (i2, p2)) crt_series.set_applied(p.patch for p in applied) crt_series.set_unapplied(sorted(unapplied_name_set, cmp = patchname_cmp)) crt_series.set_hidden(sorted(hidden_name_set, cmp = patchname_cmp)) out.done() stgit-0.17.1/stgit/commands/id.py0000644002002200200220000000353711743516173017011 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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' usage = ['[options] [--] []'] description = """ Print the SHA1 value of a Git id (defaulting to HEAD). In addition to the standard Git id's like heads and tags, this command also accepts '[:]' for the id of a patch, '[:]\{base\}' for the base of the stack and '[:]\{public\}' for the public branch corresponding to the stack (see the 'publish' command for details). If no branch is specified, it defaults to the current one. The bottom of a patch is accessible with the '[:]^' format.""" args = [argparse.applied_patches, argparse.unapplied_patches, argparse.hidden_patches] options = [] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Show the applied patches """ if len(args) == 0: id_str = 'HEAD' elif len(args) == 1: id_str = args[0] else: parser.error('incorrect number of arguments') out.stdout(common.git_commit(id_str, directory.repository).sha1) stgit-0.17.1/stgit/commands/float.py0000644002002200200220000000554511743516173017523 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2006, Robin Rosenberg Modified by Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import re import sys from stgit.argparse import opt from stgit.commands import common from stgit.lib import transaction from stgit import argparse help = 'Push patches to the top, even if applied' kind = 'stack' usage = ['[--] ', '-s '] description = """ Push a patch or a range of patches to the top even if applied. The 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', metavar = 'FILE', short = 'Rearrange according to the series FILE') ] + argparse.keep_option() directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Reorder patches to make the named patch the topmost one. """ if options.series and args: parser.error(' cannot be used with --series') elif not options.series and not args: parser.error('incorrect number of arguments') stack = directory.repository.current_stack if options.series: if options.series == '-': f = sys.stdin else: f = file(options.series) patches = [] for line in f: patch = re.sub('#.*$', '', line).strip() if patch: patches.append(patch) else: patches = common.parse_patches(args, stack.patchorder.all) if not patches: raise common.CmdException('No patches to float') applied = [p for p in stack.patchorder.applied if p not in patches] + \ patches unapplied = [p for p in stack.patchorder.unapplied if not p in patches] iw = stack.repository.default_iw clean_iw = (not options.keep and iw) or None trans = transaction.StackTransaction(stack, 'float', check_clean_iw = clean_iw) try: trans.reorder_patches(applied, unapplied, iw = iw) except transaction.TransactionHalted: pass return trans.run(iw) stgit-0.17.1/stgit/commands/push.py0000644002002200200220000001015311743516173017364 0ustar cmarinascmarinas __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 from stgit.argparse import opt help = 'Push one or more patches onto the stack' kind = 'stack' usage = ['[options] [--] [] [] [..]'] description = """ Push one or more patches (defaulting to the first unapplied one) onto the stack. The 'push' operation allows patch reordering by commuting them with the three-way merge algorithm. If there are conflicts while pushing a patch, those conflicts are written to the work tree, and the command halts. Conflicts raised during the push operation have to be fixed and the 'git add --update' command run (alternatively, you may undo the 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'), opt('-n', '--number', type = 'int', short = 'Push the specified number of patches', long = ''' Push the specified number of patches. With a negative number, push all but that many patches.'''), opt('--reverse', action = 'store_true', short = 'Push the patches in reverse order'), opt('--set-tree', action = 'store_true', short = 'Push the patch with the original tree', long = """ Push the patches, but don't perform a merge. Instead, the resulting tree will be identical to the tree that the patch previously created. This can be useful when splitting a patch by first popping the patch and creating a new patch with some of the changes. Pushing the original patch with '--set-tree' will avoid conflicts and only the remaining changes will be in the patch.""") ] + argparse.keep_option() + argparse.merged_option() directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Pushes the given patches or the first unapplied onto the stack.""" stack = directory.repository.current_stack iw = stack.repository.default_iw clean_iw = (not options.keep and iw) or None trans = transaction.StackTransaction(stack, 'push', check_clean_iw = clean_iw) if options.number == 0: # explicitly allow this without any warning/error message return if not trans.unapplied: raise common.CmdException('No patches to push') if options.all: patches = list(trans.unapplied) elif options.number is not None: patches = trans.unapplied[:options.number] elif not args: patches = [trans.unapplied[0]] else: patches = common.parse_patches(args, trans.unapplied) if not patches: raise common.CmdException('No patches to push') if options.reverse: patches.reverse() if options.set_tree: for pn in patches: trans.push_tree(pn) else: try: if options.merged: merged = set(trans.check_merged(patches)) else: merged = set() for pn in patches: trans.push_patch(pn, iw, allow_interactive = True, already_merged = pn in merged) except transaction.TransactionHalted: pass return trans.run(iw) stgit-0.17.1/stgit/commands/reset.py0000644002002200200220000000515211743516173017532 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2008, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 git, log, transaction from stgit.out import out from stgit import argparse, utils help = 'Reset the patch stack to an earlier state' kind = 'stack' usage = ['[options] [--] [ []]'] description = """ Reset the patch stack to an earlier state. If no state is specified, reset only the changes in the worktree. The state is specified with a commit id from a stack log; "stg log" lets you view this log, and "stg reset" lets you reset to any state you see in the log. 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')] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): stack = directory.repository.current_stack iw = stack.repository.default_iw if len(args) >= 1: ref, patches = args[0], args[1:] state = log.get_log_entry(stack.repository, ref, stack.repository.rev_parse(ref)) elif options.hard: iw.checkout_hard(stack.head.data.tree) return utils.STGIT_SUCCESS else: raise common.CmdException('Wrong options or number of arguments') trans = transaction.StackTransaction(stack, 'reset', discard_changes = options.hard, allow_bad_head = True) try: if patches: log.reset_stack_partially(trans, iw, state, patches) else: log.reset_stack(trans, iw, state) except transaction.TransactionHalted: pass return trans.run(iw, allow_bad_head = not patches) stgit-0.17.1/stgit/commands/delete.py0000644002002200200220000000660311743516173017654 0ustar cmarinascmarinas __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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' usage = ['[options] [--] [] [..]'] description = """ Delete the patches passed as arguments.""" args = [argparse.patch_range(argparse.applied_patches, argparse.unapplied_patches)] options = [ opt('--spill', action = 'store_true', short = 'Spill patch contents to worktree and index', long = """ Delete the patches, but do not touch the index and worktree. This only works with applied patches at the top of the stack. The effect is to "spill" the patch contents into the index and worktree. This can be useful e.g. if you want to split a patch into several smaller pieces."""), opt('-b', '--branch', args = [argparse.stg_branches], short = 'Use BRANCH instead of the default branch'), opt('-t', '--top', action = 'store_true', short = 'Delete top patch'),] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Delete one or more patches.""" stack = directory.repository.get_stack(options.branch) if options.branch: iw = None # can't use index/workdir to manipulate another branch else: iw = stack.repository.default_iw if args and options.top: parser.error('Either --top or patches must be specified') elif args: patches = set(common.parse_patches(args, list(stack.patchorder.all), len(stack.patchorder.applied))) elif options.top: applied = stack.patchorder.applied if applied: patches = set([applied[-1]]) else: raise common.CmdException, 'No patches applied' else: parser.error('No patches specified') if options.spill: if set(stack.patchorder.applied[-len(patches):]) != patches: parser.error('Can only spill topmost applied patches') iw = None # don't touch index+worktree def allow_conflicts(trans): # Allow conflicts if the topmost patch stays the same. if stack.patchorder.applied: return (trans.applied and trans.applied[-1] == stack.patchorder.applied[-1]) else: return not trans.applied trans = transaction.StackTransaction(stack, 'delete', allow_conflicts = allow_conflicts) try: to_push = trans.delete_patches(lambda pn: pn in patches) for pn in to_push: trans.push_patch(pn, iw) except transaction.TransactionHalted: pass return trans.run(iw) stgit-0.17.1/stgit/commands/goto.py0000644002002200200220000000504311743516173017357 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2006, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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 from stgit.argparse import opt help = 'Push or pop patches to the given one' kind = 'stack' usage = ['[options] [--] '] 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 = argparse.keep_option() + argparse.merged_option() directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): if len(args) != 1: parser.error('incorrect number of arguments') patch = args[0] stack = directory.repository.current_stack iw = stack.repository.default_iw clean_iw = (not options.keep and iw) or None trans = transaction.StackTransaction(stack, 'goto', check_clean_iw = clean_iw) if not patch in trans.all_patches: candidate = common.get_patch_from_list(patch, trans.all_patches) if candidate is None: raise common.CmdException('Patch "%s" does not exist' % patch) patch = candidate if patch in trans.applied: to_pop = set(trans.applied[trans.applied.index(patch)+1:]) assert not trans.pop_patches(lambda pn: pn in to_pop) elif patch in trans.unapplied: try: to_push = trans.unapplied[:trans.unapplied.index(patch)+1] if options.merged: merged = set(trans.check_merged(to_push)) else: merged = set() for pn in to_push: trans.push_patch(pn, iw, allow_interactive = True, already_merged = pn in merged) except transaction.TransactionHalted: pass else: raise common.CmdException('Cannot goto a hidden patch') return trans.run(iw) stgit-0.17.1/stgit/commands/sync.py0000644002002200200220000001266211743516173017370 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2006, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os import stgit.commands.common from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import argparse, stack, git help = 'Synchronise patches with a branch or a series' kind = 'patch' usage = ['[options] [--] [] [] [..]'] description = """ For each of the specified patches perform a three-way merge with the same patch in the specified branch or series. The command can be used 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', args = [argparse.stg_branches], short = 'Syncronise patches with BRANCH'), opt('-s', '--series', args = [argparse.files], short = 'Syncronise patches with SERIES')] directory = DirectoryGotoToplevel(log = True) def __check_all(): check_local_changes() check_conflicts() check_head_top_equal(crt_series) def __branch_merge_patch(remote_series, pname): """Merge a patch from a remote branch into the current tree. """ patch = remote_series.get_patch(pname) git.merge_recursive(patch.get_bottom(), git.get_head(), patch.get_top()) def __series_merge_patch(base, patchdir, pname): """Merge a patch file with the given StGIT patch. """ patchfile = os.path.join(patchdir, pname) git.apply_patch(filename = patchfile, base = base) def func(parser, options, args): """Synchronise a range of patches """ if options.ref_branch: remote_series = stack.Series(options.ref_branch) if options.ref_branch == crt_series.get_name(): raise CmdException, 'Cannot synchronise with the current branch' remote_patches = remote_series.get_applied() # the merge function merge_patch(patch, pname) merge_patch = lambda patch, pname: \ __branch_merge_patch(remote_series, pname) elif options.series: patchdir = os.path.dirname(options.series) remote_patches = [] f = file(options.series) for line in f: p = re.sub('#.*$', '', line).strip() if not p: continue remote_patches.append(p) f.close() # the merge function merge_patch(patch, pname) merge_patch = lambda patch, pname: \ __series_merge_patch(patch.get_bottom(), patchdir, pname) else: raise CmdException, 'No remote branch or series specified' applied = crt_series.get_applied() unapplied = crt_series.get_unapplied() if options.all: patches = applied elif len(args) != 0: patches = parse_patches(args, applied + unapplied, len(applied), ordered = True) elif applied: patches = [crt_series.get_current()] else: parser.error('no patches applied') if not patches: raise CmdException, 'No patches to synchronise' __check_all() # only keep the patches to be synchronised sync_patches = [p for p in patches if p in remote_patches] if not sync_patches: raise CmdException, 'No common patches to be synchronised' # pop to the one before the first patch to be synchronised first_patch = sync_patches[0] if first_patch in applied: to_pop = applied[applied.index(first_patch) + 1:] if to_pop: pop_patches(crt_series, to_pop[::-1]) pushed = [first_patch] else: to_pop = [] pushed = [] popped = to_pop + [p for p in patches if p in unapplied] for p in pushed + popped: if p in popped: # push this patch push_patches(crt_series, [p]) if p not in sync_patches: # nothing to synchronise continue # the actual sync out.start('Synchronising "%s"' % p) patch = crt_series.get_patch(p) bottom = patch.get_bottom() top = patch.get_top() # reset the patch backup information. patch.set_top(top, backup = True) # the actual merging (either from a branch or an external file) merge_patch(patch, p) if git.local_changes(verbose = False): # index (cache) already updated by the git merge. The # backup information was already reset above crt_series.refresh_patch(cache_update = False, backup = False, log = 'sync') out.done('updated') else: out.done() stgit-0.17.1/stgit/commands/publish.py0000644002002200200220000002015211743516173020053 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2009, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from stgit import argparse from stgit.argparse import opt from stgit.commands import common from stgit.config import config from stgit.lib import git, stack, transaction from stgit.out import out from stgit import utils help = 'Push the stack changes to a merge-friendly branch' kind = 'stack' usage = ['[options] [--] [branch]'] description = """ This command commits a set of changes on a separate (called public) branch based on the modifications of the given or current stack. The history of the public branch is not re-written, making it merge-friendly and feasible for publishing. The heads of the stack and public branch may be different but the corresponding tree objects are always the same. If the trees of the stack and public branch are different (otherwise the command has no effect), StGit first checks for a rebase of the stack since the last publishing. If a rebase is detected, StGit creates a commit on the public branch corresponding to a merge between the new stack base and the latest public head. If no rebasing was detected, StGit checks for new patches that may have been created on top of the stack since the last publishing. If new patches are found and are not empty, they are checked into the public branch keeping the same commit information (e.g. log message, author, committer, date). If the above tests fail (e.g. patches modified or removed), StGit creates a new commit on the public branch having the same tree as the stack but the public head as its parent. The editor will be invoked if no "--message" option is given. It is recommended that stack modifications falling in different categories as described above are separated by a publish command in order to keep the public branch history cleaner (otherwise StGit would generate a big commit including several stack modifications). The '--unpublished' option can be used to check if there are applied patches that have not been published to the public branch. This is done by trying to revert the patches in the public tree (similar to the 'push --merged' detection). The '--last' option tries to find the last published patch by checking the SHA1 of the patch tree agains the public tree. This may fail if the stack was rebased since the last publish command. The public branch name can be set via the branch..public configuration variable (defaulting to ".public"). """ args = [argparse.all_branches] options = [ opt('-b', '--branch', args = [argparse.stg_branches], short = 'Use BRANCH instead of the default branch'), opt('-l', '--last', action = 'store_true', short = 'Show the last published patch'), opt('-u', '--unpublished', action = 'store_true', short = 'Show applied patches that have not been published') ] + (argparse.author_options() + argparse.message_options(save_template = False) + argparse.sign_options()) directory = common.DirectoryHasRepositoryLib() def __create_commit(repository, tree, parents, options, message = ''): """Return a new Commit object.""" cd = git.CommitData( tree = tree, parents = parents, message = message, author = git.Person.author(), committer = git.Person.committer()) cd = common.update_commit_data(cd, options) return repository.commit(cd) def __get_published(stack, tree): """Check the patches that were already published.""" trans = transaction.StackTransaction(stack, 'publish') published = trans.check_merged(trans.applied, tree = tree, quiet = True) trans.abort() return published def __get_last(stack, tree): """Return the name of the last published patch.""" for p in reversed(stack.patchorder.applied): pc = stack.patches.get(p).commit if tree.sha1 == pc.data.tree.sha1: return p return None def func(parser, options, args): """Publish the stack changes.""" repository = directory.repository stack = repository.get_stack(options.branch) if not args: public_ref = common.get_public_ref(stack.name) elif len(args) == 1: public_ref = args[0] else: parser.error('incorrect number of arguments') if not public_ref.startswith('refs/heads/'): public_ref = 'refs/heads/' + public_ref # just clone the stack if the public ref does not exist if not repository.refs.exists(public_ref): if options.unpublished or options.last: raise common.CmdException('"%s" does not exist' % public_ref) repository.refs.set(public_ref, stack.head, 'publish') out.info('Created "%s"' % public_ref) return public_head = repository.refs.get(public_ref) public_tree = public_head.data.tree # find the last published patch if options.last: last = __get_last(stack, public_tree) if not last: raise common.CmdException('Unable to find the last published patch ' '(possibly rebased stack)') out.info('%s' % last) return # check for same tree (already up to date) if public_tree.sha1 == stack.head.data.tree.sha1: out.info('"%s" already up to date' % public_ref) return # check for unpublished patches if options.unpublished: published = set(__get_published(stack, public_tree)) for p in stack.patchorder.applied: if p not in published: print p return # check for rebased stack. In this case we emulate a merge with the stack # base by setting two parents. merge_bases = set(repository.get_merge_bases(public_head, stack.base)) if public_head in merge_bases: # fast-forward the public ref repository.refs.set(public_ref, stack.head, 'publish') out.info('Fast-forwarded "%s"' % public_ref) return if not stack.base in merge_bases: message = 'Merge %s into %s' % (repository.describe(stack.base).strip(), utils.strip_prefix('refs/heads/', public_ref)) public_head = __create_commit(repository, stack.head.data.tree, [public_head, stack.base], options, message) repository.refs.set(public_ref, public_head, 'publish') out.info('Merged the stack base into "%s"' % public_ref) return # check for new patches from the last publishing. This is done by checking # whether the public tree is the same as the bottom of the checked patch. # If older patches were modified, new patches cannot be detected. The new # patches and their metadata are pushed directly to the published head. for p in stack.patchorder.applied: pc = stack.patches.get(p).commit if public_tree.sha1 == pc.data.parent.data.tree.sha1: if pc.data.is_nochange(): out.info('Ignored new empty patch "%s"' % p) continue cd = pc.data.set_parent(public_head) public_head = repository.commit(cd) public_tree = public_head.data.tree out.info('Published new patch "%s"' % p) # create a new commit (only happens if no new patches are detected) if public_tree.sha1 != stack.head.data.tree.sha1: public_head = __create_commit(repository, stack.head.data.tree, [public_head], options) # update the public head repository.refs.set(public_ref, public_head, 'publish') out.info('Updated "%s"' % public_ref) stgit-0.17.1/stgit/commands/__init__.py0000644002002200200220000000630711743516173020152 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2005, Catalin Marinas Copyright (C) 2008, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import os from stgit import utils def get_command(mod): """Import and return the given command module.""" return __import__(__name__ + '.' + mod, globals(), locals(), ['*']) _kinds = [('repo', 'Repository commands'), ('stack', 'Stack (branch) commands'), ('patch', 'Patch commands'), ('wc', 'Index/worktree commands'), ('alias', 'Alias commands')] _kind_order = [kind for kind, desc in _kinds] _kinds = dict(_kinds) def _find_commands(): for p in __path__: for fn in os.listdir(p): if not fn.endswith('.py'): continue mod = utils.strip_suffix('.py', fn) m = get_command(mod) if not hasattr(m, 'usage'): continue yield mod, m def get_commands(allow_cached = True): """Return a map from command name to a tuple of module name, command type, and one-line command help.""" if allow_cached: try: from stgit.commands.cmdlist import command_list return command_list except ImportError: # cmdlist.py doesn't exist, so do it the expensive way. pass return dict((getattr(m, 'name', mod), (mod, _kinds[m.kind], m.help)) for mod, m in _find_commands()) def py_commands(commands, f): f.write('command_list = {\n') for key, val in sorted(commands.iteritems()): f.write(' %r: %r,\n' % (key, val)) f.write(' }\n') def _command_list(commands): kinds = {} for cmd, (mod, kind, help) in commands.iteritems(): kinds.setdefault(kind, {})[cmd] = help for kind in _kind_order: kind = _kinds[kind] try: yield kind, sorted(kinds[kind].iteritems()) except KeyError: pass def pretty_command_list(commands, f): cmd_len = max(len(cmd) for cmd in commands.iterkeys()) sep = '' for kind, cmds in _command_list(commands): f.write(sep) sep = '\n' f.write('%s:\n' % kind) for cmd, help in cmds: f.write(' %*s %s\n' % (-cmd_len, cmd, help)) def _write_underlined(s, u, f): f.write(s + '\n') f.write(u*len(s) + '\n') def asciidoc_command_list(commands, f): for kind, cmds in _command_list(commands): _write_underlined(kind, '~', f) f.write('\n') for cmd, help in cmds: f.write('linkstgsub:%s[]::\n' % cmd) f.write(' %s\n' % help) f.write('\n') stgit-0.17.1/stgit/commands/commit.py0000644002002200200220000001070711743516173017702 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software 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.out import * from stgit import argparse help = 'Permanently store the applied patches into the stack base' kind = 'stack' usage = ['', '[--] ', '-n NUM', '--all'] description = """ Merge one or more patches into the base of the current stack and remove them from the series while advancing the base. This is the opposite of 'stg uncommit'. Use this command if you no longer want to manage a patch with StGIT. By default, the bottommost patch is committed. If patch names are given, the stack is rearranged so that those patches are at the bottom, and then they are committed. 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'), opt('-a', '--all', action = 'store_true', short = 'Commit all applied patches')] directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Commit a number of patches.""" stack = directory.repository.current_stack args = common.parse_patches(args, list(stack.patchorder.all_visible)) if len([x for x in [args, options.number != None, options.all] if x]) > 1: parser.error('too many options') if args: patches = [pn for pn in stack.patchorder.all_visible if pn in args] bad = set(args) - set(patches) if bad: raise common.CmdException('Nonexistent or hidden patch names: %s' % ', '.join(sorted(bad))) elif options.number != None: if options.number <= len(stack.patchorder.applied): patches = stack.patchorder.applied[:options.number] else: raise common.CmdException('There are not that many applied patches') elif options.all: patches = stack.patchorder.applied else: patches = stack.patchorder.applied[:1] if not patches: raise common.CmdException('No patches to commit') iw = stack.repository.default_iw def allow_conflicts(trans): # As long as the topmost patch stays where it is, it's OK to # run "stg commit" with conflicts in the index. return len(trans.applied) >= 1 trans = transaction.StackTransaction(stack, 'commit', allow_conflicts = allow_conflicts) try: common_prefix = 0 for i in xrange(min(len(stack.patchorder.applied), len(patches))): if stack.patchorder.applied[i] == patches[i]: common_prefix += 1 else: break if common_prefix < len(patches): to_push = [pn for pn in stack.patchorder.applied[common_prefix:] if pn not in patches[common_prefix:]] # this pops all the applied patches from common_prefix trans.pop_patches(lambda pn: pn in to_push) for pn in patches[common_prefix:]: trans.push_patch(pn, iw) else: to_push = [] new_base = trans.patches[patches[-1]] for pn in patches: trans.patches[pn] = None trans.applied = [pn for pn in trans.applied if pn not in patches] trans.base = new_base out.info('Committed %d patch%s' % (len(patches), ['es', ''][len(patches) == 1])) for pn in to_push: trans.push_patch(pn, iw) except transaction.TransactionHalted: pass return trans.run(iw) stgit-0.17.1/stgit/commands/init.py0000644002002200200220000000245111271344641017345 0ustar cmarinascmarinas __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from stgit.commands import common from stgit.lib import stack help = 'Initialise the current branch for use with StGIT' kind = 'stack' usage = [''] description = """ 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() def func(parser, options, args): """Performs the repository initialisation """ if len(args) != 0: parser.error('incorrect number of arguments') stack.Stack.initialise(directory.repository) stgit-0.17.1/stgit/commands/export.py0000644002002200200220000001435111743516173017732 0ustar cmarinascmarinas"""Export command """ __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import os import sys from stgit.argparse import opt from stgit.commands import common from stgit import argparse, git, templates from stgit.out import out from stgit.lib import git as gitlib help = 'Export patches to a directory' kind = 'patch' usage = ['[options] [--] [] [] [..]'] description = """ Export a range of applied patches to a given directory (defaults to 'patches-') in a standard unified GNU diff format. A template file (defaulting to '.git/patchexport.tmpl' or '~/.stgit/templates/patchexport.tmpl' or '/usr/share/stgit/templates/patchexport.tmpl') can be used for the patch format. The following variables are supported in the template file: %(description)s - patch description %(shortdescr)s - the first line of the patch description %(longdescr)s - the rest of the patch description, after the first line %(diffstat)s - the diff statistics %(authname)s - author's name %(authemail)s - author's e-mail %(authdate)s - patch creation date %(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', 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'), opt('-e', '--extension', 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', args = [argparse.files], short = 'Use FILE as a template'), 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'), ] + argparse.diff_opts_option() directory = common.DirectoryHasRepositoryLib() def func(parser, options, args): """Export a range of patches. """ stack = directory.repository.get_stack(options.branch) if options.dir: dirname = options.dir else: dirname = 'patches-%s' % stack.name directory.cd_to_topdir() if not options.branch and git.local_changes(): out.warn('Local changes in the tree;' ' you might want to commit them first') if not options.stdout: if not os.path.isdir(dirname): os.makedirs(dirname) series = file(os.path.join(dirname, 'series'), 'w+') applied = stack.patchorder.applied unapplied = stack.patchorder.unapplied if len(args) != 0: patches = common.parse_patches(args, applied + unapplied, len(applied)) else: patches = applied num = len(patches) if num == 0: raise common.CmdException, 'No patches applied' zpadding = len(str(num)) if zpadding < 2: zpadding = 2 # get the template if options.template: tmpl = file(options.template).read() else: tmpl = templates.get_template('patchexport.tmpl') if not tmpl: tmpl = '' # note the base commit for this series if not options.stdout: base_commit = stack.base.sha1 print >> series, '# This series applies on GIT commit %s' % base_commit patch_no = 1; for p in patches: pname = p if options.patch: pname = '%s.patch' % pname elif options.extension: pname = '%s.%s' % (pname, options.extension) if options.numbered: pname = '%s-%s' % (str(patch_no).zfill(zpadding), pname) pfile = os.path.join(dirname, pname) if not options.stdout: print >> series, pname # get the patch description patch = stack.patches.get(p) cd = patch.commit.data descr = cd.message.strip() descr_lines = descr.split('\n') short_descr = descr_lines[0].rstrip() long_descr = reduce(lambda x, y: x + '\n' + y, descr_lines[1:], '').strip() diff = stack.repository.diff_tree(cd.parent.data.tree, cd.tree, options.diff_flags) tmpl_dict = {'description': descr, 'shortdescr': short_descr, 'longdescr': long_descr, 'diffstat': gitlib.diffstat(diff).rstrip(), 'authname': cd.author.name, 'authemail': cd.author.email, 'authdate': cd.author.date.isoformat(), 'commname': cd.committer.name, 'commemail': cd.committer.email} for key in tmpl_dict: if not tmpl_dict[key]: tmpl_dict[key] = '' try: descr = tmpl % tmpl_dict except KeyError, err: raise common.CmdException, 'Unknown patch template variable: %s' \ % err except TypeError: raise common.CmdException, 'Only "%(name)s" variables are ' \ 'supported in the patch template' if options.stdout: f = sys.stdout else: f = open(pfile, 'w+') if options.stdout and num > 1: print '-'*79 print patch.name print '-'*79 f.write(descr) f.write(diff) if not options.stdout: f.close() patch_no += 1 if not options.stdout: series.close() stgit-0.17.1/stgit/commands/pick.py0000644002002200200220000001746311743516173017346 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os from stgit.argparse import opt from stgit.commands.common import * from stgit.utils import * from stgit.out import * from stgit import argparse, stack, git from stgit.stack import Series help = 'Import a patch from a different branch or a commit object' kind = 'patch' usage = ['[options] [--] ([] [] [..])|'] description = """ Import one or more patches from a different branch or a commit object into the current series. By default, the name of the imported patch is used as the name of the current patch. It can be overridden with the '--name' option. A commit object can be reverted with the '--revert' 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', args = [argparse.stg_branches], short = 'Pick patches from BRANCH'), opt('-r', '--revert', action = 'store_true', short = 'Revert the given commit object'), 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'), opt('--fold', action = 'store_true', short = 'Fold the commit object into the current patch'), opt('--update', action = 'store_true', short = 'Like fold but only update the current patch files'), opt('-f', '--file', action = 'append', short = 'Only fold the given file (can be used multiple times)'), opt('--unapplied', action = 'store_true', short = 'Keep the patch unapplied')] directory = DirectoryGotoToplevel(log = True) def __pick_commit(commit_id, patchname, options): """Pick a commit id. """ commit = git.Commit(commit_id) if options.name: patchname = options.name elif patchname and options.revert: patchname = 'revert-' + patchname if patchname: patchname = find_patch_name(patchname, crt_series.patch_exists) if options.parent: parent = git_id(crt_series, options.parent) else: parent = commit.get_parent() if not options.revert: bottom = parent top = commit_id else: bottom = commit_id top = parent if options.fold: out.start('Folding commit %s' % commit_id) # try a direct git apply first if not git.apply_diff(bottom, top, files = options.file): if options.file: raise CmdException('Patch folding failed') else: git.merge_recursive(bottom, git.get_head(), top) out.done() elif options.update: rev1 = git_id(crt_series, 'HEAD^') rev2 = git_id(crt_series, 'HEAD') files = git.barefiles(rev1, rev2).split('\n') out.start('Updating with commit %s' % commit_id) if not git.apply_diff(bottom, top, files = files): raise CmdException, 'Patch updating failed' out.done() else: message = commit.get_log() if options.revert: if message: lines = message.splitlines() subject = lines[0] body = '\n'.join(lines[2:]) else: subject = commit.get_id_hash() body = '' message = 'Revert "%s"\n\nThis reverts commit %s.\n\n%s\n' \ % (subject, commit.get_id_hash(), body) elif options.expose: message += '(imported from commit %s)\n' % commit.get_id_hash() author_name, author_email, author_date = \ name_email_date(commit.get_author()) if options.revert: author_name = author_email = None out.start('Importing commit %s' % commit_id) newpatch = crt_series.new_patch(patchname, message = message, can_edit = False, unapplied = True, bottom = bottom, top = top, author_name = author_name, author_email = author_email, author_date = author_date) # in case the patch name was automatically generated patchname = newpatch.get_name() # find a patchlog to fork from refbranchname, refpatchname = parse_rev(patchname) if refpatchname: if refbranchname: # assume the refseries is OK, since we already resolved # commit_str to a git_id refseries = Series(refbranchname) else: refseries = crt_series patch = refseries.get_patch(refpatchname) if patch.get_log(): out.info("Log was %s" % newpatch.get_log()) out.info("Setting log to %s\n" % patch.get_log()) newpatch.set_log(patch.get_log()) out.info("Log is now %s" % newpatch.get_log()) else: out.info("No log for %s\n" % patchname) if not options.unapplied: modified = crt_series.push_patch(patchname) else: modified = False if crt_series.empty_patch(patchname): out.done('empty patch') elif modified: out.done('modified') else: out.done() def func(parser, options, args): """Import a commit object as a new patch """ if not args: parser.error('incorrect number of arguments') if options.file and not options.fold: parser.error('--file can only be specified with --fold') if not options.unapplied: check_local_changes() check_conflicts() check_head_top_equal(crt_series) if options.ref_branch: remote_series = Series(options.ref_branch) else: remote_series = crt_series applied = remote_series.get_applied() unapplied = remote_series.get_unapplied() try: patches = parse_patches(args, applied + unapplied, len(applied)) commit_id = None except CmdException: if len(args) > 1: raise # no patches found, try a commit id commit_id = git_id(remote_series, args[0]) if not commit_id and len(patches) > 1: if options.name: raise CmdException, '--name can only be specified with one patch' if options.parent: raise CmdException, '--parent can only be specified with one patch' if options.update and not crt_series.get_current(): raise CmdException, 'No patches applied' if commit_id: # Try to guess a patch name if the argument was : try: patchname = args[0].split(':')[1] except IndexError: patchname = None __pick_commit(commit_id, patchname, options) else: if options.unapplied: patches.reverse() for patch in patches: __pick_commit(git_id(remote_series, patch), patch, options) print_crt_patch(crt_series) stgit-0.17.1/stgit/__init__.py0000644002002200200220000000127510300327163016333 0ustar cmarinascmarinas__copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ stgit-0.17.1/stgit/run.py0000644002002200200220000001753311743516173015421 0ustar cmarinascmarinas# -*- coding: utf-8 -*- __copyright__ = """ Copyright (C) 2007, Karl Hasselström This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import datetime, os, subprocess from stgit.exception import * from stgit.out import * class RunException(StgException): """Thrown when something bad happened when we tried to run the subprocess.""" pass def get_log_mode(spec): if not ':' in spec: spec += ':' (log_mode, outfile) = spec.split(':', 1) all_log_modes = ['debug', 'profile'] if log_mode and not log_mode in all_log_modes: out.warn(('Unknown log mode "%s" specified in $STGIT_SUBPROCESS_LOG.' % log_mode), 'Valid values are: %s' % ', '.join(all_log_modes)) if outfile: f = MessagePrinter(open(outfile, 'a')) else: f = out return (log_mode, f) (_log_mode, _logfile) = get_log_mode(os.environ.get('STGIT_SUBPROCESS_LOG', '')) if _log_mode == 'profile': _log_starttime = datetime.datetime.now() _log_subproctime = 0.0 def duration(t1, t2): d = t2 - t1 return 86400*d.days + d.seconds + 1e-6*d.microseconds def finish_logging(): if _log_mode != 'profile': return ttime = duration(_log_starttime, datetime.datetime.now()) rtime = ttime - _log_subproctime _logfile.info('Total time: %1.3f s' % ttime, 'Time spent in subprocess calls: %1.3f s (%1.1f%%)' % (_log_subproctime, 100*_log_subproctime/ttime), 'Remaining time: %1.3f s (%1.1f%%)' % (rtime, 100*rtime/ttime)) class Run: exc = RunException def __init__(self, *cmd): self.__cmd = list(cmd) for c in cmd: if type(c) != str: raise Exception, 'Bad command: %r' % (cmd,) self.__good_retvals = [0] self.__env = self.__cwd = None self.__indata = None self.__discard_stderr = False def __log_start(self): if _log_mode == 'debug': _logfile.start('Running subprocess %s' % self.__cmd) if self.__cwd != None: _logfile.info('cwd: %s' % self.__cwd) if self.__env != None: for k in sorted(self.__env.iterkeys()): if k not in os.environ or os.environ[k] != self.__env[k]: _logfile.info('%s: %s' % (k, self.__env[k])) elif _log_mode == 'profile': _logfile.start('Running subprocess %s' % self.__cmd) self.__starttime = datetime.datetime.now() def __log_end(self, retcode): global _log_subproctime, _log_starttime if _log_mode == 'debug': _logfile.done('return code: %d' % retcode) elif _log_mode == 'profile': n = datetime.datetime.now() d = duration(self.__starttime, n) _logfile.done('%1.3f s' % d) _log_subproctime += d _logfile.info('Time since program start: %1.3f s' % duration(_log_starttime, n)) def __check_exitcode(self): if self.__good_retvals == None: return if self.exitcode not in self.__good_retvals: raise self.exc('%s failed with code %d' % (self.__cmd[0], self.exitcode)) def __run_io(self): """Run with captured IO.""" self.__log_start() try: p = subprocess.Popen(self.__cmd, env = self.__env, cwd = self.__cwd, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) # TODO: only use communicate() once support for Python 2.4 is # dropped (write() needed because of performance reasons) if self.__indata: p.stdin.write(self.__indata) outdata, errdata = p.communicate() self.exitcode = p.returncode except OSError, e: raise self.exc('%s failed: %s' % (self.__cmd[0], e)) if errdata and not self.__discard_stderr: out.err_raw(errdata) self.__log_end(self.exitcode) self.__check_exitcode() return outdata def __run_noio(self): """Run without captured IO.""" assert self.__indata == None self.__log_start() try: p = subprocess.Popen(self.__cmd, env = self.__env, cwd = self.__cwd) self.exitcode = p.wait() except OSError, e: raise self.exc('%s failed: %s' % (self.__cmd[0], e)) self.__log_end(self.exitcode) self.__check_exitcode() def __run_background(self): """Run in background.""" assert self.__indata == None try: p = subprocess.Popen(self.__cmd, env = self.__env, cwd = self.__cwd, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) except OSError, e: raise self.exc('%s failed: %s' % (self.__cmd[0], e)) self.stdin = p.stdin self.stdout = p.stdout self.stderr = p.stderr self.wait = p.wait self.pid = lambda: p.pid def returns(self, retvals): self.__good_retvals = retvals return self def discard_exitcode(self): self.__good_retvals = None return self def discard_stderr(self, discard = True): self.__discard_stderr = discard return self def env(self, env): self.__env = dict(os.environ) self.__env.update(env) return self def cwd(self, cwd): self.__cwd = cwd return self def raw_input(self, indata): self.__indata = indata return self def input_lines(self, lines): self.__indata = ''.join(['%s\n' % line for line in lines]) return self def input_nulterm(self, lines): self.__indata = ''.join('%s\0' % line for line in lines) return self def no_output(self): outdata = self.__run_io() if outdata: raise self.exc, '%s produced output' % self.__cmd[0] def discard_output(self): self.__run_io() def raw_output(self): return self.__run_io() def output_lines(self): outdata = self.__run_io() if outdata.endswith('\n'): outdata = outdata[:-1] if outdata: return outdata.split('\n') else: return [] def output_one_line(self): outlines = self.output_lines() if len(outlines) == 1: return outlines[0] else: raise self.exc('%s produced %d lines, expected 1' % (self.__cmd[0], len(outlines))) def run(self): """Just run, with no IO redirection.""" self.__run_noio() def run_background(self): """Run as a background process.""" self.__run_background() return self def xargs(self, xargs): """Just run, with no IO redirection. The extra arguments are appended to the command line a few at a time; the command is run as many times as needed to consume them all.""" step = 100 basecmd = self.__cmd for i in xrange(0, len(xargs), step): self.__cmd = basecmd + xargs[i:i+step] self.__run_noio() self.__cmd = basecmd stgit-0.17.1/stgit/builtin_version.py0000644002002200200220000000012712222320003017771 0ustar cmarinascmarinas# This file was generated automatically. Do not edit by hand. version = '0.17.1-dirty' stgit-0.17.1/templates/0000755002002200200220000000000012222320003015050 5ustar cmarinascmarinasstgit-0.17.1/templates/patchexport.tmpl0000644002002200200220000000012411271344641020325 0ustar cmarinascmarinas%(shortdescr)s From: %(authname)s <%(authemail)s> %(longdescr)s --- %(diffstat)s stgit-0.17.1/templates/patchandattch.tmpl0000644002002200200220000000102712152142021020560 0ustar cmarinascmarinasFrom: %(sender)s Subject: [%(prefix)s%(pspace)sPATCH%(vspace)s%(version)s%(nspace)s%(number)s] %(shortdescr)s Mime-Version: 1.0 Content-Type: multipart/mixed; boundary=MIMEBOUNDARY This is a MIME message. --MIMEBOUNDARY Content-Type: text/plain Content-Disposition: inline %(fromauth)s%(longdescr)s --- %(diffstat)s %(diff)s --- %(diffstat)s --MIMEBOUNDARY Content-Type: text/plain; name=%(patch)s.patch Content-Disposition: attachment; filename=%(patch)s.patch %(fromauth)s%(longdescr)s --- %(diffstat)s %(diff)s --MIMEBOUNDARY-- stgit-0.17.1/templates/mailattch.tmpl0000644002002200200220000000074012152142021017721 0ustar cmarinascmarinasFrom: %(sender)s Subject: [%(prefix)sPATCH%(version)s%(number)s] %(shortdescr)s Mime-Version: 1.0 Content-Type: multipart/mixed; boundary=MIMEBOUNDARY This is a MIME message. --MIMEBOUNDARY Content-Type: text/plain Content-Disposition: inline %(fromauth)s%(longdescr)s --- %(diffstat)s --MIMEBOUNDARY Content-Type: text/plain; name=%(patch)s.patch Content-Disposition: attachment; filename=%(patch)s.patch %(fromauth)s%(longdescr)s --- %(diffstat)s %(diff)s --MIMEBOUNDARY-- stgit-0.17.1/templates/patchmail.tmpl0000644002002200200220000000024312152142021017713 0ustar cmarinascmarinasFrom: %(sender)s Subject: [%(prefix)s%(pspace)sPATCH%(vspace)s%(version)s%(nspace)s%(number)s] %(shortdescr)s %(fromauth)s%(longdescr)s --- %(diffstat)s %(diff)s stgit-0.17.1/templates/covermail.tmpl0000644002002200200220000000031112152142021017726 0ustar cmarinascmarinasFrom: %(sender)s Subject: [%(prefix)s%(pspace)sPATCH%(vspace)s%(version)s%(nspace)s%(number)s] Series short description The following series implements... --- %(shortlog)s %(diffstat)s -- Signature stgit-0.17.1/Documentation/0000755002002200200220000000000012222320003015663 5ustar cmarinascmarinasstgit-0.17.1/Documentation/stg-pick.txt0000644002002200200220000000236511743520634020176 0ustar cmarinascmarinasstg-pick(1) =========== NAME ---- stg-pick - Import a patch from a different branch or a commit object SYNOPSIS -------- [verse] 'stg' pick [options] [--] ([] [] [..])| DESCRIPTION ----------- Import one or more patches from a different branch or a commit object into the current series. By default, the name of the imported patch is used as the name of the current patch. It can be overridden with the '--name' option. A commit object can be reverted with the '--revert' option. The log and author information are those of the commit object. OPTIONS ------- -n NAME:: --name NAME:: Use NAME as the patch name. -B REF-BRANCH:: --ref-branch REF-BRANCH:: Pick patches from BRANCH. -r:: --revert:: Revert the given commit object. -p COMMITID:: --parent COMMITID:: Use COMMITID as parent. -x:: --expose:: Append the imported commit id to the patch log. --fold:: Fold the commit object into the current patch. --update:: Like fold but only update the current patch files. -f FILE:: --file FILE:: Only fold the given file (can be used multiple times). --unapplied:: Keep the patch unapplied. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-coalesce.txt0000644002002200200220000000221611100067174021011 0ustar cmarinascmarinasstg-coalesce(1) =============== NAME ---- stg-coalesce - Coalesce two or more patches into one SYNOPSIS -------- [verse] 'stg' coalesce [options] DESCRIPTION ----------- Coalesce two or more patches, creating one big patch that contains all their changes. 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. OPTIONS ------- -n NAME:: --name NAME:: Name of coalesced patch. -m MESSAGE:: --message MESSAGE:: Use MESSAGE instead of invoking the editor. -f FILE:: --file FILE:: Use the contents of FILE instead of invoking the editor. (If FILE is "-", write to stdout.) --save-template FILE:: Instead of running the command, just write the message template to FILE, and exit. (If FILE is "-", write to stdout.) + When driving StGit from another program, it is often useful to first call a command with '--save-template', then let the user edit the message, and then call the same command with '--file'. StGit ----- Part of the StGit suite - see manlink:stg[1] stgit-0.17.1/Documentation/stg-prev.txt0000644002002200200220000000052511743520634020220 0ustar cmarinascmarinasstg-prev(1) =========== NAME ---- stg-prev - Print the name of the previous patch SYNOPSIS -------- [verse] 'stg' prev DESCRIPTION ----------- Print the name of the previous patch. OPTIONS ------- -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/command-list.txt0000644002002200200220000000542311743520632021040 0ustar cmarinascmarinasRepository commands ~~~~~~~~~~~~~~~~~~~ linkstgsub:clone[]:: Make a local clone of a remote repository linkstgsub:id[]:: Print the git hash value of a StGit reference Stack (branch) commands ~~~~~~~~~~~~~~~~~~~~~~~ linkstgsub:branch[]:: Branch operations: switch, list, create, rename, delete, ... linkstgsub:clean[]:: Delete the empty patches in the series linkstgsub:commit[]:: Permanently store the applied patches into the stack base linkstgsub:float[]:: Push patches to the top, even if applied linkstgsub:goto[]:: Push or pop patches to the given one linkstgsub:hide[]:: Hide a patch in the series linkstgsub:init[]:: Initialise the current branch for use with StGIT linkstgsub:log[]:: Display the patch changelog linkstgsub:next[]:: Print the name of the next patch linkstgsub:patches[]:: Show the applied patches modifying a file linkstgsub:pop[]:: Pop one or more patches from the stack linkstgsub:prev[]:: Print the name of the previous patch linkstgsub:publish[]:: Push the stack changes to a merge-friendly branch linkstgsub:pull[]:: Pull changes from a remote repository linkstgsub:push[]:: Push one or more patches onto the stack linkstgsub:rebase[]:: Move the stack base to another point in history linkstgsub:redo[]:: Undo the last undo operation linkstgsub:repair[]:: Fix StGit metadata if branch was modified with git commands linkstgsub:reset[]:: Reset the patch stack to an earlier state linkstgsub:series[]:: Print the patch series linkstgsub:sink[]:: Send patches deeper down the stack linkstgsub:squash[]:: Squash two or more patches into one linkstgsub:top[]:: Print the name of the top patch linkstgsub:uncommit[]:: Turn regular git commits into StGit patches linkstgsub:undo[]:: Undo the last operation linkstgsub:unhide[]:: Unhide a hidden patch Patch commands ~~~~~~~~~~~~~~ linkstgsub:delete[]:: Delete patches linkstgsub:edit[]:: Edit a patch description or diff linkstgsub:export[]:: Export patches to a directory linkstgsub:files[]:: Show the files modified by a patch (or the current patch) linkstgsub:fold[]:: Integrate a GNU diff patch into the current patch linkstgsub:import[]:: Import a GNU diff file as a new patch linkstgsub:mail[]:: Send a patch or series of patches by e-mail linkstgsub:new[]:: Create a new, empty patch linkstgsub:pick[]:: Import a patch from a different branch or a commit object linkstgsub:refresh[]:: Generate a new commit for the current patch linkstgsub:rename[]:: Rename a patch linkstgsub:show[]:: Show the commit corresponding to a patch linkstgsub:sync[]:: Synchronise patches with a branch or a series Index/worktree commands ~~~~~~~~~~~~~~~~~~~~~~~ linkstgsub:diff[]:: Show the tree diff stgit-0.17.1/Documentation/stg-resolved.txt0000644002002200200220000000116111207540106021053 0ustar cmarinascmarinasstg-resolved(1) =============== NAME ---- stg-resolved - Mark a file conflict as solved SYNOPSIS -------- [verse] 'stg' resolved [options] [] DESCRIPTION ----------- Mark a merge conflict as resolved. The conflicts can be seen with the 'status' command, the corresponding files being prefixed with a 'C'. OPTIONS ------- -a:: --all:: Mark all conflicts as solved. -r (ancestor|current|patched):: --reset (ancestor|current|patched):: Reset the file(s) to the given state. -i:: --interactive:: Run the interactive merging tool. StGit ----- Part of the StGit suite - see manlink:stg[1] stgit-0.17.1/Documentation/stg-clean.txt0000644002002200200220000000104211743520633020320 0ustar cmarinascmarinasstg-clean(1) ============ NAME ---- stg-clean - Delete the empty patches in the series SYNOPSIS -------- [verse] 'stg' clean DESCRIPTION ----------- 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. OPTIONS ------- -a:: --applied:: Delete the empty applied patches. -u:: --unapplied:: Delete the empty unapplied patches. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-clone.txt0000644002002200200220000000124111743520633020337 0ustar cmarinascmarinasstg-clone(1) ============ NAME ---- stg-clone - Make a local clone of a remote repository SYNOPSIS -------- [verse] 'stg' clone DESCRIPTION ----------- Clone a git repository into the local directory (using linkstg:clone[]) and initialise the local branch "master". This operation is for example suitable to start working using the "tracking branch" workflow (see link:stg[1]). Other means to setup an StGit stack are linkstg:init[] and the '--create' and '--clone' commands of linkstg:branch[]. The target directory will be created by this command, and must not already exist. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-edit.txt0000644002002200200220000000477211743520633020200 0ustar cmarinascmarinasstg-edit(1) =========== NAME ---- stg-edit - Edit a patch description or diff SYNOPSIS -------- [verse] 'stg' edit [options] [--] [] DESCRIPTION ----------- Edit the description and author information of the given patch (or the current patch if no patch name was given). With --diff, also edit the diff. The editor is invoked with the following contents: From: A U Thor Date: creation date Patch description If --diff was specified, the diff appears at the bottom, after a separator: --- Diff text Command-line options can be used to modify specific information without invoking the editor. (With the --edit option, the editor is invoked even if such command-line options are given.) 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. With --set-tree you set the git tree of the patch to the specified TREE-ISH without changing the tree of any other patches. When used on the top patch, the index and work tree will be updated to match the tree. This low-level option is primarily meant to be used by tools built on top of StGit, such as the Emacs mode. See also the --set-tree flag of stg push. OPTIONS ------- -d:: --diff:: Edit the patch diff. -e:: --edit:: Invoke interactive editor. --sign:: Add a "Signed-off-by:" to the end of the patch. --ack:: Add an "Acked-by:" line to the end of the patch. -m MESSAGE:: --message MESSAGE:: Use MESSAGE instead of invoking the editor. -f FILE:: --file FILE:: Use the contents of FILE instead of invoking the editor. (If FILE is "-", write to stdout.) --save-template FILE:: Instead of running the command, just write the message template to FILE, and exit. (If FILE is "-", write to stdout.) + When driving StGit from another program, it is often useful to first call a command with '--save-template', then let the user edit the message, and then call the same command with '--file'. --author "NAME ":: Set the author details. --authname NAME:: Set the author name. --authemail EMAIL:: Set the author email. --authdate DATE:: Set the author date. -O OPTIONS:: --diff-opts OPTIONS:: Extra options to pass to "git diff". -t TREE-ISH:: --set-tree TREE-ISH:: Set the git tree of the patch to TREE-ISH. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-undo.txt0000644002002200200220000000067711743520635020222 0ustar cmarinascmarinasstg-undo(1) =========== NAME ---- stg-undo - Undo the last operation SYNOPSIS -------- [verse] 'stg' undo DESCRIPTION ----------- Reset the patch stack to the previous state. Consecutive invocations of "stg undo" will take you ever further into the past. OPTIONS ------- -n N:: --number N:: Undo the last N commands. --hard:: Discard changes in your index/worktree. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-hide.txt0000644002002200200220000000065211743520633020155 0ustar cmarinascmarinasstg-hide(1) =========== NAME ---- stg-hide - Hide a patch in the series SYNOPSIS -------- [verse] 'stg' hide [options] [--] DESCRIPTION ----------- Hide a range of unapplied patches so that they are no longer shown in the plain 'series' command output. OPTIONS ------- -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/tutorial.txt0000644002002200200220000011113011743516173020312 0ustar cmarinascmarinasStGit tutorial ============== StGit is a command-line application that provides functionality similar to link:http://savannah.nongnu.org/projects/quilt/[Quilt] (i.e. pushing/popping patches to/from a stack), but using Git instead of +diff+ and +patch+. StGit stores its patches in a Git repository as normal Git commits, and provides a number of commands to manipulate them in various ways. This tutorial assumes you are already familiar with the basics of Git (for example, branches, commits, and conflicts). For more information on Git, see linkman:git[1] or link:http://git.or.cz/[the Git home page]. Help ==== For a full list of StGit commands: $ stg help For quick help on individual subcommands: $ stg help For more extensive help on a subcommand: $ man stg- (The documentation is also available in link:stg.html[HTML format].) Getting started =============== StGit is not a stand-alone program -- it operates on a Git repository that you have already created, using +git init+ or +git clone+. So get one of those; if you don't have one at hand, try for example $ git clone git://repo.or.cz/stgit.git $ cd stgit Before you can create StGit patches, you have to run linkstg:init[]: $ stg init This initializes the StGit metadata for the current branch. (So if you want to have StGit patches in another branch too, you need to run +stg init+ again in that branch.) NOTE: As a shortcut, linkstg:clone[] will run +git clone+ followed by +stg init+ for you. Creating a patch ---------------- Now we're ready to create our first patch: $ stg new my-first-patch This will create a patch called +my-first-patch+, and open an editor to let you edit the patch's commit message. (If you don't give a name on the command line, StGit will make one up based on the first line of the commit message.) This patch is empty, as linkstg:show[] will tell you: $ stg show But it won't stay that way for long! Open one of the files in your favorite text editor, change something, and save. You now have some local changes in your tree: $ stg status M stgit/main.py Then linkstgsub:refresh[] the patch: $ stg refresh And voilà -- the patch is no longer empty: $ stg show commit 3de32068c600d40d8af2a9cf1f1c762570ae9610 Author: Audrey U. Thor Date: Sat Oct 4 16:10:54 2008 +0200 Tell the world that I've made a patch diff --git a/stgit/main.py b/stgit/main.py index e324179..6398958 100644 --- a/stgit/main.py +++ b/stgit/main.py @@ -171,6 +171,7 @@ def _main(): sys.exit(ret or utils.STGIT_SUCCESS) def main(): + print 'My first patch!' try: _main() finally: (I'm assuming you're already familiar with link:http://en.wikipedia.org/wiki/Diff#Unified_format[unified diff] patches like this from Git, but it's really quite simple; in this example, I've added the +$$print 'My first patch!'$$+ line to the file +stgit/main.py+, at around line 171.) Since the patch is also a regular Git commit, you can also look at it with regular Git tools such as linkman:gitk[]. Creating another patch ---------------------- We want to make another improvement, so let's create a new patch for it: $ echo 'Audrey U. Thor' > AUTHORS $ stg new credit --message 'Give me some credit' $ stg refresh Note that we can give the commit message on the command line, and that it doesn't matter whether we run linkstg:new[] before or after we edit the files. So now we have two patches: $ stg series --description + my-first-patch # This is my first patch > credit # Give me some credit linkstg:series[] lists the patches from bottom to top; +$$+$$+ means that a patch is 'applied', and +>+ that it is the 'current', or topmost, patch. If we want to make further changes to the topmost patch, we just edit the files and run +stg refresh+. But what if we wanted to change +my-first-patch+? The simplest way is to linkstgsub:pop[] the +credit+ patch, so that +my-first-patch+ becomes topmost again: $ stg pop credit Checking for changes in the working directory ... done Popping patch "credit" ... done Now at patch "my-first-patch" $ stg series --description > my-first-patch # This is my first patch - credit # Give me some credit linkstg:series[] now shows that +my-first-patch+ is topmost again, which means that linkstg:refresh[] will update it with any changes we make. The minus sign says that +credit+ is 'unapplied' -- this means that it's been temporarily put aside. If you look at the +AUTHORS+ file, you'll see that our change to it is gone; and tools such as linkman:gitk[] will not show it, because it's been edited out of the Git history. But it's just one linkstg:push[] command away from being restored: $ stg push credit Checking for changes in the working directory ... done Fast-forwarded patch "credit" Now at patch "credit" NOTE: You can omit the patch name argument to linkstg:push[] and linkstg:pop[]. If you do, you will push the next unapplied patch, and pop the topmost patch, respectively. NOTE: There are at least two more ways to update a non-topmost patch. One is to use linkstg:refresh[] with the +$$--patch$$+ flag, the other to create a new patch for the update and then merge it into the other patch with linkstg:squash[]. Keeping commit messages up to date ---------------------------------- Since StGit is all about creating readable Git history (or a readable patch series, which is essentially the same thing), one thing you'll want to pay attention to is the commit messages of your patches. linkstg:new[] asks you for a commit message when you create a new patch, but as time goes by and you refresh the patch again and again, chances are that the original commit message isn't quite correct anymore. Fortunately, editing the commit message is very easy: $ stg edit In addition to linkstg:edit[], you can also give the +$$--edit$$+ flag to linkstg:refresh[] -- that way, you get to change the commit message and update the patch at the same time. Use whichever feels most natural to you. NOTE: linkstg:edit[] has a +$$--diff$$+ flag, which gives you the diff text and not just the commit message in your editor. Be aware, though, that if you change the diff so that it no longer applies, the edit will be saved to a file instead of being carried out. If you're not comfortable editing diffs, just treat +$$--diff$$+ as a way to get to 'see' the diff while you edit the commit message. If the patch changes considerably, it might even deserve a new name. linkstg:rename[] is your friend there. Conflicts --------- Normally, when you pop a patch, change something, and then later push it again, StGit sorts out everything for you automatically. For example, let's create two patches that modify different files: $ stg clone http://homepage.ntlworld.com/cmarinas/stgit.git stgit $ cd stgit $ stg new first --message 'First patch' $ echo '- Do something' >> TODO $ stg refresh $ stg new second --message 'Second patch' $ echo '- Install something' >> INSTALL $ stg refresh then pop them both: $ stg pop --all and then push them in the opposite order: $ stg push second first $ stg series + second > first StGit had no problems reordering these patches for us, since they didn't touch the same file. But it would have worked just fine even if they had touched the same file, as long as they didn't change the same part of the file. But what if they did? Let's find out. $ stg pop Checking for changes in the working directory ... done Popping patch "first" ... done Now at patch "second" $ echo '- Do something else' >> TODO $ stg refresh Now, both patches add a new line at the end of +TODO+. So what happens when we try to have them both applied? $ stg push Pushing patch "first" ... CONFLICT (content): Merge conflict in TODO Error: The merge failed during "push". Revert the operation with "stg undo". stg push: 1 conflict(s) StGit is telling us that it couldn't figure out how to push +first+ on top of +second+, now that they both modify +TODO+. We can take a look at the situation with linkstg:status[]: $ stg status C TODO As we were told by linkstg:push[], the conflict is in the file +TODO+. (If the patch was bigger and touched multiple files, they would all be listed here; prefixed with +C+ if they had conflicts, and +M+ if StGit managed to automatically resolve everything in the file.) At this point, we have two options: 1. Undo the failed merge with linkstg:undo[]. (Remember to use the +$$--hard$$+ flag, since the unresolved conflict means the worktree is not clean.) 2. Manually resolve the conflict (editing the file directly followed by +git add+ or using +git mergetool+.) To resolve the conflict, open +TODO+ in your favorite editor. It ends like this: ---------------------------------------------------------------------- - numeric shortcuts for naming patches near top (eg. +1, -2) - (config?) parameter for number of patches included by "series -s" <<<<<<< current:TODO - Do something else ======= - Do something >>>>>>> patched:TODO ---------------------------------------------------------------------- The 'conflict markers' +<<<<<<<+, +=======+, and +>>>>>>>+ indicate which lines were already there (+current+) and which were added by the patch (+patched+). Edit the file so that it looks like it should; in this case, we want something like this: ---------------------------------------------------------------------- - numeric shortcuts for naming patches near top (eg. +1, -2) - (config?) parameter for number of patches included by "series -s" - Do something - Do something else ---------------------------------------------------------------------- Note that ``looks like it should'' includes removing the conflict markers. Now that we've resolved the conflict, we just need to tell StGit about it: $ git add TODO $ stg status M TODO +TODO+ is listed as being modified, not in conflict. And we know from before how to deal with modified files: $ stg refresh The conflict is now resolved. We can see that +first+ now looks a little different; it no longer adds a line at the end of the file: $ stg show commit 8e3ae5f6fa6e9a5f831353524da5e0b91727338e Author: Audrey U. Thor Date: Sun Oct 5 14:43:42 2008 +0200 First patch diff --git a/TODO b/TODO index 812d236..4ef3841 100644 --- a/TODO +++ b/TODO @@ -24,4 +24,5 @@ The future, when time allows or if someone else does them: they have scripts for moving the changes in one to the others) - numeric shortcuts for naming patches near top (eg. +1, -2) - (config?) parameter for number of patches included by "series -s" +- Do something - Do something else Workflow: Development branch ============================ One common use of StGit is to ``polish'' a Git branch before you publish it for others to see. Such history falsification can often be a 'good' thing -- when you (or someone else) needs to look at what you did six months later, you are not really interested in all the false starts and the steps needed to corect them. What you want is the final solution, presented in a way that makes it easy to read and understand. Of course, there are limits. Editing the last few days' worth of history is probably a good idea; editing the last few months' probably isn't. A rule of thumb might be to not mess with history old enough that you don't remember the details anymore. And rewriting history that you have published for others to see (and base their own work on) usually just makes everyone more confused, not less. So, let's take a concrete example. Say that you're hacking on StGit, and have made several Git commits as your work progressed, with commit messages such as ``Improve the snarfle cache'', ``Remove debug printout'', ``New snarfle cache test'', ``Oops, spell function name correctly'', ``Fix documentation error'', and ``More snarfle cache''. Now, this is the actual history, but for obvious reasons, this isn't the kind of history you'd ideally want to find when you six months from now try to figure out exactly where that elusive snarfle cache bug was introduced. So let's turn this into the history we can be proud of. The first step is to make StGit patches out of all those Git commits: $ stg uncommit --number 6 Uncommitting 6 patches ... Now at patch "more-snarfle-cache" done $ stg series --description + improve-the-snarfle-cache # Improve the snarfle cache + remove-debug-printout # Remove debug printout + new-snarfle-cache-test # New snarfle cache test + oops-spell-function-name-corre # Oops, spell function name correctly + fix-documentation-error # Fix documentation error > more-snarfle-cache # More snarfle cache As you can see, linkstg:uncommit[] adds StGit metadata to the last few Git commits, turning them into StGit patches so that we can do stuff with them. NOTE: With the +$$--number$$+ flag, linkstg:uncommit[] uncommits that many commits and generates names for them based on their commit messages. If you like, you can instead list the patch names you want on the command line. At this point, there are a number of things we could do: * Continue developing, and take advantage of e.g. linkstg:goto[] or +stg refresh $$--patch$$+ to stick updates in the right patch to begin with. * Use e.g. linkstg:float[], linkstg:sink[], linkstg:push[], and linkstg:pop[] to reorder patches. * Use linkstg:squash[] to merge two or more patches into one. linkstgsub:squash[] pushes and pops so that the patches to be merged are consecutive and unrelated patches aren't in the way, then makes one big patch out of the patches to be merged, and finally pushes the other patches back. + Of course, as always when there is pushing involved, there is the possibility of conflicts. If a push results in a conflict, the operation will be halted, and we'll be given the option of either resolving the conflict or undoing. Once we feel that the history is as good as it's going to get, we can remove the StGit metadata, turning the patches back into regular Git commits again: $ stg commit --all TIP: linkstg:commit[] can also commit specific patches (named on the command line), leaving the rest alone. This can be used to retire patches as they mature, while keeping the newer and more volatile patches as patches. Workflow: Tracking branch ========================= In the 'Development branch' workflow described above, we didn't have to worry about other people; we're working on our branch, they are presumably working on theirs, and when the time comes and we're ready to publish our branch, we'll probably end up merging our branch with those other peoples'. That's how Git is designed to work. Or rather, one of the ways Git is designed to work. An alternative, popular in e.g. the Linux kernel community (for which Git was originally created), is that contributors send their patches by e-mail to a mailing list. Others read the patches, try them out, and provide feedback; often, the patch author is asked to send a new and improved version of the patches. Once the project maintainer is satisfied that the patches are good, she'll 'apply' them to a branch and publish it. StGit is ideally suited for the process of creating patches, mailing them out for review, revising them, mailing them off again, and eventually getting them accepted. Getting patches upstream ------------------------ We've already covered how to clone a Git repository and start writing patches. As for the next step, there are two commands you might use to get patches out of StGit: linkstg:mail[] and linkstg:export[]. linkstg:export[] will export your patches to a filesystem directory as one text file per patch, which can be useful if you are going to send the patches by something other than e-mail. Most of the time, though, linkstg:mail[] is what you want. NOTE: Git comes with tools for sending commits via e-mail. Since StGit patches are Git commits, you can use the Git tools if you like them better for some reason. NOTE: For exporting single patches -- as opposed to a whole bunch of them -- you could also use linkstg:show[] or linkstg:diff[]. Mailing a patch is as easy as this: $ stg mail --to recipient@example.com You can list one or more patches, or ranges of patches. Each patch will be sent as a separate mail, with the first line of the commit message as subject line. Try mailing patches to yourself to see what the result looks like. NOTE: linkstg:mail[] uses +sendmail+ on your computer to send the mails. If you don't have +sendmail+ properly set up, you can instruct it to use any SMTP server with the +$$--smtp-server$$+ flag. There are many command-line options to control exactly how mails are sent, as well as a message template you can modify if you want. The man page has all the details; I'll just mention two more here. +$$--edit-cover$$+ will open an editor and let you write an introductory message; all the patch mails will then be sent as replies to this 'cover message'. This is usually a good idea if you send more than one patch, so that reviewers can get a quick overview of the patches you sent. +$$--edit-patches$$+ will let you edit each patch before it is sent. You can change anything, but note that you are only editing the outgoing mail, not the patch itself; if you want to make changes to the patch, you probably want to use the regular StGit commands to do so. What this 'is' useful for, though, is to add notes for the patch recipients: ---------------------------------------------------------------------- From: Audrey U. Thor Subject: [PATCH] First line of the commit message The rest of the commit message --- Everything after the line with the three dashes and before the diff is just a comment, and not part of the commit message. If there's anything you want the patch recipients to see, but that shouldn't be recorded in the history if the patch is accepted, write it here. stgit/main.py | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/stgit/main.py b/stgit/main.py index e324179..6398958 100644 --- a/stgit/main.py +++ b/stgit/main.py @@ -171,6 +171,7 @@ def _main(): sys.exit(ret or utils.STGIT_SUCCESS) def main(): + print 'My first patch!' try: _main() finally: ---------------------------------------------------------------------- Rebasing a patch series ----------------------- While you are busy writing, submitting, and revising your patch series, other people will be doing the same thing. As a result, even though you started writing your patches on top of what was the latest history at the time, your stack base will grow ever more out of date. When you clone a repository, $ stg clone http://homepage.ntlworld.com/cmarinas/stgit.git stgit you initially get one local branch, +master+. You also get a number of 'remote' branches, one for each branch in the repository you cloned. In the case of the StGit repository, these are +remotes/origin/stable+, +remotes/origin/master+, and +remotes/origin/proposed+. +remotes+ means that it's not a local branch, just a snapshot of a branch in another repository; and +origin+ is the default name for the first remote repository (you can set up more; see the man page for +git remote+). Right after cloning, +master+ and +remotes/origin/master+ point at the same commit. When you start writing patches, +master+ will advance, and always point at the current topmost patch, but +remotes/origin/master+ will stay the same because it represents the master branch in the repository you cloned from -- your 'upstream' repository. Unless you are the only one working on the project, however, the upstream repository will not stay the same forever. New commits will be added to its branches; to update your clone, run $ git remote update This will update all your remote branches, but won't touch your local branches. To get the latest changes into your local +master+ branch, use linkstg:rebase[]: $ stg rebase remotes/origin/master This command will do three things: 1. Pop all patches, so that your local branch (+master+, in this example) points at the stack base. This is the same commit that +remotes/origin/master+ pointed at at the time you started writing your patches. 2. Set the stack base to the given commit (the current, updated value of +remotes/origin/master+). 3. Push the patches that were popped in the first step. The end result is that your patches are now applied on top of the latest version of +remotes/origin/master+. The primary reason for rebasing is to reduce the amount of conflicts between your work and others'. If one of your patches changes the same part of the same file as a patch someone else has written, you will get a conflict when you run linkstg:rebase[] the next time after the other person's patch has been accepted upstream. It is almost always less work to rebase often and resolve these one at a time, rather than a whole lot at once. After all, you have to rebase eventually; if you mail out patches that are based on an outdated branch, everyone who tries to apply them has to resolve the conflicts instead. There are more effective ways to get popular. When your patches are accepted ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If and when some or all of your patches are accepted upstream, you update and rebase just like usual -- but be sure to use the +$$--merged$$+ flag to linkstg:rebase[]: $ git remote update $ stg rebase --merged remotes/origin/master This flag makes the rebase operation better at detecting that your patches have been merged, at some cost in performance. The patches that had been merged will still be present in your patch stack after the rebase, but they will be empty, since the change they added is now already present in the stack base. Run linkstg:clean[] to get rid of such empty patches if you don't want them hanging around: $ stg clean Importing patches ----------------- While you are busy producing patches, there's hopefully someone -- the 'maintainer' -- at the other end who recieves them and 'applies' them to her Git tree, which is then published for all (or parts of) the world to see. It's perfectly fine for this person to not have the foggiest idea what StGit is. In that case, she'll probably apply your patches with something like +git am+, and everything will just work, exactly as if you'd used Git to send those patches. But she might be an StGit user too, in which case she might use linkstg:import[]. There are basically four kinds if stuff you can import with linkstg:import[]: 1. A patch in a file. 2. Several files containing one patch each, and a 'series' file listing those other files in the correct order. 3. An e-mail containing a single patch. 4. A mailbox file (in standard Unix +mbox+ format) containing multiple e-mails with one patch in each. Importing a plain patch ~~~~~~~~~~~~~~~~~~~~~~~ Importing a plain patch, such as produced by e.g. GNU +diff+, +git diff+, +git show+, linkstg:diff[], or linkstg:show[], is very easy. Just say $ stg import my-patch and you'll have a new patch at the top of your stack. If you don't give a file name on the command line, linkstg:import[] will read the patch from its standard input -- in other words, you can pipe a patch to it directly from the command that produces it. By default, the new patch's name will be taken from the file name, and its commit message and author info will be taken from the beginning of the patch, if they are there. However, there are command line switches to override all of these things; see the man page for details. Importing several patches at once ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Some programs -- among them linkstg:export[] -- will create a bunch of files with one patch in each, and a 'series' file (often called +series+) listing the other files in the correct order. Give +$$--series$$+ and the name of the series file to linkstg:import[], and it will import all the patches for you, in the correct order. Importing a patch from an e-mail ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Importing a patch from an e-mail is simple too: $ stg import --mail my-mail The e-mail should be in standard Git mail format (which is what e.g. linkstg:mail[] produces) -- that is, with the patch in-line in the mail, not attached. The authorship info is taken from the mail headers, and the commit message is read from the 'Subject:' line and the mail body. If you don't give a file name, the mail will be read from the standard input. This means that, if your mail reader supports it, you can pipe a mail directly to +stg import $$--mail$$+ and the patch will be applied. Importing a mailbox full of patches ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Finally, in case importing one patch at a time is too much work, linkstg:import[] also accepts an entire Unix +mbox+-format mailbox, either on the command line or on its standard input; just use the +$$--mbox$$+ flag. Each mail should contain one patch, and is imported just like with +$$--mail$$+. Mailboxes full of patches are produced by e.g. linkstg:mail[] with the +$$--mbox$$+ flag, but most mail readers can produce them too, meaning that you can copy all the patch mails you want to apply to a separate mailbox, and then import them all in one go. Other stuff that needs to be placed somewhere ============================================= Undo ---- TODO:: undo, redo, log, reset Interoperating with Git ----------------------- TODO:: * git commit + repair * git reset HEAD~n + repair * don't do git rebase or git merge, because it won't work Patch stuff ----------- TODO:: This section needs revising. I've only fixed the formatting. Most of it should go under "Workflow: Tracking branch" As mentioned in the introduction, StGit stores modifications to your working tree in the form of Git commits. This means if you want to apply your changes to a tree not managed by Git, or send your changes to someone else in e-mail, you need to convert your StGit patches into normal textual diffs that can be applied with the GNU patch command. linkstg:diff[] is a powerful way to generate and view textual diffs of patches managed by StGit. To view a diff of the topmost patch: $ stg diff -r / Observe that this does not show any changes in the working directory that have not been saved by a linkstgsub:refresh[]. To view just the changes you've made since the last refresh, use: $ stg diff -r /top If you want to see the changes made by the patch combined with any unsaved changes in the working directory, try: $ stg diff -r /bottom You can also show the changes to any patch in your stack with: $ stg diff -r / Use this command to view all the changes in your stack up through the current patch: $ stg diff -r base linkstg:diff[] supports a number of other features that are very useful. Be sure to take a look at the help information for this command. To convert your StGit patches into patch files: $ stg export [--range=[[:]]] [] linkstg:export[] supports options to automatically number the patches (+-n+) or add the +.diff+ extension (+-d+). If you don't tell linkstgsub:export[] where to put the patches, it will create directory named +patch-+ in your current directory, and store the patches there. To e-mail a patch or range of patches: $ stg mail [--to=...] (--all | --range=[[:]] | ) linkstg:mail[] has a lot of options, so read the output of +stg mail -h+ for more information. You can also import an existing GNU diff patch file as a new StGit patch with a single command. linkstg:import[] will automatically parse through the patch file and extract a patch description. Use: $ stg import [] This is the equivalent of $ stg new $ patch -i $ stg refresh -e Sometimes the patch file won't apply cleanly. In that case, linkstg:import[] will leave you with an empty StGit patch, to which you then apply the patch file by hand using "patch -i" and your favorite editor. To merge a GNU diff file (defaulting to the standard input) into the topmost patch: $ stg fold [] This command supports a +$$--threeway$$+ option which applies the patch onto the bottom of the topmost one and performs a three-way merge. Templates --------- TODO:: This section needs revising. I've only fixed the formatting. linkstg:export[] and linkstg:mail[] use templates for generating the patch files or e-mails. The default templates are installed under +/share/stgit/templates/+ and, combined with the extra options available for these commands, should be enough for most users. The template format uses the standard Python string formatting rules. The variables available are listed in the the manual pages for each command. linkstg:mail[] can also send an initial 'cover' e-mail for which there is no default template. The +/share/stgit/examples/firstmail.tmpl+ file can be used as an example. A default description for new patches can be defined in the +.git/ patchdescr.tmpl+ file. This is useful for things like signed-off-by lines. Emacs ===== StGit comes with an Emacs mode, +stgit-mode+, supporting Emacs versions 22 and later. To start using it, add the +stgit/contrib+ directory to your Emacs load-path and run +(require 'stgit)+. For example, you can add the following to your +.emacs+ file: ---------------------------------------------------------------------- (add-to-list 'load-path "/path/to/stgit/contrib") (require 'stgit) ---------------------------------------------------------------------- Start +stgit-mode+ using +M-x stgit+ and select the directory where the source code you want to use StGit on can be found. If StGit has not been initialized in this directory yet, you will need to run +M-x stgit-init+ before you continue. The +stgit-mode+ buffer (usually named +$$*stgit*$$+) has the following layout: ---------------------------------------------------------------------- Branch: name-of-branch + The first applied patch + Another applied patch > The topmost patch Index Work Tree - An unapplied patch - Another unapplied patch -- ---------------------------------------------------------------------- The above means that the active branch name is +name-of-branch+ and it contains five patches, three of which are applied. The git index and working tree contain no (modified) files. Basic Commands -------------- To get help, press +h+. This opens a new buffer which lists all commands supported in +stgit-mode+. Also, if you have the menu bar enabled (toggled using +M-x menu-bar-mode+), a new menu entry called +StGit+ will appear, from which you can run several StGit commands. As the menu should be self-explanatory, the rest of this tutorial will focus on available keyboard commands. Move the point (cursor) between lines using +C-p+ and +C-n+, or between patches using +p+ and +n+. You can linkstgsub:undo[] and linkstgsub:redo[] StGit commands using +C-/+ and +C-c C-/+, respectively. Creating New Patches -------------------- If you want to create a new patch, first make some changes that should go into it. As you save a file which is Git-controlled, it will appear as a modification in the +Work Tree+ section: ---------------------------------------------------------------------- Work Tree Modified foo.txt ---------------------------------------------------------------------- To create a new patch based on the changes in the index or, if it contains no changes, the working tree, press +c+. This opens a new buffer where you can enter the patch description. When you are done, press +C-c C-c+. Your new patch will now show up in the +$$*stgit*$$+ buffer, and the working tree will no longer contain any modifications: ---------------------------------------------------------------------- + The topmost patch > First line of your new description Index Work Tree ---------------------------------------------------------------------- As you make additional changes in your files, and want to include them in the topmost patch, press +r+, which runs linkstg:refresh[]. If you want to add the changes to a patch which is not topmost, move the point to the line of that patch and press +C-u r+. Inspecting Patches ------------------ To see what a particular patch contains, you can move the point (cursor) to the line of that patch, and press +RET+ (Enter). This will "expand" the patch and show which files it changes: ---------------------------------------------------------------------- + My patch Modified foo.txt Deleted bar.c ---------------------------------------------------------------------- You can press +=+, which will show the diff of that patch or file in a new buffer. With a prefix argument (+C-u =+), the diff will not show changes in whitespace. To visit (open) a modified file in another Emacs window, press +o+ on that line. +RET+ will visit it in the current window. Changing the Patch Series ------------------------- You can linkstgsub:push[] and linkstgsub:pop[] patches using +>+ (pushes the topmost unapplied patch onto the stack) and +<+ (pops the topmost applied patch off the stack). By moving the point to a particular patch and pressing +P+ (+S-p+) you either (if it already was applied) pop that patch off the stack, or (if it was unapplied) push it onto the stack. You can move patches by first marking one or more using +m+. Then, move the point to where in the series you want these patches to move, and press +M+. Use +DEL+ or +u+ to remove a mark. You can also combine (linkstgsub:squash[]) two or more patches by marking them and pressing +S+ (+S-s+). This will open a new buffer where you can edit the patch description of the new, combined, patch. When done, press +C-c C-c+, and the topmost of the marked patches will be replaced in the stack by one combined patch. You can linkstgsub:delete[] a patch by moving to its line and pressing +D+. If you press +C-u D+, the contents of the patch will be spilled to the index. To linkstgsub:edit[] the description of a patch, press +e+. This opens the patch description in a new buffer. Press +C-c C-c+ when you are done editing the patch. These operations may result in merge conflicts; more about that later. Patch Names ----------- By default, the patch description is shown but not the patch names. You can toggle showing the names using +t n+. To rename a patch, press +C-c C-r+. Showing Committed Patches ------------------------- Sometimes it is convenient to be able to investigate already committed patches. Toggle showing these using +t h+. With a prefix argument, you can set how many to show; e.g., +7 t h+ will show seven already committed patches. Using the Index and Working Tree -------------------------------- You can move a changed file between the index and the working tree using +i+. This is useful if your working tree contains a number of changes and you want to create or refresh a patch using only some of the changed files. Once you have the correct set of files in the index, use +c+ to create or +r+ to refresh patches using only the files in the index. If you want to revert a change in either the index or the working tree, move the point to that line and press +U+. If you press +U+ on the +Index+ or +Work Tree+ lines, all the changes in the index or working tree will be reverted. Branches -------- You can switch to another linkstgsub:branch[] by pressing +B+. If you type the name of a branch that does not exist, you will be asked whether to create one. The new branch will be based off the current +HEAD+. Use +C-c C-b+ to linkstgsub:rebase[] the current branch. It will default to rebasing to the +git config+ setting for +branch._branch_.stgit.parentbranch+. Merge Conflicts --------------- If an operation resulted in a merge conflict, the files with conflicts will show as +Unmerged+ in the +$$*stgit*$$+ buffer. To handle the conflicts, you can linkstgsub:undo[] the operation that caused the conflict using +C-u C-/+. Note the +C-u+ prefix, which will tell the undo operation to continue despite the index or working tree containing changes. If you instead want to resovle the changes, you must first edit the files with conflicts to make sure they are in the correct state. Once you have done this, press +R+ on the line of that file, which will remove the +Unmerged+ flag. Once all conflicts have been resolved, you can continue working as normal, for example by refreshing the patch that had the conflict. To investigate the reason of conflicts, you can use the +d b+, +d o+, and +d t+ commands to, respectively, show the diffs against the merge base, our branch, or their branch. +d c+ shows a combined diff. See linkman:git-diff[1] for more information about these commands. A more powerful tool to resolve conflicts is the Emacs +smerge-mode+. To start using it to resolve a conflict, press +d m+. It is outside the scope of this tutorial to explain how to use it, but press +q+ to leave +smerge-mode+. stgit-0.17.1/Documentation/stg-fold.txt0000644002002200200220000000165011743520633020167 0ustar cmarinascmarinasstg-fold(1) =========== NAME ---- stg-fold - Integrate a GNU diff patch into the current patch SYNOPSIS -------- [verse] 'stg' fold [options] [--] [] DESCRIPTION ----------- Apply the given GNU diff file (or the standard input) onto the top of the current patch. With the '--threeway' option, the patch is applied onto the bottom of the current patch and a three-way merge is 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. OPTIONS ------- -t:: --threeway:: Perform a three-way merge with the current patch. -b BASE:: --base BASE:: Use BASE instead of HEAD when applying the patch. -p N:: --strip N:: Remove N leading slashes from diff paths (default 1). --reject:: Leave the rejected hunks in corresponding *.rej files. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-top.txt0000644002002200200220000000052511743520635020047 0ustar cmarinascmarinasstg-top(1) ========== NAME ---- stg-top - Print the name of the top patch SYNOPSIS -------- [verse] 'stg' top DESCRIPTION ----------- Print the name of the current (topmost) patch. OPTIONS ------- -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-rename.txt0000644002002200200220000000066111743520635020515 0ustar cmarinascmarinasstg-rename(1) ============= NAME ---- stg-rename - Rename a patch SYNOPSIS -------- [verse] 'stg' rename [options] [--] [oldpatch] DESCRIPTION ----------- Rename into in a series. If is not given, the top-most patch will be renamed. OPTIONS ------- -b BRANCH:: --branch BRANCH:: use BRANCH instead of the default one. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-redo.txt0000644002002200200220000000111511743520635020172 0ustar cmarinascmarinasstg-redo(1) =========== NAME ---- stg-redo - Undo the last undo operation SYNOPSIS -------- [verse] 'stg' redo DESCRIPTION ----------- If the last command was an undo, reset the patch stack to the state it had before the undo. Consecutive invocations of "stg redo" will undo the effects of consecutive invocations of "stg undo". It is an error to run "stg redo" if the last command was not an undo. OPTIONS ------- -n N:: --number N:: Undo the last N undos. --hard:: Discard changes in your index/worktree. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-id.txt0000644002002200200220000000130711743520633017636 0ustar cmarinascmarinasstg-id(1) ========= NAME ---- stg-id - Print the git hash value of a StGit reference SYNOPSIS -------- [verse] 'stg' id [options] [--] [] DESCRIPTION ----------- Print the SHA1 value of a Git id (defaulting to HEAD). In addition to the standard Git id's like heads and tags, this command also accepts '[:]' for the id of a patch, '[:]\{base\}' for the base of the stack and '[:]\{public\}' for the public branch corresponding to the stack (see the 'publish' command for details). If no branch is specified, it defaults to the current one. The bottom of a patch is accessible with the '[:]^' format. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-squash.txt0000644002002200200220000000272311743520635020553 0ustar cmarinascmarinasstg-squash(1) ============= NAME ---- stg-squash - Squash two or more patches into one SYNOPSIS -------- [verse] 'stg' squash [options] [--] DESCRIPTION ----------- Squash two or more patches, creating one big patch that contains all their changes. In more detail: 1. Pop all the given patches, plus any other patches on top of them. 2. Push the given patches in the order they were given on the command line. 3. Squash the given patches into one big patch. 4. Allow the user to edit the commit message of the new patch interactively. 5. Push the other patches that were popped in step (1). Conflicts can occur whenever we push a patch; that is, in step (2) and (5). If there are conflicts, the command will stop so that you can resolve them. OPTIONS ------- -n NAME:: --name NAME:: Name of squashed patch. -m MESSAGE:: --message MESSAGE:: Use MESSAGE instead of invoking the editor. -f FILE:: --file FILE:: Use the contents of FILE instead of invoking the editor. (If FILE is "-", write to stdout.) --save-template FILE:: Instead of running the command, just write the message template to FILE, and exit. (If FILE is "-", write to stdout.) + When driving StGit from another program, it is often useful to first call a command with '--save-template', then let the user edit the message, and then call the same command with '--file'. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-mail.txt0000644002002200200220000001140411743520634020164 0ustar cmarinascmarinasstg-mail(1) =========== NAME ---- stg-mail - Send a patch or series of patches by e-mail SYNOPSIS -------- [verse] 'stg' mail [options] [--] [] [] [..] DESCRIPTION ----------- Send a patch or a range of patches by e-mail using the SMTP server specified by the 'stgit.smtpserver' configuration option, or the '--smtp-server' command line option. This option can also be an absolute path to 'sendmail' followed by command line arguments. The From address and the e-mail format are generated from the template file passed as argument to '--template' (defaulting to '.git/patchmail.tmpl' or '~/.stgit/templates/patchmail.tmpl' or '/usr/share/stgit/templates/patchmail.tmpl'). A patch can be sent as attachment using the --attach option in which case the 'mailattch.tmpl' template will be used instead of 'patchmail.tmpl'. The To/Cc/Bcc addresses can either be added to the template file or passed via the corresponding command line options. They can be e-mail addresses or aliases which are automatically expanded to the values stored in the [mail "alias"] section of GIT configuration files. A preamble e-mail can be sent using the '--cover' and/or '--edit-cover' options. The first allows the user to specify a file to be used as a template. The latter option will invoke the editor on the specified file (defaulting to '.git/covermail.tmpl' or '~/.stgit/templates/covermail.tmpl' or '/usr/share/stgit/templates/covermail.tmpl'). All the subsequent e-mails appear as replies to the first e-mail sent (either the preamble or the first patch). E-mails can be seen as replies to a different e-mail by using the '--in-reply-to' option. SMTP authentication is also possible with '--smtp-user' and '--smtp-password' options, also available as configuration settings: 'smtpuser' and 'smtppassword'. TLS encryption can be enabled by '--smtp-tls' option and 'smtptls' setting. The following variables are accepted by both the preamble and the patch e-mail templates: %(diffstat)s - diff statistics %(number)s - empty if only one patch is sent or ' patchnr/totalnr' %(snumber)s - stripped version of '%(number)s' %(patchnr)s - patch number %(sender)s - 'sender' or 'authname ' as per the config file %(totalnr)s - total number of patches to be sent %(version)s - ' version' string passed on the command line (or empty) In addition to the common variables, the preamble e-mail template accepts the following: %(shortlog)s - first line of each patch description, listed by author In addition to the common variables, the patch e-mail template accepts the following: %(authdate)s - patch creation date %(authemail)s - author's email %(authname)s - author's name %(commemail)s - committer's e-mail %(commname)s - committer's name %(diff)s - unified diff of the patch %(fromauth)s - 'From: author\n\n' if different from sender %(longdescr)s - the rest of the patch description, after the first line %(patch)s - patch name %(prefix)s - 'prefix ' string passed on the command line %(shortdescr)s - the first line of the patch description OPTIONS ------- -a:: --all:: E-mail all the applied patches. --to TO:: Add TO to the To: list. --cc CC:: Add CC to the Cc: list. --bcc BCC:: Add BCC to the Bcc: list. --auto:: Automatically cc the patch signers. --no-thread:: Do not send subsequent messages as replies. --unrelated:: Send patches without sequence numbering. --attach:: Send a patch as attachment. -v VERSION:: --version VERSION:: Add VERSION to the [PATCH ...] prefix. --prefix PREFIX:: Add PREFIX to the [... PATCH ...] prefix. -t FILE:: --template FILE:: Use FILE as the message template. -c FILE:: --cover FILE:: Send FILE as the cover message. -e:: --edit-cover:: Edit the cover message before sending. -E:: --edit-patches:: Edit each patch before sending. -s SECONDS:: --sleep SECONDS:: Sleep for SECONDS between e-mails sending. --in-reply-to REFID:: Use REFID as the reference id. --smtp-server HOST[:PORT] or "/path/to/sendmail -t -i":: SMTP server or command to use for sending mail. -u USER:: --smtp-user USER:: Username for SMTP authentication. -p PASSWORD:: --smtp-password PASSWORD:: Password for SMTP authentication. -T:: --smtp-tls:: Use SMTP with TLS encryption. -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. -m:: --mbox:: Generate an mbox file instead of sending. --git:: Use git send-email (EXPERIMENTAL). -O OPTIONS:: --diff-opts OPTIONS:: Extra options to pass to "git diff". StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-publish.txt0000644002002200200220000000600111743520634020705 0ustar cmarinascmarinasstg-publish(1) ============== NAME ---- stg-publish - Push the stack changes to a merge-friendly branch SYNOPSIS -------- [verse] 'stg' publish [options] [--] [branch] DESCRIPTION ----------- This command commits a set of changes on a separate (called public) branch based on the modifications of the given or current stack. The history of the public branch is not re-written, making it merge-friendly and feasible for publishing. The heads of the stack and public branch may be different but the corresponding tree objects are always the same. If the trees of the stack and public branch are different (otherwise the command has no effect), StGit first checks for a rebase of the stack since the last publishing. If a rebase is detected, StGit creates a commit on the public branch corresponding to a merge between the new stack base and the latest public head. If no rebasing was detected, StGit checks for new patches that may have been created on top of the stack since the last publishing. If new patches are found and are not empty, they are checked into the public branch keeping the same commit information (e.g. log message, author, committer, date). If the above tests fail (e.g. patches modified or removed), StGit creates a new commit on the public branch having the same tree as the stack but the public head as its parent. The editor will be invoked if no "--message" option is given. It is recommended that stack modifications falling in different categories as described above are separated by a publish command in order to keep the public branch history cleaner (otherwise StGit would generate a big commit including several stack modifications). The '--unpublished' option can be used to check if there are applied patches that have not been published to the public branch. This is done by trying to revert the patches in the public tree (similar to the 'push --merged' detection). The '--last' option tries to find the last published patch by checking the SHA1 of the patch tree agains the public tree. This may fail if the stack was rebased since the last publish command. The public branch name can be set via the branch..public configuration variable (defaulting to ".public"). OPTIONS ------- -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. -l:: --last:: Show the last published patch. -u:: --unpublished:: Show applied patches that have not been published. --author "NAME ":: Set the author details. --authname NAME:: Set the author name. --authemail EMAIL:: Set the author email. --authdate DATE:: Set the author date. -m MESSAGE:: --message MESSAGE:: Use MESSAGE instead of invoking the editor. -f FILE:: --file FILE:: Use the contents of FILE instead of invoking the editor. (If FILE is "-", write to stdout.) --sign:: Add a "Signed-off-by:" to the end of the patch. --ack:: Add an "Acked-by:" line to the end of the patch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-branch.txt0000644002002200200220000000674111743520633020506 0ustar cmarinascmarinasstg-branch(1) ============= NAME ---- stg-branch - Branch operations: switch, list, create, rename, delete, ... SYNOPSIS -------- [verse] 'stg' branch 'stg' branch [--merge] [--] 'stg' branch --list 'stg' branch --create [--] [] 'stg' branch --clone [--] [] 'stg' branch --rename [--] 'stg' branch --protect [--] [] 'stg' branch --unprotect [--] [] 'stg' branch --delete [--force] [--] 'stg' branch --cleanup [--force] [--] [] 'stg' branch --description= [--] [] DESCRIPTION ----------- Create, clone, switch between, rename, or delete development branches within a git repository. 'stg branch':: Display the name of the current branch. 'stg branch' :: Switch to the given branch. OPTIONS ------- -l:: --list:: List each branch in the current repository, followed by its branch description (if any). The current branch is prefixed with '>'. Branches that have been initialized for StGit (with linkstg:init[]) are prefixed with 's'. Protected branches are prefixed with 'p'. -c:: --create:: Create (and switch to) a new branch. The new branch is already initialized as an StGit patch stack, so you do not have to run linkstg:init[] manually. If you give a committish argument, the new branch is based there; otherwise, it is based at the current HEAD. + StGit will try to detect the branch off of which the new branch is forked, as well as the remote repository from which that parent branch is taken (if any), so that running linkstg:pull[] will automatically pull new commits from the correct branch. It will warn if it cannot guess the parent branch (e.g. if you do not specify a branch name as committish). --clone:: Clone the current branch, under the name if specified, or using the current branch's name plus a timestamp. + The description of the new branch is set to tell it is a clone of the current branch. The parent information of the new branch is copied from the current branch. -r:: --rename:: Rename an existing branch. -p:: --protect:: Prevent StGit from modifying a branch -- either the current one, or one named on the command line. -u:: --unprotect:: Allow StGit to modify a branch -- either the current one, or one named on the command line. This undoes the effect of an earlier 'stg branch --protect' command. --delete:: Delete the named branch. If there are any patches left in the branch, StGit will refuse to delete it unless you give the '--force' flag. + A protected branch cannot be deleted; it must be unprotected first (see '--unprotect' above). + If you delete the current branch, you are switched to the "master" branch, if it exists. --cleanup:: Remove the StGit information for the current or given branch. If there are patches left in the branch, StGit refuses the operation unless '--force' is given. + A protected branch cannot be cleaned up; it must be unprotected first (see '--unprotect' above). + A cleaned up branch can be re-initialised using the 'stg init' command. -d DESCRIPTION:: --description DESCRIPTION:: Set the branch description. --merge:: Merge work tree changes into the other branch. --force:: Force a delete when the series is not empty. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-diff.txt0000644002002200200220000000133511743520633020153 0ustar cmarinascmarinasstg-diff(1) =========== NAME ---- stg-diff - Show the tree diff SYNOPSIS -------- [verse] 'stg' diff [options] [--] [] DESCRIPTION ----------- Show the diff (default) or diffstat between the current working copy or a tree-ish object and another tree-ish object (defaulting to HEAD). File names can also be given to restrict the diff output. The tree-ish object has the format accepted by the linkstg:id[] command. OPTIONS ------- -r rev1[..[rev2]]:: --range rev1[..[rev2]]:: Show the diff between revisions. -s:: --stat:: Show the stat instead of the diff. -O OPTIONS:: --diff-opts OPTIONS:: Extra options to pass to "git diff". StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-commit.txt0000644002002200200220000000177211743520633020540 0ustar cmarinascmarinasstg-commit(1) ============= NAME ---- stg-commit - Permanently store the applied patches into the stack base SYNOPSIS -------- [verse] 'stg' commit 'stg' commit [--] 'stg' commit -n NUM 'stg' commit --all DESCRIPTION ----------- Merge one or more patches into the base of the current stack and remove them from the series while advancing the base. This is the opposite of 'stg uncommit'. Use this command if you no longer want to manage a patch with StGIT. By default, the bottommost patch is committed. If patch names are given, the stack is rearranged so that those patches are at the bottom, and then they are committed. 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. OPTIONS ------- -n NUMBER:: --number NUMBER:: Commit the specified number of patches. -a:: --all:: Commit all applied patches. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-reset.txt0000644002002200200220000000125411743520635020367 0ustar cmarinascmarinasstg-reset(1) ============ NAME ---- stg-reset - Reset the patch stack to an earlier state SYNOPSIS -------- [verse] 'stg' reset [options] [--] [ []] DESCRIPTION ----------- Reset the patch stack to an earlier state. If no state is specified, reset only the changes in the worktree. The state is specified with a commit id from a stack log; "stg log" lets you view this log, and "stg reset" lets you reset to any state you see in the log. If one or more patch names are given, reset only those patches, and leave the rest alone. OPTIONS ------- --hard:: Discard changes in your index/worktree. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-float.txt0000644002002200200220000000122111743520633020342 0ustar cmarinascmarinasstg-float(1) ============ NAME ---- stg-float - Push patches to the top, even if applied SYNOPSIS -------- [verse] 'stg' float [--] 'stg' float -s DESCRIPTION ----------- Push a patch or a range of patches to the top even if applied. The 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). OPTIONS ------- -s FILE:: --series FILE:: Rearrange according to the series FILE. -k:: --keep:: Keep the local changes. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/Makefile0000644002002200200220000000401711271344640017345 0ustar cmarinascmarinasCOMMANDS = $(shell ../stg-build --commands) COMMANDS_TXT = $(patsubst %,stg-%.txt,$(COMMANDS)) MAN1_TXT= stg.txt $(COMMANDS_TXT) DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT)) ARTICLES = tutorial DOC_HTML += $(patsubst %,%.html,$(ARTICLES)) DOC_PDF += $(patsubst %,%.pdf,$(ARTICLES)) DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT)) prefix?=$(HOME) htmldir?=$(prefix)/share/doc/stgit mandir?=$(prefix)/share/man man1dir=$(mandir)/man1 # DESTDIR= ASCIIDOC=asciidoc --unsafe ASCIIDOC_EXTRA = INSTALL?=install # # Please note that there is a minor bug in asciidoc. # The version after 6.0.3 _will_ include the patch found here: # http://marc.theaimsgroup.com/?l=git&m=111558757202243&w=2 # # Until that version is released you may have to apply the patch # yourself - yes, all 6 characters of it! # all: html man html: $(DOC_HTML) pdf: $(DOC_PDF) $(DOC_HTML) $(DOC_MAN1): asciidoc.conf man: man1 man1: $(DOC_MAN1) install: man $(INSTALL) -d -m755 $(DESTDIR)$(man1dir) $(INSTALL) -m644 $(DOC_MAN1) $(DESTDIR)$(man1dir) install-html: html $(INSTALL) -d -m755 $(DESTDIR)$(htmldir) $(INSTALL) -m644 $(DOC_HTML) $(DESTDIR)$(htmldir) # # Determine "include::" file references in asciidoc files. # doc.dep : $(wildcard *.txt) build-docdep.perl rm -f $@+ $@ perl ./build-docdep.perl >$@+ mv $@+ $@ -include doc.dep clean: rm -f *.xml *.html *.pdf *.1 doc.dep $(COMMANDS_TXT) command-list.txt ALL_PY = $(shell find ../stgit -name '*.py') $(COMMANDS_TXT): $(ALL_PY) ../stg-build --asciidoc $(basename $(subst stg-,,$@)) > $@ command-list.txt: $(ALL_PY) ../stg-build --cmd-list > $@ %.html : %.txt $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf $(ASCIIDOC_EXTRA) $< %.1 : %.xml xmlto -m callouts.xsl man $< %.xml : %.txt $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf $< %.pdf: %.xml xmlto pdf $< # special formatting rules tutorial.html : %.html : %.txt $(ASCIIDOC) -b xhtml11 -d article -a toc -f tutorial.conf \ $(ASCIIDOC_EXTRA) $< tutorial.xml : %.xml : %.txt $(ASCIIDOC) -b docbook -d article -f tutorial.conf $< stgit-0.17.1/Documentation/stg-pop.txt0000644002002200200220000000160111743520634020036 0ustar cmarinascmarinasstg-pop(1) ========== NAME ---- stg-pop - Pop one or more patches from the stack SYNOPSIS -------- [verse] 'stg' pop [options] [--] [] [] [..] DESCRIPTION ----------- Pop the topmost patch or a range of patches from the stack. The command fails if there are conflicts or local changes (and --keep was not specified). A series of pop and push operations are performed so that only the 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). OPTIONS ------- -a:: --all:: Pop all the applied patches. -n NUMBER:: --number NUMBER:: Pop the specified number of patches. + With a negative number, pop all but that many patches. -k:: --keep:: Keep the local changes. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-uncommit.txt0000644002002200200220000000275411743520635021106 0ustar cmarinascmarinasstg-uncommit(1) =============== NAME ---- stg-uncommit - Turn regular git commits into StGit patches SYNOPSIS -------- [verse] 'stg' uncommit [--] [ ...] 'stg' uncommit -n NUM [--] [] 'stg' uncommit -t [-x] DESCRIPTION ----------- Take one or more git commits at the base of the current stack and turn them into StGIT patches. The new patches are created as applied patches at the bottom of the stack. This is the opposite of 'stg commit'. By default, the number of patches to uncommit is determined by the number of patch names provided on the command line. First name is used for the first patch to uncommit, i.e. for the newest patch. The -n/--number option specifies the number of patches to uncommit. In this case, at most one patch name may be specified. It is used as prefix to which the patch number is appended. If no patch names are provided on the command line, StGIT automatically generates them based on the first line of the patch description. The -t/--to option specifies that all commits up to and including the given commit should be uncommitted. Only commits with exactly one parent can be uncommitted; in other words, you can't uncommit a merge. OPTIONS ------- -n NUMBER:: --number NUMBER:: Uncommit the specified number of commits. -t TO:: --to TO:: Uncommit to the specified commit. -x:: --exclusive:: Exclude the commit specified by the --to option. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/asciidoc.conf0000644002002200200220000000472711747465264020360 0ustar cmarinascmarinas[attributes] asterisk=* plus=+ caret=^ startsb=[ endsb=] tilde=~ ifdef::backend-docbook[] ifndef::docbook-xsl-172[] # "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this. # v1.72 breaks with this because it replaces dots not in roff requests. [listingblock] {title} ifdef::doctype-manpage[] .ft C endif::doctype-manpage[] | ifdef::doctype-manpage[] .ft endif::doctype-manpage[] {title#} endif::docbook-xsl-172[] endif::backend-docbook[] ifdef::doctype-manpage[] ifdef::backend-docbook[] [header] template::[header-declarations] {mantitle} {manvolnum} StGit {stgit_version} StGit Manual {manname} {manpurpose} endif::backend-docbook[] endif::doctype-manpage[] ## linkman: macro # # Usage: linkman:command[manpage-section] # # Note, {0} is the manpage section, while {target} is the command. # # Show link as: (
); if section is defined, else just # show the command. ifdef::backend-docbook[] [linkman-inlinemacro] {0%{target}} {0#} {0#{target}{0}} {0#} endif::backend-docbook[] ifdef::backend-xhtml11[] [linkman-inlinemacro] {target}{0?({0})} endif::backend-xhtml11[] ## linkstg: macro # # Usage: linkstg:command[] # # Show StGit link as: stg-(1) in man pages, stg in # html. ifdef::backend-docbook[] [linkstg-inlinemacro] stg-{target}1 endif::backend-docbook[] ifdef::backend-xhtml11[] [linkstg-inlinemacro] stg {target} endif::backend-xhtml11[] ## linkstgsub: macro # # Usage: linkstgsub:command[] # # Show StGit link as: . ifdef::backend-docbook[] [linkstgsub-inlinemacro] {target} endif::backend-docbook[] ifdef::backend-xhtml11[] [linkstgsub-inlinemacro] {target} endif::backend-xhtml11[] [macros] # regexen to match macro templates above (?Plinkman):(?P\w+)\[(?P\d+)]= (?Plinkstgsub):(?P\w+)\[]= (?Plinkstg):(?P\w+)\[]= stgit-0.17.1/Documentation/stg-series.txt0000644002002200200220000000300011743520635020526 0ustar cmarinascmarinasstg-series(1) ============= NAME ---- stg-series - Print the patch series SYNOPSIS -------- [verse] 'stg' series [options] [--] [] DESCRIPTION ----------- Show all the patches in the series, or just those in the given range, ordered from top to bottom. The applied patches are prefixed with a +++ (except the current patch, which is prefixed with a +>+), the unapplied patches with a +-+, and the hidden patches with a +!+. Empty patches are prefixed with a '0'. OPTIONS ------- -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. -a:: --all:: Show all patches, including the hidden ones. -A:: --applied:: Show the applied patches only. -U:: --unapplied:: Show the unapplied patches only. -H:: --hidden:: Show the hidden patches only. -m BRANCH:: --missing BRANCH:: Show patches in BRANCH missing in current. -c:: --count:: Print the number of patches in the series. -d:: --description:: Show a short description for each patch. --author:: Show the author name for each patch. -e:: --empty:: Before the +++, +>+, +-+, and +!+ prefixes, print a column that contains either +0+ (for empty patches) or a space (for non-empty patches). --showbranch:: Append the branch name to the listed patches. --noprefix:: Do not show the patch status prefix. -s:: --short:: List just the patches around the topmost patch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg.txt0000644002002200200220000001310711271344640017243 0ustar cmarinascmarinasstg(1) ====== Yann Dirson NAME ---- stg - Manage stacks of patches using the Git content tracker SYNOPSIS -------- [verse] 'stg' [--version | --help] 'stg' [--help | --help] 'stg' [COMMAND OPTIONS] [ARGS] DESCRIPTION ----------- StGit (Stacked Git) is an application that provides a convenient way to maintain a 'patch stack' on top of a Git branch: * The topmost (most recent) commits of a branch are given names. Such a named commit is called a 'patch'. * After making changes to the worktree, you can incorporate the changes into an existing patch; this is called 'refreshing'. You may refresh any patch, not just the topmost one. * You can 'pop' a patch: temporarily putting it aside, so that the patch below it becomes the topmost patch. Later you may 'push' it onto the stack again. Pushing and popping can be used to reorder patches. * You can easily 'rebase' your patch stack on top of any other Git commit. (The 'base' of a patch stack is the most recent Git commit that is not an StGit patch.) For example, if you started making patches on top of someone else's branch, and that person publishes an updated branch, you can take all your patches and apply them on top of the updated branch. * As you would expect, changing what is below a patch can cause that patch to no longer apply cleanly -- this can occur when you reorder patches, rebase patches, or refresh a non-topmost patch. StGit uses Git's rename-aware three-way merge capability to automatically fix up what it can; if it still fails, it lets you manually resolve the conflict just like you would resolve a merge conflict in Git. * The patch stack is just some extra metadata attached to regular Git commits, so you can continue to use most Git tools along with StGit. Typical uses ~~~~~~~~~~~~ Tracking branch:: Tracking changes from a remote branch, while maintaining local modifications against that branch, possibly with the intent of sending some patches upstream. You can modify your patch stack as much as you want, and when your patches are finally accepted upstream, the permanent recorded Git history will contain just the final sequence of patches, and not the messy sequence of edits that produced them. + Commands of interest in this workflow are e.g. linkstgsub:rebase[] and linkstgsub:mail[]. Development branch:: Even if you have no "upstream" to send patches to, you can use StGit as a convenient way to modify the recent history of a Git branch. For example, instead of first committing change 'A', then change 'B', and then 'A2' to fix 'A' because it wasn't quite right, you could incorporate the fix directly into 'A'. This way of working results in a much more readable Git history than if you had immortalized every misstep you made on your way to the right solution. + Commands of interest in this workflow are e.g. linkstgsub:uncommit[], which can be used to move the patch stack base downwards -- i.e., turn Git commits into StGit patches after the fact -- and linkstgsub:commit[], its inverse. For more information, see link:tutorial.html[the tutorial]. Specifying patches ~~~~~~~~~~~~~~~~~~ Many StGit commands take references to StGit patches as arguments. Patches in the stack are identified with short names, each of which must be unique in the stack. Patches in the current branch are simply referred to by their name. Some commands allow you to specify a patch in another branch of the repository; this is done by prefixing the patch name with the branch name and a colon (e.g. +otherbranch:thatpatch+). Specifying commits ~~~~~~~~~~~~~~~~~~ Some StGit commands take Git commits as arguments. StGit accepts all commit expressions that Git does; and in addition, a patch name (optionally prefixed by a branch name and a colon) is allowed in this context. The usual Git modifiers $$^$$ and $$~$$ are also allowed; e.g., +abranch:apatch~2+ is the grandparent of the commit that is the patch +apatch+ on branch +abranch+. Instead of a patch name, you can say +$${base}$$+ to refer to the stack base (the commit just below the bottommost patch); so, +abranch:$${base}$$+ is the base of the stack in branch +abranch+. If you need to pass a given StGit reference to a Git command, linkstg:id[] will convert it to a Git commit id for you. OPTIONS ------- The following generic option flags are available. Additional options are available for (and documented with) the different subcommands. --version:: Prints the StGit version, as well as version of other components used, such as Git and Python. --help:: Prints the synopsis and a list of all subcommands. If an StGit subcommand is given, prints the synposis for that subcommand. STGIT COMMANDS -------------- We divide StGit commands in thematic groups, according to the primary type of object they create or change. ifdef::backend-docbook[] Here is a short description of each command. A more detailed description is available in individual command manpages. Those manpages are named 'stg-(1)'. endif::backend-docbook[] include::command-list.txt[] CONFIGURATION MECHANISM ----------------------- StGit uses the same configuration mechanism as Git. See linkman:git[7] for more details. TEMPLATES --------- A number of StGit commands make use of template files to provide useful default texts to be edited by the user. These +.tmpl+ template files are searched in the following directories: . +$GITDIR/+ (in practice, the +.git/+ directory in your repository) . +$HOME/.stgit/templates/+ . +/usr/share/stgit/templates/+ stgit-0.17.1/Documentation/stg-import.txt0000644002002200200220000000430711743520634020560 0ustar cmarinascmarinasstg-import(1) ============= NAME ---- stg-import - Import a GNU diff file as a new patch SYNOPSIS -------- [verse] 'stg' import [options] [--] [|] DESCRIPTION ----------- Create a new patch and apply the given GNU diff file (or the standard input). By default, the file name is used as the patch name but this can be overridden with the '--name' option. The patch can either be a normal file with the description at the top or it can have standard mail format, the Subject, From and Date headers being used for generating the patch information. The command can also read series and mbox files. If a patch does not apply cleanly, the failed diff is written to the .stgit-failed.patch file and an empty StGIT patch is added to the stack. The patch description has to be separated from the data with a '---' line. OPTIONS ------- -m:: --mail:: Import the patch from a standard e-mail file. -M:: --mbox:: Import a series of patches from an mbox file. -s:: --series:: Import a series of patches from a series file or a tar archive. -u:: --url:: Import a patch from a URL. -n NAME:: --name NAME:: Use NAME as the patch name. -p N:: --strip N:: Remove N leading slashes from diff paths (default 1). -t:: --stripname:: Strip numbering and extension from patch name. -i:: --ignore:: Ignore the applied patches in the series. --replace:: Replace the unapplied patches in the series. -b BASE:: --base BASE:: Use BASE instead of HEAD for file importing. --reject:: Leave the rejected hunks in corresponding *.rej files. -e:: --edit:: Invoke an editor for the patch description. -d:: --showdiff:: Show the patch content in the editor buffer. -a "NAME ":: --author "NAME ":: Use "NAME " as the author details. --authname AUTHNAME:: Use AUTHNAME as the author name. --authemail AUTHEMAIL:: Use AUTHEMAIL as the author e-mail. --authdate AUTHDATE:: Use AUTHDATE as the author date. --sign:: Add a "Signed-off-by:" to the end of the patch. --ack:: Add an "Acked-by:" line to the end of the patch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-files.txt0000644002002200200220000000144011743520633020342 0ustar cmarinascmarinasstg-files(1) ============ NAME ---- stg-files - Show the files modified by a patch (or the current patch) SYNOPSIS -------- [verse] 'stg' files [options] [--] [[:]] DESCRIPTION ----------- List the files modified by the given patch (defaulting to the current one). Passing the '--stat' option shows the diff statistics for the 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. OPTIONS ------- -s:: --stat:: Show the diffstat. --bare:: Bare file names (useful for scripting). -O OPTIONS:: --diff-opts OPTIONS:: Extra options to pass to "git diff". StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-goto.txt0000644002002200200220000000070011743520633020206 0ustar cmarinascmarinasstg-goto(1) =========== NAME ---- stg-goto - Push or pop patches to the given one SYNOPSIS -------- [verse] 'stg' goto [options] [--] DESCRIPTION ----------- Push/pop patches to/from the stack until the one given on the command line becomes current. OPTIONS ------- -k:: --keep:: Keep the local changes. -m:: --merged:: Check for patches merged upstream. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/build-docdep.perl0000755002002200200220000000203110732722750021124 0ustar cmarinascmarinas#!/usr/bin/perl my %include = (); my %included = (); for my $text (<*.txt>) { open I, '<', $text || die "cannot read: $text"; while () { if (/^include::/) { chomp; s/^include::\s*//; s/\[\]//; $include{$text}{$_} = 1; $included{$_} = 1; } } close I; } # Do we care about chained includes??? my $changed = 1; while ($changed) { $changed = 0; while (my ($text, $included) = each %include) { for my $i (keys %$included) { # $text has include::$i; if $i includes $j # $text indirectly includes $j. if (exists $include{$i}) { for my $j (keys %{$include{$i}}) { if (!exists $include{$text}{$j}) { $include{$text}{$j} = 1; $included{$j} = 1; $changed = 1; } } } } } } while (my ($text, $included) = each %include) { if (! exists $included{$text} && (my $base = $text) =~ s/\.txt$//) { my ($suffix) = '1'; if ($base eq 'git') { $suffix = '7'; # yuck... } print "$base.html $base.$suffix : ", join(" ", keys %$included), "\n"; } } stgit-0.17.1/Documentation/stg-init.txt0000644002002200200220000000057311743520634020212 0ustar cmarinascmarinasstg-init(1) =========== NAME ---- stg-init - Initialise the current branch for use with StGIT SYNOPSIS -------- [verse] 'stg' init DESCRIPTION ----------- 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. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-new.txt0000644002002200200220000000344011743520634020034 0ustar cmarinascmarinasstg-new(1) ========== NAME ---- stg-new - Create a new, empty patch SYNOPSIS -------- [verse] 'stg' new [options] [--] [] DESCRIPTION ----------- Create a new, empty patch on the current stack. The new patch is created on top of the currently applied patches, and is made the new top of the stack. Uncommitted changes in the work tree are not included in the patch -- that is handled by linkstg:refresh[]. The given name must be unique in the stack, and may only contain alphanumeric characters, dashes and underscores. If no name is given, one is generated from the first line of the patch's commit message. An editor will be launched to edit the commit message to be used for the patch, unless the '--message' flag already specified one. The 'patchdescr.tmpl' template file (if available) is used to pre-fill the editor. OPTIONS ------- --author "NAME ":: Set the author details. --authname NAME:: Set the author name. --authemail EMAIL:: Set the author email. --authdate DATE:: Set the author date. -m MESSAGE:: --message MESSAGE:: Use MESSAGE instead of invoking the editor. -f FILE:: --file FILE:: Use the contents of FILE instead of invoking the editor. (If FILE is "-", write to stdout.) --save-template FILE:: Instead of running the command, just write the message template to FILE, and exit. (If FILE is "-", write to stdout.) + When driving StGit from another program, it is often useful to first call a command with '--save-template', then let the user edit the message, and then call the same command with '--file'. --sign:: Add a "Signed-off-by:" to the end of the patch. --ack:: Add an "Acked-by:" line to the end of the patch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-show.txt0000644002002200200220000000135611743520635020230 0ustar cmarinascmarinasstg-show(1) =========== NAME ---- stg-show - Show the commit corresponding to a patch SYNOPSIS -------- [verse] 'stg' show [options] [--] [] [] [..] DESCRIPTION ----------- Show the commit log and the diff corresponding to the given patches. The output is similar to that generated by 'git show'. OPTIONS ------- -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. -a:: --applied:: Show the applied patches. -u:: --unapplied:: Show the unapplied patches. -s:: --stat:: Show a diffstat summary of the specified patches. -O OPTIONS:: --diff-opts OPTIONS:: Extra options to pass to "git diff". StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/tutorial.conf0000644002002200200220000000005610732722750020421 0ustar cmarinascmarinas[titles] underlines="##","==","--","~~","^^" stgit-0.17.1/Documentation/stg-next.txt0000644002002200200220000000051511743520634020221 0ustar cmarinascmarinasstg-next(1) =========== NAME ---- stg-next - Print the name of the next patch SYNOPSIS -------- [verse] 'stg' next DESCRIPTION ----------- Print the name of the next patch. OPTIONS ------- -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-sync.txt0000644002002200200220000000144711743520635020225 0ustar cmarinascmarinasstg-sync(1) =========== NAME ---- stg-sync - Synchronise patches with a branch or a series SYNOPSIS -------- [verse] 'stg' sync [options] [--] [] [] [..] DESCRIPTION ----------- For each of the specified patches perform a three-way merge with the same patch in the specified branch or series. The command can be used 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. OPTIONS ------- -a:: --all:: Synchronise all the applied patches. -B REF-BRANCH:: --ref-branch REF-BRANCH:: Syncronise patches with BRANCH. -s SERIES:: --series SERIES:: Syncronise patches with SERIES. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-refresh.txt0000644002002200200220000000420011743520635020675 0ustar cmarinascmarinasstg-refresh(1) ============== NAME ---- stg-refresh - Generate a new commit for the current patch SYNOPSIS -------- [verse] 'stg' refresh [options] [--] [] DESCRIPTION ----------- Include the latest work tree and index changes in the current patch. This command generates a new git commit object for the patch; the old commit is no longer visible. You may optionally list one or more files or directories relative to the current working directory; if you do, only matching files will be updated. Behind the scenes, stg refresh first creates a new temporary patch with your updates, and then merges that patch into the patch you asked to have refreshed. If you asked to refresh a patch other than the topmost patch, there can be conflicts; in that case, the temporary patch will be left for you to take care of, for example with stg squash. The creation of the temporary patch is recorded in a separate entry in 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. OPTIONS ------- -u:: --update:: Only update the current patch files. -i:: --index:: Instead of setting the patch top to the current contents of the worktree, set it to the current contents of the index. -p PATCH:: --patch PATCH:: Refresh (applied) PATCH instead of the top patch. -e:: --edit:: Invoke an editor for the patch description. -a NOTE:: --annotate NOTE:: Annotate the patch log entry. -m MESSAGE:: --message MESSAGE:: Use MESSAGE instead of invoking the editor. -f FILE:: --file FILE:: Use the contents of FILE instead of invoking the editor. (If FILE is "-", write to stdout.) --sign:: Add a "Signed-off-by:" to the end of the patch. --ack:: Add an "Acked-by:" line to the end of the patch. --author "NAME ":: Set the author details. --authname NAME:: Set the author name. --authemail EMAIL:: Set the author email. --authdate DATE:: Set the author date. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-unhide.txt0000644002002200200220000000064611743520635020525 0ustar cmarinascmarinasstg-unhide(1) ============= NAME ---- stg-unhide - Unhide a hidden patch SYNOPSIS -------- [verse] 'stg' unhide [options] [--] DESCRIPTION ----------- Unhide a hidden range of patches so that they are shown in the plain 'stg series' command output. OPTIONS ------- -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-rebase.txt0000644002002200200220000000145111743520634020504 0ustar cmarinascmarinasstg-rebase(1) ============= NAME ---- stg-rebase - Move the stack base to another point in history SYNOPSIS -------- [verse] 'stg' rebase [options] [--] DESCRIPTION ----------- Pop all patches from current stack, move the stack base to the given and push the patches back. If you experience merge conflicts, resolve the problem and continue the rebase by executing the following sequence: $ git add --update $ stg refresh $ stg goto top-patch Or if you want to skip that patch: $ stg undo --hard $ stg push next-patch..top-patch OPTIONS ------- -n:: --nopush:: Do not push the patches back after rebasing. -m:: --merged:: Check for patches merged upstream. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-delete.txt0000644002002200200220000000136111743520633020504 0ustar cmarinascmarinasstg-delete(1) ============= NAME ---- stg-delete - Delete patches SYNOPSIS -------- [verse] 'stg' delete [options] [--] [] [..] DESCRIPTION ----------- Delete the patches passed as arguments. OPTIONS ------- --spill:: Delete the patches, but do not touch the index and worktree. This only works with applied patches at the top of the stack. The effect is to "spill" the patch contents into the index and worktree. This can be useful e.g. if you want to split a patch into several smaller pieces. -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. -t:: --top:: Delete top patch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-sink.txt0000644002002200200220000000243511743520635020213 0ustar cmarinascmarinasstg-sink(1) =========== NAME ---- stg-sink - Send patches deeper down the stack SYNOPSIS -------- [verse] 'stg' sink [-t ] [-n] [--] [] DESCRIPTION ----------- This is the opposite operation of linkstg:float[]: move the specified patches down the stack. It is for example useful to group stable patches near the bottom of the stack, where they are less likely to be impacted by the push of another patch, and from where they can be more easily committed or pushed. If no patch is specified on command-line, the current patch gets sunk. By default patches are sunk to the bottom of the stack, but the '--to' option allows to place them under any applied patch. Sinking internally involves popping all patches (or all patches including ), then pushing the patches to sink, and then (unless '--nopush' is also given) pushing back into place the formerly-applied patches. OPTIONS ------- -n:: --nopush:: Do not push back on the stack the formerly-applied patches. Only the patches to sink are pushed. -t TARGET:: --to TARGET:: Specify a target patch to place the patches below, instead of sinking them to the bottom of the stack. -k:: --keep:: Keep the local changes. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-log.txt0000644002002200200220000000153411743520634020026 0ustar cmarinascmarinasstg-log(1) ========== NAME ---- stg-log - Display the patch changelog SYNOPSIS -------- [verse] 'stg' log [options] [--] [] DESCRIPTION ----------- List the history of the patch stack: the stack log. If one or more patch names are given, limit the list to the log entries that touch 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. OPTIONS ------- -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default one. -d:: --diff:: Show the refresh diffs. -n NUMBER:: --number NUMBER:: Limit the output to NUMBER commits. -f:: --full:: Show the full commit ids. -g:: --graphical:: Run gitk instead of printing. --clear:: Clear the log history. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-pull.txt0000644002002200200220000000160411743520634020217 0ustar cmarinascmarinasstg-pull(1) =========== NAME ---- stg-pull - Pull changes from a remote repository SYNOPSIS -------- [verse] 'stg' pull [options] [--] [] DESCRIPTION ----------- Pull the latest changes from the given remote repository (defaulting to branch..remote, or 'origin' if not set). This command works by popping all the patches from the stack, pulling the changes in the parent repository, setting the base of the stack to the latest parent HEAD and pushing the patches back (unless '--nopush' is specified). The 'push' operation can fail if there are conflicts. They need to be resolved and the patch pushed again. Check the 'git fetch' documentation for the format. OPTIONS ------- -n:: --nopush:: Do not push the patches back after pulling. -m:: --merged:: Check for patches merged upstream. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-status.txt0000644002002200200220000000176711447101670020574 0ustar cmarinascmarinasstg-status(1) ============= NAME ---- stg-status - Show the tree status SYNOPSIS -------- [verse] 'stg' status [options] [--] [] DESCRIPTION ----------- Show the status of the whole working copy or the given files. The command also shows the files in the current directory which are not under revision control. The files are prefixed as follows: M - locally modified N - newly added to the repository D - deleted from the repository C - conflict ? - unknown An 'stg refresh' command clears the status of the modified, new and deleted files. OPTIONS ------- -m:: --modified:: Show modified files only. -n:: --new:: Show new files only. -d:: --deleted:: Show deleted files only. -c:: --conflict:: Show conflict files only. -u:: --unknown:: Show unknown files only. -x:: --noexclude:: Do not exclude any files from listing. --reset:: Reset the current tree changes. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-export.txt0000644002002200200220000000313311743520633020562 0ustar cmarinascmarinasstg-export(1) ============= NAME ---- stg-export - Export patches to a directory SYNOPSIS -------- [verse] 'stg' export [options] [--] [] [] [..] DESCRIPTION ----------- Export a range of applied patches to a given directory (defaults to 'patches-') in a standard unified GNU diff format. A template file (defaulting to '.git/patchexport.tmpl' or '~/.stgit/templates/patchexport.tmpl' or '/usr/share/stgit/templates/patchexport.tmpl') can be used for the patch format. The following variables are supported in the template file: %(description)s - patch description %(shortdescr)s - the first line of the patch description %(longdescr)s - the rest of the patch description, after the first line %(diffstat)s - the diff statistics %(authname)s - author's name %(authemail)s - author's e-mail %(authdate)s - patch creation date %(commname)s - committer's name %(commemail)s - committer's e-mail OPTIONS ------- -d DIR:: --dir DIR:: Export patches to DIR instead of the default. -p:: --patch:: Append .patch to the patch names. -e EXTENSION:: --extension EXTENSION:: Append .EXTENSION to the patch names. -n:: --numbered:: Prefix the patch names with order numbers. -t FILE:: --template FILE:: Use FILE as a template. -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. -s:: --stdout:: Dump the patches to the standard output. -O OPTIONS:: --diff-opts OPTIONS:: Extra options to pass to "git diff". StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-push.txt0000644002002200200220000000316411743520634020225 0ustar cmarinascmarinasstg-push(1) =========== NAME ---- stg-push - Push one or more patches onto the stack SYNOPSIS -------- [verse] 'stg' push [options] [--] [] [] [..] DESCRIPTION ----------- Push one or more patches (defaulting to the first unapplied one) onto the stack. The 'push' operation allows patch reordering by commuting them with the three-way merge algorithm. If there are conflicts while pushing a patch, those conflicts are written to the work tree, and the command halts. Conflicts raised during the push operation have to be fixed and the 'git add --update' command run (alternatively, you may undo the 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. OPTIONS ------- -a:: --all:: Push all the unapplied patches. -n NUMBER:: --number NUMBER:: Push the specified number of patches. + With a negative number, push all but that many patches. --reverse:: Push the patches in reverse order. --set-tree:: Push the patches, but don't perform a merge. Instead, the resulting tree will be identical to the tree that the patch previously created. + This can be useful when splitting a patch by first popping the patch and creating a new patch with some of the changes. Pushing the original patch with '--set-tree' will avoid conflicts and only the remaining changes will be in the patch. -k:: --keep:: Keep the local changes. -m:: --merged:: Check for patches merged upstream. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/callouts.xsl0000644002002200200220000000204110732722750020261 0ustar cmarinascmarinas .sp .br stgit-0.17.1/Documentation/stg-patches.txt0000644002002200200220000000115711743520634020675 0ustar cmarinascmarinasstg-patches(1) ============== NAME ---- stg-patches - Show the applied patches modifying a file SYNOPSIS -------- [verse] 'stg' patches [options] [--] [] DESCRIPTION ----------- Show the applied patches modifying the given files. Without arguments, 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. OPTIONS ------- -d:: --diff:: Show the diff for the given files. -b BRANCH:: --branch BRANCH:: Use BRANCH instead of the default branch. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/Documentation/stg-repair.txt0000644002002200200220000000411711743520635020530 0ustar cmarinascmarinasstg-repair(1) ============= NAME ---- stg-repair - Fix StGit metadata if branch was modified with git commands SYNOPSIS -------- [verse] 'stg' repair DESCRIPTION ----------- If you modify an StGit stack (branch) with some git commands -- such as commit, pull, merge, and rebase -- you will leave the StGit metadata in an inconsistent state. In that situation, you have two options: 1. Use "stg undo" to undo the effect of the git commands. (If you know what you are doing and want more control, "git reset" or similar will work too.) 2. Use "stg repair". This will fix up the StGit metadata to accomodate the modifications to the branch. Specifically, it will do the following: * If you have made regular git commits on top of your stack of StGit patches, "stg repair" makes new StGit patches out of them, preserving their contents. * However, merge commits cannot become patches; if you have committed a merge on top of your stack, "repair" will simply mark all patches below the merge unapplied, since they are no longer reachable. If this is not what you want, use "stg undo" to get rid of the merge and run "stg repair" again. * The applied patches are supposed to be precisely those that are reachable from the branch head. If you have used e.g. "git reset" to move the head, some applied patches may no longer be reachable, and some unapplied patches may have become reachable. "stg repair" will correct the appliedness of such patches. "stg repair" will fix these inconsistencies reliably, so as long as you like what it does, you have no reason to avoid causing them in the first place. For example, you might find it convenient to make commits with a graphical tool and then have "stg repair" make proper patches of the commits. 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. StGit ----- Part of the StGit suite - see linkman:stg[1] stgit-0.17.1/stg0000755002002200200220000000274112161010176013612 0ustar cmarinascmarinas#!/usr/bin/env python2 # -*- python-mode -*- """Takes care of starting the Init function """ __copyright__ = """ Copyright (C) 2005, Catalin Marinas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import sys, os # Try to detect where it is run from and set prefix and the search path. # It is assumed that the user installed StGIT using the --prefix= option prefix, bin = os.path.split(sys.path[0]) if bin == 'bin' and prefix != sys.prefix: sys.prefix = prefix sys.exec_prefix = prefix major, minor = sys.version_info[0:2] local_path = [os.path.join(prefix, 'lib', 'python'), os.path.join(prefix, 'lib', 'python%s.%s' % (major, minor)), os.path.join(prefix, 'lib', 'python%s.%s' % (major, minor), 'site-packages')] sys.path = local_path + sys.path from stgit.main import main if __name__ == '__main__': main() stgit-0.17.1/t/0000755002002200200220000000000012222320003013315 5ustar cmarinascmarinasstgit-0.17.1/t/t1700-goto-top.sh0000755002002200200220000000063711743516173016231 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Ilpo Järvinen # test_description='Test goto to the current patch. ' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ 'stg init ' test_expect_success \ 'Create the first patch' \ ' stg new foo -m "Foo Patch" && echo foo > test && stg add test && stg refresh ' test_expect_success \ 'Goto current patch' \ ' stg goto `stg top` ' test_done stgit-0.17.1/t/t3103-undo-hard.sh0000755002002200200220000000254311743516173016337 0ustar cmarinascmarinas#!/bin/sh test_description='Simple test cases for "stg undo"' . ./test-lib.sh # Ignore our own output files. cat > .git/info/exclude <> a && stg add a && git commit -m a && echo 111 >> a && git commit -a -m p1 && echo 222 >> a && git commit -a -m p2 && echo 333 >> a && git commit -a -m p3 && stg uncommit -n 3 && test "$(stg id)" = "$(stg id $(stg top))" ' cat > expected.txt < actual.txt && test_cmp expected.txt actual.txt && test "$(echo $(stg series))" = "+ p1 > p3 - p2" && test "$(stg id)" = "$(stg id $(stg top))" ' test_expect_success 'Try to undo without --hard' ' command_error stg undo && stg status a > actual.txt && test_cmp expected.txt actual.txt && test "$(echo $(stg series))" = "+ p1 > p3 - p2" && test "$(stg id)" = "$(stg id $(stg top))" ' cat > expected.txt < actual.txt && test_cmp expected.txt actual.txt && test "$(echo $(stg series))" = "+ p1 + p2 > p3" && test "$(stg id)" = "$(stg id $(stg top))" ' test_done stgit-0.17.1/t/t3100-reset.sh0000755002002200200220000000640411743516173015575 0ustar cmarinascmarinas#!/bin/sh test_description='Simple test cases for "stg reset"' . ./test-lib.sh # Ignore our own output files. cat > .git/info/exclude <> a && stg add a && git commit -m a && echo 111 >> a && git commit -a -m p1 && echo 222 >> a && git commit -a -m p2 && echo 333 >> a && git commit -a -m p3 && stg uncommit -n 3 && stg pop ' cat > expected.txt < expected.txt < expected.txt < expected.txt < expected.txt < expected.txt < expected.txt <> a && stg refresh && test "$(echo $(stg series --all))" = "+ p1 > p2" && test_cmp expected.txt a ' cat > expected.txt <> foo.txt && stg add foo.txt ' test_expect_success 'Diff with some local changes' ' stg diff ' test_expect_success 'Initialize StGit stuff' ' stg init && stg new foo -m foo ' test_expect_success 'Diff with some local changes' ' stg diff ' test_expect_success 'Refresh patch' ' stg refresh ' test_expect_success 'Diff with no local changes' ' stg diff ' test_done stgit-0.17.1/t/t1501-sink.sh0000755002002200200220000000314711743516173015423 0ustar cmarinascmarinas#!/bin/sh test_description='Test "stg sink"' . ./test-lib.sh test_expect_success 'Initialize StGit stack' ' echo 0 >> f0 && stg add f0 && git commit -m initial && echo 1 >> f1 && stg add f1 && git commit -m p1 && echo 2 >> f2 && stg add f2 && git commit -m p2 && echo 3 >> f3 && stg add f3 && git commit -m p3 && echo 4 >> f4 && stg add f4 && git commit -m p4 && echo 22 >> f2 && stg add f2 && git commit -m p22 && stg init && stg uncommit p22 p4 p3 p2 p1 && stg pop -a ' test_expect_success 'sink default without applied patches' ' command_error stg sink ' test_expect_success 'sink and reorder specified without applied patches' ' stg sink p2 p1 && test "$(echo $(stg series --applied --noprefix))" = "p2 p1" ' test_expect_success 'sink patches to the bottom of the stack' ' stg sink p4 p3 p2 && test "$(echo $(stg series --applied --noprefix))" = "p4 p3 p2 p1" ' test_expect_success 'sink current below a target' ' stg sink --to=p2 && test "$(echo $(stg series --applied --noprefix))" = "p4 p3 p1 p2" ' test_expect_success 'bring patches forward' ' stg sink --to=p2 p3 p4 && test "$(echo $(stg series --applied --noprefix))" = "p1 p3 p4 p2" ' test_expect_success 'sink specified patch below a target' ' stg sink --to=p3 p2 && test "$(echo $(stg series --applied --noprefix))" = "p1 p2 p3 p4" ' test_expect_success 'sink with conflict' ' conflict stg sink --to=p2 p22 && test "$(echo $(stg series --applied --noprefix))" = "p1 p22" && test "$(echo $(stg status))" = "DU f2" ' test_done stgit-0.17.1/t/t1900-mail.sh0000755002002200200220000000506712221306627015400 0ustar cmarinascmarinas#!/bin/sh # Copyright (c) 2006 Karl Hasselström test_description='Test the mail command' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ ' git config stgit.sender "A U Thor " && for i in 1 2 3 4 5; do touch foo.txt && echo "line $i" >> foo.txt && stg add foo.txt && git commit -a -m "Patch $i" done && stg init && stg uncommit -n 5 foo ' test_expect_success \ 'Put all the patches in an mbox' \ 'stg mail --to="Inge Ström " -a -m \ -t $STG_ROOT/templates/patchmail.tmpl > mbox0' test_expect_success \ 'Import the mbox and compare' \ ' t1=$(git cat-file -p $(stg id) | grep ^tree) stg pop -a && stg import -M mbox0 && t2=$(git cat-file -p $(stg id) | grep ^tree) && [ "$t1" = "$t2" ] ' test_expect_success \ 'Put all the patches in an mbox with patch attachments' \ 'stg mail --to="Inge Ström " -a -m \ -t $STG_ROOT/templates/mailattch.tmpl > mbox1' test_expect_success \ 'Import the mbox containing patch attachments and compare' \ ' t1=$(git cat-file -p $(stg id) | grep ^tree) stg pop -a && stg import -M mbox1 && t2=$(git cat-file -p $(stg id) | grep ^tree) && [ "$t1" = "$t2" ] ' test_expect_success \ 'Check the To:, Cc: and Bcc: headers' \ ' stg mail --to=a@a --cc="b@b, c@c" --bcc=d@d $(stg top) -m \ -t $STG_ROOT/templates/patchmail.tmpl > mbox && test "$(cat mbox | grep -e "^To:")" = "To: a@a" && test "$(cat mbox | grep -e "^Cc:")" = "Cc: b@b, c@c" && test "$(cat mbox | grep -e "^Bcc:")" = "Bcc: d@d" ' test_expect_success \ 'Check the --auto option' \ ' stg edit --sign && stg mail --to=a@a --cc="b@b, c@c" --bcc=d@d --auto $(stg top) -m \ -t $STG_ROOT/templates/patchmail.tmpl > mbox && test "$(cat mbox | grep -e "^To:")" = "To: a@a" && test "$(cat mbox | grep -e "^Cc:")" = \ "Cc: C O Mitter , b@b, c@c" && test "$(cat mbox | grep -e "^Bcc:")" = "Bcc: d@d" ' test_expect_success \ 'Check the e-mail address duplicates' \ ' stg mail --to="a@a, b b " --cc="b@b, c@c" \ --bcc="c@c, d@d, committer@example.com" --auto $(stg top) -m \ -t $STG_ROOT/templates/patchmail.tmpl > mbox && test "$(cat mbox | grep -e "^To:")" = "To: b b , a@a" && test "$(cat mbox | grep -e "^Cc:")" = \ "Cc: C O Mitter , c@c" && test "$(cat mbox | grep -e "^Bcc:")" = "Bcc: d@d" ' test_done stgit-0.17.1/t/t2700-refresh.sh0000755002002200200220000000475611743516173016126 0ustar cmarinascmarinas#!/bin/sh test_description='Run "stg refresh"' . ./test-lib.sh test_expect_success 'Initialize StGit stack' ' stg init && echo expected*.txt >> .git/info/exclude && echo patches.txt >> .git/info/exclude && echo show.txt >> .git/info/exclude && echo diff.txt >> .git/info/exclude && stg new p0 -m "base" && for i in 1 2 3; do echo base >> foo$i.txt && stg add foo$i.txt done stg refresh && for i in 1 2 3; do stg new p$i -m "foo $i" && echo "foo $i" >> foo$i.txt && stg refresh done ' cat > expected.txt <> foo3.txt && stg refresh && stg status && test -z "$(stg status)" && stg patches foo3.txt > patches.txt && test_cmp expected.txt patches.txt ' cat > expected.txt <> foo2.txt && stg refresh -p p2 && stg status && test -z "$(stg status)" && stg patches foo2.txt > patches.txt && test_cmp expected.txt patches.txt ' cat > expected.txt <> foo1.txt && stg refresh -p p1 && stg status && test -z "$(stg status)" && stg patches foo1.txt > patches.txt && test_cmp expected.txt patches.txt ' cat > expected.txt < expected2.txt < expected3.txt <> foo1.txt && stg add foo1.txt && echo blah 1 >> foo1.txt && echo baz 2 >> foo2.txt && stg refresh --index && stg patches foo1.txt > patches.txt && git diff HEAD^..HEAD > show.txt && stg diff > diff.txt && test_cmp expected.txt patches.txt && test_cmp expected2.txt show.txt && test_cmp expected3.txt diff.txt && stg new p5 -m "cleanup again" && stg refresh ' test_expect_success 'Refresh moved files' ' stg mv foo1.txt foo1-new.txt && stg refresh ' test_done stgit-0.17.1/t/t1800-import/0000755002002200200220000000000012222320003015401 5ustar cmarinascmarinasstgit-0.17.1/t/t1800-import/email-mbox0000644002002200200220000000437610527275347017422 0ustar cmarinascmarinasFrom nobody Sat Nov 11 12:45:27 2006 From: Inge =?utf-8?q?Str=C3=B6m?= Subject: [PATCH 1/3] Change 1 To: Upstream Date: Sat, 11 Nov 2006 12:45:27 +0100 Message-ID: <20061111114527.31778.12942.stgit@localhost> User-Agent: StGIT/0.11 MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Status: RO Content-Length: 304 Lines: 19 Signed-off-by: Inge Ström --- foo.txt | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/foo.txt b/foo.txt index ad01662..91527b1 100644 --- a/foo.txt +++ b/foo.txt @@ -7,7 +7,7 @@ dobidum dobodam dobodim dobodum -dibedam +dibedad dibedim dibedum dibidam From nobody Sat Nov 11 12:45:27 2006 From: Inge =?utf-8?q?Str=C3=B6m?= Subject: [PATCH 2/3] Change 2 To: Upstream Date: Sat, 11 Nov 2006 12:45:27 +0100 Message-ID: <20061111114527.31778.92851.stgit@localhost> In-Reply-To: <20061111114527.31778.12942.stgit@localhost> References: <20061111114527.31778.12942.stgit@localhost> User-Agent: StGIT/0.11 MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Status: RO Content-Length: 296 Lines: 18 Signed-off-by: Inge Ström --- foo.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/foo.txt b/foo.txt index 91527b1..79922d7 100644 --- a/foo.txt +++ b/foo.txt @@ -18,6 +18,7 @@ dibodim dibodum dabedam dabedim +dibedam dabedum dabidam dabidim From nobody Sat Nov 11 12:45:27 2006 From: Inge =?utf-8?q?Str=C3=B6m?= Subject: [PATCH 3/3] Change 3 To: Upstream Date: Sat, 11 Nov 2006 12:45:27 +0100 Message-ID: <20061111114527.31778.45876.stgit@localhost> In-Reply-To: <20061111114527.31778.12942.stgit@localhost> References: <20061111114527.31778.12942.stgit@localhost> User-Agent: StGIT/0.11 MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Status: RO Content-Length: 278 Lines: 16 Signed-off-by: Inge Ström --- foo.txt | 1 - 1 files changed, 0 insertions(+), 1 deletions(-) diff --git a/foo.txt b/foo.txt index 79922d7..6f978b4 100644 --- a/foo.txt +++ b/foo.txt @@ -24,5 +24,4 @@ dabidam dabidim dabidum dabodam -dabodim dabodum stgit-0.17.1/t/t1800-import/stg-export0000644002002200200220000000054510527275347017476 0ustar cmarinascmarinastest patch --- foo.txt | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/foo.txt b/foo.txt index ad01662..d3cd5b6 100644 --- a/foo.txt +++ b/foo.txt @@ -3,6 +3,7 @@ dobedim dobedum dobidam dobidim +dabadadash dobidum dobodam dobodim @@ -20,6 +21,7 @@ dabedam dabedim dabedum dabidam +dadadadash dabidim dabidum dabodam stgit-0.17.1/t/t1800-import/email-qp0000644002002200200220000000135310527275347017065 0ustar cmarinascmarinasFrom: Inge =?utf-8?q?Str=C3=B6m?= Subject: [PATCH] test patch To: Upstream Date: Sat, 11 Nov 2006 11:58:14 +0100 Message-ID: <20061111105814.23209.46952.stgit@localhost> User-Agent: StGIT/0.11 MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Signed-off-by: Inge Str=C3=B6m --- foo.txt | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/foo.txt b/foo.txt index ad01662..d3cd5b6 100644 --- a/foo.txt +++ b/foo.txt @@ -3,6 +3,7 @@ dobedim dobedum dobidam dobidim +pum-p=C3=B6ddelip=C3=A5m dobidum dobodam dobodim @@ -20,6 +21,7 @@ dabedam dabedim dabedum dabidam +pum-d=C3=A4ddelidum dabidim dabidum dabodam stgit-0.17.1/t/t1800-import/git-diff-p00000644002002200200220000000040711271344641017354 0ustar cmarinascmarinasdiff --git foo.txt foo.txt index ad01662..d3cd5b6 100644 --- foo.txt +++ foo.txt @@ -3,6 +3,7 @@ dobedim dobedum dobidam dobidim +dabadadash dobidum dobodam dobodim @@ -20,6 +21,7 @@ dabedam dabedim dabedum dabidam +dadadadash dabidim dabidum dabodam stgit-0.17.1/t/t1800-import/git-diff0000644002002200200220000000041710527275347017051 0ustar cmarinascmarinasdiff --git a/foo.txt b/foo.txt index ad01662..d3cd5b6 100644 --- a/foo.txt +++ b/foo.txt @@ -3,6 +3,7 @@ dobedim dobedum dobidam dobidim +dabadadash dobidum dobodam dobodim @@ -20,6 +21,7 @@ dabedam dabedim dabedum dabidam +dadadadash dabidim dabidum dabodam stgit-0.17.1/t/t1800-import/email-8bit0000644002002200200220000000131710527275347017313 0ustar cmarinascmarinasFrom: Inge =?utf-8?q?Str=C3=B6m?= Subject: [PATCH] test patch To: Upstream Date: Sat, 11 Nov 2006 11:58:14 +0100 Message-ID: <20061111105814.23209.46952.stgit@localhost> User-Agent: StGIT/0.11 MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Signed-off-by: Inge Ström --- foo.txt | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/foo.txt b/foo.txt index ad01662..d3cd5b6 100644 --- a/foo.txt +++ b/foo.txt @@ -3,6 +3,7 @@ dobedim dobedum dobidam dobidim +pum-pöddelipÃ¥m dobidum dobodam dobodim @@ -20,6 +21,7 @@ dabedam dabedim dabedum dabidam +pum-däddelidum dabidim dabidum dabodam stgit-0.17.1/t/t1800-import/foo.txt0000644002002200200220000000033010732722750016744 0ustar cmarinascmarinasdobedam dobedim dobedum dobidam dobidim dobidum dobodam dobodim dobodum dibedam dibedim dibedum dibidam dibidim dibidum dibodam dibodim dibodum dabedam dabedim dabedum dabidam dabidim dabidum dabodam dabodim dabodum stgit-0.17.1/t/t1800-import/gnu-diff0000644002002200200220000000046110527275347017056 0ustar cmarinascmarinasdiff -Naur old/foo.txt new/foo.txt --- old/foo.txt 2006-11-11 11:26:18.000000000 +0100 +++ new/foo.txt 2006-11-11 11:25:18.000000000 +0100 @@ -3,6 +3,7 @@ dobedum dobidam dobidim +dabadadash dobidum dobodam dobodim @@ -20,6 +21,7 @@ dabedim dabedum dabidam +dadadadash dabidim dabidum dabodam stgit-0.17.1/t/t1004-pack-ref.sh0000755002002200200220000000077410732722750016145 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2007 Karl Hasselström # test_description='Test that StGIT can handle packed refs' . ./test-lib.sh stg init test_expect_success \ 'Pack refs and make sure that we can still see them' ' stg branch -c foo && [ $(stg branch -l | tee /dev/stderr | wc -l) -eq 2 ] && git pack-refs --all && [ $(stg branch -l | tee /dev/stderr | wc -l) -eq 2 ] ' test_expect_success \ 'Try to delete a branch whose ref has been packed' ' stg branch -d master ' test_done stgit-0.17.1/t/t1000-branch-create.sh0000755002002200200220000000540411743516173017145 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Yann Dirson # test_description='Branch operations. Exercises the "stg branch" commands. ' . ./test-lib.sh test_expect_success \ 'Create a branch when the current one is not an StGIT stack' ' git branch origin && stg branch --create new origin && test $(stg branch) = "new" ' test_expect_success \ 'Create a spurious patches/ entry' ' stg branch master && stg init && mkdir -p .git/patches && touch .git/patches/foo1 ' test_expect_success \ 'Try to create an stgit branch with a spurious patches/ entry' ' command_error stg branch -c foo1 ' test_expect_success \ 'Check that no part of the branch was created' ' test "$(find .git -name foo1 | tee /dev/stderr)" = ".git/patches/foo1" && test "$(git show-ref | grep foo1 | wc -l)" = 0 && test "$(git symbolic-ref HEAD)" = "refs/heads/master" ' test_expect_success \ 'Create a git branch' ' git update-ref refs/heads/foo2 refs/heads/master ' test_expect_success \ 'Try to create an stgit branch with an existing git branch by that name' ' command_error stg branch -c foo2 ' test_expect_success \ 'Check that no part of the branch was created' ' test "$(find .git -name foo2 | tee /dev/stderr \ | grep -v ^\\.git/refs/heads/foo2$ \ | grep -v ^\\.git/logs/refs/heads/foo2$ | wc -l)" = 0 && test "$(git show-ref | grep foo2 | wc -l)" = 1 && test "$(git symbolic-ref HEAD)" = "refs/heads/master" ' test_expect_success \ 'Create an invalid refs/heads/ entry' ' touch .git/refs/heads/foo3 && command_error stg branch -c foo3 ' test_expect_failure \ 'Check that no part of the branch was created' ' test "$(find .git -name foo3 | tee /dev/stderr \ | grep -v ^\\.git/refs/heads/foo3$ | wc -l)" = 0 && test "$(git show-ref | grep foo3 | wc -l)" = 0 && test "$(git symbolic-ref HEAD)" = "refs/heads/master" ' # Workaround for the test failure to make the rest of the subtests # succeed. (HEAD was erroneously overwritten with the bad foo3 ref, so # we need to reset it.) git symbolic-ref HEAD refs/heads/master test_expect_success \ 'Setup two commits including removal of generated files' ' git init && touch file1 file2 && stg add file1 file2 && git commit -m 1 && stg rm file1 file2 && git commit -m 2 && touch file2 ' test_expect_success \ 'Create branch down the stack, behind the conflict caused by the generated file' ' command_error stg branch --create foo4 master^ ' test_expect_success \ 'Check the branch was not created' ' test ! -e file1 && test "$(find .git -name foo4 | tee /dev/stderr | wc -l)" = 0 && test "$(git show-ref | grep foo4 | wc -l)" = 0 && test "$(git symbolic-ref HEAD)" = "refs/heads/master" ' test_done stgit-0.17.1/t/t2000-sync.sh0000755002002200200220000000771411743516173015432 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Catalin Marinas # test_description='Test the sync command.' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ ' stg init ' test_expect_success \ 'Create some patches' \ ' stg new p1 -m p1 && echo foo1 > foo1.txt && stg add foo1.txt && stg refresh && stg new p2 -m p2 && echo foo2 > foo2.txt && stg add foo2.txt && stg refresh && stg new p3 -m p3 && echo foo3 > foo3.txt && stg add foo3.txt && stg refresh && stg export && stg pop && [ "$(echo $(stg series --applied --noprefix))" = "p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p3" ] ' test_expect_success \ 'Create a branch with empty patches' \ ' stg branch -c foo {base} && stg new p1 -m p1 && stg new p2 -m p2 && stg new p3 -m p3 && [ "$(echo $(stg series --applied --noprefix))" = "p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_expect_success \ 'Synchronise second patch with the master branch' \ ' stg sync -B master p2 && [ "$(echo $(stg series --applied --noprefix))" = "p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] && test $(cat foo2.txt) = "foo2" ' test_expect_success \ 'Synchronise the first two patches with the master branch' \ ' stg sync -B master -a && [ "$(echo $(stg series --applied --noprefix))" = "p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] && test $(cat foo1.txt) = "foo1" && test $(cat foo2.txt) = "foo2" ' test_expect_success \ 'Synchronise all the patches with the exported series' \ ' stg sync -s patches-master/series -a && [ "$(echo $(stg series --applied --noprefix))" = "p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] && test $(cat foo1.txt) = "foo1" && test $(cat foo2.txt) = "foo2" && test $(cat foo3.txt) = "foo3" ' test_expect_success \ 'Modify the master patches' \ ' stg branch master && [ "$(echo $(stg series --applied --noprefix))" = "p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p3" ] && stg goto p1 && echo bar1 >> foo1.txt && stg refresh && stg goto p2 && echo bar2 > bar2.txt && stg add bar2.txt && stg refresh && stg goto p3 && echo bar3 >> foo3.txt && stg refresh && [ "$(echo $(stg series --applied --noprefix))" = "p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] && stg export && stg branch foo ' test_expect_success \ 'Synchronise second patch with the master branch' \ ' stg sync -B master p2 && [ "$(echo $(stg series --applied --noprefix))" = "p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] && test $(cat bar2.txt) = "bar2" ' test_expect_success \ 'Synchronise the first two patches with the master branch (to fail)' \ ' conflict_old stg sync -B master -a ' test_expect_success \ 'Restore the stack status after the failed sync' \ ' [ "$(echo $(stg series --applied --noprefix))" = "p1" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p2 p3" ] && stg add --update && stg refresh && stg goto p3 [ "$(echo $(stg series --applied --noprefix))" = "p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_expect_success \ 'Synchronise the third patch with the exported series (to fail)' \ ' conflict_old stg sync -s patches-master/series p3 ' test_expect_success \ 'Restore the stack status after the failed sync' \ ' [ "$(echo $(stg series --applied --noprefix))" = "p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] && stg add --update && stg refresh && [ "$(echo $(stg series --applied --noprefix))" = "p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_done stgit-0.17.1/t/t1302-repair-interop.sh0000755002002200200220000000334211743516173017413 0ustar cmarinascmarinas#!/bin/sh test_description='Test git/StGit interoperability with "stg repair"' . ./test-lib.sh test_expect_success 'Create some git-only history' ' echo foo > foo.txt && stg add foo.txt && git commit -a -m foo && git tag foo-tag && for i in 0 1 2 3 4; do echo foo$i >> foo.txt && git commit -a -m foo$i; done ' test_expect_success 'Initialize the StGit repository' ' stg init ' test_expect_success 'Create five patches' ' for i in 0 1 2 3 4; do stg new p$i -m p$i; done && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 p3 p4" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_expect_success 'Pop two patches with git reset' ' git reset --hard HEAD~2 && command_error stg refresh && stg repair && stg refresh && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p3 p4" ] ' test_expect_success 'Create a new patch' ' stg new q0 -m q0 && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 q0" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p3 p4" ] ' test_expect_success 'Go to an unapplied patch with with git reset' ' git reset --hard $(stg id p3) && command_error stg refresh && stg repair && stg refresh && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "q0 p4" ] ' test_expect_success 'Go back to below the stack base with git reset' ' git reset --hard foo-tag && stg repair && [ "$(echo $(stg series --applied --noprefix))" = "" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p0 p1 p2 p3 q0 p4" ] ' test_done stgit-0.17.1/t/t4000-upgrade.sh0000755002002200200220000000201312221306627016063 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2007 Karl Hasselström # test_description='Make sure that we can use old StGIT repositories' . ./test-lib.sh for ver in 0.12 0.8; do tar zxf $STG_ROOT/t/t4000-upgrade/$ver.tar.gz cd $ver test_expect_success \ "v$ver: Check the list of applied and unapplied patches" ' [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p3 p4" ] ' test_expect_success \ "v$ver: Make sure the 'description' file is no longer there" ' [ ! -e .git/patches/master/description ] && [ "$(echo $(git config branch.master.description))" = "cool branch" ] ' test_expect_success \ "v$ver: Make sure the 'current' file is no longer there" ' [ ! -e .git/patches/master/current ] ' test_expect_success \ "v$ver: Make sure the base ref is no longer there" ' must_fail git show-ref --verify --quiet refs/bases/master ' cd .. done test_done stgit-0.17.1/t/t4100-publish.sh0000755002002200200220000000552711743516173016127 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2009 Catalin Marinas # test_description='Exercise the publish command. Create/modify patches on the stack and publish them to a separate branch.' . ./test-lib.sh test_same_tree () { stack_tree=$(git rev-parse master^{tree}) public_tree=$(git rev-parse master.public^{tree}) test "$stack_tree" = "$public_tree" } test_expect_success \ 'Initialize the StGit repository' \ ' stg init ' test_expect_success \ 'Create some patches' \ ' stg new p1 -m p1 && echo foo1 > foo1.txt && stg add foo1.txt && stg refresh && stg new p2 -m p2 && echo foo2 > foo2.txt && stg add foo2.txt && stg refresh && stg new p3 -m p3 && echo foo3 > foo3.txt && stg add foo3.txt && stg refresh ' test_expect_success \ 'Publish the stack for the first time' \ ' stg publish && test "$(stg id)" = "$(stg id master.public)" ' test_expect_success \ 'Modify a patch and publish the changes' \ ' stg pop && echo foo2 >> foo2.txt && stg refresh && stg push && old_public=$(stg id master.public) && stg publish -m "p2 updated" && test_same_tree && new_public=$(stg id master.public) && test "$(git rev-list $old_public..$new_public | wc -l)" = "1" ' test_expect_success \ 'Create new patches and publish them' \ ' stg new p4 -m p4 && echo foo4 > foo4.txt && stg add foo4.txt && stg refresh && stg new p5 -m p5 && echo foo5 > foo5.txt && stg add foo5.txt && stg refresh && stg new empty -m empty && old_public=$(stg id master.public) && stg publish -m "Ignored message" && test_same_tree && new_public=$(stg id master.public) && test "$(git rev-list $old_public..$new_public | wc -l)" = "2" ' test_expect_success \ 'Rebase the current stack and publish a merge' \ ' stg pop -a && echo foo0 > foo0.txt && stg add foo0.txt && git commit -m "foo0.txt added" && stg push -a && old_public=$(stg id master.public) && stg publish -m "Merge with base" && test_same_tree && new_public=$(stg id master.public) && test "$(git rev-list $old_public..$new_public | wc -l)" = "2" && test "$(git merge-base master.public master)" = "$(stg id {base})" ' test_expect_success \ 'Re-publish without any changes' \ ' old_public=$(stg id master.public) && stg publish -m "Ignored message" && test_same_tree && new_public=$(stg id master.public) && test "$old_public" = "$new_public" ' test_expect_success \ 'Reorder patches and publish the changes' \ ' stg float p5 p4 p3 p2 p1 && old_public=$(stg id master.public) && stg publish -m "Ignored message" && test_same_tree && new_public=$(stg id master.public) && test "$old_public" = "$new_public" ' test_expect_success \ 'Pop a patch and publish the changes' \ ' stg pop p3 && old_public=$(stg id master.public) && stg publish -m "p3 removed" && test_same_tree && new_public=$(stg id master.public) && test "$(git rev-list $old_public..$new_public | wc -l)" = "1" ' test_done stgit-0.17.1/t/t1701-goto-hidden.sh0000755002002200200220000000101011743516173016645 0ustar cmarinascmarinas#!/bin/sh test_description='Test "stg goto" with hidden patches' . ./test-lib.sh test_expect_success 'Initialize StGit stack' ' stg init && echo foo > foo.txt && stg add foo.txt && stg new -m hidden-patch && stg refresh && stg pop && stg hide hidden-patch && test "$(echo $(stg series --all))" = "! hidden-patch" ' test_expect_success 'Refuse to go to a hidden patch' ' command_error stg goto hidden-patch && test "$(echo $(stg series --all))" = "! hidden-patch" ' test_done stgit-0.17.1/t/t0000-dummy.sh0000755002002200200220000000033310426061622015564 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Yann Dirson # test_description='Dummy test. Only to test the testing environment. ' . ./test-lib.sh test_expect_success \ 'check stgit can be run' \ 'stg version' test_done stgit-0.17.1/t/t4000-upgrade/0000755002002200200220000000000012222320003015511 5ustar cmarinascmarinasstgit-0.17.1/t/t4000-upgrade/0.12.tar.gz0000644002002200200220000002550510732722750017252 0ustar cmarinascmarinas‹Æ-NFì]MŒ#IV!ä\€ ‡W1U®vÚùçL»jªzª»ç§wv~ÔÝÃh§«»&23²œ”éÉLWµ·»«s .œ!!í !q‰¸ •8±œ¤EÜx/"Ó™þ«Ÿ· jòÍTÛÎŒx/âÅ{/¾xi+ Uk¾öjI²Z-|U­–R|Íè5U15ÝÐLKÁrVK×_#­WÜ.NÃ8¡!¯wé¹å.ºÿÿ”ÿÆ‘Ÿ¼B#¸òø«Šnåø¯‚òñ˜¿#¸Úøë0þª®«åø¯‚¦Æ¿Ë¨»t+¸ºÿk†j•ã¿ š;þ}',Zž Ó0KÝDÿW–Ö‚sèk>þ¶ç˜ºÞ6,Õ³ü£Y kw ¯c2Íñ4¦X–«XméºZÒ+¡)ÿOèÑòAÀÕ⿆ñ_3Í2þ¯‚¦Æß¦1»æù_çó¿VŽÿJhîø_ëü¯óùßPÊùäx¦Õ6™ÒqÃjÛðŸÛjiJG£žîx®®Ð–çÔ)çÿ›ISþ? ‰Ó]ö ðñß2•2þ¯‚Œ¿˜–dW_ÿ/Çtþø–2_}þ7Zj¹þ_ µmËÓ[×eLõÚ UTE3Û®m:m¥eèTÓ<Åp™]Îÿ7“.òÿF/<úÊ2®ìÿ-Í*ý%ÄÚºfêšêÚµ(k)F«Õ¢ž©µ©IaVÇÒ4M/ýÿfÒþ¯.EÆ•üßó¿^úÿ*ȵ:ŽÎlW1ÍдŽbšmi§Cu2ÇÕõ¶b·¬Òÿo&]äÿ«Ÿÿ 1ÿ—þ¿rYÇñt¥ÝQ˜nµi»Õ騶­{šÝrÕŽÕÑu»£è-³ôÿ›Iø¿¶/3ÿ¥ÿ¯‚Êýÿ¯7]äÿ×6ÿ—þ¿jµŒ¶Í:šé±6µ¼–îé†jÚ¶¥u4×`º§šÞny¥ÿßLºÀÿõ¥Èx™ù¿Uúÿ*ÈeJGq©é(Ì`SC·˜îZŽít¨ª›mAˆpXéÿ7“.òÿÕÏÿ-1ÿ›¥ÿ¯‚×ÕMÚQ(ÓÛÕËs¨mè¦ÖÒ[žk·mOƒÏ´œÿo(]àÿÆRd\Ùÿaþ·Jÿ_¹³ÃÒfë¦e[¥®Ç,Í Š×iµm‹¹ªªº´ôÿ›IùÿµÍÿ¥ÿ¯„ÌŽFÛfÛë0Õ5užTL×tÚ¶«9-ÃTM @«ôÿ›I¹ÿÛ –ö—˸ÒùOþüÚ*ŸÿX åãß ÃãWóøÕÇ_åû?åø¿zš:ôFÈýø+Ïü©Œ æ˜a¦ÆßÐËüÿjhíõ¦í͸+­Ikd? ìízŒ 9؉üAB’ÀÌà“¤Ëˆöû~B’>‹czÄHBY@ìpÈí‡xQØ'8Ê}ê÷²Â .èQ7“Ð ‡=¤ËS?é’ äo³($0.É0&Ô Jü8úÁ°2¢pù4aã&øA4Hblnœ„ƒBk$—èÇÈ¢× O™‹E™  Ë8z~/k, ¨ JIº~̹ÔIú,>cAès† jHRƒ€CÉqWŽY2H ‹"?#Õõwï?:¼wÿAêkB úY•¼ñ†TAçZ®Þª®¿U=“¶—…Ǧý?·$¯]èÿª¡¶¦ü_/÷ÿWDËððr< üÉqÓOËp‡Fht4ì³ ©sí3zü=:TOº4!]ÏqÄ¢ï.ŒÀbA¼¸J´.óâÅ+Žk䓹~8g›4”E]—PòÐ? ˜+‡ž'ƒv{>¨îaÁ‚ú~tgw}TONÀš1‚ìòè½Þ¿÷ö‡È Cóå€lÄͧ›­½ƒZck½9Á{›¨ÍÁF ØEl@ä/bR}º¬«•Ô*yñ‚0§Â~mo_–x8‡že–ãˆq‡ƒžï Úg»C¿yX¬VÉ.°Ùä7žN5h#,UHF ôcø_ÙÉzÄÈFóécRy²¥â¿Mw£ÆÛù¢)6uï Ü;¿nMªt&}׸ÓñÆ™û.OÆøOÓ§ñ¿Ži 2þ¯€.ÿ1:ƒ‡;"È‹KIƈ*ù&æÈ(?H¢Ð:, zAˆY¯†pÉïaçN»>ÀX<­³ß‹³Ô‚,蹸 @šøa'Í~ ãž=Ú¿­†`ˆú؉ ° °™,§#øÞÛû÷ˆ¶×tÙI3özh(*XzåÉ(à‘Dþ*9¤»¼’Äz1“*kä÷߾÷ðÓ¼¿MNÇ=¤£¹ˆaÄÈß$#èƒÁf¯Mƒj8J8H®HeŘä-ÄL vû‹wüÀO|Ú#~ÑdCªlKžO^HõxþG"@ýY÷ÂaàÚÔ%»DÙÉ/ÃbÆ1¿‚Ð5{=Ìna ªšßÇÄPŠÏñÐ&ÀòçÝžK¸¿¹~ÚÕE±T}ëpGª@¯6_Ï›PòXŒ:úðѽ·< Õ­ƒäÌ»A¾öÁM!¡Añ0øŽ‚ñ‹©”'«¶VÏoL¨A…‹g¢qsz =kgQƒÉý /T2O‡U£ÔiN¨6²É™ª¸&NsÙNK‰W^EŸñÁOÀS7ßÜã Çö_<å¦'˘¥ÍÍÆVØÍuýŸsëÅÖªi_ö,ÉõÔ|úÖ[D>xx‹ÜÚríp4š‡¨ nš$£Ý0`|>}*ŠŽ„^>ƒüm¸8Á«šÉx,­„w~cv « ²X?<Á.4ªÙ¶YªW‡ƒ@¾O ·ëÄò½LÐ p…)s‚Ê|0 }g€ø ÒРnƒÒ<À@/P2¬3¸Qåˆåà7:Ä ‡ê”rƺxšÉúóÔdÏpÛ`Ëç…êÚ‚ê9Í©ž[y¡b'³À9ÓtªþÜÎsiy#…ØÏ92˜9Q\…ó›;t´ñÈp '¡Œ‰Qa´Ù W^jÀ*ÂES'’*0€âJÚ0ìè…ÆYP`n •ÀWU§=lµÿàÝ_|¬<ÙI¯÷ã#TìVVO¢j¼#PxÁ5¿p&Í”®’L€ú$U!/ã!Ì­›T 3ÙÆ::}j?ÒÚW$Iâ«ñ¸˜£ ;ÒÈ'Â3‹³õñ ©¿I0[·gáûgƒÇ„ѱ‡.Aãx؇’©GlKÙ"|;1 €Ç‚a5íx}üŽû9Ä·©‚šCœèõ²É|ô:X4GÌÁÜÀñôS­±Pî8X; Ü“ Ü ãÆ h±/ë˜b–†GX ¯>c}ˆùôå‹?a„­ÀúŒF=C5X"=fqZ°Ó™9à‰X= ^x²†3À…|â²C?YãxH$W0ÆÇIäs% ÷•i4âq)í&iøtÊ`€Æ@`O"\øVTì£aµ} Á’Ï0žòÐ JNAÔvº48bã´Ë—QþÑ0â ,¸)SÌâ´ûâÇ}h×§þyV+3ë:WÅ)ýYæ8OÁ¼7Õi4¸¸›²4Œ·\èD…H+ Cø-p #¾‚›ò?ô RÉpà»có)´"¢hþ¼²ÓcP~8ÈRY©ª@ŸunÝü\@Ñ©,Ç&SÞÅ1Ýd3@Áh6`[)^ã“c(Œ‹P3v8r¢ê€…hE„ÜÁÑûĈŒ#HA‘œ6%S(Dð`R¤Mí<­r…NC®±opÆ`[—…Løñ´PE"'r{£ú`âµç8¶,Oô,k4žEÓˆÑà.‘)=b=îØéÁ3Ñšæ¦@Å<˜c¡7ñêy,(çÂ7Œ¸øÔåê©fS€™ ÇàQ´ P¡K±4ãt¡H[Q/bqºÕ„Gø¸ŠP“«Rú&K6bÒÃù‚&Â)RÛ¯`6"”eù¿Ôèyf ³×™÷X /P¨‰6ÙŸ[¨9umN™ÂUüs€ÛøÜ™-Ù,üåt0S°XÎæM¼;Q2-×$ç³lr­_F¹"3” Iûuèzø]¾äš ""”ïsÁ“¤žÿ ‚|ê’§p1õˆyLâuîð:žpÅY/YàuiÝlÒÆ¦¤Ñû…}tt«³™”˹˹b˜Žhj€°1³©¨+€÷Ý”_“êðÆÃƒû"â±PzD˜tgFð#œ ûƒ!ÄÝhÄsW1O…6ùR}~‰”x ±ø­c\Ï®“r:¸‹çÝ+6F›mLºnˆ¹5"¦}¾?Å¡ðº³$7—¦óK>ú%d\°ÿkêÓçÿ5K/Ϭ„.»ÿk÷B:À  Géʇa: €Âì0¬ ˜Âd<4–&Ѳ=àmŒv|/&îRU!:ò7°°øÊ'ÆÆ‡ÅDR¡1¼‘9Ánê ƒ ‚ézÄCFc ïÓ.ãˆe¦ë§~¯‡A9{:€cOñ)C9‚ÈíÑaçXh…ÁbWlŸh4²ßǘ)UŒÈ¨‚b ’Â{­*nÄ{½šU|Ëßd$6‰1/õX$ŽÒuA•<Ù!…|_õ ¡Åt€3ôÄ+Y3UÌ1eÉf ãp hSäâ!ø!©@. ùOV&ë yºµGÞý7¢3{µ¬dž-È»‘j¢J2ð ¢vþY0™éæ'x$`û©³B³qàÆ2mâá GUN—Z€–ÀLØR ùå÷A tí‡4ˆÂ_‚^ÆÙƒ ê`£pf¨P¾Z+h¤P—kaêÊ.ô=@•Ohîd­d~ƒÉVô<¸ˆÍƒ®2{F‰[[[äcÁ¼Ø|Á00bÆýd¡>ÅsC£ÃÇî éˆá|ÁVGø‚:ÍŽ‘ãɱڡ),¨à¹`±¶¦l-®¢¬]Á&ËUnˆqth"ó^ÈIÖ Zq§!3»zf`¼2ßn¨ä¿“¹Ubjâ× >È.Âr,J3o^ž¾[[Ëy`Z÷¬º>mCUò:Œf Y¡“•ÂØ`âaZj¬OH­gÉ• j¥µiã†F¬é†NSœ¼„“2Š£'3<‘-IÿÇç'^É×\ñû?Lüþ]/ÿq%45þì™Óº«]ÿ+º9óý?fùý_«¡5¾LïÅÇ€BÄx“š‚ŒkÃÝ Ÿkæû1¨ÃôìöÆÚOã¦Á ÿâq–’tBú‡`Á‘ún= ÖˆMù1¾¢Ä559 a) Rül8—ŠDƒ˜lN„Ç>),NŠa¯×0®o5‡ô ¾ùå2“8ESþóÕòe\øýÚTü×4µÌÿ­„.ûûß•™Ÿ‡—.ûÓ!•YtÙ_­ÌÿQJ鲿Z¸ >~©¥tÙ_>›ÏC•.ûËI êó6\M…3?Î!]ö×Ôz¸ä7ÀÏç¡K—ýéõE.ù-´óyÒe¿ÅrA}Þ†ëöÄë¡â÷¿Š\Ê+q%üÏñŸbX%þ_åã_Èð.[Æø_±´ü¯—ßÿ¿zÙÍ‚rOþfPîÿ¡«³ëÿÿ,ãÿ*hvü11»\#¸Úø·øùC/Ç´`üñ¹my Æ–5®FÍ–Í(…¥…¡)Ô2lS¥†©Ø¶ãj ,}ž cñüßR&Öÿ0þ,ÈÊßÿ] }¼÷}xù1ø{ý·úÙïýÚoüÓ7î©?ùÎ~Tû“ŸûÃÛ÷ÿõoÈ—Ç_üÁOýÖÕôþúgýýÿþ‡_yü—ÿòÿúNøþÖßþü7ÿÙ¸÷½Öþµ?{úµ/¾üžöûþ÷ú›?þÙïüÅï¾÷Ç?S—ôC§ÿµç?ï>üàËÚÎÿ|öýÿúÕwŸ}÷‡íƒ¿ûîÙ?žŒþ—½3‹ªêûøÀ¨ˆ€â‚XŽ ‚t÷{Ç0 °ÄP×;÷ÎEŒMÁ|ÔÔ2×Ws7·G%µreI 3Ä@@dST·Rs!åQÔž;Œ=¢\Ïô2çÛçM3çæg¹çwÎÝÿN¿ðj+÷Ý6æ=ì:weJʆ“ ¦Cnçt{X:s:¨ÆíltUq,{wÛ7–?úòëÉÁ+—Ƶ¶ÞüUQG«A{jkžë=µúü|òÁ¥æËöê/à²;}ñ¸T{|sËþ£ Z;¼röè¼_/ÍJÜfËÛZpÝÆ¤sUW“3FïÞlwû½k¥³&ªƒôÆ­Ouv,>¾jnp‘&¾tÇÔ“K{?øvðá“ç Öòæ­{v)vn¥ûC®XN»ºý†µ•ùö¡¸ÃêS¿ û99$ ?”–Ý'v惼2_»B×/Þ¹1¢ïÊ’]éc»[¿1Âx~·õ“œ·ÍÛqa†lš‰oÿypï¹K6¾Á}ØmohÈüÅ.lu‡¸õ³?ä2Gz:§(ò]JdiIš½S¾¦Ä­˜IwS:•bù'J\úäÙ+cT!¶¾§9w¶i'™fx×È¢pÒÒÝLoÅÌcI”ùèÀO½“öü™qô…ÐÜù“¿îj4ß!ËÖ¬×Y`šIlµÜª‹e›)CÌËV¯¤škZ14(n}JnܬɳÏjø9T9ØC~Ôs1‡Â—sâW\LºHDù´_Îã=Íqyö û(.Jî/Ç£\e·Z^­ Íïï[yŠUäÛ^ÓîÈç8Š‘yS‘Ò{¡íÞ ›]ŽT–a'?j±ÄdßÜç<õÛ~S'„ÍRß7š|ÙÇ«F>Üëð²ð«Ûïö':¦D.+ª¼åÎŒ›ôr¬”!ÒSsR35™™©99iLq&&F¦Ä%•é%%iЬ¢=u'Ù½qVkj¿“…G¼=P«§.; ogò½ÖÚõœÜçL‘¥V5ñHÏ¢Š+5Þ›Gß,íÚ¶\™óÞÐjåÙΣŠ"Ž8fø¥Ì· Ì^Û¥Cì7G÷r)B"Ô®dâ9«…“V¯æÇMw¶¹àþë¯yâWr(ͤëî„”Ï;Õìºv©pラÓ[ÝôŸ¿Â¡Eœ&4ì»kÁƒínµš÷Ó/¿ûcj ›½÷fåœm¥_mQ´Ü2»ÇÚÅÞo¯ü&päåE}zU›œùÅgTüìúªÖK+ãvŒZ—Òßy¬íTü |Äÿ¾‘£=²ÜrKŠ0²ÉMSä§žHÍÌéáåRš–Z¤aò3sSíÈà§ßH÷N2“ ÖÝî·xnòìo>ð LX»oßçó²~ NKŒöJÞK L$|HïÁ¸7‰ÐCPdÿšÎßåMÄöqÂóý2YëŒÛù,Ãm”uu´i}×èE•ÿ»">qØ$æ®Ñ‹®€¿$CüHªÉ”jù”ÎOݹùجÿÈjçõîǃ§|úèEáæ mªˆ­EIIs<+ ÿ*"j«}6ß~ëÅÜ'*ÑO\‘dã*»‹ZeWË2¹Úi|ßÏz_ÿé9sÂæÂK®÷Íëfz<žW˜{D•žæí·jŠ.Æ'ÙDqbÎøÏ£6•þ[‹Ÿ9ö®²‡û¬ÎèÍÚ«Pl4§Š¦Cÿ›ýPÍ…Àò"“5|¯¤Šd‡Å•£¢ËܯÈa[ÍPÖ.ytVÆÎ-ãlñdïy#&š§$¶©Ùø^¿¤nÑ€H§ÞÏ´’ð·~àgå^5xÆåóÄ“M{ÿXrÃbÜ‘'«V‡žž>ËêÀÐ÷¸{b£¹ûgehªÝ¿Rkúì~kÊÑ{³Ï;üÍk@±Ëï×ÓMº­Þ\êï>_Müˆ3ÝFN4©F~㺗=øäR‹á÷·mnõsžO…©ÌÍ)Os)NÅJ5å=L[ûò]ÜÆeq»ÂEË ýY´ó-\æÛnù[«&·°_eäØ9ÂHfù…õ²»ò…3É gÏÿÇÌÈxbYȧ}{Üò½¦» ‡íw$‡±O'ú(Q‡ÒÌ&¿DÓ£8­Gny¹›CijA¦"÷Rì–^~B£A¦nù­¯LCYu½kô¢ªþý š+^A‡L¢«åír—ÅÚ-îöÐHöyñ‰I{VŸöx¶ÞzÑÆ§gTÔQû…6GùÄ¢$^î!—›ÚÚÈ5Â'JᜯYc.»ÕÑ*²Z~uäÍÇn4ê×?<ºóǃ:[÷²=¦yæ 0÷¯*"í m²ä•6•D"ÕÞÃ<Ñ~9/^¯=q싾½«¬ê+‡ßÇw}»‡Ó޵§Gßî6ý ïûñ]ü+ÄLSÌŸìÞfk¾æ£Ñ¦=[†Êîäd»d Êæuú]þËÃ7dß[YO4~6GñÙ·Ä4¦N6%ÄØ £‚7ßæ4í_e{W…ÿÐ_¶ç`›+Éuë2Ï\æ~²å·us¸³i4±yæ¸Evé7«‡<úqÜ~ÜËV¶o”ÅÒwÿ–ÁY·@yFO3ÛÂfžÍ¸9,-ßb÷á·lFÌ|˜üU©l§ÆâúŸ6–™v0ª¬óÉ8ª:ÿºo§=Êi±ïv^òÃ䫇Üò”eÜh»‰[/×úŒÈвu5'º>yãîÙJ㎑ŸM¼£ßq(D?4Éü/˜{a/žÿádÝù?†a4 çÿ`Œþ’ŒŸ$ùK¤MœZ6Aj%!™4Z‹ÉTBÒÞE7—ڈɢ‘©í_’e%«z’õ?$µ{’Ú7"uxAêø—Ôé9©sɶžÔåIêÚ@zãIê&1)ž“º7ÚÔw£@”vi}S2ñòžPßðT¼œM»íSñ٬€©›ü¦ti»8tAÆŠÝâëóñ }¯¹2}—EÔçÙþý‡wêZ¶Z”ÈtÏߌ¿Þnà¿c¿æÏM+¹›Îõ:&ŽBLúŽ?VîÔ—ø¿yձ妊ÛoNŸì)~Æl}s11Ÿë#fy]´\~nËliÖêì »ðb=¯Ô7:ó÷™0±ìd6BµlFí,šnŸ2ÏÙIü2ê»#æŸ6©¦}ÞšÛY|R=âòìão_s›Š7ë›è‰¯o›ªôôž»nrÇÌ.“§om‹Ù4Ç4Sl6’ë›\ˆùo_š³Ã|Í®ÎȪ_?èèýØÎxU€XO-¾ÿ‹³¦ÛR¯3>ñtå©ì¬ÍÔƒáÇÅzÆÔ7=ëyãò† ëòOzø,tÚX’vq‘ÿ—b=kê›Y‰q·¨oª#¾¾üÎ^­Ê(J]:û–ÇêóùíÇ÷¯«6ÿYë¹°hãøýFù†ù˜|dµ¸¾rá§œÿïÚuør”û‘òU÷oûx_öÿœgÇMï‰õ?šBáúžíø¿ÉŒ  ú?(ªNüÅÙ,ôa¨¢ñK}ðTÖæÃSýkÛ|Me4¤¬îþ?ÏC¤Zè[Ï.5}W ˆ§ú×kðZÊh@ÿ(^÷ù?( ÷ÿ‚a´v£èXívú?ýŸÚ­³lôud”ö¬‡~ºS_CÔ¡á¼öÔ|íqf¦*í¯~ ­=_!$<ˆ Ñž]U»a7êÏ·éûoƒ4ÌSýÿ¹+â5”Ѩù®óÂùžÿ“]1MYF£ããÚñŒÿëç¹ñ×>Æ-XÍ7I ÿQ´Žÿ— Øÿ!1‹@Í"0ØY&ÏÕLX“µ ê¡êèŸÄP¸ÿ ¸YÅo°I6 Íéëÿ®á!/én¼þi ê pýß°‘¸þÿòâ—½œÿ‡‚Ï‚š!iµ@©H^©FPœÇJ  ”¡T‡2(ŠQ¨’c9¸E ™Ò°þQÀþOBçÿ„û? %þ`ýŸ„Îÿ ×ý_†ýƒõ:ÿ'ôAêSp¡þ›'RôÖÿIèüŸ°ÿ‚Îÿ‰ê»=!Eÿ`ýŸµú_ƒúô6RõÎÿIèüŸÐÿèÿ4l¤è¬ÿ“Ðù?áüÐÿiØHÕ?8ÿç“þ®ÿú? )úëÿ$tþOøü ðj%'à£DÔ8Ͱ ©TªT*\ÀT$*i%Ž«”NR°IhžH_ÿåÿ$tþO¨ ÀõÃFâú?@ÿ§Îÿýß`@1ŽWb4ªå(µÀ‰-Â<É#Ë JZü%àþfJÃúÇôãÿ„ç¿AJüõáÿDáý @ÿ—a#EÿzñÂýÿ@P %Jœ QAÅ‘â¿0Q3JBPRjŒ05BÓ¨13‰;Y¸0¾ïhÜÎ{û™ÃÖ‘ÁzøC!õòTÿ!áAQM3Þ{¦ŒFÿjõpÿêÄ?R-4ýEиøcbü1Œ&`üAP_ü'ªY¾)¯‚ÆëǵÏñý*¢|àstgit-0.17.1/t/t4000-upgrade/.gitignore0000644002002200200220000000004210732722750017520 0ustar cmarinascmarinas/stgit-0.8 /stgit-0.12 /0.8 /0.12 stgit-0.17.1/t/t4000-upgrade/0.8.tar.gz0000644002002200200220000002004010732722750017164 0ustar cmarinascmarinas‹Â-NFì\ ”ÕyvÊ£Õ@OôPJI¸Ö ¯´ÖH=÷Á®½~ÆøQ?xØ»^Fw¤éJ3bf´ka/MÀ„HShÓ¦ÅÐ@š’„B’64@O˜Ú„ÓÓB!'‡@ÚN›“Ó’RÒGúÿ÷Þ‘FÒ®×&»r ºÇkfîýïÿ×ýîï(Œ¯Xæ’€’ËdðSÉeþO¯¬PÙd*ÌæI¸ŸKCu’YnưÔWµ Y1]R[o±çÿOKô+îrÁIë_I¤”lOÿÝ( ýÛTw–ÉNFÿÙ\ô¯$s‰žþ»QZõ_¢jaé­àäý.•žþ»QæÓEu\j/aP²éôÂúÏ(múOe2©$±t,,\Þáú§ŠšIÂ|«§Óƒi5—¤ƒƒÓÊ žÓò ZÈäòZÒ4éT3Ú+ËRZýßU‹ËNnþÏbüO&{ø¿+¥UÿyÕ¡§vþçø/™Êöôß•2ŸþOåüÏõŸÊÀGoþïBIä@ê ]M+©Bš erétB£ªš. æõ¡>SsTéÍÿoÏÒðÿ¼­šZi¢ÿ[šÿ¥ÿ»Rú/YÖô2%€N^ÿJ×=ý/iÓ¿Z­–ëUÕÕJrÅ).U‹ÌÿٜҦÿt*Õ[ÿw¥ô­Œç 3>©Œ›„P+Õ2%hÄÑl£ê×"05hÓÄ-Q¢Y•Šá’²U$ê8j‘W¦&É×BÓ€ˆn[¢E¹¢e¯rŒu´«äõP²jåô $g ·DLË”o ¶E@/nÍ!ªX”ŽS3Ì"ã}ØVÕ6T—6X0t‚TÓu]ǵª>nc¤Ù£á ‰rÙš¥¬J ÐÐ70¢n”=f¡’©æA(nÉp•(©À˜ùw¬ ZÍÅJ1IŠð(Ù)ÉukUÉ¥ŽKä$º|Ó®© ›vg㢣ɪURi·Rè ²:Zœ“†— µù³·%¢Ïú8¾ÿ+i˜ì[ý?•îåÿºS–ÂÿÁKÈzð(ð§|™¾¨ÃÚ2)Qíb­BM7Ê(˜j…Kg×è<ÐÜ-©.)©Î<Žè÷Ý£X ^œL´*óÅ‹eŽ}d·‰Ô@>ŒržBoØ—Z(•ì4Š&-È–®Ë ݲâ„gXÑ'þÛֆ z2ÖŒd|÷®+¶í˜Ú´aãÖ]äq€}Ù$ýN|ßD8606‰ „â-´‡É„¯öG€\ѦU"_ïà¾BTR‚äÐ!Bµ’_ؽ±1v[báFæYŽ†ÑŸ:¤P«– ÅÞ9ÆÍÂb0HFL˜õØ¿¯¡~ѱ Že»0Žši\OdÍ%ýñ}{I`r@Áÿã…þãó DSdulU’l8>fMŠ4'½׸mñ¿j9ž÷.a‹à¿dªÿ§°z/þw¡œXüÇè ®ñ ÏC¬Jœš¦AÒkåFàÄZµðVCá0ÙjÁ#³øNtÅSRæóÿZµÑr ûXÄÿSJ®çÿ§¨œ(þ«Âü¬Ú€äHUÕ¦!ÀwË1\Ë®ݲIÍH7Cm R¨UòĵUÓ©Âœí¼•P€82¨•*V¬>@|F„ Á–Iˆ2ù-XdÙбl˜ºÕ '[Úýߦrs ¿T},–ÿI¤;ò?ÉÞþOWʉú?x˜¡×ɬ@jÞª±ûyo%äÒ¿ýÍ-k&݆÷sœLŽ¿’?“sJƒÖ<þ¿Ôð1ÿO%Óø?Qzþß²”þü1LÃ5Ô21*ˆ&cR`XÒ rHªR»Ìò?R©“nÕÌÂT^-Q’iÞ•bƱy¡« ö:å=ÂT°ùC¦Å¿;µ<’S,ïvP Àóph¶TòjhºvjD À¨Â+›,D°.¶#…îܵaãŽ$80aB?ó= ×}0Sp,`È©9UC3,0~>•²dÕð‚Í›ZÄ ÀÍ9ÎÜ<£†5¤³Ãd“Ù¬äëe>úD½¶SB±‘0¤q„l­Ø 2,jñOV»žcÿƒŸ€§†/cŒã+‡ö1Ó“eLƒªñpl Bòñ %t¨1¸Ÿ[EŒÅ¤ܦœâûÖ®%òÄÎÕdbux¢°:o¶æü°¶D& µ'-V¯ž·*æbWÇ®­•¬JU\3ÒNˆ×x&Î9‚`œSÑpÎ×~ß„3@$xLÁ(¢["Ó8CɤÂk†÷^:6:9˜›ë X3mêXå T¡vC°©— Ím§7çS)Æÿ°Ï…F¤þwdJ¶«eügS<¸”}ÿ)‰d:Ûÿ’ÙþëFiÅë­jÝF”CÂZ„$AmäÊšiXd=¹B­¨¦µ¤j-_6œÒ(3 M…e›Aø¾N¨wv’^3´Ñ ï%“P2(¶£¶S¯ä-ˆ`ƒ:ûa²—$ :ŠÍ¾¢!Ö(è@â§U£>ÒkÖÄ"R`dDÂ+d¤aÀ Ä\0ßPÛ®´µ€?w 6uT ßVkëbº«@U\ž!¬WÂyÈ…O¸· ~EDˆÓÒ g @î&é¶ÍˆtxEø¾ûØäb:`°:’lƒ‘·]#›S†9Å¿2é04’‡¨ ~êºõQˤl>ÝÇ«Œ¸\ö7ô ß7[h…Jûc¢BÜù™¡ºHÞ¦k‡ zÛfB®lŸGI¾Æö2A‚@¦Ìª"1eúÆNñùzCƒZBÓ1¼ìCɰÎ`D‘#–·Ìr} L)mÂiÈbŸg$¡ƒÂdçpÛ`Ë~_óäÍ›ežæM+÷ñÁw2}”=I ñ7í¼Ù[“IÞí~† :4Ç뀫0zóªN-Û`üuR«Ê®%cb”­§ôÀ[RX€»¨p") äwc8ÐEÓ'À¦š>€¨ZŒ°ÕøŽË¯Þ›˜÷+N;àÕÀ_Ñ5Ý´!Ö“ç¢/3Ð~‰cDíµÊ@K ÌÞ„<¤Êúäê‰cý@ÞèQ©æˆS-4‰O˜ñ¨`O™dp‘‚<Ò2­õH¶ÑD$â„{9åP’o^ÚÉÉÇS|Q¢w`¨!ÿY‡òHiNTådå}oEÕø„3xÁµycNê¨$^ʤ!«ý 6­Y v†oc^ØÔ÷SIb«qÇŸ£PuZ¬©vÁi ÏÔñÖÇ3x¤üF¡·n÷Â÷,αìi]BuœZj –$2@¶™lÛ2 €L;œ`P <Ú¸b¾„Añ­°Q³…“‚ZT “„©^‹f›j˜[ƒ8.¾Eb öÛÖÈ÷dÍBK f°k¾/[b–†EX ¯&å>“¯ šÓ—ÁOüX6rí©j— Õ`‰ê4u¢ÀIg;8ÒÀ]¾z@¹°d #€ yרz‡~<æXˆ'W0Æ;®m0!˜÷•U»Îâ’&iØtJaf€ÅiJ«8›¶åh\l×@°d3Œ¬,´‚…1ðÖZI5‹”%ذGÏŠ¢b|W„h¹½ó Âpx¢,*ŒJm¤Ñ¢`vA³ ”mòº2Ã¥¨Ä³hœ*Ä\_ Ì|°yx§]¶Œ2Š5›%°à¡ÔH1ó'À+ŽÅp*À×5þYVË3ë(Å, ý^fÚ´fÁ¼ÃJ„Äb1\ܵYÆ[&Ft"_¤å ƒ ¯*²\›ÿi žÒpkU£Ð0¶ŠæÏke õkU/•%DòŒ2ëfçüNÍa86ió.†éZ٣ـm ¼Æ&G‹QuÌØ¡æxÓ*µÐŠYʰÐû¸FÄ'HFYñ <˜)œ<žT™@Û!WÃ7a°-“õ…ƒàLØñ´-("žƒ~Ëõh`b­çqlYn#XV½1‹Špm0—ð„nÓ2slqðŒD¹)1 fàXèM¬y3”3|áF¯Ù¬{árQ!Y0Eç<|…*”ÔªÃM#]ÈÓVªnSGl5á>f‡<Ô4E)]EÝ~‡”q¾P]îÂCÁö˜°dY^ôO=kðÐûì¸ÆZxC…–ø—'ãóVŠ·Ý›§Žï.þi@­ñGÖuÖŒûþše¢£¢¿^ž±¸¾¥¦¨'Ç'gR?6é¡IÂÐÃ׳%Wká¡|œù ž$Õä…KÎÂMá97š1‰µYÇÚè†É<g=w¯m½IYÑÇ…_ tt«½™”õ³žQÅÐÑÔac(¦mQ—ïõ‚žˆIQ¸Ðñ Å8xÔ”n«E*­ëèxNЕj â„n4âyW1û¸4ÙR}þ¢°|ñRǸž%MÈí à¬ÞžŸ™d'3b9ãs«è„OûlÀ  ‡ÂS%yû–¶üßRýâ},²ÿ›MµŸÿÇÏ^þ¯åD÷óeKƒ…NÍTMˆ ¨E±ò¡˜N Ð¹ +jÌP‰$š·<ŒÑŽíÅ8%U‘-ˆŽì?õ‰±Æa1žÔDh 2+pÁl=Æb°o@ð@¬Gò–ÅÀ¨Caá=[¢ ±t }Ö(—1({o0ìÉß" ¾ãq#ë¹uµVf9Z–ÙØùã|“• Æ|̘HB0<£ ‚H ×É ²áש ×p',Ý:ß$ƼÔ^ž8ë‚ ™!¾|_pëÚ®™\ŠBÁzb‰XsL^C0§0Ž6yÉ©á":d!ýÖÆ$” —°ÆÈ¥|ä$úús>¦ùÝ)ìk”“ñr•¾\šêÊl²ë1ñï4xfõ Œ5fÛ æå Dy7ñ}ä–7a9f»Sž7‡Š«¾¾& L«±‘Cí6$+A›®]£¾A|ºÁÄC{¯Qjé5ê%WšQŠÛAÓ:„êü”wƒúqC(¢ÔUrˆÈ™ìçÇ20úy¡€¶ª°t!ÃK6cŠÔ€ÿFFZå\híkTä;9­†à«….m¯çÚ0Ûàj¾¥Á8nÖÙ¢– m@PsDÚ[õß_‘ZÄÁ¦æêà qL™˜Ì¦q•Éç¾ò׉g:,Õ…5Z ˆK§!ߦ:Ëeb±â¥K»0ÿÏwþ_LÚK×Ç"çs¹öóÿé„ÒÛÿíJ9!üçÅò[æMWåm" Œ,.ø‰½ô‡‹HUÓh½¿F òzˆ˜|¯4éܳÄÒÓoªƒseAòXf[µb‰8nÁÀ“ÁìEŽ+ˆÃ:æ}†ÐAÇàñe0®Fj«j:“J*Ù‚’OÑôÍéY}h0Vt5“Jg ­U“ ’Ôsj^OSxžL&rƒƒCz&™×éÕ )%ŸÒ•4éøUE&0‡²1®mä…ßyÇû‘5Æ7*Z^ e n¶¶0˜7T3‚ðQê‹‘xͱA…ªMãK‹ó“—𥵿ödŠ'²%©éÿøúÄÏÀï¿òßT’™Þïv¥´êŸÐʵB—×ÿ‰T¶ã÷²©^üïJécËô²Ãà°(„ˆ a 2® G;L¤q®™íÇ  ÅÙíþ¾~–ÆÁ _ÿbqV%bB*‡`Á‘z}TkÄ¦ì˜ [Qâšš-XÊÀ;ÎzÅ ¢¦CÂ-á±B|‹S„b˜EÅûŒë±½–:‰7ö2‰m¥ÕÿqºZ†>ñ%›mÿýg¥çÿÝ)'úûŽß‡”Nô§ƒ èTºW¼âûýO¾”^Ž>Nÿ‰ùFÿu£4ôïKð-y‹ÄÿD.Ù®ÿL.Ó‹ÿÝ(o5YÜÛ“}{”†ÿ[yç§<þ‹õ.ëÅÿn”ýc^n‰àäõó@ï÷»Ræ×?þ'”lNÔ‰|FIgUUWÒ¹\‚j:Õ²´JÑtAOåµÖ>nPÒ ÏÿÉt6תÿd2èýþOWÊöñõ›áãçàïÜ»ñÀ‘[îxZ:šxÏÆ7Þ¼íųîúãß¹ü¾}g{ßʼn{¶~j%pÎê;ã‹/=÷ÊW»äöÁ3û}*¿yøtû}·þÒW®ú«grϧ.yðüoü J>x$󸦹ö7Ÿ?˜VÎøþ#g?<“üÂßܶI]ôò™ÿw|áé»ÿä»+÷ÞûÀ{/8oOQ}ÿ‡ÞùÜÖ7v„ž<ç™êžÏÿ׋ky|û+ç\ø£w=ß¿ö!ÆÞí~ö6½~8xÛôßÝrãWC÷lâ…sƒÿùæÕ’ù¥Ÿ|å[WÿÁ;~ë…O^té¦;ÎýîËáíÅ>zí­w‰®>?µûÆì³º%´ù景_pKòy©˜9üÉï¿gËö;ž:ë†G®ÿLõý¿:ñ•g?›?f ¼~ó‘µîa+sô²Øy·Ëo<ÿèQÆÝO^\c]ßï]öìwNû៯½™q÷4ãî–WæîÍÿÇM!ãáí®—öç?VXµësqøƾüÑ›öû×WoþûÔ§ò›Î{zÿM÷?sáÅÚç¿9rË]§ I›Lÿuþ©o;s(r·{ö×ߌm¾kã…vçöUÇ”ÇÖýÃc;võÿûsüôÑw™Õÿ¹ì+Gïýöà3ï~é_Œo}`ÇØž÷¹ósw>ýµûÿéK™‡«‡SGžÿÞoüøU9öÂVì9´â'¬}­“¿«þ0ýËáû=òµÓ®ZµsàÚ½ëN¿øÛW>ñPñG¯?ñé§_ôøoïœ9rö…¿ÆÁò=\~ä¡cÞ9¼*ók»ëŸ¯[ùñ ®üÞ¯ÜllùÂkÚÙ_ûæž{îßö¢ó»_¾æ‡Ÿyr×Ë+rÞø‰½jܺðÕ[Ëõ g?þÈG*çÙøÂËÉG^;ðÙÝÒE¯œý@æ/ÿèëžµû}k|÷±[;õ;εûí TGÇ{@†Ó/²¦‰Ä ¾¾æÂ[ qâë½jL˜£Ç3¼¯lVâI\!Q0ñ¡fU‰à¹ŅȱºÞ/ÐÆç±&lð`»iaŸÌáÎTçÁ÷{Ô›yMOUAÍÿ«úª«êS•ÔtT‡©ƒ%K oŽò'¢„ÿ_Ÿkë–N«¿u·Ï—Ý;ðPUÞåPÀKÑaçöüç¬lZ¯½åö×ô‡^™1êÒÍÊs±]–ö{{V7Ãúù™î¡«Ô+¯§þô•ÛkûŽ&,¹òŠß½jÍÜrùê£}V~VÕûšvñªœ­†ÁCfþÅ-xßá9%WNpµ 9aÙ¤·~¥FýЛ¯Ý©[ßnØí…%=ªÇïÝš?0r«ßÉÍÃFÜWý^Ïû¿&xN_š ò¨eéáÒa¾Ýs¾´ 0}¹vMtٻǺy­:‚?Ü~%Sê±ïÈÏ!:ÿy ÜÛ0'h%yaÿÈ‹ÙEù„hó^Ú[¸Ú¼$ùüÝÉõ—¯ß?ú W>ìê·òž»$ih†?-mìèŠqYfCÇÚ•…]:ÞòÆÔº1•?ndNï Àvû‡¸;ÿ]V9f"QuO2ö½€rÏQ½’%Xò ‹L¾eLjnܼù—ò×™ Çví÷ù¾Ð².˽±Qç:n³ÌáÞÆiÌg‹ÞüüLPÉíÚ˜G{s/=º¶{ŠïšaOeðöüT÷R‹O+·$.ºZz{lñIßî6iÑÃÂOâ¼°ÿð¬hú´æ(¹añÉ4yíÉš¨»TóRßÞ’qà›ê˜;XéO~ˆ´ î¼…”l™ßÿ£ÝgË’¦Nx8ûÇÿþdroq¶qFÿoÔ§Y/ÃFÿ¯  ËþŸbàüçß'7''w'¤v¤Ž\êd#yØ™<íL^.JÒÿ3y#J>6’ï“äç¤äßB pQêÜBê‚&¹{0xÝÈ¡ÑOŽð¹•{áËL¼nk5÷5—??=´êÝ%§'ÌÙ8nvo¿U ï—®ÝÉ]/¢³KCg}¿ Ï7ùOã‡OìñÜ¥L.Ÿm¯õÉ­é¢þ4u³þÚ¼ ?—èú—ßãî¿õVùåaÌê嵩—½ð»}¼ñßfÑ‘fž™?±¯Ï}¡9j2ðÃê?r÷÷š´¿ltÍTÿ®mÚD­)ˬ¨ 2íã®§Åθt®‚_õï˜(ßqfÁ‹—‡ àʽ=óA×YŸI|çÖNúnéqõÝšÄAœ9H_|cûW‡—_ëññûG"‚h¼ù9õÜýgÖT~î•דX·öÔæºî¯?r[7«Ïöm;:TÎXl\„]Ì©©Æz”<.:Ë•ö¬… N]Z?ú—ü<¿Ìîú¦=õÞ±9{×ô­½VþèüÂýãúq׫Jƒ‡Ÿ}=¶$?2ê¾ýnÝvŸ\ÎÜýPXU”|ø_w¢¦|í½p½éм¾+w]]½¹¯ûýѯ'Ýì¯ïY[áwvÞÕ‹•œ‰éý]öõõ'Ï…Y1`gêÔ Å߬ÿAKƒ‘)/Ön®ðˆ9¬Î>`ŒðŒÂ%ÿäòµfý¿ Ö=Ãüÿó?h¹ýùñŸóØ|þÓìù?Í aü‡€?àŽÏõÁ©\m‡&ýó§-¹ª [ú§,×sÝøHHb a-¬R»Z"šô/ìjsM¶ÖÿÑ–ç¿“2Øÿ†iüFéüvªÆõüÖ MÊl6)™ßë7T8õ+žM0éùSSùíhR/-¿Âw(nÐ4쯋7Åiâãù³ 6l$7Þ&ößئIÿOŽr{ý‡ÿ›TÈ ðÿPЬý…€S¿Žùÿ$ïÿQ4¬ÿABkíÏGñ0²zç”aküO’þ?#S@ü$˜ ©™”š)è¬Û'­éV¢ó,€Mý–ó2’ù?$˜i©™ñ·[ZÓ¿3·ƒØîÿ‹ñ¿Œ3 èø3Œžœ“"v]ô´¦3á<ÐqÿŸ–)àù/¬´¿Ö”’bJpF¶ì¿Œ´´ÿÜØØ»ÿ:‡¶‰ý§˜ÌN*Ãaý34 Ï‘ $XZ£Õ)t„’–Ñ*†P¨TJŽdRKÓ„B£WèT¬Áúo›XÑ¿Ó\@Ûó? ýË ú$4D©EóŸ~ƒ´¦ݬ¤$6Ñ9QÀm럶|þç¿!žü´oZíÿIqæÜ{~þGç?!ÁJû‹2ÿ#´¿Œ»ì? ÀÿkßXÑ¿(ó?‚þJñ Ó’2-¥Rjå ZGè Áèe*½†Ð*X•VnÐëi¹Á@Ò ÿ¶‰ý‹2ÿ#è_NPÐÿ#A˜ÿ!Å® ­êŸÙÿƒõßH°ÒþâúØÀø¯}cEÿbú4ë¿‘`oÐÛÄŠþÅõÿ ÿG‚àÿQbW‰VõO‹åÿ5ìÿ•É!þ¬´¿Hþ)ø°ÿ 0þkßXÑ¿Hþ)ø0þCÃ22­F®Ñ3 Œ·R©U* ”^Å2*š$e*Š%eJÐÛÄŠþEòÿHÁÿƒþ ‚ÿG‹] @$ZÕ?#²ÿû‘`¥ýÅõÿ`ÿ`ü×¾±¢qý?Øÿ‹F£#N®Tr•Â@z•JOiYKòͤ“X­\/ý·M¬è_\ÿú$þ#v5‘hÒ¿1QÏZã÷ìeØÐ?Ù,þ çBÿ„õ¸pLˆ'y5:´˜¿ÖøŠaž°tïc¶$ÃÜo l1–»Ádz’ç„q‘‘/”Ä éð®µ_T–åTýÒ¯ÓAsPð&&Òó€(Ð"Mú7ŹäðwìYâ‘Ìÿ áéöoãü2h!þEÑ4´? Zh!3ËpXÿ4ÍÏÿAû»žVÛ_ðS†õñ© šŸÿÏÿÐ@Ø nïA‘x8ƒGS¸/>DgJHhx;‚MÓ$˜ãÙAÜ•a8I*T2J)§•xŸ¹—pþ²1јbÔÄÃÕÂ;\øÔîÒí=ÏÀ¡jªžTSjwîön«q¤ ÑX »s·wu׳UÃîÜí}ÈàP5ÈÆjØ»½sÏV »swI£PÕpTÿOÙ×tÜþ“ Î@ذÿ`ÿÁþ7„üfõ¡¼à cõùMYÆçÄöý„øß|³ã©Æ”a¸™eãY=|Û¼þÿ7sï¢2l韒YèŸ úGÁï‰,NH^HáN„hüt8±stgit-0.17.1/t/t4000-upgrade/make-repo.sh0000644002002200200220000000304410732722750017751 0ustar cmarinascmarinas# This script makes several versions of a small test repository that # can be used for testing the format version upgrade code. LANG=C LC_ALL=C PAGER=cat TZ=UTC export LANG LC_ALL PAGER TZ unset AUTHOR_DATE unset AUTHOR_EMAIL unset AUTHOR_NAME unset COMMIT_AUTHOR_EMAIL unset COMMIT_AUTHOR_NAME unset GIT_ALTERNATE_OBJECT_DIRECTORIES unset GIT_AUTHOR_DATE GIT_AUTHOR_EMAIL=author@example.com GIT_AUTHOR_NAME='A U Thor' unset GIT_COMMITTER_DATE GIT_COMMITTER_EMAIL=committer@example.com GIT_COMMITTER_NAME='C O Mitter' unset GIT_DIFF_OPTS unset GIT_DIR unset GIT_EXTERNAL_DIFF unset GIT_INDEX_FILE unset GIT_OBJECT_DIRECTORY unset SHA1_FILE_DIRECTORIES unset SHA1_FILE_DIRECTORY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME for ver in 0.12 0.8; do if [ -e $ver.tar.gz ]; then continue; fi # Get the required stgit version. ( cd ../.. git archive --format=tar --prefix=stgit-$ver/ v$ver ) | tar xf - # Set up a repository. mkdir $ver cd $ver git init touch foo git add foo git commit -m 'Initial commit' # Use the old stgit. ( pwd PATH=../stgit-$ver:$PATH stg --version stg init echo 'cool branch' > .git/patches/master/description for i in 0 1 2 3 4; do stg new p$i -m "Patch $i" echo "Line $i" >> foo stg refresh done stg pop -n 2 ) # Reduce the number of small files. git gc # Make a tarball. cd .. tar zcf $ver.tar.gz $ver done stgit-0.17.1/t/t1206-push-hidden.sh0000755002002200200220000000126611743516173016671 0ustar cmarinascmarinas#!/bin/sh test_description='Test "stg push" with hidden patches' . ./test-lib.sh test_expect_success 'Initialize StGit stack' ' stg init && echo foo > foo.txt && stg add foo.txt && stg new -m hidden-patch && stg refresh && stg pop && stg hide hidden-patch && test "$(echo $(stg series --all))" = "! hidden-patch" ' test_expect_success 'Push an implicitly named hidden patch (should fail)' ' command_error stg push && test "$(echo $(stg series --all))" = "! hidden-patch" ' test_expect_failure 'Push an explicitly named hidden patch (should work)' ' stg push hidden-patch && test "$(echo $(stg series --all))" = "> hidden-patch" ' test_done stgit-0.17.1/t/t1100-clone-under.sh0000755002002200200220000000131610426061622016650 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Yann Dirson # test_description='Check cloning in a repo subdir Check that "stg clone" works in a subdir of a git tree. This ensures (to some point) that a clone within a tree does not corrupt the enclosing repo. This test must be run before any tests making use of clone. ' . ./test-lib.sh # Here we are in a repo, we have a ./.git # Do not get rid of it, or a bug may bite out stgit repo hard # Need a repo to clone test_create_repo foo test_expect_success \ 'stg clone right inside a git tree' \ "stg clone foo bar" # now work in a subdir mkdir sub mv foo sub cd sub test_expect_success \ 'stg clone deeper under a git tree' \ "stg clone foo bar" test_done stgit-0.17.1/t/t1500-float.sh0000755002002200200220000000311711743516173015560 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Robin Rosenberg # test_description='Test floating a number of patches to the top of the stack ' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ 'stg init && stg new A -m "a" && echo A >a.txt && stg add a.txt && stg refresh && stg new B -m "b" && echo B >b.txt && stg add b.txt && stg refresh && stg new C -m "c" && echo C >c.txt && stg add c.txt && stg refresh && stg new D -m "d" && echo D >d.txt && stg add d.txt && stg refresh && stg new E -m "e" && echo E >e.txt && stg add e.txt && stg refresh && stg new F -m "f" && echo F >f.txt && stg add f.txt && stg refresh && stg new G -m "g" && echo G >g.txt && stg add g.txt && stg refresh && stg pop && test "$(echo $(stg series --applied --noprefix))" = "A B C D E F" ' test_expect_success \ 'Float A to top' \ 'stg float A && test "$(echo $(stg series --applied --noprefix))" = "B C D E F A" ' test_expect_success \ 'Float A to top (noop)' \ 'stg float A && test "$(echo $(stg series --applied --noprefix))" = "B C D E F A" ' test_expect_success \ 'Float B C to top' \ 'stg float B C && test "$(echo $(stg series --applied --noprefix))" = "D E F A B C" ' test_expect_success \ 'Float E A to top' \ 'stg float E A && test "$(echo $(stg series --applied --noprefix))" = "D F B C E A" ' test_expect_success \ 'Float E to top' \ 'stg float E && test "$(echo $(stg series --applied --noprefix))" = "D F B C A E" ' test_expect_success \ 'Float G F to top' \ 'stg float G F && test "$(echo $(stg series --applied --noprefix))" = "D B C A E G F" ' test_done stgit-0.17.1/t/t3000-dirty-merge.sh0000755002002200200220000000154711743516173016705 0ustar cmarinascmarinas#!/bin/sh test_description='Try a push that requires merging a file that is dirty' . ./test-lib.sh test_expect_success 'Initialize StGit stack with two patches' ' stg init && touch a && stg add a && git commit -m a && echo 1 > a && git commit -a -m p1 && echo 2 > a && git commit -a -m p2 && stg uncommit -n 2 ' test_expect_success 'Pop one patch and update the other' ' stg goto p1 && echo 3 > a && stg refresh ' test_expect_success 'Push with dirty worktree' ' echo 4 > a && [ "$(echo $(stg series --applied --noprefix))" = "p1" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p2" ] && conflict stg goto --keep p2 && [ "$(echo $(stg series --applied --noprefix))" = "p1" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p2" ] && [ "$(echo $(cat a))" = "4" ] ' test_done stgit-0.17.1/t/t2800-goto-subdir.sh0000755002002200200220000000266511743516173016724 0ustar cmarinascmarinas#!/bin/sh test_description='Run "stg goto" in a subdirectory' . ./test-lib.sh test_expect_success 'Initialize StGit stack' ' stg init && echo expected1.txt >> .git/info/exclude && echo expected2.txt >> .git/info/exclude && echo actual.txt >> .git/info/exclude && mkdir foo && for i in 1 2 3; do echo foo$i >> foo/bar && stg new p$i -m p$i && stg add foo/bar && stg refresh done ' cat > expected1.txt < expected2.txt < actual.txt && test_cmp expected1.txt actual.txt && ls foo > actual.txt && test_cmp expected2.txt actual.txt ' test_expect_success 'Prepare conflicting goto' ' stg delete p2 ' # git gives this result before commit 606475f3 ... cat > expected1a.txt <>>>>>> patched:foo/bar EOF # ... and this result after commit 606475f3. cat > expected1b.txt <>>>>>> patched EOF cat > expected2.txt < actual.txt && ( test_cmp expected1a.txt actual.txt \ || test_cmp expected1b.txt actual.txt ) && ls foo > actual.txt && test_cmp expected2.txt actual.txt ' test_done stgit-0.17.1/t/t2201-rebase-file-dir.sh0000755002002200200220000000120612221306627017372 0ustar cmarinascmarinas#!/bin/sh test_description='Rebase between trees with a file and a dir with the same name' . ./test-lib.sh test_expect_success 'Set up a repo with two branches' ' mkdir x && echo "Jag älskar morötter" > x/y && git add x/y && git commit -m "Commit where x is a directory" && git tag x-dir && git reset --hard HEAD^ && echo "Jag älskar grisfötter" > x && git add x && git commit -m "Commit where x is a file" && git tag x-file && stg init ' test_expect_success 'Rebase from file to dir' ' stg rebase x-dir ' test_expect_success 'Rebase from dir to file' ' stg rebase x-file ' test_done stgit-0.17.1/t/t2200-rebase.sh0000755002002200200220000000143611743516173015714 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2007 Yann Dirson # test_description='Test the "rebase" command.' . ./test-lib.sh test_expect_success \ 'Setup a multi-commit branch and fork an stgit stack' \ ' echo foo > file1 && stg add file1 && git commit -m a && echo foo > file2 && stg add file2 && git commit -m b && stg branch --create stack && stg new p -m . && echo bar >> file1 && stg refresh ' test_expect_success \ 'Rebase to previous commit' \ ' stg rebase master~1 && test `stg id stack:{base}` = `git rev-parse master~1` && test `stg series --applied -c` = 1 ' test_expect_success \ 'Attempt rebase to non-existing commit' \ ' command_error stg rebase not-a-ref ' test_expect_success \ 'Check patches were re-applied' \ ' test $(stg series --applied -c) = 1 ' test_done stgit-0.17.1/t/t1502-float-conflict-1.sh0000755002002200200220000000160411743516173017516 0ustar cmarinascmarinas#!/bin/sh test_description='Test that "stg float" can handle conflicts' . ./test-lib.sh test_expect_success 'Test setup' ' stg init && echo expected.txt >> .git/info/exclude && echo first line > foo.txt && git add foo.txt && git commit -m p0 && echo foo >> foo.txt && git add foo.txt && git commit -m p1 && echo foo2 >> foo.txt && git add foo.txt && git commit -m p2 && stg uncommit -n 3 ' cat > expected.txt <>>>>>> patched EOF test_expect_success 'Float a patch, causing a conflict with the next patch' ' conflict stg float p1 && test "$(echo $(stg series))" = "+ p0 > p2 - p1" && test "$(stg id p2)" = "$(git rev-list HEAD~0 -n 1)" && test "$(stg id p0)" = "$(git rev-list HEAD~1 -n 1)" && test "$(stg status)" = "UU foo.txt" && test_cmp foo.txt expected.txt ' test_done stgit-0.17.1/t/t2900-rename.sh0000755002002200200220000000177111271344641015726 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2008 Onno Kortmann # Parts taken from the other test scripts # in this directory. # test_description='stg rename test Tests some parts of the stg rename command.' . ./test-lib.sh stg init test_expect_success 'Rename in empty' ' command_error stg rename foo ' test_expect_success 'Rename single top-most' ' stg new -m foo && stg rename bar ' # bar test_expect_success 'Rename non-existing' ' command_error stg rename neithersuchpatch norsuchpatch ' test_expect_success 'Rename with two arguments' ' stg new -m baz && stg rename bar foo ' # foo,baz test_expect_success 'Rename to existing name' ' command_error stg rename foo baz ' test_expect_success 'Rename to same name' ' command_error stg rename foo foo ' test_expect_success 'Rename top-most when others exist' ' stg rename bar ' test_expect_failure 'Rename hidden' ' stg pop && stg hide bar && stg rename bar pub && test "$(echo $(stg series --all))" = "> foo ! pub" ' test_done stgit-0.17.1/t/t1202-push-undo.sh0000755002002200200220000000206711743516173016377 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Catalin Marinas # test_description='Exercise stg undo with push of missing files. Test the case where a patch fails to be pushed because it modifies a missing file. The "stg undo" command has to be able to revert it. ' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ 'stg init ' test_expect_success \ 'Create the first patch' \ ' stg new foo -m foo && echo foo > test && stg add test && stg refresh ' test_expect_success \ 'Create the second patch' \ ' stg new bar -m bar && echo bar > test && stg add test && stg refresh ' test_expect_success \ 'Pop all patches' \ ' stg pop --all ' test_expect_success \ 'Push the second patch with conflict' \ ' conflict stg push bar ' test_expect_success \ 'Undo the previous push' \ ' stg undo --hard ' test_expect_success \ 'Check the push after undo fails as well' \ ' conflict stg push bar ' test_expect_success \ 'Undo with disappeared newborn' \ ' touch newfile && stg add newfile && rm newfile && stg undo --hard ' test_done stgit-0.17.1/t/t3400-pick.sh0000755002002200200220000000274111743516173015404 0ustar cmarinascmarinas#!/bin/sh test_description='Test the pick command' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ ' stg init && stg new A -m "a" && echo A > a && stg add a && stg refresh && stg new B -m "b" && echo B > b && stg add b && stg refresh && stg branch --clone foo && stg new C -m "c" && echo C > c && stg add c && stg refresh && stg new D-foo -m "d" && echo D > d && stg add d && stg refresh && stg branch master ' test_expect_success \ 'Pick remote patch' \ ' stg pick foo:C && test "$(echo $(stg series --applied --noprefix))" = "A B C" ' test_expect_success \ 'Pick --unapplied remote patch' \ ' stg pick --unapplied --ref-branch foo --name D D-foo && test "$(echo $(stg series --applied --noprefix))" = "A B C" && test "$(echo $(stg series --unapplied --noprefix))" = "D" ' test_expect_success \ 'Pick local unapplied patch' \ ' stg pick D && test "$(echo $(stg series --applied --noprefix))" = "A B C D-0" && test "$(echo $(stg series --unapplied --noprefix))" = "D" ' test_expect_success \ 'Pick --fold --revert local patch' \ ' stg pick --fold --revert D && stg refresh && stg clean && test "$(echo $(stg series --applied --noprefix))" = "A B C" && test "$(echo $(stg series --unapplied --noprefix))" = "D" ' test_expect_success \ 'Pick --fold without applied patches' \ ' stg pop --all && stg pick --fold D && test "$(echo $(stg series --unapplied --noprefix))" = "A B C D" && test "$(echo $(stg status))" = "A d" ' test_done stgit-0.17.1/t/t3101-reset-hard.sh0000755002002200200220000000230411743516173016505 0ustar cmarinascmarinas#!/bin/sh test_description='Simple test cases for "stg reset"' . ./test-lib.sh # Ignore our own output files. cat > .git/info/exclude <> a && stg add a && git commit -m a && echo 111 >> a && git commit -a -m p1 && echo 222 >> a && git commit -a -m p2 && echo 333 >> a && git commit -a -m p3 && stg uncommit -n 3 ' cat > expected.txt < actual.txt && test_cmp expected.txt actual.txt && test "$(echo $(stg series))" = "+ p1 > p3 - p2" ' test_expect_success 'Try to reset without --hard' ' command_error stg reset master.stgit^~1 && stg status a > actual.txt && test_cmp expected.txt actual.txt && test "$(echo $(stg series))" = "+ p1 > p3 - p2" ' cat > expected.txt < actual.txt && test_cmp expected.txt actual.txt && test "$(echo $(stg series))" = "+ p1 + p2 > p3" ' test_done stgit-0.17.1/t/Makefile0000644002002200200220000000023212221306627014771 0ustar cmarinascmarinas# Run tests # # Copyright (c) 2005 Junio C Hamano # SHELL_PATH ?= $(SHELL) TAR ?= $(TAR) all: python test.py clean: rm -rf trash* .PHONY: all clean stgit-0.17.1/t/t1001-branch-rename.sh0000755002002200200220000000115211271344641017141 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Yann Dirson # test_description='Branch renames. Exercises branch renaming commands. ' . ./test-lib.sh test_expect_success \ 'Create an stgit branch from scratch' \ 'stg init && stg branch -c foo && stg new p1 -m "p1" ' test_expect_success \ 'Rename the current stgit branch' \ 'command_error stg branch -r foo bar ' test_expect_success \ 'Rename an stgit branch' \ 'stg branch -c buz && stg branch -r foo bar && [ -z $(find .git -type f | grep foo | tee /dev/stderr) ] && test -z $(git config -l | grep branch\\.foo) ' test_done stgit-0.17.1/t/t2600-squash.sh0000755002002200200220000000234411743516173015762 0ustar cmarinascmarinas#!/bin/sh test_description='Run "stg squash"' . ./test-lib.sh test_expect_success 'Initialize StGit stack' ' stg init && for i in 0 1 2 3; do stg new p$i -m "foo $i" && echo "foo $i" >> foo.txt && stg add foo.txt && stg refresh done ' test_expect_success 'Squash some patches' ' [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] && stg squash --name=q0 --message="wee woo" p1 p2 && [ "$(echo $(stg series --applied --noprefix))" = "p0 q0 p3" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_expect_success 'Squash at stack top' ' stg squash --name=q1 --message="wee woo wham" q0 p3 && [ "$(echo $(stg series --applied --noprefix))" = "p0 q1" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' cat > editor <> foo.txt && git commit -a -m "a new commit" && EDITOR=./editor command_error stg squash --name=r0 p0 q1 && test "$(echo $(stg series))" = "+ p0 > q1" && test ! -e editor-invoked ' test_done stgit-0.17.1/t/t1304-repair-hidden.sh0000755002002200200220000000243611743516173017173 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2010 Juergen Wieferink # test_description='Test repair with hidden patches ' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ 'stg init && stg new A -m "a" && echo A >a.txt && stg add a.txt && stg refresh && stg new B -m "b" && echo B >b.txt && stg add b.txt && stg refresh && stg new C -m "c" && echo C >c.txt && stg add c.txt && stg refresh && stg new D -m "d" && echo D >d.txt && stg add d.txt && stg refresh && stg pop && stg hide D && stg pop && test "$(echo $(stg series --applied --noprefix))" = "A B" && test "$(echo $(stg series --unapplied --noprefix))" = "C" && test "$(echo $(stg series --hidden --noprefix))" = "D" ' test_expect_success \ 'Repair and check that nothing has changed' \ 'stg repair && test "$(echo $(stg series --applied --noprefix))" = "A B" && test "$(echo $(stg series --unapplied --noprefix))" = "C" && test "$(echo $(stg series --hidden --noprefix))" = "D" ' test_expect_success \ 'Nontrivial repair' \ 'echo Z >z.txt && git add z.txt && git commit -m z && stg repair && test "$(echo $(stg series --applied --noprefix))" = "A B z" && test "$(echo $(stg series --unapplied --noprefix))" = "C" && test "$(echo $(stg series --hidden --noprefix))" = "D" ' test_done stgit-0.17.1/t/t0100-help.sh0000755002002200200220000000030310732722750015365 0ustar cmarinascmarinas#!/bin/sh test_description='Run "stg help"' . ./test-lib.sh test_expect_success 'Run "stg help"' ' stg help ' test_expect_success 'Run "stg --help"' ' stg --help ' test_done stgit-0.17.1/t/t1600-delete-one.sh0000755002002200200220000000525311743516173016500 0ustar cmarinascmarinas#!/bin/sh # Copyright (c) 2006 Karl Hasselström test_description='Test the delete command (deleting one patch at a time).' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ 'stg init' test_expect_success \ 'Create a patch' \ ' stg new foo -m foo && echo foo > foo.txt && stg add foo.txt && stg refresh ' test_expect_success \ 'Try to delete a non-existing patch' \ ' [ $(stg series --applied -c) -eq 1 ] && command_error stg delete bar && [ $(stg series --applied -c) -eq 1 ] ' test_expect_success \ 'Try to delete the topmost patch while dirty' \ ' echo dirty >> foo.txt && [ $(stg series --applied -c) -eq 1 ] && command_error stg delete foo && [ $(stg series --applied -c) -eq 1 ] && git reset --hard ' test_expect_success \ 'Delete the topmost patch' \ ' [ $(stg series --applied -c) -eq 1 ] && stg delete foo && [ $(stg series --applied -c) -eq 0 ] ' test_expect_success \ 'Create an unapplied patch' \ ' stg new foo -m foo && echo foo > foo.txt && stg add foo.txt && stg refresh && stg pop ' test_expect_success \ 'Delete an unapplied patch' \ ' [ $(stg series --unapplied -c) -eq 1 ] && stg delete foo && [ $(stg series --unapplied -c) -eq 0 ] ' test_expect_success \ 'Create three patches' \ ' stg new foo -m foo && echo foo > foo.txt && stg add foo.txt && stg refresh && stg new bar -m bar && echo bar > bar.txt && stg add bar.txt && stg refresh && stg new baz -m baz && echo baz > baz.txt && stg add baz.txt && stg refresh ' test_expect_success \ 'Try to delete a topmost patch with --top option' \ ' [ $(stg series --applied -c) -eq 3 ] && stg delete --top && [ $(stg series --applied -c) -eq 2 ] ' test_expect_success \ 'Try to delete a non-topmost applied patch' \ ' [ $(stg series --applied -c) -eq 2 ] && stg delete foo && [ $(stg series --applied -c) -eq 1 ] ' test_expect_success \ 'Create another branch, and put one patch in each branch' \ ' stg branch --create br && stg new baz -m baz && echo baz > baz.txt && stg add baz.txt && stg refresh && stg branch master && stg new baz -m baz && echo baz > baz.txt && stg add baz.txt && stg refresh ' test_expect_success \ 'Delete a patch in another branch' \ ' [ $(stg series --applied -c) -eq 2 ] && [ $(stg series --applied -b br -c) -eq 1 ] && stg delete -b br baz && [ $(stg series --applied -c) -eq 2 ] && [ $(stg series --applied -b br -c) -eq 0 ] ' test_done stgit-0.17.1/t/t1201-pull-trailing.sh0000755002002200200220000000223011743516173017227 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Yann Dirson # test_description='test ' . ./test-lib.sh # don't need this repo, but better not drop it, see t1100 #rm -rf .git # Need a repo to clone test_create_repo foo test_expect_success \ 'Setup and clone tree, and setup changes' \ "(cd foo && printf 'a\nb\n' > file && stg add file && git commit -m . ) && stg clone foo bar && (cd bar && stg new p1 -m p1 printf 'c\n' >> file && stg refresh ) " test_expect_success \ 'Port those patches to orig tree' \ '(cd foo && GIT_DIR=../bar/.git git format-patch --stdout \ $(cd ../bar && stg id master:{base})..HEAD | git am -3 -k ) ' test_expect_success \ 'Pull those patches applied upstream, without pushing' \ "(cd bar && stg pull --nopush ) " test_expect_success \ 'Try to push those patches without merge detection' \ "(cd bar && stg push --all ) " test_expect_success \ 'Pull those patches applied upstream' \ "(cd bar && stg undo && stg push --all --merged ) " test_expect_success \ 'Check that all went well' \ "test_cmp foo/file bar/file " test_done stgit-0.17.1/t/t2102-pull-policy-rebase.sh0000755002002200200220000000164611743516173020167 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2007 Yann Dirson # test_description='Excercise pull-policy "rebase".' . ./test-lib.sh test_expect_success \ 'Fork stack off parent branch, and add patches to the stack' \ ' git branch -m master parent && stg init && stg branch --create stack && git config branch.stack.stgit.pull-policy rebase && git config --list && stg new c1 -m c1 && echo a > file && stg add file && stg refresh ' test_expect_success \ 'Add non-rewinding commit in parent and pull the stack' \ ' stg branch parent && stg new u1 -m u1 && echo b > file2 && stg add file2 && stg refresh && stg branch stack && stg pull && test -e file2 ' test_expect_success \ 'Rewind/rewrite commit in parent and pull the stack' \ ' stg branch parent && echo b >> file2 && stg refresh && stg branch stack && stg pull && test `wc -l test && stg add test && stg refresh ' test_expect_success \ 'Create the second patch' \ ' stg new bar -m "Bar Patch" && echo bar > test && stg add test && stg refresh ' test_expect_success \ 'Commit the patches' \ ' stg commit --all ' test_expect_success \ 'Uncommit the patches using names' \ ' stg uncommit bar foo && [ "$(stg id foo)" = "$(stg id bar^)" ] && stg commit --all ' test_expect_success \ 'Uncommit the patches using prefix' \ ' stg uncommit --number=2 foobar && [ "$(stg id foobar1)" = "$(stg id foobar2^)" ] && stg commit --all ' test_expect_success \ 'Uncommit the patches using auto names' \ ' stg uncommit --number=2 && [ "$(stg id foo-patch)" = "$(stg id bar-patch^)" ] && stg commit --all ' test_expect_success \ 'Uncommit the patches one by one' \ ' stg uncommit && stg uncommit && [ "$(stg id foo-patch)" = "$(stg id bar-patch^)" ] && stg commit --all ' test_expect_success \ 'Uncommit the patches with --to' ' stg uncommit --to HEAD^ && [ "$(stg id foo-patch)" = "$(stg id bar-patch^)" ] && stg commit --all ' test_expect_success 'Uncommit a commit with not precisely one parent' ' command_error stg uncommit -n 5 && [ "$(echo $(stg series))" = "" ] ' # stg uncommit should work even when top != head, and should not touch # the head. test_expect_success 'Uncommit when top != head' ' stg new -m foo && git reset --hard HEAD^ && h=$(git rev-parse HEAD) stg uncommit bar && test $(git rev-parse HEAD) = $h && test "$(echo $(stg series))" = "+ bar > foo" ' test_done stgit-0.17.1/t/t0001-subdir-branches.sh0000755002002200200220000000271211743516173017521 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Karl Hasselström # test_description='Branch names containing slashes Test a number of operations on a repository that has branch names containing slashes (that is, branches living in a subdirectory of .git/refs/heads).' . ./test-lib.sh test_expect_success 'Create a patch' \ 'stg init && echo "foo" > foo.txt && stg add foo.txt && stg new foo -m "Add foo.txt" && stg refresh' test_expect_success 'Try id with non-slashy branch' \ 'stg id && stg id foo && stg id foo^ && stg id master:foo && stg id master:foo^' test_expect_success 'Clone branch to slashier name' \ 'stg branch --clone x/y/z' test_expect_success 'Try new id with slashy branch' \ 'stg id foo && stg id foo^ && stg id x/y/z:foo && stg id x/y/z:foo^' test_expect_success 'Try old id with slashy branch' ' command_error stg id foo/ && command_error stg id foo/top && command_error stg id foo@x/y/z/top ' test_expect_success 'Create patch in slashy branch' \ 'echo "bar" >> foo.txt && stg new bar -m "Add another line" && stg refresh' test_expect_success 'Rename branches' \ 'stg branch --rename master goo/gaa && must_fail git show-ref --verify --quiet refs/heads/master && stg branch --rename goo/gaa x1/x2/x3/x4 && must_fail git show-ref --verify --quiet refs/heads/goo/gaa && stg branch --rename x1/x2/x3/x4 servant && must_fail git show-ref --verify --quiet refs/heads/x1/x2/x3/x4 ' test_done stgit-0.17.1/t/t1800-import.sh0000755002002200200220000001364712221306627015772 0ustar cmarinascmarinas#!/bin/sh # Copyright (c) 2006 Karl Hasselström test_description='Test the import command' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ ' cp $STG_ROOT/t/t1800-import/foo.txt . && stg add foo.txt && git commit -a -m "initial version" && stg init ' test_expect_success \ 'Apply a patch created with "git diff"' \ ' stg import $STG_ROOT/t/t1800-import/git-diff && [ $(git cat-file -p $(stg id) \ | grep -c "tree e96b1fba2160890ff600b675d7140d46b022b155") = 1 ] && stg delete .. ' test_expect_success \ 'Apply a patch created with "git diff" using -p1' \ ' stg import -p1 $STG_ROOT/t/t1800-import/git-diff && [ $(git cat-file -p $(stg id) \ | grep -c "tree e96b1fba2160890ff600b675d7140d46b022b155") = 1 ] && stg delete .. ' test_expect_success \ 'Apply a patch created with "git diff" using -p0' \ ' stg import -p0 $STG_ROOT/t/t1800-import/git-diff-p0 && [ $(git cat-file -p $(stg id) \ | grep -c "tree e96b1fba2160890ff600b675d7140d46b022b155") = 1 ] && stg delete .. ' test_expect_success \ 'Apply a patch created with "git diff" using -p2' \ ' ! stg import -p2 $STG_ROOT/t/t1800-import/git-diff && [ $(git cat-file -p $(stg id) \ | grep -c "tree a5850c97490398571d41d6304dd940800550f507") = 1 ] && stg delete .. ' test_expect_success \ 'Apply a patch created with "git diff" from a subdirectory' \ ' mkdir subdir && cd subdir && stg import $STG_ROOT/t/t1800-import/git-diff && [ $(git cat-file -p $(stg id) \ | grep -c "tree e96b1fba2160890ff600b675d7140d46b022b155") = 1 ] && stg delete .. && cd .. ' test_expect_success \ 'Apply a patch created with GNU diff' \ ' stg import $STG_ROOT/t/t1800-import/gnu-diff && [ $(git cat-file -p $(stg id) \ | grep -c "tree e96b1fba2160890ff600b675d7140d46b022b155") = 1 ] && stg delete .. ' test_expect_success \ 'Apply a patch created with "stg export"' \ ' stg import $STG_ROOT/t/t1800-import/stg-export && [ $(git cat-file -p $(stg id) \ | grep -c "tree e96b1fba2160890ff600b675d7140d46b022b155") = 1 ] && stg delete .. ' test_expect_success \ 'Apply a patch from an 8bit-encoded e-mail' \ ' stg import -m $STG_ROOT/t/t1800-import/email-8bit && [ $(git cat-file -p $(stg id) \ | grep -c "tree 030be42660323ff2a1958f9ee79589a4f3fbee2f") = 1 ] && [ $(git cat-file -p $(stg id) \ | grep -c "author Inge Ström ") = 1 ] && stg delete .. ' test_expect_success \ 'Apply a patch from a QP-encoded e-mail' \ ' stg import -m $STG_ROOT/t/t1800-import/email-qp && [ $(git cat-file -p $(stg id) \ | grep -c "tree 030be42660323ff2a1958f9ee79589a4f3fbee2f") = 1 ] && [ $(git cat-file -p $(stg id) \ | grep -c "author Inge Ström ") = 1 ] && stg delete .. ' test_expect_success \ 'Apply several patches from an mbox file' \ ' stg import -M $STG_ROOT/t/t1800-import/email-mbox && [ $(git cat-file -p $(stg id change-1) \ | grep -c "tree 401bef82cd9fb403aba18f480a63844416a2e023") = 1 ] && [ $(git cat-file -p $(stg id change-1) \ | grep -c "author Inge Ström ") = 1 ] && [ $(git cat-file -p $(stg id change-2) \ | grep -c "tree e49dbce010ec7f441015a8c64bce0b99108af4cc") = 1 ] && [ $(git cat-file -p $(stg id change-2) \ | grep -c "author Inge Ström ") = 1 ] && [ $(git cat-file -p $(stg id change-3) \ | grep -c "tree 166bbaf27a44aee21ba78c98822a741e6f7d78f5") = 1 ] && [ $(git cat-file -p $(stg id change-3) \ | grep -c "author Inge Ström ") = 1 ] && stg delete .. ' test_expect_success \ 'Apply a bzip2 patch created with "git diff"' \ ' bzip2 -c $STG_ROOT/t/t1800-import/git-diff > \ $STG_ROOT/t/t1800-import/bzip2-git-diff && stg import $STG_ROOT/t/t1800-import/bzip2-git-diff && [ $(git cat-file -p $(stg id) \ | grep -c "tree e96b1fba2160890ff600b675d7140d46b022b155") = 1 ] && rm $STG_ROOT/t/t1800-import/bzip2-git-diff && stg delete .. ' test_expect_success \ 'Apply a bzip2 patch with a .bz2 suffix' \ ' bzip2 -c $STG_ROOT/t/t1800-import/git-diff > \ $STG_ROOT/t/t1800-import/git-diff.bz2 && stg import $STG_ROOT/t/t1800-import/git-diff.bz2 && [ $(git cat-file -p $(stg id) \ | grep -c "tree e96b1fba2160890ff600b675d7140d46b022b155") = 1 ] && rm $STG_ROOT/t/t1800-import/git-diff.bz2 && stg delete .. ' test_expect_success \ 'Apply a gzip patch created with GNU diff' \ ' gzip -c $STG_ROOT/t/t1800-import/gnu-diff > \ $STG_ROOT/t/t1800-import/gzip-gnu-diff && stg import $STG_ROOT/t/t1800-import/gzip-gnu-diff && [ $(git cat-file -p $(stg id) \ | grep -c "tree e96b1fba2160890ff600b675d7140d46b022b155") = 1 ] && rm $STG_ROOT/t/t1800-import/gzip-gnu-diff && stg delete .. ' test_expect_success \ 'Apply a gzip patch with a .gz suffix' \ ' gzip -c $STG_ROOT/t/t1800-import/gnu-diff > \ $STG_ROOT/t/t1800-import/gnu-diff.gz && stg import $STG_ROOT/t/t1800-import/gnu-diff.gz && [ $(git cat-file -p $(stg id) \ | grep -c "tree e96b1fba2160890ff600b675d7140d46b022b155") = 1 ] && rm $STG_ROOT/t/t1800-import/gnu-diff.gz && stg delete .. ' test_expect_success \ 'apply a series from a tarball' \ ' rm -f jabberwocky.txt && touch jabberwocky.txt && stg add jabberwocky.txt && git commit -m "empty file" jabberwocky.txt && (cd $STG_ROOT/t/t1800-import; tar -cjf jabberwocky.tar.bz2 patches) && stg import --series $STG_ROOT/t/t1800-import/jabberwocky.tar.bz2 [ $(git cat-file -p $(stg id) \ | grep -c "tree 2c33937252a21f1550c0bf21f1de534b68f69635") = 1 ] && rm $STG_ROOT/t/t1800-import/jabberwocky.tar.bz2 ' test_done stgit-0.17.1/t/t2702-refresh-rm.sh0000755002002200200220000000407212222316716016525 0ustar cmarinascmarinas#!/bin/sh test_description='"stg refresh" with removed files' . ./test-lib.sh # Ignore our own temp files. cat >> .git/info/exclude < /dev/null git reset --hard > /dev/null } test_expect_success 'Initialize StGit stack' ' stg init && echo x > x.txt && echo y > y.txt && stg add x.txt y.txt && git commit -m "Add some files" ' cat > expected0.txt < expected1.txt test_expect_success 'stg rm a file' ' stg new -m p0 && stg rm y.txt && stg status > status0.txt && test_cmp expected0.txt status0.txt && stg refresh && stg status > status1.txt && test_cmp expected1.txt status1.txt && stg files > files.txt && test_cmp -w expected0.txt files.txt ' reset cat > expected0.txt < expected1.txt test_expect_success 'stg rm a file together with other changes' ' stg new -m p1 && echo x2 >> x.txt && stg rm y.txt && stg status > status0.txt && test_cmp expected0.txt status0.txt && stg refresh --force && stg status > status1.txt && test_cmp expected1.txt status1.txt && stg files > files.txt && test_cmp -w expected0.txt files.txt ' reset cat > expected0.txt < expected1.txt test_expect_success 'rm a file' ' stg new -m p2 && rm y.txt && stg status > status0.txt && test_cmp expected0.txt status0.txt && stg refresh && stg status > status1.txt && test_cmp expected1.txt status1.txt && stg files > files.txt && test_cmp -w expected0.txt files.txt ' reset cat > expected0.txt < expected1.txt test_expect_success 'rm a file together with other changes' ' stg new -m p3 && echo x2 >> x.txt && rm y.txt && stg status > status0.txt && test_cmp expected0.txt status0.txt && stg refresh && stg status > status1.txt && test_cmp expected1.txt status1.txt && stg files > files.txt && test_cmp -w expected0.txt files.txt ' test_done stgit-0.17.1/t/t1002-branch-clone.sh0000755002002200200220000000143111743516173017000 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Catalin Marinas # test_description='Branch cloning. Exercises branch cloning options. ' . ./test-lib.sh test_expect_success \ 'Create a GIT commit' \ ' echo bar > bar.txt && stg add bar.txt && git commit -a -m bar ' test_expect_success \ 'Try to create a patch in a GIT branch' \ ' command_error stg new p0 -m "p0" ' test_expect_success \ 'Clone the current GIT branch' \ ' stg branch --clone foo && stg new p1 -m "p1" && test $(stg series --applied -c) -eq 1 ' test_expect_success \ 'Clone the current StGIT branch' \ ' stg branch --clone bar && test $(stg series --applied -c) -eq 1 && stg new p2 -m "p2" && test $(stg series --applied -c) -eq 2 ' test_done stgit-0.17.1/t/t1003-new.sh0000755002002200200220000000071311271344641015234 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2007 Karl Hasselström # test_description='Test the new command. ' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' ' stg init ' test_expect_success \ 'Create a named patch' ' stg new foo -m foobar && [ $(stg series --applied -c) -eq 1 ] ' test_expect_success \ 'Create a patch without giving a name' ' stg new -m yo && [ $(stg series --applied -c) -eq 2 ] ' test_done stgit-0.17.1/t/t3102-undo.sh0000755002002200200220000000267211743516173015425 0ustar cmarinascmarinas#!/bin/sh test_description='Simple test cases for "stg undo"' . ./test-lib.sh # Ignore our own output files. cat > .git/info/exclude <> a && stg add a && git commit -m a && echo 111 >> a && git commit -a -m p1 && echo 222 >> a && git commit -a -m p2 && echo 333 >> a && git commit -a -m p3 && stg uncommit -n 3 && stg pop ' cat > expected.txt < expected.txt < expected.txt < expected.txt < expected.txt < file && stg add file && stg refresh ) ' test_expect_success \ 'Add non-rewinding commit upstream and pull it from clone' \ ' (cd upstream && stg new u1 -m u1 && echo a > file2 && stg add file2 && stg refresh) && (cd clone && stg pull) && test -e clone/file2 ' # note: with pre-1.5 Git the clone is not automatically recorded # as rewinding, and thus heads/origin is not moved, but the stack # is still correctly rebased test_expect_success \ 'Rewind/rewrite upstream commit and pull it from clone, without --merged' \ ' (cd upstream && echo b >> file2 && stg refresh) && (cd clone && conflict_old stg pull) ' test_expect_success \ '"Solve" the conflict (pretend there is none)' \ '(cd clone && stg add file2 && EDITOR=cat git commit)' test_expect_success \ 'Push the stack back' \ '(cd clone && stg push -a)' test_done stgit-0.17.1/t/t1301-repair.sh0000755002002200200220000000334611743516173015740 0ustar cmarinascmarinas#!/bin/sh # Copyright (c) 2006 Karl Hasselström test_description='Test the repair command.' . ./test-lib.sh test_expect_success \ 'Repair in a non-initialized repository' \ 'command_error stg repair' test_expect_success \ 'Initialize the StGIT repository' \ 'stg init' test_expect_success \ 'Repair in a repository without patches' \ 'stg repair' test_expect_success \ 'Create a patch' \ ' stg new foo -m foo && echo foo > foo.txt && stg add foo.txt && stg refresh ' test_expect_success \ 'Repair when there is nothing to do' \ 'stg repair' test_expect_success \ 'Create a GIT commit' \ ' echo bar > bar.txt && stg add bar.txt && git commit -a -m bar ' test_expect_success 'Turn one GIT commit into a patch' ' [ $(stg series --applied -c) -eq 1 ] && stg repair && [ $(stg series --applied -c) -eq 2 ] ' test_expect_success \ 'Create three more GIT commits' \ ' echo one > numbers.txt && stg add numbers.txt && git commit -a -m one && echo two >> numbers.txt && git commit -a -m two && echo three >> numbers.txt && git commit -a -m three ' test_expect_success 'Turn three GIT commits into patches' ' [ $(stg series --applied -c) -eq 2 ] && stg repair && [ $(stg series --applied -c) -eq 5 ] ' test_expect_success \ 'Create a merge commit' \ ' git checkout -b br master^^ && echo woof > woof.txt && stg add woof.txt && git commit -a -m woof && git checkout master && git pull . br ' test_expect_success 'Repair in the presence of a merge commit' ' [ $(stg series --applied -c) -eq 5 ] && stg repair && [ $(stg series --applied -c) -eq 0 ] ' test_done stgit-0.17.1/t/t3105-undo-external-mod.sh0000755002002200200220000000233511743516173020021 0ustar cmarinascmarinas#!/bin/sh test_description='Undo external modifications of the stack' . ./test-lib.sh # Ignore our own output files. cat > .git/info/exclude <> a && stg add a && git commit -m p0 && echo 111 >> a && stg add a && git commit -m p1 && stg uncommit -n 1 ' cat > expected.txt < head0.txt && echo 222 >> a && stg add a && git commit -m p2 && git rev-parse HEAD > head1.txt && stg repair && test "$(echo $(stg series))" = "+ p1 > p2" && test_cmp expected.txt a ' cat > expected.txt < head2.txt && test_cmp head1.txt head2.txt && test "$(echo $(stg series))" = "> p1" && test_cmp expected.txt a ' cat > expected.txt < head3.txt && test_cmp head0.txt head3.txt && test "$(echo $(stg series))" = "> p1" && test_cmp expected.txt a ' test_done stgit-0.17.1/t/t1205-push-subdir.sh0000755002002200200220000000336611743516173016730 0ustar cmarinascmarinas#!/bin/sh test_description='Test the push command from a subdirectory' . ./test-lib.sh stg init test_expect_success 'Create some patches' ' mkdir foo for i in 0 1 2; do stg new p$i -m p$i && echo x$i >> x.txt && echo y$i >> foo/y.txt && stg add x.txt foo/y.txt && stg refresh done && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_expect_success 'Fast-forward push from a subdir' ' stg pop && [ "$(echo $(cat x.txt))" = "x0 x1" ] && [ "$(echo $(cat foo/y.txt))" = "y0 y1" ] && cd foo && stg push && cd .. && [ "$(echo $(cat x.txt))" = "x0 x1 x2" ] && [ "$(echo $(cat foo/y.txt))" = "y0 y1 y2" ] ' test_expect_success 'Modifying push from a subdir' ' stg pop && [ "$(echo $(cat x.txt))" = "x0 x1" ] && [ "$(echo $(cat foo/y.txt))" = "y0 y1" ] && stg new extra -m extra && echo extra >> extra.txt && stg add extra.txt && stg refresh && cd foo && stg push && cd .. && [ "$(echo $(cat x.txt))" = "x0 x1 x2" ] && [ "$(echo $(cat foo/y.txt))" = "y0 y1 y2" ] ' test_expect_success 'Conflicting push from subdir' ' stg pop p1 p2 && [ "$(echo $(cat x.txt))" = "x0" ] && [ "$(echo $(cat foo/y.txt))" = "y0" ] && cd foo && conflict stg push p2 && cd .. && [ "$(echo $(stg status))" = "UU foo/y.txt UU x.txt" ] ' test_expect_success 'Conflicting add/unknown file in subdir' ' stg reset --hard && stg new foo -m foo && mkdir d && echo foo > d/test && stg add d/test && stg refresh && stg pop && mkdir -p d && echo bar > d/test && command_error stg push foo && [ $(stg top) != "foo" ] ' test_done stgit-0.17.1/t/t1200-push-modified.sh0000755002002200200220000000363311743516173017210 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 Yann Dirson # test_description='Exercise pushing patches applied upstream. Especially, consider the case of a patch that adds a file, while a subsequent one modifies it, so we have to use --merged for push to detect the merge. Reproduce the common workflow where one does not specify --merged, then rollback and retry with the correct flag.' . ./test-lib.sh # don't need this repo, but better not drop it, see t1100 #rm -rf .git # Need a repo to clone test_create_repo foo test_expect_success \ 'Clone tree and setup changes' ' stg clone foo bar && ( cd bar && stg new p1 -m p1 && printf "a\nc\n" > file && stg add file && stg refresh && stg new p2 -m p2 && printf "a\nb\nc\n" > file && stg refresh && [ "$(echo $(stg series --applied --noprefix))" = "p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ) ' test_expect_success \ 'Port those patches to orig tree' ' ( cd foo && GIT_DIR=../bar/.git git format-patch --stdout \ $(cd ../bar && stg id master:{base})..HEAD | git am -3 -k ) ' test_expect_success \ 'Pull to sync with parent, preparing for the problem' \ "(cd bar && stg pop --all && stg pull ) " test_expect_success \ 'Attempt to push the first of those patches without --merged' \ "(cd bar && conflict stg push ) " test_expect_success \ 'Rollback the push' ' ( cd bar && stg undo --hard && [ "$(echo $(stg series --applied --noprefix))" = "" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p1 p2" ] ) ' test_expect_success \ 'Push those patches while checking they were merged upstream' ' ( cd bar && stg push --merged --all [ "$(echo $(stg series --applied --noprefix))" = "p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ) ' test_done stgit-0.17.1/t/t2500-clean.sh0000755002002200200220000000206011743516173015532 0ustar cmarinascmarinas#!/bin/sh test_description='Run "stg clean"' . ./test-lib.sh test_expect_success 'Initialize StGit stack' ' stg init && stg new e0 -m e0 && stg new p0 -m p0 && echo foo > foo.txt && stg add foo.txt && stg refresh && stg new e1 -m e1 && stg new e2 -m e2 && stg pop ' test_expect_success 'Clean empty patches' ' [ "$(echo $(stg series --applied --noprefix))" = "e0 p0 e1" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "e2" ] && stg clean && [ "$(echo $(stg series --applied --noprefix))" = "p0" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_expect_success 'Create a conflict' ' stg new p1 -m p1 && echo bar > foo.txt && stg refresh && stg pop && stg new p2 -m p2 echo quux > foo.txt && stg refresh && conflict stg push ' test_expect_success 'Make sure conflicting patches are preserved' ' stg clean && [ "$(echo $(stg series --applied --noprefix))" = "p0 p2 p1" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_done stgit-0.17.1/t/t3104-redo.sh0000755002002200200220000000401611743516173015405 0ustar cmarinascmarinas#!/bin/sh test_description='Simple test cases for "stg redo"' . ./test-lib.sh # Ignore our own output files. cat > .git/info/exclude <> a && stg add a && git commit -m a && echo 111 >> a && git commit -a -m p1 && echo 222 >> a && git commit -a -m p2 && echo 333 >> a && git commit -a -m p3 && stg uncommit -n 3 ' cat > expected.txt < expected.txt < expected.txt < expected.txt < expected.txt < expected.txt < expected.txt < expected.txt < foo.txt && stg add foo.txt && stg refresh && for i in 1 2 3 4 5 6 7 8 9; do stg new p$i -m p$i && echo p$i >> foo.txt && stg refresh; done && stg pop -n 5 ' test_expect_success \ 'Delete some patches' \ ' [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 p3 p4" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p5 p6 p7 p8 p9" ] && stg delete p7 p6 p3 p4 && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p5 p8 p9" ] ' test_expect_success \ 'Delete some more patches, some of which do not exist' \ ' [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p5 p8 p9" ] && command_error stg delete p7 p8 p2 p0 && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p5 p8 p9" ] ' test_expect_success \ 'Delete a range of patches' \ ' [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p5 p8 p9" ] && stg delete p1..p8 && [ "$(echo $(stg series --applied --noprefix))" = "p0" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p9" ] ' test_done stgit-0.17.1/t/t2300-refresh-subdir.sh0000755002002200200220000000343211743516173017376 0ustar cmarinascmarinas#!/bin/sh test_description='Test the refresh command from a subdirectory' . ./test-lib.sh stg init test_expect_success 'Refresh from a subdirectory' ' stg new p0 -m p0 && echo foo >> foo.txt && mkdir bar && echo bar >> bar/bar.txt && stg add foo.txt bar/bar.txt && cd bar && stg refresh && cd .. && [ "$(stg status)" = "" ] ' test_expect_success 'Refresh again' ' echo foo2 >> foo.txt && echo bar2 >> bar/bar.txt && cd bar && stg refresh && cd .. && [ "$(stg status)" = "" ] ' test_expect_success 'Refresh file in subdirectory' ' echo foo3 >> foo.txt && echo bar3 >> bar/bar.txt && cd bar && stg refresh bar.txt && cd .. && [ "$(stg status)" = " M foo.txt" ] ' test_expect_success 'Refresh whole subdirectory' ' echo bar4 >> bar/bar.txt && stg refresh bar && [ "$(stg status)" = " M foo.txt" ] ' test_expect_success 'Refresh subdirectories recursively' ' echo bar5 >> bar/bar.txt && stg refresh . && [ "$(stg status)" = "" ] ' test_expect_success 'refresh -u' ' echo baz >> bar/baz.txt && stg new p1 -m p1 && stg add bar/baz.txt && stg refresh --index && echo xyzzy >> foo.txt && echo xyzzy >> bar/bar.txt && echo xyzzy >> bar/baz.txt && stg refresh -u && test "$(echo $(stg status))" = "M bar/bar.txt M foo.txt" ' test_expect_success 'refresh -u -p ' ' echo xyzzy >> bar/baz.txt && stg refresh -p p0 -u bar && test "$(echo $(stg status))" = "M bar/baz.txt M foo.txt" ' test_expect_success 'refresh an unapplied patch' ' stg refresh -u && stg goto --keep p0 && test "$(stg status)" = " M foo.txt" && stg refresh -p p1 && test "$(stg status)" = "" && test "$(echo $(stg files p1))" = "A bar/baz.txt M foo.txt" ' test_done stgit-0.17.1/t/t1203-push-conflict.sh0000755002002200200220000000216711743516173017235 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2006 David KÃ¥gedal # test_description='Exercise push conflicts. Test that the index has no modifications after a push with conflicts. ' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ 'stg init ' test_expect_success \ 'Create the first patch' \ ' stg new foo -m foo && echo foo > test && echo fie > test2 && stg add test test2 && stg refresh && stg pop ' test_expect_success \ 'Create the second patch' \ ' stg new bar -m bar && echo bar > test && stg add test && stg refresh ' test_expect_success \ 'Push the first patch with conflict' \ ' conflict stg push foo ' test_expect_success \ 'Show the, now empty, first patch' \ ' ! stg show foo | grep -q -e "^diff " ' test_expect_success \ 'Check that the index has the non-conflict updates' \ ' git diff --cached --stat | grep -q -e "^ test2 | *1 " ' test_expect_success \ 'Check that pop will fail while there are unmerged conflicts' \ ' command_error stg pop ' test_expect_success \ 'Resolve the conflict' \ ' echo resolved > test && stg resolved test && stg refresh ' test_done stgit-0.17.1/t/t1207-push-tree.sh0000755002002200200220000000150611743516173016373 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2009 David KÃ¥gedal # test_description='Exercise pushing patches with --set-tree.' . ./test-lib.sh test_expect_success \ 'Create initial patches' ' stg init && stg new A -m A && echo hello world > a && stg add a && stg refresh stg new B -m B && echo HELLO WORLD > a && stg refresh ' test_expect_success \ 'Back up and create a partial patch' ' stg pop && stg new C -m C && echo hello WORLD > a && stg refresh ' test_expect_success \ 'Reapply patch B' ' stg push --set-tree B ' test_expect_success \ 'Compare results' ' stg pop -a && stg push && test "$(echo $(cat a))" = "hello world" && stg push && test "$(echo $(cat a))" = "hello WORLD" && stg push && test "$(echo $(cat a))" = "HELLO WORLD" ' test_done stgit-0.17.1/t/t1005-branch-delete.sh0000755002002200200220000000156110772155311017143 0ustar cmarinascmarinas#!/bin/sh test_description='Attempt to delete branches' . ./test-lib.sh stg init test_expect_success 'Create a branch (and switch to it)' ' stg branch --create foo ' test_expect_success 'Delete a branch' ' stg branch --delete master ' test_expect_success 'Create a non-StGIT branch and delete it' ' git branch bar && stg branch --delete bar ' test_expect_success 'Delete a nonexistent branch' ' stg branch --delete bar ' test_expect_success 'Make sure the branch ref was deleted' ' [ -z "$(git show-ref | grep master | tee /dev/stderr)" ] ' test_expect_success 'Make sure the branch config was deleted' ' [ -z "$(git config -l | grep branch\\.master | tee /dev/stderr)" ] ' test_expect_success 'Make sure the branch files were deleted' ' [ -z "$(find .git -type f | grep master | tee /dev/stderr)" ] ' test_done stgit-0.17.1/t/t0002-status.sh0000755002002200200220000000661512222316716015772 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2007 David Kågedal # test_description='Basic stg status Test that "stg status" works.' . ./test-lib.sh stg init # Ignore our own output files. cat > .git/info/exclude < expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' rm -f foo cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt <> foo/bar && stg status > output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' test_expect_success 'Add another file' ' echo lajbans > fie && stg add fie && stg refresh ' test_expect_success 'Make a conflicting patch' ' stg pop && stg new -m "third patch" && echo "woo" >> foo/bar && stg refresh ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < output.txt && test_cmp expected.txt output.txt ' cat > expected.txt < fay EOF test_expect_success 'Status after renaming a file' ' stg rm foo/bar && stg mv fie fay && stg status > output.txt && test_cmp expected.txt output.txt ' test_done stgit-0.17.1/t/t3200-non-ascii-filenames.sh0000755002002200200220000000246211743516173020275 0ustar cmarinascmarinas#!/bin/sh test_description='Handle files with non-ASCII characters in their names' . ./test-lib.sh # Ignore our own output files. cat > .git/info/exclude < skärgÃ¥rdsö.txt && stg add skärgÃ¥rdsö.txt && git commit -m "Create island" && stg init && echo foo > unrelated.txt && stg add unrelated.txt && stg new p0 -m "Unrelated file" && stg refresh && stg pop && rm skärgÃ¥rdsö.txt && git commit -a -m "Remove island" && git tag upstream && git reset --hard HEAD^ && stg push ' test_expect_success 'Rebase onto changed non-ASCII file' ' stg rebase upstream ' test_expect_success 'Setup' ' stg delete p0 && git reset --hard HEAD^ && echo "-- ett liv mitt ute i vattnet" >> skärgÃ¥rdsö.txt && stg new p1 -m "Describe island" ' cat > expected.txt < output.txt && diff -u expected.txt output.txt ' test_expect_success 'Refresh changes to non-ASCII file' ' stg refresh ' cat > expected.txt < output.txt && diff -u expected.txt output.txt ' test_done stgit-0.17.1/t/t3300-edit.sh0000755002002200200220000001525612221306627015400 0ustar cmarinascmarinas#!/bin/sh test_description='Test "stg edit"' . ./test-lib.sh test_expect_success 'Setup' ' printf "000\n111\n222\n333\n" >> foo && stg add foo && git commit -m "Initial commit" && sed -i "s/000/000xx/" foo && git commit -a -m "First change" && sed -i "s/111/111yy/" foo && git commit -a -m "Second change" && sed -i "s/222/222zz/" foo && git commit -a -m "Third change" && sed -i "s/333/333zz/" foo && git commit -a -m "Fourth change" && stg init && stg uncommit -n 4 p && stg pop -n 2 && stg hide p4 && test "$(echo $(stg series --all))" = "+ p1 > p2 - p3 ! p4" ' # Commit parse functions. msg () { git cat-file -p $1 | sed '1,/^$/d' | tr '\n' / | sed 's,/*$,,' ; } auth () { git log -n 1 --pretty=format:"%an, %ae" $1 ; } adate () { git log -n 1 --pretty=format:%ai $1 ; } test_expect_success 'Edit message of top patch' ' test "$(msg HEAD)" = "Second change" && stg edit p2 -m "Second change 2" && test "$(msg HEAD)" = "Second change 2" ' test_expect_success 'Edit message of non-top patch' ' test "$(msg HEAD^)" = "First change" && stg edit p1 -m "First change 2" && test "$(msg HEAD^)" = "First change 2" ' test_expect_success 'Edit message of unapplied patch' ' test "$(msg $(stg id p3))" = "Third change" && stg edit p3 -m "Third change 2" && test "$(msg $(stg id p3))" = "Third change 2" ' test_expect_success 'Edit message of hidden patch' ' test "$(msg $(stg id p4))" = "Fourth change" && stg edit p4 -m "Fourth change 2" && test "$(msg $(stg id p4))" = "Fourth change 2" ' test_expect_success 'Set patch message with --file ' ' test "$(msg HEAD)" = "Second change 2" && echo "Pride or Prejudice" > commitmsg && stg edit p2 -f commitmsg && test "$(msg HEAD)" = "Pride or Prejudice" ' test_expect_success 'Set patch message with --file -' ' echo "Pride and Prejudice" | stg edit p2 -f - && test "$(msg HEAD)" = "Pride and Prejudice" ' ( printf 'From: A U Thor \nDate: ' printf '\n\nPride and Prejudice' ) > expected-tmpl omit_date () { sed "s/^Date:.*$/Date: /" ; } test_expect_success 'Save template to file' ' stg edit --save-template saved-tmpl p2 && omit_date < saved-tmpl > saved-tmpl-d && test_cmp expected-tmpl saved-tmpl-d ' test_expect_success 'Save template to stdout' ' stg edit --save-template - p2 > saved-tmpl2 && omit_date < saved-tmpl2 > saved-tmpl2-d && test_cmp expected-tmpl saved-tmpl2-d ' # Test the various ways of invoking the interactive editor. The # preference order should be # # 1. GIT_EDITOR # 2. stgit.editor (legacy) # 3. core.editor # 4. VISUAL # 5. EDITOR # 6. vi mkeditor () { cat > "$1" <> "\$1" EOF chmod a+x "$1" } mkeditor vi test_expect_success 'Edit commit message interactively (vi)' ' m=$(msg HEAD) && PATH=.:$PATH stg edit p2 && test "$(msg HEAD)" = "$m/vi" ' mkeditor e1 test_expect_success 'Edit commit message interactively (EDITOR)' ' m=$(msg HEAD) && EDITOR=./e1 PATH=.:$PATH stg edit p2 && echo $m && echo $(msg HEAD) && test "$(msg HEAD)" = "$m/e1" ' mkeditor e2 test_expect_success 'Edit commit message interactively (VISUAL)' ' m=$(msg HEAD) && VISUAL=./e2 EDITOR=./e1 PATH=.:$PATH stg edit p2 && test "$(msg HEAD)" = "$m/e2" ' mkeditor e3 test_expect_success 'Edit commit message interactively (core.editor)' ' m=$(msg HEAD) && git config core.editor e3 && VISUAL=./e2 EDITOR=./e1 PATH=.:$PATH stg edit p2 && test "$(msg HEAD)" = "$m/e3" ' mkeditor e4 test_expect_success 'Edit commit message interactively (stgit.editor)' ' m=$(msg HEAD) && git config stgit.editor e4 && VISUAL=./e2 EDITOR=./e1 PATH=.:$PATH stg edit p2 && test "$(msg HEAD)" = "$m/e4" ' mkeditor e5 test_expect_success 'Edit commit message interactively (GIT_EDITOR)' ' m=$(msg HEAD) && GIT_EDITOR=./e5 VISUAL=./e2 EDITOR=./e1 PATH=.:$PATH stg edit p2 && test "$(msg HEAD)" = "$m/e5" ' rm -f vi e1 e2 e3 e4 e5 git config --unset core.editor git config --unset stgit.editor mkeditor twoliner test_expect_success 'Both noninterative and interactive editing' ' EDITOR=./twoliner stg edit -e -m "oneliner" p2 && test "$(msg HEAD)" = "oneliner/twoliner" ' rm -f twoliner cat > diffedit <" && test "$(auth HEAD)" = "Jane Austin, jaustin@example.com" ' test_expect_success 'Fail to set broken author' ' command_error stg edit p2 --author "No Mail Address" && test "$(auth HEAD)" = "Jane Austin, jaustin@example.com" ' test_expect_success 'Set author name' ' stg edit p2 --authname "Jane Austen" && test "$(auth HEAD)" = "Jane Austen, jaustin@example.com" ' test_expect_success 'Set author email' ' stg edit p2 --authemail "jausten@example.com" && test "$(auth HEAD)" = "Jane Austen, jausten@example.com" ' test_expect_success 'Set author date (RFC2822 format)' ' stg edit p2 --authdate "Wed, 10 Jul 2013 23:39:00 -0300" && test "$(adate HEAD)" = "2013-07-10 23:39:00 -0300" ' test_expect_success 'Set author date (ISO 8601 format)' ' stg edit p2 --authdate "2013-01-28 22:30:00 -0300" && test "$(adate HEAD)" = "2013-01-28 22:30:00 -0300" ' test_expect_success 'Fail to set invalid author date' ' command_error stg edit p2 --authdate "28 Jan 1813" && test "$(adate HEAD)" = "2013-01-28 22:30:00 -0300" ' test_expect_success 'Set author date to "now"' ' before=$(date "+%F %T %z") && stg edit p2 --authdate now && after=$(date "+%F %T %z") && printf "$before\n$(adate HEAD)\n$after\n" | sort -C - ' test_expect_success 'Set patch tree' ' p2tree=$(git log -1 --pretty=format:%T $(stg id p2)) && p4commit=$(stg id p4) && stg edit --set-tree $p4commit && test $(git write-tree) = $(git rev-parse ${p4commit}^{tree}) && grep "^333zz$" foo && stg pop && stg edit --set-tree $p2tree p2 && stg push --set-tree && test $(git write-tree) = $p2tree && grep "^333$" foo && stg edit --set-tree $p2tree p1 && test "$(echo $(stg series --empty --all))" = "+ p1 0> p2 - p3 ! p4" ' test_done stgit-0.17.1/t/test-lib.sh0000644002002200200220000002135412221306627015420 0ustar cmarinascmarinas#!/bin/sh # # Copyright (c) 2005 Junio C Hamano # Copyright (c) 2006 Yann Dirson - tuning for stgit # # Keep the original TERM for say_color ORIGINAL_TERM=$TERM # For repeatability, reset the environment to known value. LANG=C LC_ALL=C PAGER=cat TZ=UTC TERM=dumb export LANG LC_ALL PAGER TERM TZ unset EDITOR unset VISUAL unset GIT_EDITOR unset AUTHOR_DATE unset AUTHOR_EMAIL unset AUTHOR_NAME unset COMMIT_AUTHOR_EMAIL unset COMMIT_AUTHOR_NAME unset EMAIL unset GIT_ALTERNATE_OBJECT_DIRECTORIES unset GIT_AUTHOR_DATE GIT_AUTHOR_EMAIL=author@example.com GIT_AUTHOR_NAME='A U Thor' unset GIT_COMMITTER_DATE GIT_COMMITTER_EMAIL=committer@example.com GIT_COMMITTER_NAME='C O Mitter' unset GIT_DIFF_OPTS unset GIT_DIR unset GIT_WORK_TREE unset GIT_EXTERNAL_DIFF unset GIT_INDEX_FILE unset GIT_OBJECT_DIRECTORY unset SHA1_FILE_DIRECTORIES unset SHA1_FILE_DIRECTORY GIT_MERGE_VERBOSITY=5 export GIT_MERGE_VERBOSITY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u} # Protect ourselves from common misconfiguration to export # CDPATH into the environment unset CDPATH case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in 1|2|true) echo "* warning: Some tests will not work if GIT_TRACE" \ "is set as to trace on STDERR ! *" echo "* warning: Please set GIT_TRACE to something" \ "other than 1, 2 or true ! *" ;; esac # Each test should start with something like this, after copyright notices: # # test_description='Description of this test... # This test checks if command xyzzy does the right thing... # ' # . ./test-lib.sh [ "x$ORIGINAL_TERM" != "xdumb" ] && ( TERM=$ORIGINAL_TERM && export TERM && [ -t 1 ] && tput bold >/dev/null 2>&1 && tput setaf 1 >/dev/null 2>&1 && tput sgr0 >/dev/null 2>&1 ) && color=t while test "$#" -ne 0 do case "$1" in -d|--d|--de|--deb|--debu|--debug) debug=t; shift ;; -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) immediate=t; shift ;; -h|--h|--he|--hel|--help) help=t; shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) export STGIT_DEBUG_LEVEL="1" verbose=t; shift ;; -q|--q|--qu|--qui|--quie|--quiet) quiet=t; shift ;; --no-color) color=; shift ;; *) break ;; esac done if test -n "$color"; then say_color () { ( TERM=$ORIGINAL_TERM export TERM case "$1" in error) tput bold; tput setaf 1;; # bold red skip) tput bold; tput setaf 2;; # bold green pass) tput setaf 2;; # green info) tput setaf 3;; # brown *) test -n "$quiet" && return;; esac shift echo "* $*" tput sgr0 ) } else say_color() { test -z "$1" && test -n "$quiet" && return shift echo "* $*" } fi error () { say_color error "error: $*" trap - exit exit 1 } say () { say_color info "$*" } test "${test_description}" != "" || error "Test script did not set test_description." if test "$help" = "t" then echo "$test_description" exit 0 fi exec 5>&1 if test "$verbose" = "t" then exec 4>&2 3>&1 else exec 4>/dev/null 3>/dev/null fi test_failure=0 test_count=0 test_fixed=0 test_broken=0 die () { echo >&5 "FATAL: Unexpected exit with code $?" exit 1 } trap 'die' exit test_tick () { if test -z "${test_tick+set}" then test_tick=1112911993 else test_tick=$(($test_tick + 60)) fi GIT_COMMITTER_DATE="$test_tick -0700" GIT_AUTHOR_DATE="$test_tick -0700" export GIT_COMMITTER_DATE GIT_AUTHOR_DATE } # You are not expected to call test_ok_ and test_failure_ directly, use # the text_expect_* functions instead. test_ok_ () { test_count=$(expr "$test_count" + 1) say_color "" " ok $test_count: $@" } test_failure_ () { test_count=$(expr "$test_count" + 1) test_failure=$(expr "$test_failure" + 1); say_color error "FAIL $test_count: $1" shift echo "$@" | sed -e 's/^/ /' test "$immediate" = "" || { trap - exit; exit 1; } } test_known_broken_ok_ () { test_count=$(expr "$test_count" + 1) test_fixed=$(($test_fixed+1)) say_color "" " FIXED $test_count: $@" } test_known_broken_failure_ () { test_count=$(expr "$test_count" + 1) test_broken=$(($test_broken+1)) say_color skip " still broken $test_count: $@" } test_debug () { test "$debug" = "" || eval "$1" } test_run_ () { eval >&3 2>&4 "$1" eval_ret="$?" return 0 } test_skip () { this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$') this_test="$this_test.$(expr "$test_count" + 1)" to_skip= for skp in $GIT_SKIP_TESTS do case "$this_test" in $skp) to_skip=t esac done case "$to_skip" in t) say_color skip >&3 "skipping test: $@" test_count=$(expr "$test_count" + 1) say_color skip "skip $test_count: $1" : true ;; *) false ;; esac } test_expect_failure () { test "$#" = 2 || error "bug in the test script: not 2 parameters to test-expect-failure" if ! test_skip "$@" then say >&3 "checking known breakage: $2" test_run_ "$2" if [ "$?" = 0 -a "$eval_ret" = 0 ] then test_known_broken_ok_ "$1" else test_known_broken_failure_ "$1" fi fi echo >&3 "" } test_expect_success () { test "$#" = 2 || error "bug in the test script: not 2 parameters to test-expect-success" if ! test_skip "$@" then say >&3 "expecting success: $2" test_run_ "$2" if [ "$?" = 0 -a "$eval_ret" = 0 ] then test_ok_ "$1" else test_failure_ "$@" fi fi echo >&3 "" } test_expect_code () { test "$#" = 3 || error "bug in the test script: not 3 parameters to test-expect-code" if ! test_skip "$@" then say >&3 "expecting exit code $1: $3" test_run_ "$3" if [ "$?" = 0 -a "$eval_ret" = "$1" ] then test_ok_ "$2" else test_failure_ "$@" fi fi echo >&3 "" } # When running an StGit command that should exit with an error, use # these instead of testing for any non-zero exit code with !. exit_code () { expected=$1 shift "$@" test $? -eq $expected } general_error () { exit_code 1 "$@" ; } command_error () { exit_code 2 "$@" ; } conflict () { exit_code 3 "$@" ; } # Old-infrastructure commands don't exit with the proper value on # conflicts. But we don't want half the tests to fail because of that, # so use this instead of "conflict" for them. conflict_old () { command_error "$@" ; } # Same thing, but for other commands that StGit where we just want to # make sure that they fail instead of crashing. must_fail () { "$@" test $? -gt 0 -a $? -le 129 } # test_cmp is a helper function to compare actual and expected output. # You can use it like: # # test_expect_success 'foo works' ' # echo expected >expected && # foo >actual && # test_cmp expected actual # ' # # This could be written as either "cmp" or "diff -u", but: # - cmp's output is not nearly as easy to read as diff -u # - not all diff versions understand "-u" test_cmp() { $GIT_TEST_CMP "$@" } # Most tests can use the created repository, but some may need to create more. # Usage: test_create_repo test_create_repo () { test "$#" = 1 || error "bug in the test script: not 1 parameter to test-create-repo" owd=$(pwd) repo="$1" mkdir "$repo" cd "$repo" || error "Cannot setup test environment" git init >/dev/null 2>&1 || error "cannot run git init" echo "empty start" | \ git commit-tree $(git write-tree) >.git/refs/heads/master 2>&4 || \ error "cannot run git commit" mv .git/hooks .git/hooks-disabled cd "$owd" } test_done () { trap - exit if test "$test_fixed" != 0 then say_color pass "fixed $test_fixed known breakage(s)" fi if test "$test_broken" != 0 then say_color error "still have $test_broken known breakage(s)" msg="remaining $(($test_count-$test_broken)) test(s)" else msg="$test_count test(s)" fi case "$test_failure" in 0) # We could: # cd .. && rm -fr $SCRATCHDIR # but that means we forbid any tests that use their own # subdirectory from calling test_done without coming back # to where they started from. # The Makefile provided will clean this test area so # we will leave things as they are. say_color pass "passed all $msg" ;; *) say_color error "failed $test_failure among $msg" ;; esac case $(($test_failure+$test_fixed)) in 0) exit 0 ;; *) exit 1 ;; esac } if test -z "$SCRATCHDIR"; then SCRATCHDIR=$(pwd)/trash fi # Test the binaries we have just built. The tests are kept in # t/ subdirectory and are run in $SCRATCHDIR subdirectory. PATH=$(pwd)/..:$PATH STG_ROOT=$(pwd)/.. HOME=$SCRATCHDIR unset GIT_CONFIG export PATH HOME # Test repository test=$SCRATCHDIR rm -fr "$test" || { trap - exit echo >&5 "FATAL: Cannot prepare test area" exit 1 } test_create_repo $test cd "$test" this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$') for skp in $GIT_SKIP_TESTS do to_skip= for skp in $GIT_SKIP_TESTS do case "$this_test" in $skp) to_skip=t esac done case "$to_skip" in t) say_color skip >&3 "skipping test $this_test altogether" say_color skip "skip all tests in $this_test" test_done esac done stgit-0.17.1/t/t1204-pop-keep.sh0000755002002200200220000000230411743516173016171 0ustar cmarinascmarinas#!/bin/sh test_description='Test "stg pop -keep"' . ./test-lib.sh stg init test_expect_success 'Create a few patches' ' for i in 0 1 2; do stg new p$i -m p$i && echo "patch$i" >> patch$i.txt && stg add patch$i.txt && stg refresh done && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_expect_success 'Make some non-conflicting local changes' ' echo "local" >> patch0.txt ' test_expect_success 'Pop two patches, keeping local changes' ' stg pop -n 2 --keep && [ "$(echo $(stg series --applied --noprefix))" = "p0" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p1 p2" ] && [ "$(echo $(ls patch?.txt))" = "patch0.txt" ] && [ "$(echo $(cat patch0.txt))" = "patch0 local" ] ' test_expect_success 'Reset and push patches again' ' git reset --hard && stg push -a ' test_expect_success 'Pop a patch without local changes' ' stg pop --keep && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p2" ] && [ "$(echo $(ls patch?.txt))" = "patch0.txt patch1.txt" ] ' test_done stgit-0.17.1/t/t2701-refresh-p.sh0000755002002200200220000000162311743516173016352 0ustar cmarinascmarinas#!/bin/sh test_description='Run "stg refresh -p"' . ./test-lib.sh # Ignore our own temp files. cat >> .git/info/exclude < $i.txt && stg add $i.txt && stg new p$i -m "Patch $i" && stg refresh done ' touch expected0.txt cat > expected1.txt < expected2.txt < status1.txt && test_cmp expected0.txt status1.txt && echo y > new.txt && stg add new.txt && stg refresh -p p1 && stg status > status2.txt && test_cmp expected0.txt status2.txt && stg files p1 > files1.txt && test_cmp expected1.txt files1.txt && stg files p2 > files2.txt && test_cmp expected2.txt files2.txt ' test_done stgit-0.17.1/t/t1208-push-and-pop.sh0000755002002200200220000000476411321623621016771 0ustar cmarinascmarinas#!/bin/sh # Copyright (c) 2007 Karl Hasselström test_description='Test the push and pop commands' . ./test-lib.sh test_expect_success \ 'Initialize the StGIT repository' \ 'stg init' test_expect_success \ 'Create ten patches' ' for i in 0 1 2 3 4 5 6 7 8 9; do stg new p$i -m p$i; done && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 p3 p4 p5 p6 p7 p8 p9" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_expect_success \ 'Pop three patches' ' stg pop -n 3 && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 p3 p4 p5 p6" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p7 p8 p9" ] ' test_expect_success \ 'Pop the remaining patches' ' stg pop -a && [ "$(echo $(stg series --applied --noprefix))" = "" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p0 p1 p2 p3 p4 p5 p6 p7 p8 p9" ] ' test_expect_success \ 'Push them back' ' stg push -a && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 p3 p4 p5 p6 p7 p8 p9" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "" ] ' test_expect_success \ 'Pop all but seven patches' ' stg pop -n -7 && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 p3 p4 p5 p6" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p7 p8 p9" ] ' test_expect_success \ 'Pop no patches (quietly)' ' [ -z "$(stg pop -n 0 2>&1)" ] && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 p3 p4 p5 p6" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p7 p8 p9" ] ' test_expect_success \ 'Pop remaining seven patches' ' stg pop -n 7 && [ "$(echo $(stg series --applied --noprefix))" = "" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p0 p1 p2 p3 p4 p5 p6 p7 p8 p9" ] ' test_expect_success \ 'Push two patches' ' stg push -n 2 && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p2 p3 p4 p5 p6 p7 p8 p9" ] ' test_expect_success \ 'Push no patches (quietly)' ' [ -z "$(stg push -n 0 2>&1)" ] && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p2 p3 p4 p5 p6 p7 p8 p9" ] ' test_expect_success \ 'Push all but three patches' ' stg push -n -3 && [ "$(echo $(stg series --applied --noprefix))" = "p0 p1 p2 p3 p4 p5 p6" ] && [ "$(echo $(stg series --unapplied --noprefix))" = "p7 p8 p9" ] ' test_done stgit-0.17.1/t/t1303-commit.sh0000755002002200200220000000167511743516173015753 0ustar cmarinascmarinas#!/bin/sh test_description='Test stg commit' . ./test-lib.sh test_expect_success 'Initialize the StGIT repository' ' stg init ' test_expect_success 'Commit middle patch' ' stg new -m p1 && stg new -m p2 && stg new -m p3 && stg new -m p4 && stg pop && stg commit p2 && test "$(echo $(stg series))" = "+ p1 > p3 - p4" ' test_expect_success 'Commit first patch' ' stg commit && test "$(echo $(stg series))" = "> p3 - p4" ' test_expect_success 'Commit all patches' ' stg push && stg commit -a && test "$(echo $(stg series))" = "" ' # stg commit with top != head should not succeed, since the committed # patches are poptentially lost. test_expect_success 'Commit when top != head (should fail)' ' stg new -m foo && git reset --hard HEAD^ && h=$(git rev-parse HEAD) command_error stg commit && test $(git rev-parse HEAD) = $h && test "$(echo $(stg series))" = "> foo" ' test_done stgit-0.17.1/t/README0000644002002200200220000001532311271344641014222 0ustar cmarinascmarinasCore GIT Tests ============== This directory holds many test scripts for core GIT tools. The first part of this short document describes how to run the tests and read their output. When fixing the tools or adding enhancements, you are strongly encouraged to add tests in this directory to cover what you are trying to fix or enhance. The later part of this short document describes how your test scripts should be organized. The mechanism that powers this testsuite is directly imported from the Core GIT Tests, in directory t/ of the git repository. Files are base on Core GIT version 1.3.0.rc4.g5069. Running Tests ------------- The easiest way to run tests is to say "make -C t". This runs all the tests. *** t0000-basic.sh *** * ok 1: .git/objects should be empty after git init in an empty repo. * ok 2: .git/objects should have 256 subdirectories. * ok 3: git update-index without --add should fail adding. ... * ok 23: no diff after checkout and git update-index --refresh. * passed all 23 test(s) *** t0100-environment-names.sh *** * ok 1: using old names should issue warnings. * ok 2: using old names but having new names should not issue warnings. ... Or you can run each test individually from command line, like this: $ sh ./t3001-ls-files-killed.sh * ok 1: git update-index --add to add various paths. * ok 2: git ls-files -k to show killed files. * ok 3: validate git ls-files -k output. * passed all 3 test(s) You can pass --verbose (or -v), --debug (or -d), and --immediate (or -i) command line argument to the test. --verbose:: This makes the test more verbose. Specifically, the command being run and their output if any are also output. --debug:: This may help the person who is developing a new test. It causes the command defined with test_debug to run. --immediate:: This causes the test to immediately exit upon the first failed test. Naming Tests ------------ The test files are named as: tNNNN-commandname-details.sh where N is a decimal digit. Here is a proposal for numbering, loosely based on the Core GIT numbering conventions. First two digit tells the particular command we are testing: 00 - stgit itself 10 - branch 11 - clone 12 - push Third and fourth digit (optionally) tells the particular switch or group of switches we are testing. If you create files under t/ directory (i.e. here) that is not the top-level test script, never name the file to match the above pattern. The Makefile here considers all such files as the top-level test script and tries to run all of them. A care is especially needed if you are creating a common test library file, similar to test-lib.sh, because such a library file may not be suitable for standalone execution. Writing Tests ------------- The test script is written as a shell script. It should start with the standard "#!/bin/sh" with copyright notices, and an assignment to variable 'test_description', like this: #!/bin/sh # # Copyright (c) 2005 Junio C Hamano # test_description='xxx test (option --frotz) This test registers the following structure in the cache and tries to run git ls-files with option --frotz.' Source 'test-lib.sh' -------------------- After assigning test_description, the test script should source test-lib.sh like this: . ./test-lib.sh This test harness library does the following things: - If the script is invoked with command line argument --help (or -h), it shows the test_description and exits. - Creates an empty test directory with an empty .git/objects database and chdir(2) into it. This directory is 't/trash' if you must know, but I do not think you care. - Defines standard test helper functions for your scripts to use. These functions are designed to make all scripts behave consistently when command line arguments --verbose (or -v), --debug (or -d), and --immediate (or -i) is given. End with test_done ------------------ Your script will be a sequence of tests, using helper functions from the test harness library. At the end of the script, call 'test_done'. Test harness library -------------------- There are a handful helper functions defined in the test harness library for your script to use. - test_expect_success