示例#1
0
 def applydiff(name):
     files = {}
     patch.patch(self.join(patchfile), self.ui, strip=1, files=files)
     files2 = {}
     for k in files.keys():
         files2[k.strip('\r')]=files[k]
     patch.updatedir(self.ui, repo, files2, similarity=sim/100.)
示例#2
0
    def _applypatch(self, repo, patchfile, sim, force=False, **opts):
        """applies a patch the old fashioned way."""
        def epwrapper(orig, *epargs, **epopts):
            if opts['reverse']:
                epargs[1].append('-R')
            return orig(*epargs, **epopts)

        def adwrapper(orig, *adargs, **adopts):
            if opts['reverse']:
                adopts['reverse'] = True
            return orig(*adargs, **adopts)

        epo = extensions.wrapfunction(patch, 'externalpatch', epwrapper)
        ado = extensions.wrapfunction(patch, 'applydiff', adwrapper)
        files, success = {}, True
        try:
            try:
                fuzz = patch.patch(self.join(patchfile), self.ui, strip = 1,
                                   cwd = repo.root, files = files)
                patch.updatedir(self.ui, repo, files, similarity = sim/100.)
            except Exception, inst:
                self.ui.note(str(inst) + '\n')
                if not self.ui.verbose:
                    self.ui.warn('patch failed, unable to continue (try -v)\n')
                success = False
        finally:
            patch.externalpatch = epo
            patch.applydiff = ado
        return success
示例#3
0
def unstash(ui, repo, task):
    '''Unstashes a working copy.  Returns True if a stash was found and applied,
    False if no stash exists.'''
    if not hasstash(repo, task):
        return False

    ui.write('unstashing task: %s\n' % task)
    ui.debug('unstashing %s from stash file: %s\n' %
             (task, stashfiles(repo, task)[0]))

    patchfile = repo.join(stashfiles(repo, task)[0])
    dirstatefile = repo.join(stashfiles(repo, task)[1])

    files = {}
    if os.path.exists(patchfile):
        try:
            fuzz = patch.internalpatch(patchfile,
                                       ui,
                                       strip=1,
                                       cwd=repo.root,
                                       files=files)
        except Exception, inst:
            ui.note(str(inst) + '\n')
            ui.warn('patch failed, unable to continue\n')
            ui.warn('see %s for stash patch\n' % patchfile)
            return False

        if files:
            patch.updatedir(ui, repo, files)
示例#4
0
文件: tasks.py 项目: Evanlec/config
def unstash(ui, repo, task):
    '''Unstashes a working copy.  Returns True if a stash was found and applied,
    False if no stash exists.'''
    if not hasstash(repo, task):
        return False

    ui.write('unstashing task: %s\n' % task)
    ui.debug('unstashing %s from stash file: %s\n'
            % (task, stashfiles(repo, task)[0]))

    patchfile = repo.join(stashfiles(repo, task)[0])
    dirstatefile = repo.join(stashfiles(repo, task)[1])

    files = {}
    if os.path.exists(patchfile):
        try:
            fuzz = patch.internalpatch(patchfile, ui, strip = 1,
                               cwd = repo.root, files = files)
        except Exception, inst:
            ui.note(str(inst) + '\n')
            ui.warn('patch failed, unable to continue\n')
            ui.warn('see %s for stash patch\n' % patchfile)
            return False

        if files:
            patch.updatedir(ui, repo, files)
示例#5
0
文件: attic.py 项目: dimus/dotfiles-1
def updatedir(*args, **kwargs):
    # updatedir moved from patch to cmdutil in hg 1.7
    if hasattr(patch, 'updatedir'):
        patch.updatedir(*args, **kwargs)
    elif hasattr(cmdutil, 'updatedir'):
        cmdutil.updatedir(*args, **kwargs)
    else:
        # hg 1.9+ moved updatedir, and more importantly it's not needed with the way patch is called now..
        scmutil.updatedir(*args, **kwargs)
示例#6
0
def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
    parent = ctx.parents()[0].node()
    hg.update(repo, parent)
    fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
    fp = os.fdopen(fd, 'w')
    diffopts = patch.diffopts(ui, opts)
    diffopts.git = True
    gen = patch.diff(repo, parent, newnode, opts=diffopts)
    for chunk in gen:
        fp.write(chunk)
    fp.close()
    files = {}
    try:
        patch.patch(patchfile, ui, cwd=repo.root, files=files, eolmode=None)
    finally:
        files = patch.updatedir(ui, repo, files)
        os.unlink(patchfile)
    newmessage = '\n***\n'.join(
        [ctx.description(), ] +
        [repo[r].description() for r in internalchanges] +
        [oldctx.description(), ])
    newmessage = ui.edit(newmessage, ui.username())
    n = repo.commit(text=newmessage, user=ui.username(), date=max(ctx.date(), oldctx.date()),
                    extra=oldctx.extra())
    return repo[n], [n, ], [oldctx.node(), ctx.node() ], [newnode, ] # xxx
