##// END OF EJS Templates
Merge with crew
Merge with crew

File last commit:

r6213:5c5e4576 default
r6227:4c1aa6af merge default
Show More
imerge.py
406 lines | 12.0 KiB | text/x-python | PythonLexer
Brendan Cully
imerge extension and test
r5044 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
# Published under the GNU GPL
'''
imerge - interactive merge
'''
from mercurial.i18n import _
Joel Rosdahl
Expand import * to allow Pyflakes to find problems
r6211 from mercurial.node import hex, short
Matt Mackall
filemerge: pull file-merging code into its own module
r6003 from mercurial import commands, cmdutil, dispatch, fancyopts
Joel Rosdahl
imerge: Fix unbound name error and add a test case
r6213 from mercurial import hg, filemerge, util, revlog
Brendan Cully
imerge extension and test
r5044 import os, tarfile
class InvalidStateFileException(Exception): pass
class ImergeStateFile(object):
def __init__(self, im):
self.im = im
def save(self, dest):
tf = tarfile.open(dest, 'w:gz')
st = os.path.join(self.im.path, 'status')
tf.add(st, os.path.join('.hg', 'imerge', 'status'))
for f in self.im.resolved:
Brendan Cully
imerge: handle renames
r5109 (fd, fo) = self.im.conflicts[f]
abssrc = self.im.repo.wjoin(fd)
tf.add(abssrc, fd)
Brendan Cully
imerge extension and test
r5044
tf.close()
def load(self, source):
wlock = self.im.repo.wlock()
lock = self.im.repo.lock()
tf = tarfile.open(source, 'r')
contents = tf.getnames()
Patrick Mezard
imerge: fix status file lookups
r5164 # tarfile normalizes path separators to '/'
Brendan Cully
imerge: simplify 1d5ebb0d366f
r5165 statusfile = '.hg/imerge/status'
Brendan Cully
imerge extension and test
r5044 if statusfile not in contents:
raise InvalidStateFileException('no status file')
tf.extract(statusfile, self.im.repo.root)
Brendan Cully
imerge: handle renames
r5109 p1, p2 = self.im.load()
if self.im.repo.dirstate.parents()[0] != p1.node():
hg.clean(self.im.repo, p1.node())
self.im.start(p2.node())
Benoit Boissinot
imerge: tarfile.extractall is only available in python2.5
r5055 for tarinfo in tf:
tf.extract(tarinfo, self.im.repo.root)
Brendan Cully
imerge extension and test
r5044 self.im.load()
class Imerge(object):
def __init__(self, ui, repo):
self.ui = ui
self.repo = repo
self.path = repo.join('imerge')
self.opener = util.opener(self.path)
Brendan Cully
imerge: handle renames
r5109 self.wctx = self.repo.workingctx()
Brendan Cully
imerge extension and test
r5044 self.conflicts = {}
self.resolved = []
def merging(self):
Brendan Cully
imerge: handle renames
r5109 return len(self.wctx.parents()) > 1
Brendan Cully
imerge extension and test
r5044
def load(self):
# status format. \0-delimited file, fields are
# p1, p2, conflict count, conflict filenames, resolved filenames
Brendan Cully
Update imerge for new filemerge interface
r5054 # conflict filenames are tuples of localname, remoteorig, remotenew
Brendan Cully
imerge extension and test
r5044
statusfile = self.opener('status')
status = statusfile.read().split('\0')
if len(status) < 3:
raise util.Abort('invalid imerge status file')
try:
Brendan Cully
imerge: handle renames
r5109 parents = [self.repo.changectx(n) for n in status[:2]]
Joel Rosdahl
imerge: Fix unbound name error and add a test case
r6213 except revlog.LookupError, e:
raise util.Abort('merge parent %s not in repository' % e.name)
Brendan Cully
imerge extension and test
r5044
status = status[2:]
Brendan Cully
Update imerge for new filemerge interface
r5054 conflicts = int(status.pop(0)) * 3
Brendan Cully
imerge extension and test
r5044 self.resolved = status[conflicts:]
Brendan Cully
Update imerge for new filemerge interface
r5054 for i in xrange(0, conflicts, 3):
self.conflicts[status[i]] = (status[i+1], status[i+2])
Brendan Cully
imerge extension and test
r5044
Brendan Cully
imerge: handle renames
r5109 return parents
Brendan Cully
imerge extension and test
r5044 def save(self):
lock = self.repo.lock()
if not os.path.isdir(self.path):
os.mkdir(self.path)
Thomas Arendsen Hein
imerge: sorted() is only available in python2.4 and above
r5056 statusfile = self.opener('status', 'wb')
Brendan Cully
imerge extension and test
r5044
Brendan Cully
imerge: handle renames
r5109 out = [hex(n.node()) for n in self.wctx.parents()]
Brendan Cully
imerge extension and test
r5044 out.append(str(len(self.conflicts)))
Thomas Arendsen Hein
imerge: sorted() is only available in python2.4 and above
r5056 conflicts = self.conflicts.items()
conflicts.sort()
for fw, fd_fo in conflicts:
out.append(fw)
out.extend(fd_fo)
Brendan Cully
imerge extension and test
r5044 out.extend(self.resolved)
Thomas Arendsen Hein
imerge: sorted() is only available in python2.4 and above
r5056 statusfile.write('\0'.join(out))
Brendan Cully
imerge extension and test
r5044
def remaining(self):
return [f for f in self.conflicts if f not in self.resolved]
Brendan Cully
imerge: add automerge flag to attempt to batch merge all conflicts
r5241 def filemerge(self, fn, interactive=True):
Brendan Cully
imerge extension and test
r5044 wlock = self.repo.wlock()
Brendan Cully
Update imerge for new filemerge interface
r5054 (fd, fo) = self.conflicts[fn]
Brendan Cully
imerge: fix ancestor calculation...
r5239 p1, p2 = self.wctx.parents()
Brendan Cully
imerge: add automerge flag to attempt to batch merge all conflicts
r5241
# this could be greatly improved
realmerge = os.environ.get('HGMERGE')
if not interactive:
os.environ['HGMERGE'] = 'merge'
Brendan Cully
imerge: fix ancestor calculation...
r5239 # The filemerge ancestor algorithm does not work if self.wctx
# already has two parents (in normal merge it doesn't yet). But
# this is very dirty.
self.wctx._parents.pop()
try:
Brendan Cully
imerge: add automerge flag to attempt to batch merge all conflicts
r5241 # TODO: we should probably revert the file if merge fails
Matt Mackall
filemerge: pull file-merging code into its own module
r6003 return filemerge.filemerge(self.repo, fn, fd, fo, self.wctx, p2)
Brendan Cully
imerge: fix ancestor calculation...
r5239 finally:
self.wctx._parents.append(p2)
Brendan Cully
imerge: add automerge flag to attempt to batch merge all conflicts
r5241 if realmerge:
os.environ['HGMERGE'] = realmerge
elif not interactive:
del os.environ['HGMERGE']
Brendan Cully
imerge extension and test
r5044
def start(self, rev=None):
Matt Mackall
filemerge: pull file-merging code into its own module
r6003 _filemerge = filemerge.filemerge
def filemerge_(repo, fw, fd, fo, wctx, mctx):
Brendan Cully
Update imerge for new filemerge interface
r5054 self.conflicts[fw] = (fd, fo)
Brendan Cully
imerge extension and test
r5044
Matt Mackall
filemerge: pull file-merging code into its own module
r6003 filemerge.filemerge = filemerge_
Brendan Cully
imerge extension and test
r5044 commands.merge(self.ui, self.repo, rev=rev)
Matt Mackall
filemerge: pull file-merging code into its own module
r6003 filemerge.filemerge = _filemerge
Brendan Cully
imerge extension and test
r5044
Brendan Cully
imerge: handle renames
r5109 self.wctx = self.repo.workingctx()
Brendan Cully
imerge extension and test
r5044 self.save()
def resume(self):
self.load()
dp = self.repo.dirstate.parents()
Brendan Cully
imerge: handle renames
r5109 p1, p2 = self.wctx.parents()
if p1.node() != dp[0] or p2.node() != dp[1]:
Brendan Cully
imerge extension and test
r5044 raise util.Abort('imerge state does not match working directory')
def next(self):
remaining = self.remaining()
return remaining and remaining[0]
def resolve(self, files):
resolved = dict.fromkeys(self.resolved)
for fn in files:
if fn not in self.conflicts:
raise util.Abort('%s is not in the merge set' % fn)
resolved[fn] = True
Thomas Arendsen Hein
imerge: sorted() is only available in python2.4 and above
r5056 self.resolved = resolved.keys()
self.resolved.sort()
Brendan Cully
imerge extension and test
r5044 self.save()
return 0
def unresolve(self, files):
resolved = dict.fromkeys(self.resolved)
for fn in files:
if fn not in resolved:
raise util.Abort('%s is not resolved' % fn)
del resolved[fn]
Thomas Arendsen Hein
imerge: sorted() is only available in python2.4 and above
r5056 self.resolved = resolved.keys()
self.resolved.sort()
Brendan Cully
imerge extension and test
r5044 self.save()
return 0
def pickle(self, dest):
'''write current merge state to file to be resumed elsewhere'''
state = ImergeStateFile(self)
return state.save(dest)
def unpickle(self, source):
'''read merge state from file'''
state = ImergeStateFile(self)
return state.load(source)
def load(im, source):
if im.merging():
raise util.Abort('there is already a merge in progress '
'(update -C <rev> to abort it)' )
m, a, r, d = im.repo.status()[:4]
if m or a or r or d:
raise util.Abort('working directory has uncommitted changes')
rc = im.unpickle(source)
if not rc:
Brendan Cully
imerge: gussy up dispatcher to support subcommand opts....
r5111 status(im)
Brendan Cully
imerge extension and test
r5044 return rc
Brendan Cully
imerge: add automerge flag to attempt to batch merge all conflicts
r5241 def merge_(im, filename=None, auto=False):
success = True
if auto and not filename:
for fn in im.remaining():
rc = im.filemerge(fn, interactive=False)
if rc:
success = False
else:
im.resolve([fn])
if success:
im.ui.write('all conflicts resolved\n')
else:
status(im)
return 0
Brendan Cully
imerge extension and test
r5044 if not filename:
filename = im.next()
if not filename:
im.ui.write('all conflicts resolved\n')
return 0
Brendan Cully
imerge: add automerge flag to attempt to batch merge all conflicts
r5241 rc = im.filemerge(filename, interactive=not auto)
Brendan Cully
imerge extension and test
r5044 if not rc:
im.resolve([filename])
if not im.next():
im.ui.write('all conflicts resolved\n')
return rc
def next(im):
n = im.next()
if n:
im.ui.write('%s\n' % n)
else:
im.ui.write('all conflicts resolved\n')
return 0
def resolve(im, *files):
if not files:
raise util.Abort('resolve requires at least one filename')
return im.resolve(files)
def save(im, dest):
return im.pickle(dest)
Brendan Cully
imerge: gussy up dispatcher to support subcommand opts....
r5111 def status(im, **opts):
if not opts.get('resolved') and not opts.get('unresolved'):
opts['resolved'] = True
opts['unresolved'] = True
if im.ui.verbose:
p1, p2 = [short(p.node()) for p in im.wctx.parents()]
im.ui.note(_('merging %s and %s\n') % (p1, p2))
conflicts = im.conflicts.keys()
conflicts.sort()
remaining = dict.fromkeys(im.remaining())
st = []
for fn in conflicts:
if opts.get('no_status'):
mode = ''
elif fn in remaining:
mode = 'U '
else:
mode = 'R '
if ((opts.get('resolved') and fn not in remaining)
or (opts.get('unresolved') and fn in remaining)):
st.append((mode, fn))
st.sort()
for (mode, fn) in st:
if im.ui.verbose:
fo, fd = im.conflicts[fn]
if fd != fn:
fn = '%s (%s)' % (fn, fd)
im.ui.write('%s%s\n' % (mode, fn))
if opts.get('unresolved') and not remaining:
im.ui.write(_('all conflicts resolved\n'))
Brendan Cully
imerge extension and test
r5044 return 0
def unresolve(im, *files):
if not files:
raise util.Abort('unresolve requires at least one filename')
return im.unresolve(files)
subcmdtable = {
Brendan Cully
imerge: gussy up dispatcher to support subcommand opts....
r5111 'load': (load, []),
Brendan Cully
imerge: add automerge flag to attempt to batch merge all conflicts
r5241 'merge':
(merge_,
[('a', 'auto', None, _('automatically resolve if possible'))]),
Brendan Cully
imerge: gussy up dispatcher to support subcommand opts....
r5111 'next': (next, []),
'resolve': (resolve, []),
'save': (save, []),
Brendan Cully
imerge: add automerge flag to attempt to batch merge all conflicts
r5241 'status':
(status,
[('n', 'no-status', None, _('hide status prefix')),
('', 'resolved', None, _('only show resolved conflicts')),
('', 'unresolved', None, _('only show unresolved conflicts'))]),
Brendan Cully
imerge: gussy up dispatcher to support subcommand opts....
r5111 'unresolve': (unresolve, [])
Brendan Cully
imerge extension and test
r5044 }
Brendan Cully
imerge: fix ancestor calculation...
r5239 def dispatch_(im, args, opts):
Brendan Cully
imerge extension and test
r5044 def complete(s, choices):
candidates = []
for choice in choices:
if choice.startswith(s):
candidates.append(choice)
return candidates
Brendan Cully
imerge: gussy up dispatcher to support subcommand opts....
r5111 c, args = args[0], list(args[1:])
Brendan Cully
imerge extension and test
r5044 cmd = complete(c, subcmdtable.keys())
if not cmd:
raise cmdutil.UnknownCommand('imerge ' + c)
if len(cmd) > 1:
Thomas Arendsen Hein
imerge: sorted() is only available in python2.4 and above
r5056 cmd.sort()
raise cmdutil.AmbiguousCommand('imerge ' + c, cmd)
Brendan Cully
imerge extension and test
r5044 cmd = cmd[0]
Brendan Cully
imerge: gussy up dispatcher to support subcommand opts....
r5111 func, optlist = subcmdtable[cmd]
opts = {}
Brendan Cully
imerge extension and test
r5044 try:
Brendan Cully
imerge: gussy up dispatcher to support subcommand opts....
r5111 args = fancyopts.fancyopts(args, optlist, opts)
return func(im, *args, **opts)
except fancyopts.getopt.GetoptError, inst:
Brendan Cully
imerge: fix ancestor calculation...
r5239 raise dispatch.ParseError('imerge', '%s: %s' % (cmd, inst))
Brendan Cully
imerge extension and test
r5044 except TypeError:
Brendan Cully
imerge: fix ancestor calculation...
r5239 raise dispatch.ParseError('imerge', _('%s: invalid arguments') % cmd)
Brendan Cully
imerge extension and test
r5044
def imerge(ui, repo, *args, **opts):
'''interactive merge
imerge lets you split a merge into pieces. When you start a merge
with imerge, the names of all files with conflicts are recorded.
You can then merge any of these files, and if the merge is
successful, they will be marked as resolved. When all files are
resolved, the merge is complete.
If no merge is in progress, hg imerge [rev] will merge the working
directory with rev (defaulting to the other head if the repository
only has two heads). You may also resume a saved merge with
hg imerge load <file>.
If a merge is in progress, hg imerge will default to merging the
next unresolved file.
The following subcommands are available:
status:
show the current state of the merge
Brendan Cully
imerge: fix ancestor calculation...
r5239 options:
-n --no-status: do not print the status prefix
--resolved: only print resolved conflicts
--unresolved: only print unresolved conflicts
Brendan Cully
imerge extension and test
r5044 next:
show the next unresolved file merge
merge [<file>]:
merge <file>. If the file merge is successful, the file will be
recorded as resolved. If no file is given, the next unresolved
file will be merged.
resolve <file>...:
mark files as successfully merged
unresolve <file>...:
mark files as requiring merging.
save <file>:
save the state of the merge to a file to be resumed elsewhere
load <file>:
load the state of the merge from a file created by save
'''
im = Imerge(ui, repo)
if im.merging():
im.resume()
else:
rev = opts.get('rev')
if rev and args:
raise util.Abort('please specify just one revision')
Thomas Arendsen Hein
Remove trailing spaces, fix indentation
r5143
Brendan Cully
imerge extension and test
r5044 if len(args) == 2 and args[0] == 'load':
pass
else:
if args:
rev = args[0]
im.start(rev=rev)
Brendan Cully
imerge: add automerge flag to attempt to batch merge all conflicts
r5241 if opts.get('auto'):
args = ['merge', '--auto']
else:
args = ['status']
Brendan Cully
imerge extension and test
r5044
if not args:
args = ['merge']
Brendan Cully
imerge: fix ancestor calculation...
r5239 return dispatch_(im, args, opts)
Brendan Cully
imerge extension and test
r5044
cmdtable = {
'^imerge':
(imerge,
Brendan Cully
imerge: add automerge flag to attempt to batch merge all conflicts
r5241 [('r', 'rev', '', _('revision to merge')),
('a', 'auto', None, _('automatically merge where possible'))],
'hg imerge [command]')
Brendan Cully
imerge extension and test
r5044 }