When there are conflicts, we want most commands to fail, since the
conflicts conceptually belong to the topmost patch. git read-tree
already checks this for us when we check out a new tree, but there are
operations where the top tree stays the same, e.g. stg new.
This patch inserts a conflict check when the tree to check out is the
same. By default, conflicts will prevent the checkout from succeeding,
but commands can choose to override this if the same patch stays on
top (for some definition of "same").
This change only affects the new-infrastructure commands; the others
always refuse to run when there are local changes of any kind.
Signed-off-by: Karl Hasselström <kha@treskal.com>
def _clean(stack, clean_applied, clean_unapplied):
def _clean(stack, clean_applied, clean_unapplied):
- trans = transaction.StackTransaction(stack, 'clean')
+ trans = transaction.StackTransaction(stack, 'clean', allow_conflicts = True)
def del_patch(pn):
if pn in stack.patchorder.applied:
if pn == stack.patchorder.applied[-1]:
def del_patch(pn):
if pn in stack.patchorder.applied:
if pn == stack.patchorder.applied[-1]:
trans.patches[name] = stack.repository.commit(new_commit_data)
trans.unapplied.insert(0, name)
trans.patches[name] = stack.repository.commit(new_commit_data)
trans.unapplied.insert(0, name)
- trans = transaction.StackTransaction(stack, 'coalesce')
+ trans = transaction.StackTransaction(stack, 'coalesce',
+ allow_conflicts = True)
push_new_patch = bool(set(patches) & set(trans.applied))
try:
new_commit_data = _coalesce_patches(trans, patches, msg, save_template)
push_new_patch = bool(set(patches) & set(trans.applied))
try:
new_commit_data = _coalesce_patches(trans, patches, msg, save_template)
raise common.CmdException('No patches to commit')
iw = stack.repository.default_iw
raise common.CmdException('No patches to commit')
iw = stack.repository.default_iw
- trans = transaction.StackTransaction(stack, 'commit')
+ 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))):
try:
common_prefix = 0
for i in xrange(min(len(stack.patchorder.applied), len(patches))):
+ list(stack.patchorder.unapplied))))
else:
parser.error('No patches specified')
+ list(stack.patchorder.unapplied))))
else:
parser.error('No patches specified')
- trans = transaction.StackTransaction(stack, 'delete')
+ 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:
try:
to_push = trans.delete_patches(lambda pn: pn in patches)
for pn in to_push:
# The patch applied, so now we have to rewrite the StGit patch
# (and any patches on top of it).
iw = stack.repository.default_iw
# 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')
+ 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)
if patchname in trans.applied:
popped = trans.applied[trans.applied.index(patchname)+1:]
assert not trans.pop_patches(lambda pn: pn in popped)
taken_names.add(pn)
patchnames.reverse()
taken_names.add(pn)
patchnames.reverse()
- trans = transaction.StackTransaction(stack, 'uncommit')
+ trans = transaction.StackTransaction(stack, 'uncommit',
+ allow_conflicts = True)
for commit, pn in zip(commits, patchnames):
trans.patches[pn] = commit
trans.applied = list(reversed(patchnames)) + trans.applied
for commit, pn in zip(commits, patchnames):
trans.patches[pn] = commit
trans.applied = list(reversed(patchnames)) + trans.applied
return self.__stack.patches.get(pn).commit
class StackTransaction(object):
return self.__stack.patches.get(pn).commit
class StackTransaction(object):
- def __init__(self, stack, msg):
+ def __init__(self, stack, msg, allow_conflicts = False):
self.__stack = stack
self.__msg = msg
self.__patches = _TransPatchMap(stack)
self.__stack = stack
self.__msg = msg
self.__patches = _TransPatchMap(stack)
self.__error = None
self.__current_tree = self.__stack.head.data.tree
self.__base = self.__stack.base
self.__error = None
self.__current_tree = self.__stack.head.data.tree
self.__base = self.__stack.base
+ if isinstance(allow_conflicts, bool):
+ self.__allow_conflicts = lambda trans: allow_conflicts
+ else:
+ self.__allow_conflicts = allow_conflicts
stack = property(lambda self: self.__stack)
patches = property(lambda self: self.__patches)
def __set_applied(self, val):
stack = property(lambda self: self.__stack)
patches = property(lambda self: self.__patches)
def __set_applied(self, val):
'This can happen if you modify a branch with git.',
'"stg repair --help" explains more about what to do next.')
self.__abort()
'This can happen if you modify a branch with git.',
'"stg repair --help" explains more about what to do next.')
self.__abort()
- if self.__current_tree != tree:
- assert iw != None
- iw.checkout(self.__current_tree, tree)
- self.__current_tree = tree
+ if self.__current_tree == tree:
+ # 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
+ iw.checkout(self.__current_tree, tree)
+ self.__current_tree = tree
@staticmethod
def __abort():
raise TransactionException(
@staticmethod
def __abort():
raise TransactionException(
self.applied.append(pn)
out.info('Pushed %s%s' % (pn, s))
if merge_conflict:
self.applied.append(pn)
out.info('Pushed %s%s' % (pn, s))
if merge_conflict:
+ # We've just caused conflicts, so we must allow them in
+ # the final checkout.
+ self.__allow_conflicts = lambda trans: True
+
self.__halt('Merge conflict')
self.__halt('Merge conflict')