示例#7
0
def pick(ui, repo, ctx, ha, opts):
    oldctx = repo[ha]
    if oldctx.parents()[0] == ctx:
        ui.debug('node %s unchanged\n' % ha)
        return oldctx, [], [], []
    hg.update(repo, ctx.node())
    fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
    fp = os.fdopen(fd, 'w')
    diffopts = patch.diffopts(ui, opts)
    diffopts.git = True
    gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
    for chunk in gen:
        fp.write(chunk)
    fp.close()
    try:
        files = {}
        try:
            patch.patch(patchfile, ui, cwd=repo.root, files=files, eolmode=None)
            if not files:
                ui.warn(_('%s: empty changeset')
                             % node.hex(ha))
                return ctx, [], [], []
        finally:
            files = patch.updatedir(ui, repo, files)
            os.unlink(patchfile)
    except Exception, inst:
        raise util.Abort(_('Fix up the change and run '
                           'hg histedit --continue'))
示例#8
0
    def applyone(self,
                 repo,
                 node,
                 cl,
                 patchfile,
                 merge=False,
                 log=False,
                 filter=None):
        '''apply the patch in patchfile to the repository as a transplant'''
        (manifest, user, (time, timezone), files, message) = cl[:5]
        date = "%d %d" % (time, timezone)
        extra = {'transplant_source': node}
        if filter:
            (user, date, message) = self.filter(filter, cl, patchfile)

        if log:
            # we don't translate messages inserted into commits
            message += '\n(transplanted from %s)' % revlog.hex(node)

        self.ui.status(_('applying %s\n') % revlog.short(node))
        self.ui.note('%s %s\n%s\n' % (user, date, message))

        if not patchfile and not merge:
            raise util.Abort(_('can only omit patchfile if merging'))
        if patchfile:
            try:
                files = {}
                try:
                    patch.patch(patchfile,
                                self.ui,
                                cwd=repo.root,
                                files=files,
                                eolmode=None)
                    if not files:
                        self.ui.warn(
                            _('%s: empty changeset') % revlog.hex(node))
                        return None
                finally:
                    files = patch.updatedir(self.ui, repo, files)
            except Exception, inst:
                if filter:
                    os.unlink(patchfile)
                seriespath = os.path.join(self.path, 'series')
                if os.path.exists(seriespath):
                    os.unlink(seriespath)
                p1 = repo.dirstate.parents()[0]
                p2 = node
                self.log(user, date, message, p1, p2, merge=merge)
                self.ui.write(str(inst) + '\n')
                raise util.Abort(
                    _('Fix up the merge and run '
                      'hg transplant --continue'))
示例#9
0
    def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
                 filter=None):
        '''apply the patch in patchfile to the repository as a transplant'''
        (manifest, user, (time, timezone), files, message) = cl[:5]
        date = "%d %d" % (time, timezone)
        extra = {'transplant_source': node}
        if filter:
            (user, date, message) = self.filter(filter, cl, patchfile)

        if log:
            # we don't translate messages inserted into commits
            message += '\n(transplanted from %s)' % revlog.hex(node)

        self.ui.status(_('applying %s\n') % revlog.short(node))
        self.ui.note('%s %s\n%s\n' % (user, date, message))

        if not patchfile and not merge:
            raise util.Abort(_('can only omit patchfile if merging'))
        if patchfile:
            try:
                files = {}
                try:
                    patch.patch(patchfile, self.ui, cwd=repo.root,
                                files=files, eolmode=None)
                    if not files:
                        self.ui.warn(_('%s: empty changeset')
                                     % revlog.hex(node))
                        return None
                finally:
                    files = patch.updatedir(self.ui, repo, files)
            except Exception, inst:
                if filter:
                    os.unlink(patchfile)
                seriespath = os.path.join(self.path, 'series')
                if os.path.exists(seriespath):
                    os.unlink(seriespath)
                p1 = repo.dirstate.parents()[0]
                p2 = node
                self.log(user, date, message, p1, p2, merge=merge)
                self.ui.write(str(inst) + '\n')
                raise util.Abort(_('Fix up the merge and run '
                                   'hg transplant --continue'))
