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 (64 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 | return revision | ||||
| 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.""" | ||||
| a, b, c, back = files | a, b, c, back = files | ||||
| ui = repo.ui | ui = repo.ui | ||||
| r = simplemerge.simplemerge(ui, a, b, c, fcd, fca, fco, | r = simplemerge.simplemerge(ui, a, b, c, fcd, fca, fco, | ||||
| label=labels, mode=mode, repo=repo) | label=labels, mode=mode, repo=repo) | ||||
| 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 | ||||
| a, b, c, back = files | a, b, c, back = files | ||||
| r = simplemerge.simplemerge(repo.ui, a, b, c, fcd, fca, fco, | r = simplemerge.simplemerge(repo.ui, a, b, c, fcd, fca, fco, | ||||
| 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, b, c, back = files | a, b, c, back = files | ||||
| fd = fcd.path() | fd = fcd.path() | ||||
| util.copyfile(a, a + ".local") | util.copyfile(a, a + ".local") | ||||
| 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 | ||||
| a, b, c, back = files | a, b, c, back = files | ||||
| out = "" | out = "" | ||||
| env = {'HG_FILE': fcd.path(), | env = {'HG_FILE': fcd.path(), | ||||
| 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(r, ui, tool, fcd, files) | r = _check(r, ui, tool, fcd, files) | ||||
| if r: | if r: | ||||
| if onfailure: | if onfailure: | ||||
| ui.warn(onfailure % fd) | ui.warn(onfailure % fd) | ||||
| r = 1 | r = 1 | ||||
| if back is not None and _toolbool(ui, tool, "fixeol"): | if back is not None and _toolbool(ui, tool, "fixeol"): | ||||
| _matcheol(a, back) | _matcheol(a, back) | ||||
| return r | return r | ||||
| def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None): | def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None): | ||||
| return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels) | return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca, | ||||
| labels=labels) | |||||
| def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None): | def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None): | ||||
| return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca, | return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca, | ||||
| labels=labels) | labels=labels) | ||||
| # tell hggettext to extract docstrings from these functions: | # tell hggettext to extract docstrings from these functions: | ||||
| i18nfunctions = internals.values() | i18nfunctions = internals.values() | ||||
| # 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( | ||||