-"""Function/variables commmon to all the commands
+"""Function/variables common to all the commands
"""
__copyright__ = """
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-import sys, os, re
+import sys, os, os.path, re
from optparse import OptionParser, make_option
from stgit.utils import *
-from stgit import stack, git
+from stgit import stack, git, basedir
+from stgit.config import config, file_extensions
+
+crt_series = None
# Command exception class
# Utility functions
-def git_id(string):
+class RevParseException(Exception):
+ """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 //."""
+ files, dirs = list_files_and_dirs(os.path.join(basedir.get(),
+ 'refs', 'heads'))
+ if len(dirs) != 0:
+ # 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
+
+def git_id(rev):
"""Return the GIT id
"""
- if not string:
+ if not rev:
return None
-
- string_list = string.split('/')
- if len(string_list) == 2:
- patch_id = string_list[1]
- if not patch_id:
- patch_id = 'top'
- elif len(string_list) == 1:
- patch_id = 'top'
- else:
- patch_id = None
-
- patch_branch = string_list[0].split('@')
- if len(patch_branch) == 1:
- series = crt_series
- elif len(patch_branch) == 2:
- series = stack.Series(patch_branch[1])
- else:
- raise CmdException, 'Unknown id: %s' % string
-
- patch_name = patch_branch[0]
- if not patch_name:
- patch_name = series.get_current()
- if not patch_name:
- raise CmdException, 'No patches applied'
-
- # patch
- if patch_name in series.get_applied() \
- or patch_name in series.get_unapplied():
- if patch_id == 'top':
- return series.get_patch(patch_name).get_top()
- elif patch_id == 'bottom':
- return series.get_patch(patch_name).get_bottom()
- # Note we can return None here.
- elif patch_id == 'top.old':
- return series.get_patch(patch_name).get_old_top()
- elif patch_id == 'bottom.old':
- return series.get_patch(patch_name).get_old_bottom()
-
- # base
- if patch_name == 'base' and len(string_list) == 1:
- return read_string(series.get_base_file())
-
- # anything else failed
- return git.rev_parse(string)
+ 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():
+ 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()
+ if patch == 'base' and patch_id == None:
+ return read_string(series.get_base_file())
+ except RevParseException:
+ pass
+ return git.rev_parse(rev + '^{commit}')
def check_local_changes():
if git.local_changes():
if not crt_series.head_top_equal():
raise CmdException, \
'HEAD and top are not the same. You probably committed\n' \
- ' changes to the tree ouside of StGIT. If you know what you\n' \
+ ' changes to the tree outside of StGIT. If you know what you\n' \
' are doing, use the "refresh -f" command'
def check_conflicts():
- if os.path.exists(os.path.join(git.base_dir, 'conflicts')):
+ if os.path.exists(os.path.join(basedir.get(), 'conflicts')):
raise CmdException, 'Unsolved conflicts. Please resolve them first'
def print_crt_patch(branch = None):
def resolved(filename, reset = None):
if reset:
- reset_file = filename + '.' + reset
+ reset_file = filename + file_extensions()[reset]
if os.path.isfile(reset_file):
if os.path.isfile(filename):
os.remove(filename)
git.update_cache([filename], force = True)
- for ext in ['.local', '.older', '.remote']:
+ for ext in file_extensions().values():
fn = filename + ext
if os.path.isfile(fn):
os.remove(fn)
if conflicts:
for filename in conflicts:
resolved(filename, reset)
- os.remove(os.path.join(git.base_dir, 'conflicts'))
+ os.remove(os.path.join(basedir.get(), 'conflicts'))
+
+def push_patches(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:
+ print 'Fast-forwarded patches "%s" - "%s"' % (patches[0],
+ patches[forwarded - 1])
+ elif forwarded == 1:
+ print 'Fast-forwarded patch "%s"' % patches[0]
+
+ names = patches[forwarded:]
+
+ # check for patches merged upstream
+ if check_merged:
+ print 'Checking for patches merged upstream...',
+ sys.stdout.flush()
-def name_email(string):
+ merged = crt_series.merged_patches(names)
+
+ print 'done (%d found)' % len(merged)
+ else:
+ merged = []
+
+ for p in names:
+ print 'Pushing patch "%s"...' % p,
+ sys.stdout.flush()
+
+ if p in merged:
+ crt_series.push_patch(p, empty = True)
+ print 'done (merged upstream)'
+ else:
+ modified = crt_series.push_patch(p)
+
+ if crt_series.empty_patch(p):
+ print 'done (empty patch)'
+ elif modified:
+ print 'done (modified)'
+ else:
+ print 'done'
+
+def pop_patches(patches):
+ """Pop the patches in the list from the stack. It is assumed that
+ the patches are listed in the stack reverse order.
+ """
+ p = patches[-1]
+ if len(patches) == 1:
+ print 'Popping patch "%s"...' % p,
+ else:
+ print 'Popping "%s" - "%s" patches...' % (patches[0], p),
+ sys.stdout.flush()
+
+ crt_series.pop_patch(p)
+
+ print 'done'
+
+def name_email(address):
"""Return a tuple consisting of the name and email parsed from a
- standard 'name <email>' string
+ standard 'name <email>' or 'email (name)' string
"""
- string = re.sub('([^\w\s<>@.])', '\\\\\\1', string)
- str_list = re.findall('^(.*)\s*<(.*)>\s*$', string)
+ address = re.sub('[\\\\"]', '\\\\\g<0>', address)
+ str_list = re.findall('^(.*)\s*<(.*)>\s*$', address)
if not str_list:
- raise CmdException, 'Incorrect "name <email>" string: %s' % string
+ 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]
-def name_email_date(string):
+def name_email_date(address):
"""Return a tuple consisting of the name, email and date parsed
from a 'name <email> date' string
"""
- string = re.sub('([^\w\s<>@.])', '\\\\\\1', string)
- str_list = re.findall('^(.*)\s*<(.*)>\s*(.*)\s*$', 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' % string
+ raise CmdException, 'Incorrect "name <email> date" string: %s' % address
return str_list[0]
+
+def make_patch_name(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
+
+ subject_line = msg.lstrip().split('\n', 1)[0]
+ return re.sub('[\W]+', '-', subject_line).strip('-')