示例#10
0
def edit(ui, repo, ctx, ha, opts):
    oldctx = repo[ha]
    hg.update(repo, ctx.node())
    fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
    fp = os.fdopen(fd, 'w')
    diffopts = patch.diffopts(ui, opts)
    diffopts.git = True
    gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
    for chunk in gen:
        fp.write(chunk)
    fp.close()
    try:
        files = {}
        try:
            patch.patch(patchfile, ui, cwd=repo.root, files=files, eolmode=None)
        finally:
            files = patch.updatedir(ui, repo, files)
            os.unlink(patchfile)
    except Exception, inst:
        pass
示例#11
0
def dorecord(ui, repo, commitfunc, *pats, **opts):
    if not ui.interactive():
        raise util.Abort(_('running non-interactively, use commit instead'))

    def recordfunc(ui, repo, message, match, opts):
        """This is generic record driver.

        Its job is to interactively filter local changes, and accordingly
        prepare working dir into a state, where the job can be delegated to
        non-interactive commit command such as 'commit' or 'qrefresh'.

        After the actual job is done by non-interactive command, working dir
        state is restored to original.

        In the end we'll record intresting changes, and everything else will be
        left in place, so the user can continue his work.
        """

        changes = repo.status(match=match)[:3]
        diffopts = mdiff.diffopts(git=True, nodates=True)
        chunks = patch.diff(repo, changes=changes, opts=diffopts)
        fp = cStringIO.StringIO()
        fp.write(''.join(chunks))
        fp.seek(0)

        # 1. filter patch, so we have intending-to apply subset of it
        chunks = filterpatch(ui, parsepatch(fp))
        del fp

        contenders = set()
        for h in chunks:
            try:
                contenders.update(set(h.files()))
            except AttributeError:
                pass

        changed = changes[0] + changes[1] + changes[2]
        newfiles = [f for f in changed if f in contenders]
        if not newfiles:
            ui.status(_('no changes to record\n'))
            return 0

        modified = set(changes[0])

        # 2. backup changed files, so we can restore them in the end
        backups = {}
        backupdir = repo.join('record-backups')
        try:
            os.mkdir(backupdir)
        except OSError, err:
            if err.errno != errno.EEXIST:
                raise
        try:
            # backup continues
            for f in newfiles:
                if f not in modified:
                    continue
                fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_') +
                                               '.',
                                               dir=backupdir)
                os.close(fd)
                ui.debug('backup %r as %r\n' % (f, tmpname))
                util.copyfile(repo.wjoin(f), tmpname)
                backups[f] = tmpname

            fp = cStringIO.StringIO()
            for c in chunks:
                if c.filename() in backups:
                    c.write(fp)
            dopatch = fp.tell()
            fp.seek(0)

            # 3a. apply filtered patch to clean repo  (clean)
            if backups:
                hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)

            # 3b. (apply)
            if dopatch:
                try:
                    ui.debug('applying patch\n')
                    ui.debug(fp.getvalue())
                    pfiles = {}
                    patch.internalpatch(fp,
                                        ui,
                                        1,
                                        repo.root,
                                        files=pfiles,
                                        eolmode=None)
                    patch.updatedir(ui, repo, pfiles)
                except patch.PatchError, err:
                    s = str(err)
                    if s:
                        raise util.Abort(s)
                    else:
                        raise util.Abort(_('patch failed to apply'))
            del fp

            # 4. We prepared working directory according to filtered patch.
            #    Now is the time to delegate the job to commit/qrefresh or the like!

            # it is important to first chdir to repo root -- we'll call a
            # highlevel command with list of pathnames relative to repo root
            cwd = os.getcwd()
            os.chdir(repo.root)
            try:
                commitfunc(ui, repo, *newfiles, **opts)
            finally:
                os.chdir(cwd)

            return 0
示例#12
0
def updatedir(*args, **kwargs):
    # updatedir moved from patch to cmdutil in hg 1.7
    if hasattr(patch, 'updatedir'):
        patch.updatedir(*args, **kwargs)
    else:
        cmdutil.updatedir(*args, **kwargs)
