Commits

ZyX_I committed 765536a

Start merge branch that will eventually contain merge and histedit implementations

Ref #88
Ref #89

  • Participants
  • Parent commits 9f914e6
  • Branches merge

Comments (0)

Files changed (7)

File aurum-addon-info.txt

         "autoload/aurum/file.vim",
         "autoload/aurum/grep.vim",
         "autoload/aurum/hyperlink.vim",
+        "autoload/aurum/integrate.vim",
         "autoload/aurum/junk.vim",
         "autoload/aurum/lineutils.vim",
         "autoload/aurum/log.vim",

File autoload/aurum/drivers/mercurial.vim

             \'failcfg': 'Failed to get property %s of repository %s',
             \'nlocbms': 'Bookmarks can’t be local',
             \'anbnimp': 'Can only get branch property using agetrepoprop',
+            \     'nd': 'Failed to run merge: %s',
             \'parsefail': 'Failed to parse changeset information',
             \ 'filefail': 'Failed to get file %s '.
             \             'from the repository %s: %s',
             \ 'nogitrev': 'No git revision associated with revision %s '.
             \             'in repository %s',
             \ 'uknphase': 'Unknown phase: %s. Known phases: %s.',
+            \
+            \'tmpunsup': '%s is temporary not supported'
         \}
 "▶1 Options
 let s:_options={
     endif
     return s:F.runcmd(a:repo, cmd, [], kwargs)
 endfunction
+"▶1 hg.merge :: [rev[, usemarkers]] → mergedict
+if s:usepythondriver "▶2
+function s:hg.merge(repo, ...)
+    let d={}
+    try
+        execute s:pya.'merge(vim.eval("a:repo.path")'
+                    \    ','.(a:0   && a:1 isnot 0 ? 'vim.eval("a:1")' : 'None')
+                    \    ','.(a:0>1 && a:2 isnot 0 ? 'vim.eval("a:2")' : 'None')
+                    \.')'
+    endtry
+    return d
+endfunction
+else "▶2
+endif
+"▶1 hg.resolve :: [file]
+function s:hg.resolve(repo, ...)
+    return s:F.runcmd(a:repo, 'resolve', a:0 ? (['--']+a:000) : [],
+                \{'mark': 1, 'all': !a:0})
+endfunction
 "▶1 hg.branch :: repo, branchname, force → + FS
 function s:hg.branch(repo, branch, force)
     return s:F.runcmd(a:repo, 'branch', [a:branch], {'force': !!a:force})

File autoload/aurum/integrate.vim

+"▶1 
+scriptencoding utf-8
+execute frawor#Setup('0.1', {'@%aurum/cmdutils': '4.3',
+            \                '@%aurum/maputils': '0.1',
+            \                 '@%aurum/bufvars': '0.0',
+            \               '@%aurum/lineutils': '0.0',
+            \                    '@%aurum/edit': '1.2',
+            \                           '@/fwc': '0.0',
+            \                            '@/os': '0.0',
+            \                      '@/mappings': '0.0',})
+let s:_messages={
+            \'unresolved': 'Unable to commit: not all conflicts were resolved',
+        \}
+"▶1 genstatus
+function s:F.genstatus(mergedict)
+    let files=[]
+    let statuses=[]
+    let lines=[]
+    for [file, st] in a:mergedict.filemap
+        let [conflicted, curstatus, otherstatus]=st
+        let line=     (conflicted ? '!' : ' ')
+                    \.toupper(curstatus[0])
+                    \.toupper(otherstatus)[0]
+                    \.' '.file
+        call add(files, file)
+        call add(statuses, st)
+        call add(lines, line)
+    endfor
+    return [files, statuses, lines]
+endfunction
+"▶1 setup
+function s:F.setup(read, repo, rev, method)
+    let mergedict=a:repo.functions.merge((empty(a:rev) ? 0 : a:rev),
+                \                        a:method is# 'markers')
+    let bvar={}
+    let [files, statuses, lines]=s:F.genstatus(mergedict)
+    let bvar.files=files
+    let bvar.statuses=statuses
+    let bvar.lines=lines
+    call setline('.', lines)
+    return bvar
+endfunction
+"▶1 aurum://integrate
+call s:_f.newcommand({
+            \'function': s:F.setup,
+            \'arguments': 2,
+            \'filetype': 'aurumintegrate',
+            \'readable': 0,
+        \})
+"▶1 unload
+function s:F.unload(bvar)
+    let sbvar=get(a:bvar, 'sbvar', a:bvar)
+    let sbuf=get(a:bvar, 'sbuf', -1)
+    if sbuf isnot 0 && bufexists(sbuf)
+        unlet sbvar.bwfunc
+        execute 'bwipeout!' sbuf
+    endif
+endfunction
+"▶1 getcontents :: mergedict, file, type → [line]
+" type is one of "local", "base", "other"
+function s:F.getcontents(repo, mergedict, file, type)
+    if !has_key(a:mergedict, a:file)
+        return readfile(s:_r.os.path.join(a:repo.path, a:file), 'b')
+    endif
+    let descr=a:mergedict[a:file][a:type]
+    if type(descr)==type(0)
+        return []
+    elseif type(descr)==type('')
+        return readfile(desrc, 'b')
+    elseif type(descr)==type([])
+        return desrc
+    endif
+endfunction
+"▶1 Methods
+let s:F.methods={}
+let common
+for [s:name, s:tabdesc] in [
+            \['2way', {
+            \      'top': ['AuIntegrateCurrent', 'AuIntegrateOther'],
+            \   'bottom': 'AuIntegrateStatus',
+            \  'oprefix': 'int',
+            \   'unload': s:F.unload,
+            \}],
+            \['3way', {
+            \      'top': ['AuIntegrateCurrent', 'AuIntegrateBase',
+            \              'AuIntegrateOther'],
+            \   'bottom': 'AuIntegrateStatus',
+            \  'oprefix': 'int',
+            \   'unload': s:F.unload,
+            \}],
+            \['markers', {
+            \      'top': ['AuIntegrateCurrent', 'AuIntegrateOther'],
+            \   'bottom': 'AuIntegrateStatus',
+            \  'oprefix': 'int',
+            \   'unload': s:F.unload,
+            \}],
+        \]
+    call s:_f.tab.new('AuIntegrate_'.s:name, s:tabdesc)
+endfor
+unlet s:name s:tabdesc
+"▶2 2way
+let s:F.methods.2way={}
+function s:F.methods.2way.open(bvar, file)
+endfunction
+"▶2 3way
+let s:F.methods.3way={}
+function s:F.methods.3way.open(bvar, file)
+endfunction
+"▶2 markers
+let s:F.methods.markers={}
+function s:F.methods.markers.open(bvar, file)
+endfunction
+"▶1 resetlines
+function s:F.resetlines(bvar)
+    for idx in range(len(a:bvar.lines))
+        call setline(idx+1, s:statchars[a:bvar.statuses[idx]].a:bvar.lines[idx])
+    endfor
+endfunction
+"▶1 run
+let s:statchars='UR r'
+function s:F.run(cmd, opts)
+    let usemarkers=(a:opts.method is# 'markers')
+    let [repo, rev] = s:_r.cmdutils.getrrf({}, 'norev', 'getrr')[1:2]
+    let mergedict=repo.functions.merge(repo, rev, usemarkers)
+    let cs=repo.functions.getcs(repo, rev)
+    let sopts={'prefix': 'U'}
+    call s:_r.run(a:cmd, 'status', repo, sopts)
+    setlocal nomodifiable
+    let bvar=s:_r.bufvars[bufnr('%')]
+    let bvar.foreignactions=s:foreignactions
+    let bvar.foreignmap=s:F.runstatmap
+    let bvar.method=a:opts.method
+    let bvar.mergedict=mergedict
+    let bvar.statuses=map(copy(bvar.files), '1-get(mergedict, v:val, -1)')
+    let bvar.copts={}
+    if has_key(a:opts, 'message')
+        let bvar.copts.message=a:opts.message
+    else
+        " TODO Include all commit messages from merged revisions
+        let bvar.copts.message='Merge revision '.
+                    \(repo.hasrevisions ? (cs.rev.' ('.cs.hex.')') : cs.hex)
+        let bvar.copts.amend=1
+    endif
+    call s:F.resetlines(a:bvar)
+    call s:_r.undo.setup(bvar, s:_f.getoption('fullundo'))
+endfunction
+"▶1 AuIntegrate
+let s:_aufunctions.cmd={'@FWC': ['-onlystrings '.
+            \'{ ?repo    '.s:_r.cmdutils.comp.repo.
+            \'  ?rev     '.s:_r.cmdutils.comp.rev.
+            \'  ?method  :"2way" key F.methods'.
+            \'  ?message type""'.
+            \' !?commit'.
+            \'}', 'filter']}
+let s:_aufunctions.comp=s:_r.cmdutils.gencompfunc(s:_aufunctions.cmd['@FWC'][0],
+            \                                     [], s:_f.fwc.compile)
+function s:_aufunctions.cmd.function(opts)
+    let layout='AuIntegrate_'.a:opts.method
+    call s:_f.tab.create(layout, run, [a:opts])
+endfunction
+"▶1 runstatmap
+let s:foreignactions={
+            \ 'track': 1,
+            \'commit': 1,
+            \'forget': 1,
+        \}
+function s:F.runstatmap(action)
+    "▶2 buf, bvar
+    let buf=get(a:000, 0, bufnr('%'))
+    let bvar=s:_r.bufvars[buf]
+    if !a:0
+        if !s:_r.undo.preaction(bvar)
+            return
+        endif
+    endif
+    "▶2 undo
+    let isundo=has_key(s:uactions, a:action)
+    if isundo
+        if !s:_r.undo.preundoaction(bvar, s:uactions[a:action])
+            return
+        endif
+    endif
+    "▶2 open action
+    if a:action is# 'open'
+        " return s:F.methods[]
+    "▶2 commit action
+    elseif a:actios is# 'commit'
+        if filter(copy(bvar.statuses), '!v:val')
+            call s:_f.throw('unresolved')
+        endif
+        call bvar.repo.functions.resolve(bvar.repo)
+        let winview=winsaveview()
+        " TODO Include information about conflicts into bvar.copts.message
+        " Note: decide what to do if bvar.copts.message was populated from 
+        " command-line
+        let r=s:_f.tab.copen(a:bvar, a:buf, s:ntypeskeys, {})
+        if r
+            call s:F.unload(bvar)
+        endif
+    endif
+endfunction
+"▶1 runleftmap
+function s:F.runleftmap(action, ...)
+    let wnrs=s:_f.tab.getwnrs()
+    let swnr=wnrs[-1]
+    let sbuf=winbufnr(swnr)
+    let sbvar=s:_r.bufvars[sbuf]
+    if a:action is# 'discard'
+    elseif a:action is# 'exit'
+    elseif a:action is# 'diffget'
+        let wnr=wnrs[1+(a:1 is# 'other')]
+        let buf=winbufnr(wnr)
+        execute 'diffget' buf
+    endif
+endfunction
+"▶1 AuIntegrate mappings
+function s:F.gm(...)
+    return ':<C-u>call call(<SID>Eval("s:F.runstatmap"),'.string(a:000).','.
+                \          '{})<CR>'
+endfunction
+function s:F.gml(...)
+    return ':<C-u>call call(<SID>Eval("s:F.runleftmap"),'.string(a:000).','.
+                \          '{})<CR>'
+endfunction
+call s:_f.mapgroup.add('AuIntegrateLeft3Way', {
+            \'DiffGetOther': {'lhs': 'do', 'rhs': s:F.gml('diffget', 'other')},
+            \'DiffGetBase':  {'lhs': 'dO', 'rhs': s:F.gml('diffget', 'base' )},
+        \}, {'silent': 1, 'mode': 'n', 'dontmap': 1,})
+call s:_f.mapgroup.add('AuIntegrateLeft', {
+            \'Discard': {'lhs': 'x', 'rhs': s:F.gml('discard')},
+            \'Exit':    {'lhs': 'X', 'rhs': s:F.gml('exit')},
+        \}, {'silent': 1, 'mode': 'n', 'dontmap': 1, 'leader': '<Leader>'})
+call s:_f.mapgroup.add('AuIntegrate', {
+            \   'Edit': {'lhs': 'O',     'rhs': s:F.gm('edit')   },
+            \   'Undo': {'lhs': 'u',     'rhs': s:F.gm('undo')   },
+            \   'Redo': {'lhs': '<C-r>', 'rhs': s:F.gm('redo')   },
+            \'Earlier': {'lhs': 'g-',    'rhs': s:F.gm('earlier')},
+            \  'Later': {'lhs': 'g+',    'rhs': s:F.gm('later')  },
+        \}, {'silent': 1})
+"▶1
+call frawor#Lockvar(s:, '_r,_pluginloaded')
+" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

File autoload/aurum/repo.vim

 "▶1
-execute frawor#Setup('5.6', { '@/resources': '0.0',
+execute frawor#Setup('5.7', { '@/resources': '0.0',
             \                        '@/os': '0.0',
             \                   '@/options': '0.0',
             \                    '@/python': '1.0',
 let s:requiredfuncs=['repo', 'getcs', 'checkdir']
 let s:optfuncs=['readfile', 'annotate', 'diff', 'status', 'commit', 'update',
             \   'diffre', 'getrepoprop', 'forget', 'branch', 'label',
-            \   'push', 'pull', 'strip']
+            \   'push', 'pull', 'strip', 'merge', 'resolve']
 "▶2 regdriver :: {f}, name, funcs → + s:drivers
 function s:F.regdriver(plugdict, fdict, name, funcs)
     "▶3 Check arguments

File doc/aurum.txt

         6.1. Built-in template styles                |aurum-logstyles|
     7. Special tabs
         7.1. Record tab                              |aurum-record|
+        7.2. Integrate tab                           |aurum-integrate|
     8. Internal objects                              |aurum-objects|
         8.1. Repository                              |aurum-repository|
         8.2. Changeset                               |aurum-changeset|
     Note: you can extend list of supported remote repositories by using 
           |g:aurum_hypsites| option.
 
+AuIntegrate {opts}                                              *:AuIntegrate*
+    Merge given revision (defaults to the default of underlying VCS command) 
+    into current branch. Arguments:
+    Argument  Description ~
+    method    String, method used to resolve conflicts. Is one of
+              Method  Description ~
+              3-way   3-window merge (current, base, other). Default.
+              2-way   2-window merge (current, other).
+              markers 2-window merge, but this variant first lets VCS merge 
+                      whatever it can. Then it takes partly merged file 
+                      containing conflict markers and splits it into 
+                      2 windows: one with version between “<<<…” and “===…”, 
+                      second between “===…” and “>>>…” markers. If file 
+                      already had these markers in the committed version 
+                      behavior is undefined, use 3-way or 2-way merges for 
+                      such files. If conflict resolution is requested for 
+                      files without conflicts 2-way merge is used.
+    rev       String, revision to merge with. Defaults to underlying VCS 
+              default.
+    message   String, commit message to use.
+    [no]commit
+              Flag. If unset, always opens tab for resolving conflicts. 
+              Default: set.
+    repo      Path. Repository that should be used.
+
 AuJunk [forget] [ignore] [remove] [ignoreglobs] glob1 [...]          *:AuJunk*
     Forget, ignore or remove (default: remove) files matching given globs 
     (specify “:” to act on current file). With “ignoreglobs” it will ignore 
     Default: 0.
 
 recheight                                                  *g:aurum_recheight*
+intheight                                                  *g:aurum_intheight*
     VimL |expression| that evaluates to unsigned integer or unsigned integer. 
-    Determines height of status window shown in record tab (|aurum-record|).
+    Determines height of status window shown in record tab (|aurum-record|) 
+    (recheight) or in integrate tab (|aurum-integrate|) (intheight).
     Default: winheight/5 (in empty tab |winheight()| will return roughly the 
              same value as 'lines').
 
 recautowrite                                            *g:aurum_recautowrite*
+intautowrite                                            *g:aurum_intautowrite*
     Boolean. Determines whether files opened for editing should be 
     automatically written on |BufWinLeave| and |BufLeave| events in record tab 
-    (|aurum-record|) (only files in top left window are supported; others are 
-    not modifiable anyway).
-    See |aurum-m-AuRecord_Edit|.
+    (|aurum-record|) (recautowrite) or in integrate tab (|aurum-integrate|) 
+    (intautowrite).
+    See |aurum-m-AuRecord_Edit| and |aurum-m-AuIntegrate_Edit|.
     Default: 1.
 
 fullundo                                                    *g:aurum_fullundo*
     Without this option each time you use |aurum-m-AuRecord_Edit| undo history 
     is discarded. This option keeps edited version in memory thus set it to 
     true if you use |:AuRecord| to partially commit large files.
+    This option also enables undo in |aurum-integrate| tab. No undo is 
+    possible for integrate tab in case this option is unset.
     Default: 1.
 
 diffopts                                                    *g:aurum_diffopts*
               edited for the second time).
 Finish    A   Write changes and switch to status buffer.
 
+------------------------------------------------------------------------------
+7.2. Integrate tab                                           *aurum-integrate*
+
+Integrate tab is used to interactively merge two revisions. |:AuIntegrate| 
+command opens a new tab page and splits it into three or four windows: result, 
+base and other. “Base” window may be absent, “other” may refer to either the 
+version of the file in the other changeset (if selected method is either 2way 
+or 3way) or to the partially merged version with conflicted changes taken from 
+other revision (markers method). Here is how it looks like:
+
+            2way or markers                          3way ~
+  +----------------+----------------+ +----------+----------+-----------+ `
+  |                |                | |          |          |           | `
+  |                |                | |          |          |           | `
+  |                |                | |          |          |           | `
+  |     result     |     other      | |  result  |   base   |   other   | `
+  |                |                | |          |          |           | `
+  |                |                | |          |          |           | `
+  |                |                | |          |          |           | `
+  +----------------+----------------+ +----------+----------+-----------+ `
+  |                                 | |                                 | `
+  |              status             | |              status             | `
+  |                                 | |                                 | `
+  +---------------------------------+ +---------------------------------+ `
+After entering integrate tab status window becomes focused. It contains output 
+similar to the output of |:AuStatus| command, but with one additional column 
+added at the beginning of the line. This column may contain one of four status 
+characters:
+Character  Meaning ~
+    R      Merged by the VCS itself.
+    C      Conflicted: VCS was unable to merge this file.
+    r      File was edited by user and is considered to be resolved.
+           Note: file is actually marked as resolved by |aurum-rf-resolve| 
+                 only before comitting.
+ <space>   File for some reason appeared in |aurum-rf-status| output, but not 
+           in |aurum-rf-merge|.
+In status window there are additional mappings available (mgid=AuIntegrate, 
+without leader by default):                          *g:frawormap_AuIntegrate*
+             *g:frawormap_AuIntegrate_Edit* *g:frawormap_AuIntegrate_Undo*
+             *g:frawormap_AuIntegrate_Redo* *g:frawormap_AuIntegrate_Earlier*
+             *g:frawormap_AuIntegrate_Later*    *aurum-m-AuIntegrate_Later*
+                 *aurum-m-AuIntegrate_Redo*     *aurum-m-AuIntegrate_Earlier*
+                 *aurum-m-AuIntegrate_Edit*     *aurum-m-AuIntegrate_Undo*
+Mapping  LHS  Description ~
+Edit      O   Edit file under cursor.
+Undo      u   Undo. Does not work unless |g:aurum_fullundo| is set.
+Redo    <C-r> Like above, but redoes (|CTRL-R|).
+Earlier   g-  Like above, but uses |g-|. Unlike |g-| is subject to 'timeout'.
+Later     g+  Like above, but uses |g+|. Unlike |g+| is subject to 'timeout'.
+UseOther  go  Resolve conflict by using other version.
+UseLocal  gl  Resolve conflict by using local version.
+
+|:AuIntegrate| also makes some AuStatus mappings change their meaning:
+Mapping                 New meaning ~
+|aurum-m-AuStatus_Commit| close record tab and commit selected changes
+
+Note that if you close status window you will also close integrate tab, but 
+     changes made will not be discarded. Unlike |:AuRecord| no backups are 
+     kept, it is supposed that when merging there are no local changes to take 
+     care about thus no backups are needed.
+
+|:AuIntegrate| defines the following mappings in result window 
+(mgid=AuIntegrateLeft, default leader: "<Leader>"):
+                                                 *g:frawormap_AuIntegrateLeft*
+       *g:frawormap_AuIntegrateLeft_Discard* *aurum-m-AuIntegrateLeft_Discard*
+       *g:frawormap_AuIntegrateLeft_Exit*    *aurum-m-AuIntegrateLeft_Exit*
+Mapping  LHS  Description ~
+Discard   x   Discard all unsaved changes made to edited file.
+Exit      X   Close integrate tab, discarding all unsaved changes to the 
+              current file.
+
+|:AuIntegrate| defines the following mappings in result window when using 3way 
+method (mgid=AuIntegrateLeft3Way, without leader by default):
+                                             *g:frawormap_AuIntegrateLeft3Way*
+*g:frawormap_AuIntegrateLeft3Way_DiffGetOther* *aurum-m-AuIntegrateLeft3Way_DiffGetOther*
+*g:frawormap_AuIntegrateLeft3Way_DiffGetBase*  *aurum-m-AuIntegrateLeft3Way_DiffGetBase*
+Mapping       LHS  Description ~
+DiffGetOther  do   Get changes from the “other” buffer.
+DiffGetBase   dO   Get changes from the “base” buffer.
+
 ==============================================================================
 8. Internal objects                                            *aurum-objects*
 
   strip :: [hex[, force]] -> _                                *aurum-rf-strip*
     Delete a changeset and its descendants. Default changeset is a working 
     directory one.
+  merge :: [hex[, usemarkers]] -> mergedict                   *aurum-rf-merge*
+    Merge given revision and return or dictionary describing merge status.
+    Resulting dictionary contains one key: "filemap".
+    {filemap} is a map {filename} -> {"conflicted": {conflicted}, …} where 
+              {conflicted} is a boolean telling whether path has conflicts 
+              that need to be resolved and … are keys {local}, {base} and 
+              {other} that are (if present) file paths (to files containing 
+              version of file in question) or lists (with file contents). Key 
+              may be not present if file is absent in respective revision, if 
+              it was requested to use markers or if there are no conflicts.
+    If {usemarkers} is true then function allows VCS to merge what it can and 
+    leave RCS-style conflict markers in the files.
+  resolve :: [ file] -> _                                   *aurum-rf-resolve*
+    Mark conflicts in the given file as resolved. If file argument is absent, 
+    marks all conflicts in all files as resolved.
   branch :: branch, force -> _                               *aurum-rf-branch*
     Create new branch.
   label :: type, label, hex, force, local -> _                *aurum-rf-label*
          |aurum-rf-aget| and |aurum-rf-aremove|.
     5.5: Added |aurum-rf-apause| and |aurum-rf-aresume|.
     5.6: Added |aurum-rf-strip|.
+    5.7: Added |aurum-rf-merge| and |aurum-rf-resolve|.
 @aurum:
     0.1: Added |:AuBranch| and |:AuName|.
     0.2: Added |:AuOther|.

File plugin/aurum.vim

             \['File',      [0],{},             0                              ],
             \['Grep',      [0],{},             0                              ],
             \['Hyperlink', [0],{'range': '%'}, 0                              ],
+            \['Integrate', [0],{},             'all',                         ],
             \['Junk',      [0],{},             [                     "status"]],
             \['Log',       [0],{},             0                              ],
             \['Move',      [0],{'bang': 1},    [                     "status"]],

File python/aurum/aumercurial.py

+# vim: fileencoding=utf-8
 from mercurial import hg, ui, commands, match
+from mercurial import merge as mergemod
 try:
     from mercurial.repo import error
 except ImportError:
 import sys
 from aurum.auutils import AurumError, outermethodgen, autoexportmethodgen, vim_extend, \
                           vim_throw, echoe, echom, get_repo_prop_gen
+from itertools import repeat, izip, chain
 
 if hasattr(error, 'RepoLookupError'):
     RepoLookupError=error.RepoLookupError
         self._captured=[]
         return r
 
+class CaptureAllUI(CaptureUI):
+    def write_err(self, *args, **kwargs):
+        return super(CaptureAllUI, self).write(*args, **kwargs)
+
+    def _printCaptured(self):
+        sys.stderr.write("".join(self._captured))
+
 class CaptureToBuf(PrintUI):
     def __init__(self, *args, **kwargs):
         self._vimbuffer=vim.current.buffer
     else:
         vim_throw('statuns', repo.path)
 
+def revstatus(status):
+    return dict(chain(*((izip(files, repeat(st))) for st, files in status.items())))
+
+@outermethod
+@autoexportmethod(utf=False)
+def merge(repo, rev=None, usemarkers=False):
+    cs=g_cs(repo, rev)
+    ui = CaptureAllUI()
+    args=[ui, repo, cs.hex() if cs else None]
+    kwargs={}
+    if usemarkers:
+        kwargs['tool']='internal:merge'
+    else:
+        kwargs['tool']='internal:dump'
+
+    success=False
+    try:
+        commands.merge(*args, **kwargs)
+        success=True
+    finally:
+        if not success:
+            ui._printCaptured()
+
+    ms=mergemod.mergestate(repo)
+
+    filemap={}
+    for f in ms:
+        # 'u' stands for “unresolved”
+        filemap[f] = {"conflicted": (ms[f] == 'u')}
+        if not usemarkers and ms[f] == 'u':
+            for key in ('local', 'base', 'other'):
+                if os.path.isfile(key):
+                    filemap[f][key]=f+'.'+key
+    return {
+        'filemap': filemap,
+    }
+
 @outermethod
 def update(repo, rev='tip', force=False):
     if not hasattr(repo, '__getitem__'):