##// END OF EJS Templates
merge: move almost all change/delete conflicts to resolve phase (BC) (API)...
merge: move almost all change/delete conflicts to resolve phase (BC) (API) We have finally laid all the groundwork to make this happen. The only change/delete conflicts that haven't been moved are .hgsubstate conflicts. Those are trickier to deal with and well outside the scope of this series. We add comprehensive testing not just for the initial selections but also for re-resolves and all possible dirstate transitions caused by merge tools. That testing managed to shake out several bugs in the way we were handling dirstate transitions. The other test changes are because we now treat change/delete conflicts as proper merges, and increment the 'merged' counter rather than the 'updated' counter. I believe this is the right approach here. For third-party extensions, if they're interacting with filemerge code they might have to deal with an absentfilectx rather than a regular filectx. Still to come: - add a 'leave unresolved' option to merges - change the default for non-interactive change/delete conflicts to be 'leave unresolved' - add debug output to go alongside debug outputs for binary and symlink file merges

File last commit:

r26728:e8f1b728 default
r27137:25e4b2f0 default
Show More
destutil.py
200 lines | 7.3 KiB | text/x-python | PythonLexer
Pierre-Yves David
update: move default destination computation to a function...
r26569 # destutil.py - Mercurial utility function for command destination
#
# Copyright Matt Mackall <mpm@selenic.com> and other
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from .i18n import _
from . import (
Pierre-Yves David
destupdate: also include bookmark related logic...
r26641 bookmarks,
Pierre-Yves David
update: move default destination computation to a function...
r26569 error,
obsolete,
)
Pierre-Yves David
destupdate: extract validation logic...
r26720 def _destupdatevalidate(repo, rev, clean, check):
"""validate that the destination comply to various rules
This exists as its own function to help wrapping from extensions."""
wc = repo[None]
p1 = wc.p1()
if not clean:
# Check that the update is linear.
#
# Mercurial do not allow update-merge for non linear pattern
# (that would be technically possible but was considered too confusing
# for user a long time ago)
#
# See mercurial.merge.update for details
if p1.rev() not in repo.changelog.ancestors([rev], inclusive=True):
dirty = wc.dirty(missing=True)
foreground = obsolete.foreground(repo, [p1.node()])
if not repo[rev].node() in foreground:
if dirty:
msg = _("uncommitted changes")
hint = _("commit and merge, or update --clean to"
" discard changes")
raise error.UpdateAbort(msg, hint=hint)
elif not check: # destination is not a descendant.
msg = _("not a linear update")
hint = _("merge or update --check to force update")
raise error.UpdateAbort(msg, hint=hint)
Pierre-Yves David
destupdate: extract logic based on obsolescence marker in its own function...
r26723 def _destupdateobs(repo, clean, check):
"""decide of an update destination from obsolescence markers"""
Pierre-Yves David
update: move default destination computation to a function...
r26569 node = None
wc = repo[None]
p1 = wc.p1()
Pierre-Yves David
destupdate: extract logic based on obsolescence marker in its own function...
r26723 movemark = None
Pierre-Yves David
update: move default destination computation to a function...
r26569
if p1.obsolete() and not p1.children():
# allow updating to successors
successors = obsolete.successorssets(repo, p1.node())
# behavior of certain cases is as follows,
#
# divergent changesets: update to highest rev, similar to what
# is currently done when there are more than one head
# (i.e. 'tip')
#
# replaced changesets: same as divergent except we know there
# is no conflict
#
# pruned changeset: no update is done; though, we could
# consider updating to the first non-obsolete parent,
# similar to what is current done for 'hg prune'
if successors:
# flatten the list here handles both divergent (len > 1)
# and the usual case (len = 1)
successors = [n for sub in successors for n in sub]
# get the max revision for the given successors set,
# i.e. the 'tip' of a set
node = repo.revs('max(%ln)', successors).first()
Pierre-Yves David
destupdate: move obsolete handling first...
r26722 if bookmarks.isactivewdirparent(repo):
movemark = repo['.'].node()
Pierre-Yves David
destupdate: extract logic based on obsolescence marker in its own function...
r26723 return node, movemark, None
Pierre-Yves David
destupdate: extract logic based on bookmarks in its own function...
r26724 def _destupdatebook(repo, clean, check):
"""decide on an update destination from active bookmark"""
# we also move the active bookmark, if any
activemark = None
node, movemark = bookmarks.calculateupdate(repo.ui, repo, None)
if node is not None:
activemark = node
return node, movemark, activemark
Pierre-Yves David
destupdate: extract logic based on branch in its own function...
r26725 def _destupdatebranch(repo, clean, check):
"""decide on an update destination from current branch"""
wc = repo[None]
movemark = node = None
try:
node = repo.branchtip(wc.branch())
if bookmarks.isactivewdirparent(repo):
movemark = repo['.'].node()
except error.RepoLookupError:
if wc.branch() == 'default': # no default branch!
node = repo.lookup('tip') # update to tip
else:
raise error.Abort(_("branch %s not found") % wc.branch())
return node, movemark, None
Pierre-Yves David
destupdate: have a generic and extensible way to run each step...
r26726 # order in which each step should be evalutated
# steps are run until one finds a destination
destupdatesteps = ['evolution', 'bookmark', 'branch']
# mapping to ease extension overriding steps.
destupdatestepmap = {'evolution': _destupdateobs,
'bookmark': _destupdatebook,
'branch': _destupdatebranch,
}
Pierre-Yves David
destupdate: extract logic based on obsolescence marker in its own function...
r26723 def destupdate(repo, clean=False, check=False):
"""destination for bare update operation
return (rev, movemark, activemark)
- rev: the revision to update to,
- movemark: node to move the active bookmark from
(cf bookmark.calculate update),
- activemark: a bookmark to activate at the end of the update.
"""
Pierre-Yves David
destupdate: have a generic and extensible way to run each step...
r26726 node = movemark = activemark = None
Pierre-Yves David
destupdate: extract logic based on obsolescence marker in its own function...
r26723
Pierre-Yves David
destupdate: have a generic and extensible way to run each step...
r26726 for step in destupdatesteps:
node, movemark, activemark = destupdatestepmap[step](repo, clean, check)
if node is not None:
break
Pierre-Yves David
destupdate: move the check related to the "clean" logic in the function...
r26628 rev = repo[node].rev()
Pierre-Yves David
destupdate: extract validation logic...
r26720 _destupdatevalidate(repo, rev, clean, check)
Pierre-Yves David
destupdate: move the check related to the "clean" logic in the function...
r26628
Pierre-Yves David
destupdate: also include bookmark related logic...
r26641 return rev, movemark, activemark
Pierre-Yves David
destutil: move default merge destination into a function...
r26714
Pierre-Yves David
destmerge: extract logic based on bookmark into its own function...
r26727 def _destmergebook(repo):
"""find merge destination in the active bookmark case"""
node = None
bmheads = repo.bookmarkheads(repo._activebookmark)
curhead = repo[repo._activebookmark].node()
if len(bmheads) == 2:
if curhead == bmheads[0]:
node = bmheads[1]
else:
node = bmheads[0]
elif len(bmheads) > 2:
raise error.Abort(_("multiple matching bookmarks to merge - "
"please merge with an explicit rev or bookmark"),
hint=_("run 'hg heads' to see all heads"))
elif len(bmheads) <= 1:
raise error.Abort(_("no matching bookmark to merge - "
"please merge with an explicit rev or bookmark"),
hint=_("run 'hg heads' to see all heads"))
assert node is not None
return node
Pierre-Yves David
destmerge: extract logic based on branch heads in its own function...
r26728 def _destmergebranch(repo):
"""find merge destination based on branch heads"""
node = None
branch = repo[None].branch()
bheads = repo.branchheads(branch)
nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
if len(nbhs) > 2:
raise error.Abort(_("branch '%s' has %d heads - "
"please merge with an explicit rev")
% (branch, len(bheads)),
hint=_("run 'hg heads .' to see heads"))
parent = repo.dirstate.p1()
if len(nbhs) <= 1:
if len(bheads) > 1:
raise error.Abort(_("heads are bookmarked - "
"please merge with an explicit rev"),
hint=_("run 'hg heads' to see all heads"))
if len(repo.heads()) > 1:
raise error.Abort(_("branch '%s' has one head - "
"please merge with an explicit rev")
% branch,
hint=_("run 'hg heads' to see all heads"))
msg, hint = _('nothing to merge'), None
if parent != repo.lookup(branch):
hint = _("use 'hg update' instead")
raise error.Abort(msg, hint=hint)
if parent not in bheads:
raise error.Abort(_('working directory not at a head revision'),
hint=_("use 'hg update' or merge with an "
"explicit revision"))
if parent == nbhs[0]:
node = nbhs[-1]
else:
node = nbhs[0]
assert node is not None
return node
Pierre-Yves David
destutil: move default merge destination into a function...
r26714 def destmerge(repo):
if repo._activebookmark:
Pierre-Yves David
destmerge: extract logic based on bookmark into its own function...
r26727 node = _destmergebook(repo)
Pierre-Yves David
destutil: move default merge destination into a function...
r26714 else:
Pierre-Yves David
destmerge: extract logic based on branch heads in its own function...
r26728 node = _destmergebranch(repo)
Pierre-Yves David
destutil: move default merge destination into a function...
r26714 return repo[node].rev()