示例#13
0
def dorecord(ui, repo, commitfunc, *pats, **opts):
    if not ui.interactive():
        raise util.Abort(_('running non-interactively, use commit instead'))

    def recordfunc(ui, repo, message, match, opts):
        """This is generic record driver.

        Its job is to interactively filter local changes, and accordingly
        prepare working dir into a state, where the job can be delegated to
        non-interactive commit command such as 'commit' or 'qrefresh'.

        After the actual job is done by non-interactive command, working dir
        state is restored to original.

        In the end we'll record intresting changes, and everything else will be
        left in place, so the user can continue his work.
        """

        changes = repo.status(match=match)[:3]
        diffopts = mdiff.diffopts(git=True, nodates=True)
        chunks = patch.diff(repo, changes=changes, opts=diffopts)
        fp = cStringIO.StringIO()
        fp.write(''.join(chunks))
        fp.seek(0)

        # 1. filter patch, so we have intending-to apply subset of it
        chunks = filterpatch(ui, parsepatch(fp))
        del fp

        contenders = set()
        for h in chunks:
            try:
                contenders.update(set(h.files()))
            except AttributeError:
                pass

        changed = changes[0] + changes[1] + changes[2]
        newfiles = [f for f in changed if f in contenders]
        if not newfiles:
            ui.status(_('no changes to record\n'))
            return 0

        modified = set(changes[0])

        # 2. backup changed files, so we can restore them in the end
        backups = {}
        backupdir = repo.join('record-backups')
        try:
            os.mkdir(backupdir)
        except OSError, err:
            if err.errno != errno.EEXIST:
                raise
        try:
            # backup continues
            for f in newfiles:
                if f not in modified:
                    continue
                fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
                                               dir=backupdir)
                os.close(fd)
                ui.debug('backup %r as %r\n' % (f, tmpname))
                util.copyfile(repo.wjoin(f), tmpname)
                backups[f] = tmpname

            fp = cStringIO.StringIO()
            for c in chunks:
                if c.filename() in backups:
                    c.write(fp)
            dopatch = fp.tell()
            fp.seek(0)

            # 3a. apply filtered patch to clean repo  (clean)
            if backups:
                hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)

            # 3b. (apply)
            if dopatch:
                try:
                    ui.debug('applying patch\n')
                    ui.debug(fp.getvalue())
                    pfiles = {}
                    patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
                                        eolmode=None)
                    patch.updatedir(ui, repo, pfiles)
                except patch.PatchError, err:
                    s = str(err)
                    if s:
                        raise util.Abort(s)
                    else:
                        raise util.Abort(_('patch failed to apply'))
            del fp

            # 4. We prepared working directory according to filtered patch.
            #    Now is the time to delegate the job to commit/qrefresh or the like!

            # it is important to first chdir to repo root -- we'll call a
            # highlevel command with list of pathnames relative to repo root
            cwd = os.getcwd()
            os.chdir(repo.root)
            try:
                commitfunc(ui, repo, *newfiles, **opts)
            finally:
                os.chdir(cwd)

            return 0
