##// END OF EJS Templates
imerge: simplify 1d5ebb0d366f
imerge: simplify 1d5ebb0d366f

File last commit:

r5165:ec24bfd8 default
r5165:ec24bfd8 default
Show More
imerge.py
362 lines | 10.4 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 _
from mercurial.node import *
Brendan Cully
imerge: gussy up dispatcher to support subcommand opts....
r5111 from mercurial import commands, cmdutil, fancyopts, hg, merge, util
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]]
Brendan Cully
imerge extension and test
r5044 except LookupError:
raise util.Abort('merge parent %s not in repository' % short(p))
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]
def filemerge(self, fn):
wlock = self.repo.wlock()
Brendan Cully
Update imerge for new filemerge interface
r5054 (fd, fo) = self.conflicts[fn]
Brendan Cully
imerge: handle renames
r5109 p2 = self.wctx.parents()[1]
return merge.filemerge(self.repo, fn, fd, fo, self.wctx, p2)
Brendan Cully
imerge extension and test
r5044
def start(self, rev=None):
_filemerge = merge.filemerge
Brendan Cully
Update imerge for new filemerge interface
r5054 def filemerge(repo, fw, fd, fo, wctx, mctx):
self.conflicts[fw] = (fd, fo)
Brendan Cully
imerge extension and test
r5044
merge.filemerge = filemerge
commands.merge(self.ui, self.repo, rev=rev)
merge.filemerge = _filemerge
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
def merge_(im, filename=None):
if not filename:
filename = im.next()
if not filename:
im.ui.write('all conflicts resolved\n')
return 0
rc = im.filemerge(filename)
if not rc:
im.resolve([filename])
if not im.next():
im.ui.write('all conflicts resolved\n')
return 0
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, []),
'merge': (merge_, []),
'next': (next, []),
'resolve': (resolve, []),
'save': (save, []),
'status': (status,
[('n', 'no-status', None, _('hide status prefix')),
('', 'resolved', None, _('only show resolved conflicts')),
('', 'unresolved', None, _('only show unresolved conflicts'))]),
'unresolve': (unresolve, [])
Brendan Cully
imerge extension and test
r5044 }
def dispatch(im, args, opts):
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:
raise cmdutil.ParseError('imerge', '%s: %s' % (cmd, inst))
Brendan Cully
imerge extension and test
r5044 except TypeError:
Brendan Cully
imerge: gussy up dispatcher to support subcommand opts....
r5111 raise cmdutil.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
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)
args = ['status']
if not args:
args = ['merge']
return dispatch(im, args, opts)
cmdtable = {
'^imerge':
(imerge,
[('r', 'rev', '', _('revision to merge'))], 'hg imerge [command]')
}