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):
def create(self):
os.mkdir(self._dir())
- self.create_empty_field('bottom')
- self.create_empty_field('top')
def delete(self, keep_log = False):
if os.path.isdir(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 update_top_ref(self):
- top = self.get_top()
- if top:
- self.__update_top_ref(top)
-
def get_old_bottom(self):
- return self._get_field('bottom.old')
+ return git.get_commit(self.get_old_top()).get_parent()
def get_bottom(self):
- return self._get_field('bottom')
-
- def set_bottom(self, value, backup = False):
- if backup:
- curr = self._get_field('bottom')
- self._set_field('bottom.old', curr)
- self._set_field('bottom', value)
+ 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 self._get_field('top')
+ return git.rev_parse(self.__top_ref)
def set_top(self, value, backup = False):
if backup:
- curr = self._get_field('top')
- self._set_field('top.old', curr)
- self._set_field('top', value)
+ 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):
- bottom = self._get_field('bottom.old')
top = self._get_field('top.old')
- if top and bottom:
- self._set_field('bottom', bottom)
- self._set_field('top', top)
+ if top:
self.__update_top_ref(top)
return True
else:
self._set_field('log', value)
self.__update_log_ref(value)
-# The current StGIT metadata format version.
-FORMAT_VERSION = 2
-
class PatchSet(StgitObject):
def __init__(self, name = None):
try:
def is_initialised(self):
"""Checks if series is already initialised
"""
- return bool(config.get(self.format_version_key()))
+ return config.get(stackupgrade.format_version_key(self.get_name())
+ ) != None
def shortlog(patches):
# Update the branch to the latest format version if it is
# initialized, but don't touch it if it isn't.
- self.update_to_current_format_version()
+ stackupgrade.update_to_current_format_version(
+ libgit.Repository.default(), self.get_name())
self.__refs_base = 'refs/patches/%s' % self.get_name()
# trash directory
self.__trash_dir = os.path.join(self._dir(), 'trash')
- def format_version_key(self):
- return 'branch.%s.stgit.stackformatversion' % self.get_name()
-
- def update_to_current_format_version(self):
- """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(self._basedir(), 'patches', self.get_name())
- 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(self.format_version_key())
- ofv = config.get('branch.%s.stgitformatversion' % self.get_name())
- 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(self.format_version_key(), ofv)
- config.unset('branch.%s.stgitformatversion' % self.get_name())
- return int(ofv)
- elif os.path.isdir(os.path.join(branch_dir, 'patches')):
- # There's a .git/patches/<branch>/patches dirctory, which
- # means this is an initialized version 1 branch.
- return 1
- elif os.path.isdir(branch_dir):
- # There's a .git/patches/<branch> 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' % (self.get_name(), v))
- config.set(self.format_version_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 git.ref_exists(ref):
- git.delete_ref(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' % self.get_name()
- 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))
- Patch(patch, patch_dir, refs_base).update_top_ref()
- 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 = read_string(desc_file)
- if desc:
- config.set('branch.%s.description' % self.get_name(), desc)
- rm(desc_file)
- rm(os.path.join(branch_dir, 'current'))
- rm_ref('refs/bases/%s' % self.get_name())
- set_format_version(2)
-
- # Make sure we're at the latest version.
- if not get_format_version() in [None, FORMAT_VERSION]:
- raise StackException('Branch %s is at format version %d, expected %d'
- % (self.get_name(), get_format_version(), FORMAT_VERSION))
-
def __patch_name_valid(self, name):
"""Raise an exception if the patch name is not valid.
"""
self.create_empty_field('applied')
self.create_empty_field('unapplied')
- config.set(self.format_version_key(), str(FORMAT_VERSION))
+ config.set(stackupgrade.format_version_key(self.get_name()),
+ str(stackupgrade.FORMAT_VERSION))
def rename(self, to_name):
"""Renames a series
"""Deletes an stgit series
"""
if self.is_initialised():
- patches = self.get_unapplied() + self.get_applied()
+ patches = self.get_unapplied() + self.get_applied() + \
+ self.get_hidden();
if not force and patches:
raise StackException, \
'Cannot delete: the series still contains patches'
try:
git.delete_branch(self.get_name())
- except GitException:
+ 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,
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,
committer_name = committer_name,
committer_email = committer_email)
- patch.set_bottom(bottom, backup = backup)
patch.set_top(commit_id, backup = backup)
patch.set_description(descr)
patch.set_authname(author_name)
return commit_id
- def undo_refresh(self):
- """Undo the patch boundaries changes caused by 'refresh'
- """
- name = self.get_current()
- assert(name)
-
- patch = self.get_patch(name)
- old_bottom = patch.get_old_bottom()
- old_top = patch.get_old_top()
-
- # the bottom of the patch is not changed by refresh. If the
- # old_bottom is different, there wasn't any previous 'refresh'
- # command (probably only a 'push')
- if old_bottom != patch.get_bottom() or old_top == patch.get_top():
- raise StackException, 'No undo information available'
-
- git.reset(tree_id = old_top, check_out = False)
- if patch.restore_old_boundaries():
- self.log_patch(patch, 'undo')
-
def new_patch(self, name, message = None, can_edit = True,
unapplied = False, show_patch = False,
top = None, bottom = None, commit = True,
committer_name = committer_name,
committer_email = committer_email)
# set the patch top to the new commit
- patch.set_bottom(bottom)
patch.set_top(commit_id)
else:
- assert top != bottom
- patch.set_bottom(bottom)
patch.set_top(top)
self.log_patch(patch, 'new')
if head == bottom:
# reset the backup information. No logging since the
# patch hasn't changed
- patch.set_bottom(head, backup = True)
patch.set_top(top, backup = True)
else:
committer_name = committer_name,
committer_email = committer_email)
- patch.set_bottom(head, backup = True)
patch.set_top(top, backup = True)
self.log_patch(patch, 'push(f)')
if head == bottom:
# A fast-forward push. Just reset the backup
# information. No need for logging
- patch.set_bottom(bottom, backup = True)
patch.set_top(top, backup = True)
git.switch(top)
# merge can fail but the patch needs to be pushed
try:
- git.merge(bottom, head, top, recursive = True)
+ git.merge_recursive(bottom, head, top)
except git.GitException, ex:
out.error('The merge failed during "push".',
- 'Use "refresh" after fixing the conflicts or'
- ' revert the operation with "push --undo".')
+ 'Revert the operation with "stg undo".')
append_string(self.__applied_file, name)
log = 'push'
self.refresh_patch(bottom = head, cache_update = False, log = log)
else:
- # we store the correctly merged files only for
- # tracking the conflict history. Note that the
- # git.merge() operations should always leave the index
- # in a valid state (i.e. only stage 0 files)
+ # we make the patch empty, with the merged state in the
+ # working tree.
self.refresh_patch(bottom = head, cache_update = False,
- log = 'push(c)')
+ empty = True, log = 'push(c)')
raise StackException, str(ex)
return modified
- def undo_push(self):
- name = self.get_current()
- assert(name)
-
- patch = self.get_patch(name)
- old_bottom = patch.get_old_bottom()
- old_top = patch.get_old_top()
-
- # the top of the patch is changed by a push operation only
- # together with the bottom (otherwise the top was probably
- # modified by 'refresh'). If they are both unchanged, there
- # was a fast forward
- if old_bottom == patch.get_bottom() and old_top != patch.get_top():
- raise StackException, 'No undo information available'
-
- git.reset()
- self.pop_patch(name)
- ret = patch.restore_old_boundaries()
- if ret:
- self.log_patch(patch, 'undo')
-
- return ret
-
def pop_patch(self, name, keep = False):
"""Pops the top patch from the stack
"""