示例#14
0
def dorecord(ui, repo, commitfunc, *pats, **opts):
    try:
        if not ui.interactive():
            raise util.Abort(_('running non-interactively, use commit instead'))
    except TypeError: # backwards compatibility with hg 1.1
        if not ui.interactive:
            raise util.Abort(_('running non-interactively, use commit instead'))

    def recordfunc(ui, repo, message, match, opts):
        """This is generic record driver.

        Its job is to interactively filter local changes, and accordingly
        prepare working dir into a state, where the job can be delegated to
        non-interactive commit command such as 'commit' or 'qrefresh'.

        After the actual job is done by non-interactive command, working dir
        state is restored to original.

        In the end we'll record interesting changes, and everything else will be
        left in place, so the user can continue his work.
        """

        merge = len(repo[None].parents()) > 1
        if merge:
            raise util.Abort(_('cannot partially commit a merge '
                               '(use hg commit instead)'))

        # status gives back
        #   modified, added, removed, deleted, unknown, ignored, clean
        # we take only the first 3 of these
        changes = repo.status(match=match)[:3]
        modified, added, removed = changes
        diffopts = opts.copy()
        diffopts['nodates'] = True
        diffopts['git'] = True
        diffopts = patch.diffopts(ui, opts=diffopts)
        chunks = patch.diff(repo, changes=changes, opts=diffopts)
        fp = cStringIO.StringIO()
        fp.write(''.join(chunks))
        fp.seek(0)

        # 1. filter patch, so we have intending-to apply subset of it
        chunks = crpatch.filterpatch(opts,
                                     crpatch.parsepatch(changes, fp),
                                     chunk_selector.chunkselector, ui)
        del fp

        contenders = set()
        for h in chunks:
            try:
                contenders.update(set(h.files()))
            except AttributeError:
                pass

        changed = changes[0] + changes[1] + changes[2]
        newfiles = [f for f in changed if f in contenders]

        if not newfiles:
            ui.status(_('no changes to record\n'))
            return 0


        # 2. backup changed files, so we can restore them in the end
        backups = {}
        newly_added_backups = {}
        backupdir = repo.join('record-backups')
        try:
            os.mkdir(backupdir)
        except OSError, err:
            if err.errno != errno.EEXIST:
                raise
        try:
            # backup continues
            for f in newfiles:
                if f not in (modified + added):
                    continue
                fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
                                               dir=backupdir)
                os.close(fd)
                ui.debug('backup %r as %r\n' % (f, tmpname))
                util.copyfile(repo.wjoin(f), tmpname)
                if f in modified:
                    backups[f] = tmpname
                elif f in added:
                    newly_added_backups[f] = tmpname

            fp = cStringIO.StringIO()
            all_backups = {}
            all_backups.update(backups)
            all_backups.update(newly_added_backups)
            for c in chunks:
                if c.filename() in all_backups:
                    c.write(fp)
            dopatch = fp.tell()
            fp.seek(0)

            # 2.5 optionally review / modify patch in text editor
            if opts['crecord_reviewpatch']:
                patchtext = fp.read()
                reviewedpatch = ui.edit(patchtext, "")
                fp.truncate(0)
                fp.write(reviewedpatch)
                fp.seek(0)

            # 3a. apply filtered patch to clean repo  (clean)
            if backups:
                hg.revert(repo, repo.dirstate.parents()[0],
                          lambda key: key in backups)
            # remove newly added files from 'clean' repo (so patch can apply)
            for f in newly_added_backups:
                os.unlink(f)

            # 3b. (apply)
            if dopatch:
                try:
                    ui.debug('applying patch\n')
                    ui.debug(fp.getvalue())
                    if hasattr(patch, 'workingbackend'): # detect 1.9
                        patch.internalpatch(ui, repo, fp, strip=1, eolmode=None)
                    else:
                        pfiles = {}
                        try:
                            patch.internalpatch(ui, repo, fp, 1, eolmode=None)
                        except (TypeError, AttributeError): # pre 17cea10c343e
                            try:
                                patch.internalpatch(ui, repo, fp, 1, repo.root,
                                                    eolmode=None)
                            except (TypeError, AttributeError): # pre 00a881581400
                                try:
                                    patch.internalpatch(fp, ui, 1, repo.root,
                                                        files=pfiles, eolmode=None)
                                except TypeError: # backwards compatible with hg 1.1
                                    patch.internalpatch(fp, ui, 1,
                                                        repo.root, files=pfiles)
                        try:
                            cmdutil.updatedir(ui, repo, pfiles)
                        except AttributeError:
                            try:
                                patch.updatedir(ui, repo, pfiles)
                            except AttributeError:
                                # from 00a881581400 onwards
                                pass
                except patch.PatchError, err:
                    s = str(err)
                    if s:
                        raise util.Abort(s)
                    else:
                        raise util.Abort(_('patch failed to apply'))
            del fp

            # 4. We prepared working directory according to filtered patch.
            #    Now is the time to delegate the job to commit/qrefresh or the like!

            # it is important to first chdir to repo root -- we'll call a
            # highlevel command with list of pathnames relative to repo root
            newfiles = [repo.wjoin(n) for n in newfiles]
            commitfunc(ui, repo, *newfiles, **opts)

            return 0
示例#15
0
文件: Backup.py 项目: wca/openzfs
        files = {}
        try:
            diff = self.bu.backupfile('diff')
            try:
                fuzz = patch.patch(diff, self.ws.ui, strip=1,
                                   cwd=self.ws.repo.root, files=files)
                if fuzz:
                    raise util.Abort('working copy diff applied with fuzz')
            except Exception, e:
                raise util.Abort("couldn't apply working copy diff: %s\n"
                                 "   %s" % (diff, e))
        finally:
            if Version.at_least("1.7"):
                cmdutil.updatedir(self.ws.ui, self.ws.repo, files)
            else:
                patch.updatedir(self.ws.ui, self.ws.repo, files)

        if not self.bu.exists('renames'):
            return

        #
        # We need to re-apply name changes where the new name
        # (rename/copy destination) is an already versioned file, as
        # Hg would otherwise ignore them.
        #
        try:
            fp = self.bu.open('renames')
            for line in fp:
                source, dest = line.strip().split()
                self.ws.copy(source, dest)
        except EnvironmentError, e:
