# HG changeset patch # User Matt Mackall # Date 1231736409 21600 # Node ID d22c437247452f84125d33ddd3e036d12a583667 # Parent 14a4337a9b9b90419236dbea92dff6e15f19658d kill imerge.py It's been broken for a while. diff -r 14a4337a9b9b -r d22c43724745 hgext/imerge.py --- 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 -# 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 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 . - - 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 []: - merge . 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 ...: - mark files as successfully merged - unresolve ...: - mark files as requiring merging. - save : - save the state of the merge to a file to be resumed elsewhere - load : - 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]')) -}