"""
import sys, os, os.path, re
-from optparse import OptionParser, make_option
-
from stgit.exception import *
from stgit.utils import *
from stgit.out import *
from stgit.run import *
from stgit import stack, git, basedir
from stgit.config import config, file_extensions
-
+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
-class RevParseException(StgException):
- """Revision spec parse error."""
- pass
-
def parse_rev(rev):
- """Parse a revision specification into its
- patchname@branchname//patch_id parts. If no branch name has a slash
- in it, also accept / instead of //."""
- if '/' in ''.join(git.get_heads()):
- # We have branch names with / in them.
- branch_chars = r'[^@]'
- patch_id_mark = r'//'
- else:
- # No / in branch names.
- branch_chars = r'[^@/]'
- patch_id_mark = r'(/|//)'
- patch_re = r'(?P<patch>[^@/]+)'
- branch_re = r'@(?P<branch>%s+)' % branch_chars
- patch_id_re = r'%s(?P<patch_id>[a-z.]*)' % patch_id_mark
-
- # Try //patch_id.
- m = re.match(r'^%s$' % patch_id_re, rev)
- if m:
- return None, None, m.group('patch_id')
-
- # Try path[@branch]//patch_id.
- m = re.match(r'^%s(%s)?%s$' % (patch_re, branch_re, patch_id_re), rev)
- if m:
- return m.group('patch'), m.group('branch'), m.group('patch_id')
-
- # Try patch[@branch].
- m = re.match(r'^%s(%s)?$' % (patch_re, branch_re), rev)
- if m:
- return m.group('patch'), m.group('branch'), None
-
- # No, we can't parse that.
- raise RevParseException
+ """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
"""
- if not rev:
- return None
+ # 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 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 '<branch>:<patch>' and can
+ be followed by standard symbols used by git rev-parse. If <patch>
+ is '{base}', it represents the bottom of the stack.
+ """
+ # 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))
+
+ # Other combination of branch and patch
try:
- patch, branch, patch_id = parse_rev(rev)
- if branch == None:
- series = crt_series
- else:
- series = stack.Series(branch)
- if patch == None:
- patch = series.get_current()
- if not patch:
- raise CmdException, 'No patches applied'
- if patch in series.get_applied() or patch in series.get_unapplied() or \
- patch in series.get_hidden():
- if patch_id in ['top', '', None]:
- return series.get_patch(patch).get_top()
- elif patch_id == 'bottom':
- return series.get_patch(patch).get_bottom()
- elif patch_id == 'top.old':
- return series.get_patch(patch).get_old_top()
- elif patch_id == 'bottom.old':
- return series.get_patch(patch).get_old_bottom()
- elif patch_id == 'log':
- return series.get_patch(patch).get_log()
- if patch == 'base' and patch_id == None:
- return series.get_base()
- except RevParseException:
+ return repository.rev_parse('patches/%s/%s' % (branch, patch),
+ discard_stderr = True)
+ except libgit.RepositoryException:
pass
- return git.rev_parse(rev + '^{commit}')
+
+ # 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 check_local_changes():
if git.local_changes():
- raise CmdException, \
- 'local changes in the tree. Use "refresh" or "status --reset"'
+ raise CmdException('local changes in the tree. Use "refresh" or'
+ ' "status --reset"')
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. The "repair" command can
- fix this situation.""")
+ 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 os.path.exists(os.path.join(basedir.get(), 'conflicts')):
- raise CmdException, \
- 'Unsolved conflicts. Please resolve them first or\n' \
- ' revert the changes with "status --reset"'
+ if git.get_conflicts():
+ raise CmdException('Unsolved conflicts. Please resolve them first'
+ ' or revert the changes with "status --reset"')
def print_crt_patch(crt_series, branch = None):
if not branch:
else:
out.info('No patches applied')
-def resolved(filename, reset = None):
- if reset:
- reset_file = filename + file_extensions()[reset]
- if os.path.isfile(reset_file):
- if os.path.isfile(filename):
- os.remove(filename)
- os.rename(reset_file, filename)
-
- git.update_cache([filename], force = True)
-
- for ext in file_extensions().values():
- fn = filename + ext
- if os.path.isfile(fn):
- os.remove(fn)
-
def resolved_all(reset = None):
conflicts = git.get_conflicts()
- if conflicts:
- for filename in conflicts:
- resolved(filename, reset)
- os.remove(os.path.join(basedir.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
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:
return patches
def name_email(address):
- """Return a tuple consisting of the name and email parsed from a
- standard 'name <email>' or 'email (name)' string
- """
- address = re.sub('[\\\\"]', '\\\\\g<0>', address)
- str_list = re.findall('^(.*)\s*<(.*)>\s*$', address)
- if not str_list:
- str_list = re.findall('^(.*)\s*\((.*)\)\s*$', address)
- if not str_list:
- raise CmdException, 'Incorrect "name <email>"/"email (name)" string: %s' % address
- return ( str_list[0][1], str_list[0][0] )
-
- return str_list[0]
+ p = parse_name_email(address)
+ if p:
+ return p
+ else:
+ raise CmdException('Incorrect "name <email>"/"email (name)" string: %s'
+ % address)
def name_email_date(address):
- """Return a tuple consisting of the name, email and date parsed
- from a 'name <email> date' string
- """
- address = re.sub('[\\\\"]', '\\\\\g<0>', address)
- str_list = re.findall('^(.*)\s*<(.*)>\s*(.*)\s*$', address)
- if not str_list:
- raise CmdException, 'Incorrect "name <email> date" string: %s' % address
-
- return str_list[0]
+ p = parse_name_email_date(address)
+ if p:
+ return p
+ else:
+ raise CmdException('Incorrect "name <email> date" string: %s' % address)
def address_or_alias(addr_str):
"""Return the address if it contains an e-mail address or look up
return (descr, authname, authemail, authdate, diff)
-def parse_patch(fobj):
- """Parse the input file and return (description, authname,
+def parse_patch(text, contains_diff):
+ """Parse the input text and return (description, authname,
authemail, authdate, diff)
"""
- descr, diff = __split_descr_diff(fobj.read())
- descr, authname, authemail, authdate = __parse_description(descr)
+ 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
pass
class _Directory(object):
- def __init__(self, needs_current_series = True):
+ 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'
+ 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'
+ lines = Run('git', 'rev-parse', '--show-cdup'
).discard_stderr().output_lines()
if len(lines) == 0:
return '.'
@readonly_constant_property
def is_inside_git_dir(self):
return { 'true': True, 'false': False
- }[Run('git-rev-parse', '--is-inside-git-dir'
+ }[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'
+ }[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):
class DirectoryHasRepository(_Directory):
def setup(self):
self.git_dir # might throw an exception
+ log.compat_log_external_mods()
class DirectoryInWorktree(DirectoryHasRepository):
def setup(self):
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()