示例#16
0
文件: attic.py 项目: axtl/dotfiles
def updatedir(*args, **kwargs):
    # updatedir moved from patch to cmdutil in hg 1.7
    if hasattr(patch, 'updatedir'):
        patch.updatedir(*args, **kwargs)
    else:
        cmdutil.updatedir(*args, **kwargs)
示例#17
0
class CdmUncommittedBackup(object):
    '''Backup of uncommitted changes'''
    def __init__(self, backup, ws):
        self.ws = ws
        self.bu = backup

    def _clobbering_renames(self):
        '''Return a list of pairs of files representing renames/copies
        that clobber already versioned files.  [(oldname newname)...]'''

        #
        # Note that this doesn't handle uncommitted merges
        # as CdmUncommittedBackup itself doesn't.
        #
        wctx = self.ws.repo.workingctx()
        parent = wctx.parents()[0]

        ret = []
        for fname in wctx.added() + wctx.modified():
            rn = wctx.filectx(fname).renamed()
            if rn and fname in parent:
                ret.append((rn[0], fname))
        return ret

    def backup(self):
        '''Backup uncommitted changes'''

        if self.ws.merged():
            raise util.Abort("Unable to backup an uncommitted merge.\n"
                             "Please complete your merge and commit")

        dirstate = node.hex(self.ws.repo.changectx().node())

        fp = None
        try:
            try:
                fp = open(self.bu.backupfile('dirstate'), 'w')
                fp.write(dirstate + '\n')
            except EnvironmentError, e:
                raise util.Abort("couldn't save working copy parent: %s" % e)
        finally:
            if fp and not fp.closed:
                fp.close()

        try:
            try:
                fp = open(self.bu.backupfile('renames'), 'w')
                for cons in self._clobbering_renames():
                    fp.write("%s %s\n" % cons)
            except EnvironmentError, e:
                raise util.Abort("couldn't save clobbering copies: %s" % e)
        finally:
            if fp and not fp.closed:
                fp.close()

        try:
            try:
                fp = open(self.bu.backupfile('diff'), 'w')
                patch.diff(self.ws.repo,
                           fp=fp,
                           opts=patch.diffopts(self.ws.ui, opts={'git': True}))
            except EnvironmentError, e:
                raise util.Abort("couldn't save working copy diff: %s" % e)
        finally:
            if fp and not fp.closed:
                fp.close()

    def _dirstate(self):
        '''Return the current working copy node'''
        fp = None
        try:
            try:
                fp = open(self.bu.backupfile('dirstate'))
                dirstate = fp.readline().strip()
                return dirstate
            except EnvironmentError, e:
                raise util.Abort("couldn't read saved parent: %s" % e)
        finally:
            if fp and not fp.closed:
                fp.close()

    def restore(self):
        '''Restore uncommitted changes'''
        diff = self.bu.backupfile('diff')
        dirstate = self._dirstate()

        try:
            self.ws.clean(rev=dirstate)
        except util.Abort, e:
            raise util.Abort("couldn't update to saved node: %s" % e)

        if not os.path.exists(diff):
            return

        #
        # There's a race here whereby if the patch (or part thereof)
        # is applied within the same second as the clean above (such
        # that mtime doesn't change) and if the size of that file
        # does not change, Hg may not see the change.
        #
        # We sleep a full second to avoid this, as sleeping merely
        # until the next second begins would require very close clock
        # synchronization on network filesystems.
        #
        time.sleep(1)

        files = {}
        try:
            try:
                fuzz = patch.patch(diff,
                                   self.ws.ui,
                                   strip=1,
                                   cwd=self.ws.repo.root,
                                   files=files)
                if fuzz:
                    raise util.Abort('working copy diff applied with fuzz')
            except Exception, e:
                raise util.Abort("couldn't apply working copy diff: %s\n"
                                 "   %s" % (diff, e))
        finally:
            patch.updatedir(self.ws.ui, self.ws.repo, files)

        if not os.path.exists(self.bu.backupfile('renames')):
            return

        #
        # We need to re-apply name changes where the new name
        # (rename/copy destination) is an already versioned file, as
        # Hg would otherwise ignore them.
        #
        try:
            fp = open(self.bu.backupfile('renames'))
            for line in fp:
                source, dest = line.strip().split()
                self.ws.repo.copy(source, dest)
        except EnvironmentError, e:
            raise util.Abort('unable to open renames file: %s' % e)