cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson#
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# This program is free software; you can redistribute it and/or modify
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# it under the terms of the GNU General Public License version 2
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# as published by the Free Software Foundation.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson#
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# This program is distributed in the hope that it will be useful,
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# but WITHOUT ANY WARRANTY; without even the implied warranty of
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# GNU General Public License for more details.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson#
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# You should have received a copy of the GNU General Public License
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# along with this program; if not, write to the Free Software
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson#
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson#
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson# Use is subject to license terms.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson#
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe# Copyright 2008, 2011, Richard Lowe
8703921742d9c7d4d3724f89a39ff0e2725cbe7bRichard Lowe#
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelsonWorkspace backup
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelsonBackup format is:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson backupdir/
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson wsname/
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson generation#/
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson dirstate (handled by CdmUncommittedBackup)
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe File containing dirstate nodeid (the changeset we need
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe to update the workspace to after applying the bundle).
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe This is the node to which the working copy changes
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe (see 'diff', below) will be applied if applicable.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson bundle (handled by CdmCommittedBackup)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson An Hg bundle containing outgoing committed changes.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson nodes (handled by CdmCommittedBackup)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson A text file listing the full (hex) nodeid of all nodes in
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson bundle, used by need_backup.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson diff (handled by CdmUncommittedBackup)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson A Git-formatted diff containing uncommitted changes.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson renames (handled by CdmUncommittedBackup)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson A list of renames in the working copy that have to be
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson applied manually, rather than by the diff.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson metadata.tar.gz (handled by CdmMetadataBackup)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson $CODEMGR_WS/.hg/hgrc
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson $CODEMGR_WS/.hg/localtags
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson $CODEMGR_WS/.hg/patches (Mq data)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe clear.tar.gz (handled by CdmClearBackup)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe <short node>/
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe copies of each modified or added file, as it is in
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe this head.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe ... for each outgoing head
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe working/
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe copies of each modified or added file in the
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe working copy if any.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson latest -> generation#
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson Newest backup generation.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelsonAll files in a given backup generation, with the exception of
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelsondirstate, are optional.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Loweimport grp, os, pwd, shutil, tarfile, time, traceback
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowefrom cStringIO import StringIO
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
036abaca93ddab92ba33036159c30112ab844810Richard Lowefrom mercurial import changegroup, cmdutil, error, node, patch, util
036abaca93ddab92ba33036159c30112ab844810Richard Lowefrom onbld.Scm import Version
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Loweclass CdmNodeMissing(util.Abort):
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe '''a required node is not present in the destination workspace.
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe This may occur both in the case where the bundle contains a
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe changeset which is a child of a node not present in the
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe destination workspace (because the destination workspace is not as
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe up-to-date as the source), or because the source and destination
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe workspace are not related.
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe It may also happen in cases where the uncommitted changes need to
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe be applied onto a node that the workspace does not possess even
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe after application of the bundle (on a branch not present
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe in the bundle or destination workspace, for instance)'''
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe def __init__(self, msg, name):
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe #
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe # If e.name is a string 20 characters long, it is
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe # assumed to be a node. (Mercurial makes this
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe # same assumption, when creating a LookupError)
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe #
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe if isinstance(name, str) and len(name) == 20:
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe n = node.short(name)
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe else:
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe n = name
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe util.Abort.__init__(self, "%s: changeset '%s' is missing\n"
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe "Your workspace is either not "
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe "sufficiently up to date,\n"
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe "or is unrelated to the workspace from "
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe "which the backup was taken.\n" % (msg, n))
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Loweclass CdmTarFile(tarfile.TarFile):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Tar file access + simple comparison to the filesystem, and
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe creation addition of files from Mercurial filectx objects.'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def __init__(self, *args, **kwargs):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tarfile.TarFile.__init__(self, *args, **kwargs)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self.errorlevel = 2
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def members_match_fs(self, rootpath):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Compare the contents of the tar archive to the directory
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe specified by rootpath. Return False if they differ.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe Every file in the archive must match the equivalent file in
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe the filesystem.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe The existence, modification time, and size of each file are
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe compared, content is not.'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def _member_matches_fs(member, rootpath):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Compare a single member to its filesystem counterpart'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fpath = os.path.join(rootpath, member.name)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not os.path.exists(fpath):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return False
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe elif ((os.path.isfile(fpath) != member.isfile()) or
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe (os.path.isdir(fpath) != member.isdir()) or
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe (os.path.islink(fpath) != member.issym())):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return False
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # The filesystem may return a modification time with a
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # fractional component (as a float), whereas the tar format
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # only stores it to the whole second, perform the comparison
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # using integers (truncated, not rounded)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe elif member.mtime != int(os.path.getmtime(fpath)):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return False
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe elif not member.isdir() and member.size != os.path.getsize(fpath):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return False
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe else:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return True
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for elt in self:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not _member_matches_fs(elt, rootpath):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return False
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return True
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def addfilectx(self, filectx, path=None):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Add a filectx object to the archive.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe Use the path specified by the filectx object or, if specified,
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe the PATH argument.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe The size, modification time, type and permissions of the tar
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe member are taken from the filectx object, user and group id
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe are those of the invoking user, user and group name are those
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe of the invoking user if information is available, or "unknown"
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if it is not.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t = tarfile.TarInfo(path or filectx.path())
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.size = filectx.size()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.mtime = filectx.date()[0]
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.uid = os.getuid()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.gid = os.getgid()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.uname = pwd.getpwuid(t.uid).pw_name
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe except KeyError:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.uname = "unknown"
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.gname = grp.getgrgid(t.gid).gr_name
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe except KeyError:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.gname = "unknown"
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # Mercurial versions symlinks by setting a flag and storing
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # the destination path in place of the file content. The
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # actual contents (in the tar), should be empty.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if 'l' in filectx.flags():
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.type = tarfile.SYMTYPE
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.mode = 0777
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.linkname = filectx.data()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe data = None
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe else:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.type = tarfile.REGTYPE
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe t.mode = 'x' in filectx.flags() and 0755 or 0644
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe data = StringIO(filectx.data())
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self.addfile(t, data)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelsonclass CdmCommittedBackup(object):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Backup of committed changes'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def __init__(self, backup, ws):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.ws = ws
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.bu = backup
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.files = ('bundle', 'nodes')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def _outgoing_nodes(self, parent):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Return a list of all outgoing nodes in hex format'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if parent:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson outgoing = self.ws.findoutgoing(parent)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson nodes = self.ws.repo.changelog.nodesbetween(outgoing)[0]
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return map(node.hex, nodes)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson else:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return []
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def backup(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Backup committed changes'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson parent = self.ws.parent()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if not parent:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.ws.ui.warn('Workspace has no parent, committed changes will '
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson 'not be backed up\n')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson out = self.ws.findoutgoing(parent)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if not out:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson cg = self.ws.repo.changegroup(out, 'bundle')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson changegroup.writebundle(cg, self.bu.backupfile('bundle'), 'HG10BZ')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson outnodes = self._outgoing_nodes(parent)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not outnodes:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp = None
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp = self.bu.open('nodes', 'w')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp.write('%s\n' % '\n'.join(outnodes))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe except EnvironmentError, e:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe raise util.Abort("couldn't store outgoing nodes: %s" % e)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe finally:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if fp and not fp.closed:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def restore(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Restore committed changes from backup'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not self.bu.exists('bundle'):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe bpath = self.bu.backupfile('bundle')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe f = None
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe f = self.bu.open('bundle')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe bundle = changegroup.readbundle(f, bpath)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self.ws.repo.addchangegroup(bundle, 'strip',
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe 'bundle:%s' % bpath)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe except EnvironmentError, e:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe raise util.Abort("couldn't restore committed changes: %s\n"
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe " %s" % (bpath, e))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe except error.LookupError, e:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe raise CdmNodeMissing("couldn't restore committed changes",
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe e.name)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe finally:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if f and not f.closed:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe f.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def need_backup(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Compare backup of committed changes to workspace'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if self.bu.exists('nodes'):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson f = None
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe f = self.bu.open('nodes')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe bnodes = set(line.rstrip('\r\n') for line in f.readlines())
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson f.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't open backup node list: %s" % e)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson finally:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if f and not f.closed:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson f.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson else:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson bnodes = set()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson outnodes = set(self._outgoing_nodes(self.ws.parent()))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # If there are outgoing nodes not in the prior backup we need
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # to take a new backup; it's fine if there are nodes in the
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # old backup which are no longer outgoing, however.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not outnodes <= bnodes:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return True
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return False
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def cleanup(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Remove backed up committed changes'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for f in self.files:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self.bu.unlink(f)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelsonclass CdmUncommittedBackup(object):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Backup of uncommitted changes'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def __init__(self, backup, ws):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.ws = ws
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.bu = backup
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self.wctx = self.ws.workingctx(worklist=True)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def _clobbering_renames(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Return a list of pairs of files representing renames/copies
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe that clobber already versioned files. [(old-name new-name)...]
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # Note that this doesn't handle uncommitted merges
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # as CdmUncommittedBackup itself doesn't.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe parent = self.wctx.parents()[0]
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson ret = []
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for fname in self.wctx.added() + self.wctx.modified():
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe rn = self.wctx.filectx(fname).renamed()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if rn and fname in parent:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson ret.append((rn[0], fname))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return ret
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def backup(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Backup uncommitted changes'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if self.ws.merged():
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("Unable to backup an uncommitted merge.\n"
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson "Please complete your merge and commit")
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe dirstate = node.hex(self.wctx.parents()[0].node())
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson fp = None
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp = self.bu.open('dirstate', 'w')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson fp.write(dirstate + '\n')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't save working copy parent: %s" % e)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp = self.bu.open('renames', 'w')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson for cons in self._clobbering_renames():
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson fp.write("%s %s\n" % cons)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't save clobbering copies: %s" % e)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp = self.bu.open('diff', 'w')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe match = self.ws.matcher(files=self.wctx.files())
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp.write(self.ws.diff(opts={'git': True}, match=match))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't save working copy diff: %s" % e)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson finally:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if fp and not fp.closed:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson fp.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def _dirstate(self):
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe '''Return the desired working copy node from the backup'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson fp = None
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp = self.bu.open('dirstate')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson dirstate = fp.readline().strip()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't read saved parent: %s" % e)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson finally:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if fp and not fp.closed:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson fp.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return dirstate
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def restore(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Restore uncommitted changes'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson dirstate = self._dirstate()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe #
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe # Check that the patch's parent changeset exists.
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe n = node.bin(dirstate)
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe self.ws.repo.changelog.lookup(n)
8703921742d9c7d4d3724f89a39ff0e2725cbe7bRichard Lowe except error.LookupError, e:
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe raise CdmNodeMissing("couldn't restore uncommitted changes",
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe e.name)
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe try:
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe self.ws.clean(rev=dirstate)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except util.Abort, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't update to saved node: %s" % e)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not self.bu.exists('diff'):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # There's a race here whereby if the patch (or part thereof)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # is applied within the same second as the clean above (such
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # that modification time doesn't change) and if the size of
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # that file does not change, Hg may not see the change.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # We sleep a full second to avoid this, as sleeping merely
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # until the next second begins would require very close clock
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # synchronization on network filesystems.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson time.sleep(1)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson files = {}
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe diff = self.bu.backupfile('diff')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson fuzz = patch.patch(diff, self.ws.ui, strip=1,
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson cwd=self.ws.repo.root, files=files)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if fuzz:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('working copy diff applied with fuzz')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except Exception, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't apply working copy diff: %s\n"
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson " %s" % (diff, e))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson finally:
036abaca93ddab92ba33036159c30112ab844810Richard Lowe if Version.at_least("1.7"):
036abaca93ddab92ba33036159c30112ab844810Richard Lowe cmdutil.updatedir(self.ws.ui, self.ws.repo, files)
036abaca93ddab92ba33036159c30112ab844810Richard Lowe else:
036abaca93ddab92ba33036159c30112ab844810Richard Lowe patch.updatedir(self.ws.ui, self.ws.repo, files)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not self.bu.exists('renames'):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # We need to re-apply name changes where the new name
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # (rename/copy destination) is an already versioned file, as
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # Hg would otherwise ignore them.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fp = self.bu.open('renames')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson for line in fp:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson source, dest = line.strip().split()
8703921742d9c7d4d3724f89a39ff0e2725cbe7bRichard Lowe self.ws.copy(source, dest)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('unable to open renames file: %s' % e)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except ValueError:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('corrupt renames file: %s' %
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.bu.backupfile('renames'))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def need_backup(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Compare backup of uncommitted changes to workspace'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe cnode = self.wctx.parents()[0].node()
2b5878de2735cb61d008168e1f27e390d2edf915Rich Lowe if self._dirstate() != node.hex(cnode):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return True
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fd = None
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe match = self.ws.matcher(files=self.wctx.files())
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe curdiff = self.ws.diff(opts={'git': True}, match=match)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if self.bu.exists('diff'):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fd = self.bu.open('diff')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson backdiff = fd.read()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fd.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't open backup diff %s\n"
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe " %s" % (self.bu.backupfile('diff'), e))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe else:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe backdiff = ''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if backdiff != curdiff:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return True
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe currrenamed = self._clobbering_renames()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe bakrenamed = None
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if self.bu.exists('renames'):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fd = self.bu.open('renames')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe bakrenamed = [tuple(line.strip().split(' ')) for line in fd]
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fd.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't open renames file %s: %s\n" %
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson (self.bu.backupfile('renames'), e))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if currrenamed != bakrenamed:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return True
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe finally:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if fd and not fd.closed:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fd.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return False
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def cleanup(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Remove backed up uncommitted changes'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for f in ('dirstate', 'diff', 'renames'):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self.bu.unlink(f)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelsonclass CdmMetadataBackup(object):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Backup of workspace metadata'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def __init__(self, backup, ws):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.bu = backup
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.ws = ws
9a70fc3be3b1e966bf78825cdb8d509963a6f0a1Mark J. Nelson self.files = ('hgrc', 'localtags', 'patches', 'cdm')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def backup(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Backup workspace metadata'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tarpath = self.bu.backupfile('metadata.tar.gz')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # Files is a list of tuples (name, path), where name is as in
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # self.files, and path is the absolute path.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe files = filter(lambda (name, path): os.path.exists(path),
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe zip(self.files, map(self.ws.repo.join, self.files)))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not files:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar = CdmTarFile.gzopen(tarpath, 'w')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe except (EnvironmentError, tarfile.TarError), e:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe raise util.Abort("couldn't open %s for writing: %s" %
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe (tarpath, e))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for name, path in files:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar.add(path, name)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe except (EnvironmentError, tarfile.TarError), e:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # tarfile.TarError doesn't include the tar member or file
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe # in question, so we have to do so ourselves.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if isinstance(e, tarfile.TarError):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe errstr = "%s: %s" % (name, e)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe else:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe errstr = str(e)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe raise util.Abort("couldn't backup metadata to %s:\n"
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe " %s" % (tarpath, errstr))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson finally:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def old_restore(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Restore workspace metadata from an pre-tar backup'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson for fname in self.files:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if self.bu.exists(fname):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe bfile = self.bu.backupfile(fname)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe wfile = self.ws.repo.join(fname)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson shutil.copy2(bfile, wfile)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't restore metadata from %s:\n"
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson " %s" % (bfile, e))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def tar_restore(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Restore workspace metadata (from a tar-style backup)'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not self.bu.exists('metadata.tar.gz'):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tarpath = self.bu.backupfile('metadata.tar.gz')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar = CdmTarFile.gzopen(tarpath)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe except (EnvironmentError, tarfile.TarError), e:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe raise util.Abort("couldn't open %s: %s" % (tarpath, e))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for elt in tar:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar.extract(elt, path=self.ws.repo.path)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except (EnvironmentError, tarfile.TarError), e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # Make sure the member name is in the exception message.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if isinstance(e, tarfile.TarError):
8703921742d9c7d4d3724f89a39ff0e2725cbe7bRichard Lowe errstr = "%s: %s" % (elt.name, e)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson else:
8703921742d9c7d4d3724f89a39ff0e2725cbe7bRichard Lowe errstr = str(e)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't restore metadata from %s:\n"
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson " %s" %
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe (tarpath, errstr))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe finally:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if tar and not tar.closed:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def restore(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Restore workspace metadata'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if self.bu.exists('hgrc'):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.old_restore()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson else:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.tar_restore()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def _walk(self):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Yield the repo-relative path to each file we operate on,
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe including each file within any affected directory'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for elt in self.files:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe path = self.ws.repo.join(elt)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not os.path.exists(path):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe continue
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if os.path.isdir(path):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for root, dirs, files in os.walk(path, topdown=True):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe yield root
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for f in files:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe yield os.path.join(root, f)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe else:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe yield path
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def need_backup(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Compare backed up workspace metadata to workspace'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def strip_trailing_pathsep(pathname):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Remove a possible trailing path separator from PATHNAME'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return pathname.endswith('/') and pathname[:-1] or pathname
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if self.bu.exists('metadata.tar.gz'):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tarpath = self.bu.backupfile('metadata.tar.gz')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar = CdmTarFile.gzopen(tarpath)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except (EnvironmentError, tarfile.TarError), e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("couldn't open metadata tarball: %s\n"
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe " %s" % (tarpath, e))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not tar.members_match_fs(self.ws.repo.path):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar.close()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return True
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tarnames = map(strip_trailing_pathsep, tar.getnames())
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson tar.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson else:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson tarnames = []
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe repopath = self.ws.repo.path
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not repopath.endswith('/'):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe repopath += '/'
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for path in self._walk():
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if path.replace(repopath, '', 1) not in tarnames:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return True
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return False
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def cleanup(self):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Remove backed up workspace metadata'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self.bu.unlink('metadata.tar.gz')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Loweclass CdmClearBackup(object):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''A backup (in tar format) of complete source files from every
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe workspace head.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe Paths in the tarball are prefixed by the revision and node of the
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe head, or "working" for the working directory.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe This is done purely for the benefit of the user, and as such takes
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe no part in restore or need_backup checking, restore always
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe succeeds, need_backup always returns False
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def __init__(self, backup, ws):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self.bu = backup
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self.ws = ws
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def _branch_pairs(self):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Return a list of tuples (parenttip, localtip) for each
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe outgoing head. If the working copy contains modified files,
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe it is a head, and neither of its parents are.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe parent = self.ws.parent()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if parent:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe outgoing = self.ws.findoutgoing(parent)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe outnodes = set(self.ws.repo.changelog.nodesbetween(outgoing)[0])
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe heads = [self.ws.repo.changectx(n) for n in self.ws.repo.heads()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if n in outnodes]
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe else:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe heads = []
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe outnodes = []
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe wctx = self.ws.workingctx()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if wctx.files(): # We only care about file changes.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe heads = filter(lambda x: x not in wctx.parents(), heads) + [wctx]
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe pairs = []
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for head in heads:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if head.rev() is None:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe c = head.parents()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson else:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe c = [head]
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe pairs.append((self.ws.parenttip(c, outnodes), head))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return pairs
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def backup(self):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Save a clear copy of each source file modified between each
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe head and that head's parenttip (see WorkSpace.parenttip).
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tarpath = self.bu.backupfile('clear.tar.gz')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe branches = self._branch_pairs()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not branches:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar = CdmTarFile.gzopen(tarpath, 'w')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe except (EnvironmentError, tarfile.TarError), e:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe raise util.Abort("Could not open %s for writing: %s" %
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe (tarpath, e))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for parent, child in branches:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tpath = child.node() and node.short(child.node()) or "working"
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe for fname, change in self.ws.status(parent, child).iteritems():
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if change not in ('added', 'modified'):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe continue
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe try:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar.addfilectx(child.filectx(fname),
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe os.path.join(tpath, fname))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe except ValueError, e:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe crev = child.rev()
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if crev is None:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe crev = "working copy"
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe raise util.Abort("Could not backup clear file %s "
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe "from %s: %s\n" % (fname, crev, e))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe finally:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe tar.close()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def cleanup(self):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Cleanup a failed Clear backup.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe Remove the clear tarball from the backup directory.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self.bu.unlink('clear.tar.gz')
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def restore(self):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Clear backups are never restored, do nothing'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe pass
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def need_backup(self):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Clear backups are never compared, return False (no backup needed).
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe Should a backup actually be needed, one of the other
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe implementation classes would notice in any situation we would.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return False
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelsonclass CdmBackup(object):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''A backup of a given workspace'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def __init__(self, ui, ws, name):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.ws = ws
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.ui = ui
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.backupdir = self._find_backup_dir(name)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # The order of instances here controls the order the various operations
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # are run.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # There's some inherent dependence, in that on restore we need
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # to restore committed changes prior to uncommitted changes
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # (as the parent revision of any uncommitted changes is quite
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # likely to not exist until committed changes are restored).
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # Metadata restore can happen at any point, but happens last
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # as a matter of convention.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.modules = [x(self, ws) for x in [CdmCommittedBackup,
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson CdmUncommittedBackup,
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe CdmClearBackup,
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson CdmMetadataBackup]]
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if os.path.exists(os.path.join(self.backupdir, 'latest')):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson generation = os.readlink(os.path.join(self.backupdir, 'latest'))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.generation = int(os.path.split(generation)[1])
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson else:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.generation = 0
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def _find_backup_dir(self, name):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Find the path to an appropriate backup directory based on NAME'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if os.path.isabs(name):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return name
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if self.ui.config('cdm', 'backupdir'):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson backupbase = os.path.expanduser(self.ui.config('cdm', 'backupdir'))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson else:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson home = None
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson home = os.getenv('HOME') or pwd.getpwuid(os.getuid()).pw_dir
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except KeyError:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson pass # Handled anyway
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if not home:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('Could not determine your HOME directory to '
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson 'find backup path')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson backupbase = os.path.join(home, 'cdm.backup')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson backupdir = os.path.join(backupbase, name)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # If backupdir exists, it must be a directory.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if (os.path.exists(backupdir) and not os.path.isdir(backupdir)):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('%s exists but is not a directory' % backupdir)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return backupdir
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def _update_latest(self, gen):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Update latest symlink to point to the current generation'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson linkpath = os.path.join(self.backupdir, 'latest')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if os.path.lexists(linkpath):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson os.unlink(linkpath)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson os.symlink(str(gen), linkpath)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def _create_gen(self, gen):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Create a new backup generation'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson os.makedirs(os.path.join(self.backupdir, str(gen)))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self._update_latest(gen)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort("Couldn't create backup generation %s: %s" %
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson (os.path.join(self.backupdir, str(gen)), e))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def backupfile(self, path):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''return full path to backup file FILE at GEN'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return os.path.join(self.backupdir, str(self.generation), path)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def unlink(self, name):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Unlink the specified path from the backup directory.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe A no-op if the path does not exist.
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe fpath = self.backupfile(name)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if os.path.exists(fpath):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe os.unlink(fpath)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def open(self, name, mode='r'):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Open the specified file in the backup directory'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return open(self.backupfile(name), mode)
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe def exists(self, name):
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe '''Return boolean indicating wether a given file exists in the
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe backup directory.'''
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe return os.path.exists(self.backupfile(name))
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def need_backup(self):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Compare backed up changes to workspace'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # If there's no current backup generation, or the last backup was
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # invalid (lacking the dirstate file), we need a backup regardless
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # of anything else.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not self.generation or not self.exists('dirstate'):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return True
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson for x in self.modules:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if x.need_backup():
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return True
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson return False
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def backup(self):
c959a081a8aebb76386b6d8ea3afa850e328f6c7Richard Lowe '''Take a backup of the current workspace
c959a081a8aebb76386b6d8ea3afa850e328f6c7Richard Lowe
c959a081a8aebb76386b6d8ea3afa850e328f6c7Richard Lowe Calling code is expected to hold both the working copy lock
c959a081a8aebb76386b6d8ea3afa850e328f6c7Richard Lowe and repository lock.'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if not os.path.exists(self.backupdir):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson os.makedirs(self.backupdir)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except EnvironmentError, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('Could not create backup directory %s: %s' %
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson (self.backupdir, e))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.generation += 1
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self._create_gen(self.generation)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson for x in self.modules:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson x.backup()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except Exception, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if isinstance(e, KeyboardInterrupt):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.ws.ui.warn("Interrupted\n")
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson else:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.ws.ui.warn("Error: %s\n" % e)
8703921742d9c7d4d3724f89a39ff0e2725cbe7bRichard Lowe show_traceback = self.ws.ui.configbool('ui', 'traceback',
8703921742d9c7d4d3724f89a39ff0e2725cbe7bRichard Lowe False)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # If it's not a 'normal' error, we want to print a stack
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # trace now in case the attempt to remove the partial
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson # backup also fails, and raises a second exception.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson #
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if (not isinstance(e, (EnvironmentError, util.Abort))
c959a081a8aebb76386b6d8ea3afa850e328f6c7Richard Lowe or show_traceback):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson traceback.print_exc()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson for x in self.modules:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson x.cleanup()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson os.rmdir(os.path.join(self.backupdir, str(self.generation)))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.generation -= 1
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if self.generation != 0:
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe self._update_latest(self.generation)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson else:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson os.unlink(os.path.join(self.backupdir, 'latest'))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('Backup failed')
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson def restore(self, gen=None):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '''Restore workspace from backup
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson Restores from backup generation GEN (defaulting to the latest)
c959a081a8aebb76386b6d8ea3afa850e328f6c7Richard Lowe into workspace WS.
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
c959a081a8aebb76386b6d8ea3afa850e328f6c7Richard Lowe Calling code is expected to hold both the working copy lock
c959a081a8aebb76386b6d8ea3afa850e328f6c7Richard Lowe and repository lock of the destination workspace.'''
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if not os.path.exists(self.backupdir):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('Backup directory does not exist: %s' %
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson (self.backupdir))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if gen:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson if not os.path.exists(os.path.join(self.backupdir, str(gen))):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('Backup generation does not exist: %s' %
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson (os.path.join(self.backupdir, str(gen))))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson self.generation = int(gen)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not self.generation: # This is OK, 0 is not a valid generation
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('Backup has no generations: %s' % self.backupdir)
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
605a716e6d38b3af09034c254382d0ae3b7d5f70Richard Lowe if not self.exists('dirstate'):
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('Backup %s/%s is incomplete (dirstate missing)' %
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson (self.backupdir, self.generation))
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson try:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson for x in self.modules:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson x.restore()
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson except util.Abort, e:
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson raise util.Abort('Error restoring workspace:\n'
cdf0c1d55d9b3b6beaf994835440dfb01aef5cf0mjnelson '%s\n'
12203c7149a0ba35c436436cd2400dc7aaba1e62Richard Lowe 'Workspace may be partially restored' % e)