In the in-memory merge branch. we'll need to call a function (`flushall`) on
the wctx inside of _xmerge.
This prepares the way so it can be done without hacks like `fcd.ctx()`.
hg-reviewers |
In the in-memory merge branch. we'll need to call a function (`flushall`) on
the wctx inside of _xmerge.
This prepares the way so it can be done without hacks like `fcd.ctx()`.
Lint Skipped |
Unit Tests Skipped |
No longer needed thanks to @martinvonz's careful eye.
Path | Packages | |||
---|---|---|---|---|
M | contrib/phabricator.py (3 lines) | |||
M | mercurial/filemerge.py (61 lines) | |||
M | mercurial/tagmerge.py (2 lines) |
raise error.Abort(_('cannot create revision for %s') % ctx) | raise error.Abort(_('cannot create revision for %s') % ctx) | ||||
return revision, diff | return revision, diff | ||||
def userphids(repo, names): | def userphids(repo, names): | ||||
"""convert user names to PHIDs""" | """convert user names to PHIDs""" | ||||
query = {'constraints': {'usernames': names}} | query = {'constraints': {'usernames': names}} | ||||
result = callconduit(repo, 'user.search', query) | result = callconduit(repo, 'user.search', query) | ||||
# username not found is not an error of the API. So check if we have missed | # username not foun | ||||
# d is not an error of the API. So check if we have missed | |||||
# some names here. | # some names here. | ||||
data = result[r'data'] | data = result[r'data'] | ||||
resolved = set(entry[r'fields'][r'username'] for entry in data) | resolved = set(entry[r'fields'][r'username'] for entry in data) | ||||
unresolved = set(names) - resolved | unresolved = set(names) - resolved | ||||
if unresolved: | if unresolved: | ||||
raise error.Abort(_('unknown username: %s') | raise error.Abort(_('unknown username: %s') | ||||
% ' '.join(sorted(unresolved))) | % ' '.join(sorted(unresolved))) | ||||
return [entry[r'phid'] for entry in data] | return [entry[r'phid'] for entry in data] |
data = util.readfile(file) | data = util.readfile(file) | ||||
style = _eoltype(data) | style = _eoltype(data) | ||||
if style: | if style: | ||||
newdata = data.replace(style, tostyle) | newdata = data.replace(style, tostyle) | ||||
if newdata != data: | if newdata != data: | ||||
util.writefile(file, newdata) | util.writefile(file, newdata) | ||||
@internaltool('prompt', nomerge) | @internaltool('prompt', nomerge) | ||||
def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): | def _iprompt(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, labels=None): | ||||
"""Asks the user which of the local `p1()` or the other `p2()` version to | """Asks the user which of the local `p1()` or the other `p2()` version to | ||||
keep as the merged version.""" | keep as the merged version.""" | ||||
ui = repo.ui | ui = repo.ui | ||||
fd = fcd.path() | fd = fcd.path() | ||||
prompts = partextras(labels) | prompts = partextras(labels) | ||||
prompts['fd'] = fd | prompts['fd'] = fd | ||||
try: | try: | ||||
if fco.isabsent(): | if fco.isabsent(): | ||||
index = ui.promptchoice( | index = ui.promptchoice( | ||||
_localchangedotherdeletedmsg % prompts, 2) | _localchangedotherdeletedmsg % prompts, 2) | ||||
choice = ['local', 'other', 'unresolved'][index] | choice = ['local', 'other', 'unresolved'][index] | ||||
elif fcd.isabsent(): | elif fcd.isabsent(): | ||||
index = ui.promptchoice( | index = ui.promptchoice( | ||||
_otherchangedlocaldeletedmsg % prompts, 2) | _otherchangedlocaldeletedmsg % prompts, 2) | ||||
choice = ['other', 'local', 'unresolved'][index] | choice = ['other', 'local', 'unresolved'][index] | ||||
else: | else: | ||||
index = ui.promptchoice( | index = ui.promptchoice( | ||||
_("keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved" | _("keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved" | ||||
" for %(fd)s?" | " for %(fd)s?" | ||||
"$$ &Local $$ &Other $$ &Unresolved") % prompts, 2) | "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2) | ||||
choice = ['local', 'other', 'unresolved'][index] | choice = ['local', 'other', 'unresolved'][index] | ||||
if choice == 'other': | if choice == 'other': | ||||
return _iother(repo, mynode, orig, fcd, fco, fca, toolconf, | return _iother(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, | ||||
labels) | labels) | ||||
elif choice == 'local': | elif choice == 'local': | ||||
return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, | return _ilocal(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, | ||||
labels) | labels) | ||||
elif choice == 'unresolved': | elif choice == 'unresolved': | ||||
return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, | return _ifail(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, | ||||
labels) | labels) | ||||
except error.ResponseExpected: | except error.ResponseExpected: | ||||
ui.write("\n") | ui.write("\n") | ||||
return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, | return _ifail(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, | ||||
labels) | labels) | ||||
@internaltool('local', nomerge) | @internaltool('local', nomerge) | ||||
def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): | def _ilocal(repo, wtcx, mynode, orig, fcd, fco, fca, toolconf, labels=None): | ||||
"""Uses the local `p1()` version of files as the merged version.""" | """Uses the local `p1()` version of files as the merged version.""" | ||||
return 0, fcd.isabsent() | return 0, fcd.isabsent() | ||||
@internaltool('other', nomerge) | @internaltool('other', nomerge) | ||||
def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): | def _iother(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, labels=None): | ||||
"""Uses the other `p2()` version of files as the merged version.""" | """Uses the other `p2()` version of files as the merged version.""" | ||||
if fco.isabsent(): | if fco.isabsent(): | ||||
# local changed, remote deleted -- 'deleted' picked | # local changed, remote deleted -- 'deleted' picked | ||||
_underlyingfctxifabsent(fcd).remove() | _underlyingfctxifabsent(fcd).remove() | ||||
deleted = True | deleted = True | ||||
else: | else: | ||||
_underlyingfctxifabsent(fcd).write(fco.data(), fco.flags()) | _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags()) | ||||
deleted = False | deleted = False | ||||
return 0, deleted | return 0, deleted | ||||
@internaltool('fail', nomerge) | @internaltool('fail', nomerge) | ||||
def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): | def _ifail(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, labels=None): | ||||
""" | """ | ||||
Rather than attempting to merge files that were modified on both | Rather than attempting to merge files that were modified on both | ||||
branches, it marks them as unresolved. The resolve command must be | branches, it marks them as unresolved. The resolve command must be | ||||
used to resolve these conflicts.""" | used to resolve these conflicts.""" | ||||
# for change/delete conflicts write out the changed version, then fail | # for change/delete conflicts write out the changed version, then fail | ||||
if fcd.isabsent(): | if fcd.isabsent(): | ||||
_underlyingfctxifabsent(fcd).write(fco.data(), fco.flags()) | _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags()) | ||||
return 1, False | return 1, False | ||||
'for %s\n') % (tool, fcd.path())) | 'for %s\n') % (tool, fcd.path())) | ||||
return False | return False | ||||
if fcd.isabsent() or fco.isabsent(): | if fcd.isabsent() or fco.isabsent(): | ||||
repo.ui.warn(_('warning: internal %s cannot merge change/delete ' | repo.ui.warn(_('warning: internal %s cannot merge change/delete ' | ||||
'conflict for %s\n') % (tool, fcd.path())) | 'conflict for %s\n') % (tool, fcd.path())) | ||||
return False | return False | ||||
return True | return True | ||||
def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode): | def _merge(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, labels, | ||||
mode): | |||||
""" | """ | ||||
Uses the internal non-interactive simple merge algorithm for merging | Uses the internal non-interactive simple merge algorithm for merging | ||||
files. It will fail if there are any conflicts and leave markers in | files. It will fail if there are any conflicts and leave markers in | ||||
the partially merged file. Markers will have two sections, one for each side | the partially merged file. Markers will have two sections, one for each side | ||||
of merge, unless mode equals 'union' which suppresses the markers.""" | of merge, unless mode equals 'union' which suppresses the markers.""" | ||||
ui = repo.ui | ui = repo.ui | ||||
r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode) | r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode) | ||||
return True, r, False | return True, r, False | ||||
@internaltool('union', fullmerge, | @internaltool('union', fullmerge, | ||||
_("warning: conflicts while merging %s! " | _("warning: conflicts while merging %s! " | ||||
"(edit, then use 'hg resolve --mark')\n"), | "(edit, then use 'hg resolve --mark')\n"), | ||||
precheck=_mergecheck) | precheck=_mergecheck) | ||||
def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): | def _iunion(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, | ||||
labels=None): | |||||
""" | """ | ||||
Uses the internal non-interactive simple merge algorithm for merging | Uses the internal non-interactive simple merge algorithm for merging | ||||
files. It will use both left and right sides for conflict regions. | files. It will use both left and right sides for conflict regions. | ||||
No markers are inserted.""" | No markers are inserted.""" | ||||
return _merge(repo, mynode, orig, fcd, fco, fca, toolconf, | return _merge(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, | ||||
files, labels, 'union') | files, labels, 'union') | ||||
@internaltool('merge', fullmerge, | @internaltool('merge', fullmerge, | ||||
_("warning: conflicts while merging %s! " | _("warning: conflicts while merging %s! " | ||||
"(edit, then use 'hg resolve --mark')\n"), | "(edit, then use 'hg resolve --mark')\n"), | ||||
precheck=_mergecheck) | precheck=_mergecheck) | ||||
def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): | def _imerge(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, | ||||
labels=None): | |||||
""" | """ | ||||
Uses the internal non-interactive simple merge algorithm for merging | Uses the internal non-interactive simple merge algorithm for merging | ||||
files. It will fail if there are any conflicts and leave markers in | files. It will fail if there are any conflicts and leave markers in | ||||
the partially merged file. Markers will have two sections, one for each side | the partially merged file. Markers will have two sections, one for each side | ||||
of merge.""" | of merge.""" | ||||
return _merge(repo, mynode, orig, fcd, fco, fca, toolconf, | return _merge(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, | ||||
files, labels, 'merge') | files, labels, 'merge') | ||||
@internaltool('merge3', fullmerge, | @internaltool('merge3', fullmerge, | ||||
_("warning: conflicts while merging %s! " | _("warning: conflicts while merging %s! " | ||||
"(edit, then use 'hg resolve --mark')\n"), | "(edit, then use 'hg resolve --mark')\n"), | ||||
precheck=_mergecheck) | precheck=_mergecheck) | ||||
def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): | def _imerge3(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, | ||||
labels=None): | |||||
""" | """ | ||||
Uses the internal non-interactive simple merge algorithm for merging | Uses the internal non-interactive simple merge algorithm for merging | ||||
files. It will fail if there are any conflicts and leave markers in | files. It will fail if there are any conflicts and leave markers in | ||||
the partially merged file. Marker will have three sections, one from each | the partially merged file. Marker will have three sections, one from each | ||||
side of the merge and one for the base content.""" | side of the merge and one for the base content.""" | ||||
if not labels: | if not labels: | ||||
labels = _defaultconflictlabels | labels = _defaultconflictlabels | ||||
if len(labels) < 3: | if len(labels) < 3: | ||||
labels.append('base') | labels.append('base') | ||||
return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels) | return _imerge(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, | ||||
labels) | |||||
def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files, | def _imergeauto(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, | ||||
labels=None, localorother=None): | labels=None, localorother=None): | ||||
""" | """ | ||||
Generic driver for _imergelocal and _imergeother | Generic driver for _imergelocal and _imergeother | ||||
""" | """ | ||||
assert localorother is not None | assert localorother is not None | ||||
tool, toolpath, binary, symlink = toolconf | tool, toolpath, binary, symlink = toolconf | ||||
r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels, | r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels, | ||||
localorother=localorother) | localorother=localorother) | ||||
of the other `p2()` changes.""" | of the other `p2()` changes.""" | ||||
success, status = _imergeauto(localorother='other', *args, **kwargs) | success, status = _imergeauto(localorother='other', *args, **kwargs) | ||||
return success, status, False | return success, status, False | ||||
@internaltool('tagmerge', mergeonly, | @internaltool('tagmerge', mergeonly, | ||||
_("automatic tag merging of %s failed! " | _("automatic tag merging of %s failed! " | ||||
"(use 'hg resolve --tool :merge' or another merge " | "(use 'hg resolve --tool :merge' or another merge " | ||||
"tool of your choice)\n")) | "tool of your choice)\n")) | ||||
def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): | def _itagmerge(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, | ||||
labels=None): | |||||
""" | """ | ||||
Uses the internal tag merge algorithm (experimental). | Uses the internal tag merge algorithm (experimental). | ||||
""" | """ | ||||
success, status = tagmerge.merge(repo, fcd, fco, fca) | success, status = tagmerge.merge(repo, wctx, fcd, fco, fca) | ||||
return success, status, False | return success, status, False | ||||
@internaltool('dump', fullmerge) | @internaltool('dump', fullmerge) | ||||
def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): | def _idump(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, | ||||
labels=None): | |||||
""" | """ | ||||
Creates three versions of the files to merge, containing the | Creates three versions of the files to merge, containing the | ||||
contents of local, other and base. These files can then be used to | contents of local, other and base. These files can then be used to | ||||
perform a merge manually. If the file to be merged is named | perform a merge manually. If the file to be merged is named | ||||
``a.txt``, these files will accordingly be named ``a.txt.local``, | ``a.txt``, these files will accordingly be named ``a.txt.local``, | ||||
``a.txt.other`` and ``a.txt.base`` and they will be placed in the | ``a.txt.other`` and ``a.txt.base`` and they will be placed in the | ||||
same directory as ``a.txt``. | same directory as ``a.txt``. | ||||
This implies permerge. Therefore, files aren't dumped, if premerge | This implies permerge. Therefore, files aren't dumped, if premerge | ||||
runs successfully. Use :forcedump to forcibly write files out. | runs successfully. Use :forcedump to forcibly write files out. | ||||
""" | """ | ||||
a = _workingpath(repo, fcd) | a = _workingpath(repo, fcd) | ||||
fd = fcd.path() | fd = fcd.path() | ||||
util.writefile(a + ".local", repo.wwritedata(fcd.path(), fcd.data())) | util.writefile(a + ".local", repo.wwritedata(fcd.path(), fcd.data())) | ||||
repo.wwrite(fd + ".other", fco.data(), fco.flags()) | repo.wwrite(fd + ".other", fco.data(), fco.flags()) | ||||
repo.wwrite(fd + ".base", fca.data(), fca.flags()) | repo.wwrite(fd + ".base", fca.data(), fca.flags()) | ||||
return False, 1, False | return False, 1, False | ||||
@internaltool('forcedump', mergeonly) | @internaltool('forcedump', mergeonly) | ||||
def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files, | def _forcedump(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, | ||||
labels=None): | labels=None): | ||||
""" | """ | ||||
Creates three versions of the files as same as :dump, but omits premerge. | Creates three versions of the files as same as :dump, but omits premerge. | ||||
""" | """ | ||||
return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, | return _idump(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, | ||||
labels=labels) | labels=labels) | ||||
def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): | def _xmerge(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, files, | ||||
labels=None): | |||||
tool, toolpath, binary, symlink = toolconf | tool, toolpath, binary, symlink = toolconf | ||||
if fcd.isabsent() or fco.isabsent(): | if fcd.isabsent() or fco.isabsent(): | ||||
repo.ui.warn(_('warning: %s cannot merge change/delete conflict ' | repo.ui.warn(_('warning: %s cannot merge change/delete conflict ' | ||||
'for %s\n') % (tool, fcd.path())) | 'for %s\n') % (tool, fcd.path())) | ||||
return False, 1, None | return False, 1, None | ||||
unused, unused, unused, back = files | unused, unused, unused, back = files | ||||
a = _workingpath(repo, fcd) | a = _workingpath(repo, fcd) | ||||
b, c = _maketempfiles(repo, fco, fca) | b, c = _maketempfiles(repo, fco, fca) | ||||
func = _xmerge | func = _xmerge | ||||
mergetype = fullmerge | mergetype = fullmerge | ||||
onfailure = _("merging %s failed!\n") | onfailure = _("merging %s failed!\n") | ||||
precheck = None | precheck = None | ||||
toolconf = tool, toolpath, binary, symlink | toolconf = tool, toolpath, binary, symlink | ||||
if mergetype == nomerge: | if mergetype == nomerge: | ||||
r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels) | r, deleted = func(repo, wctx, mynode, orig, fcd, fco, fca, toolconf, | ||||
labels) | |||||
return True, r, deleted | return True, r, deleted | ||||
if premerge: | if premerge: | ||||
if orig != fco.path(): | if orig != fco.path(): | ||||
ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd)) | ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd)) | ||||
else: | else: | ||||
ui.status(_("merging %s\n") % fd) | ui.status(_("merging %s\n") % fd) | ||||
if markerstyle != 'basic': | if markerstyle != 'basic': | ||||
labels = _formatlabels(repo, fcd, fco, fca, labels) | labels = _formatlabels(repo, fcd, fco, fca, labels) | ||||
if premerge and mergetype == fullmerge: | if premerge and mergetype == fullmerge: | ||||
r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels) | r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels) | ||||
# complete if premerge successful (r is 0) | # complete if premerge successful (r is 0) | ||||
return not r, r, False | return not r, r, False | ||||
needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca, | needcheck, r, deleted = func(repo, wctx, mynode, orig, fcd, fco, fca, | ||||
toolconf, files, labels=labels) | toolconf, files, labels=labels) | ||||
if needcheck: | if needcheck: | ||||
r = _check(repo, r, ui, tool, fcd, files) | r = _check(repo, r, ui, tool, fcd, files) | ||||
if r: | if r: | ||||
if onfailure: | if onfailure: | ||||
ui.warn(onfailure % fd) | ui.warn(onfailure % fd) |
# the merged node list has 3 parts: | # the merged node list has 3 parts: | ||||
# - common nodes | # - common nodes | ||||
# - non common lowest ranking nodes | # - non common lowest ranking nodes | ||||
# - non common highest ranking nodes | # - non common highest ranking nodes | ||||
# note that the common nodes plus the non common lowest ranking nodes is the | # note that the common nodes plus the non common lowest ranking nodes is the | ||||
# whole list of lr nodes | # whole list of lr nodes | ||||
return lrnodes + hrnodes[commonidx:] | return lrnodes + hrnodes[commonidx:] | ||||
def merge(repo, fcd, fco, fca): | def merge(repo, wctx, fcd, fco, fca): | ||||
''' | ''' | ||||
Merge the tags of two revisions, taking into account the base tags | Merge the tags of two revisions, taking into account the base tags | ||||
Try to minimize the diff between the merged tags and the first parent tags | Try to minimize the diff between the merged tags and the first parent tags | ||||
''' | ''' | ||||
ui = repo.ui | ui = repo.ui | ||||
# read the p1, p2 and base tags | # read the p1, p2 and base tags | ||||
# only keep the line numbers for the p1 tags | # only keep the line numbers for the p1 tags | ||||
p1tags = readtagsformerge( | p1tags = readtagsformerge( |