--- a/hgext/imerge.py Sun Jan 11 22:55:36 2009 -0600
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,407 +0,0 @@
-# Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
-# Published under the GNU GPL
-
-'''
-imerge - interactive merge
-'''
-
-from mercurial.i18n import _
-from mercurial.node import hex, short
-from mercurial import commands, cmdutil, dispatch, fancyopts
-from mercurial import hg, filemerge, util, error
-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:
- (fd, fo) = self.im.conflicts[f]
- abssrc = self.im.repo.wjoin(fd)
- tf.add(abssrc, fd)
-
- tf.close()
-
- def load(self, source):
- wlock = self.im.repo.wlock()
- lock = self.im.repo.lock()
-
- tf = tarfile.open(source, 'r')
- contents = tf.getnames()
- # tarfile normalizes path separators to '/'
- statusfile = '.hg/imerge/status'
- if statusfile not in contents:
- raise InvalidStateFileException('no status file')
-
- tf.extract(statusfile, self.im.repo.root)
- 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())
- for tarinfo in tf:
- tf.extract(tarinfo, self.im.repo.root)
- 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)
-
- self.wctx = self.repo.workingctx()
- self.conflicts = {}
- self.resolved = []
-
- def merging(self):
- return len(self.wctx.parents()) > 1
-
- def load(self):
- # status format. \0-delimited file, fields are
- # p1, p2, conflict count, conflict filenames, resolved filenames
- # conflict filenames are tuples of localname, remoteorig, remotenew
-
- statusfile = self.opener('status')
-
- status = statusfile.read().split('\0')
- if len(status) < 3:
- raise util.Abort(_('invalid imerge status file'))
-
- try:
- parents = [self.repo.changectx(n) for n in status[:2]]
- except error.LookupError, e:
- raise util.Abort(_('merge parent %s not in repository') %
- short(e.name))
-
- status = status[2:]
- conflicts = int(status.pop(0)) * 3
- self.resolved = status[conflicts:]
- for i in xrange(0, conflicts, 3):
- self.conflicts[status[i]] = (status[i+1], status[i+2])
-
- return parents
-
- def save(self):
- lock = self.repo.lock()
-
- if not os.path.isdir(self.path):
- os.mkdir(self.path)
- statusfile = self.opener('status', 'wb')
-
- out = [hex(n.node()) for n in self.wctx.parents()]
- out.append(str(len(self.conflicts)))
- conflicts = self.conflicts.items()
- conflicts.sort()
- for fw, fd_fo in conflicts:
- out.append(fw)
- out.extend(fd_fo)
- out.extend(self.resolved)
-
- statusfile.write('\0'.join(out))
-
- def remaining(self):
- return [f for f in self.conflicts if f not in self.resolved]
-
- def filemerge(self, fn, interactive=True):
- wlock = self.repo.wlock()
-
- (fd, fo) = self.conflicts[fn]
- p1, p2 = self.wctx.parents()
-
- # this could be greatly improved
- realmerge = os.environ.get('HGMERGE')
- if not interactive:
- os.environ['HGMERGE'] = 'internal:merge'
-
- # 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:
- # TODO: we should probably revert the file if merge fails
- return filemerge.filemerge(self.repo, fn, fd, fo, self.wctx, p2)
- finally:
- self.wctx._parents.append(p2)
- if realmerge:
- os.environ['HGMERGE'] = realmerge
- elif not interactive:
- del os.environ['HGMERGE']
-
- def start(self, rev=None):
- _filemerge = filemerge.filemerge
- def filemerge_(repo, fw, fd, fo, wctx, mctx):
- self.conflicts[fw] = (fd, fo)
-
- filemerge.filemerge = filemerge_
- commands.merge(self.ui, self.repo, rev=rev)
- filemerge.filemerge = _filemerge
-
- self.wctx = self.repo.workingctx()
- self.save()
-
- def resume(self):
- self.load()
-
- dp = self.repo.dirstate.parents()
- p1, p2 = self.wctx.parents()
- if p1.node() != dp[0] or p2.node() != dp[1]:
- 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
- self.resolved = resolved.keys()
- self.resolved.sort()
- 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]
- self.resolved = resolved.keys()
- self.resolved.sort()
- 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:
- status(im)
- return rc
-
-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
-
- if not filename:
- filename = im.next()
- if not filename:
- im.ui.write('all conflicts resolved\n')
- return 0
-
- rc = im.filemerge(filename, interactive=not auto)
- 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)
-
-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'))
-
- return 0
-
-def unresolve(im, *files):
- if not files:
- raise util.Abort(_('unresolve requires at least one filename'))
- return im.unresolve(files)
-
-subcmdtable = {
- 'load': (load, []),
- 'merge':
- (merge_,
- [('a', 'auto', None, _('automatically resolve if possible'))]),
- '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, [])
-}
-
-def dispatch_(im, args, opts):
- def complete(s, choices):
- candidates = []
- for choice in choices:
- if choice.startswith(s):
- candidates.append(choice)
- return candidates
-
- c, args = args[0], list(args[1:])
- cmd = complete(c, subcmdtable.keys())
- if not cmd:
- raise cmdutil.UnknownCommand('imerge ' + c)
- if len(cmd) > 1:
- cmd.sort()
- raise cmdutil.AmbiguousCommand('imerge ' + c, cmd)
- cmd = cmd[0]
-
- func, optlist = subcmdtable[cmd]
- opts = {}
- try:
- args = fancyopts.fancyopts(args, optlist, opts)
- return func(im, *args, **opts)
- except fancyopts.getopt.GetoptError, inst:
- raise dispatch.ParseError('imerge', '%s: %s' % (cmd, inst))
- except TypeError:
- raise dispatch.ParseError('imerge', _('%s: invalid arguments') % cmd)
-
-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
- options:
- -n --no-status: do not print the status prefix
- --resolved: only print resolved conflicts
- --unresolved: only print unresolved conflicts
- 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'))
-
- if len(args) == 2 and args[0] == 'load':
- pass
- else:
- if args:
- rev = args[0]
- im.start(rev=rev)
- if opts.get('auto'):
- args = ['merge', '--auto']
- else:
- args = ['status']
-
- if not args:
- args = ['merge']
-
- return dispatch_(im, args, opts)
-
-cmdtable = {
- '^imerge':
- (imerge,
- [('r', 'rev', '', _('revision to merge')),
- ('a', 'auto', None, _('automatically merge where possible'))],
- _('hg imerge [command]'))
-}