Backup.py revision 9a70fc3be3b1e966bf78825cdb8d509963a6f0a1
#
# it under the terms of the GNU General Public License version 2
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#
# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
'''
Workspace backup
Backup format is:
backupdir/
wsname/
generation#/
dirstate (handled by CdmUncommittedBackup)
File containing dirstate nodeid (the tip we expect to be at
after applying the bundle).
bundle (handled by CdmCommittedBackup)
An Hg bundle containing outgoing committed changes.
nodes (handled by CdmCommittedBackup)
A text file listing the full (hex) nodeid of all nodes in
bundle, used by need_backup.
diff (handled by CdmUncommittedBackup)
A Git-formatted diff containing uncommitted changes.
renames (handled by CdmUncommittedBackup)
A list of renames in the working copy that have to be
applied manually, rather than by the diff.
metadata.tar.gz (handled by CdmMetadataBackup)
latest -> generation#
Newest backup generation.
All files in a given backup generation, with the exception of
dirstate, are optional.
'''
class CdmCommittedBackup(object):
'''Backup of committed changes'''
'''Return a list of all outgoing nodes in hex format'''
if parent:
else:
return []
'''Backup committed changes'''
if not parent:
'not be backed up\n')
return
if not out:
return
if outnodes:
fp = None
try:
try:
except EnvironmentError, e:
finally:
'''Restore committed changes from backup'''
f = None
try:
try:
'bundle:%s' % bfile)
except EnvironmentError, e:
" %s" % (bfile, e))
finally:
if f and not f.closed:
f.close()
def need_backup(self):
'''Compare backup of committed changes to workspace'''
f = None
try:
try:
f.close()
except EnvironmentError, e:
finally:
if f and not f.closed:
f.close()
else:
return True
return False
'''Remove backed up committed changes'''
class CdmUncommittedBackup(object):
'''Backup of uncommitted changes'''
def _clobbering_renames(self):
that clobber already versioned files. [(oldname newname)...]'''
#
# Note that this doesn't handle uncommitted merges
# as CdmUncommittedBackup itself doesn't.
#
ret = []
return ret
'''Backup uncommitted changes'''
"Please complete your merge and commit")
fp = None
try:
try:
except EnvironmentError, e:
finally:
try:
try:
except EnvironmentError, e:
finally:
try:
try:
except EnvironmentError, e:
finally:
'''Return the current working copy node'''
fp = None
try:
try:
return dirstate
except EnvironmentError, e:
finally:
'''Restore uncommitted changes'''
try:
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.
#
files = {}
try:
try:
if fuzz:
except Exception, e:
" %s" % (diff, e))
finally:
return
#
# We need to re-apply name changes where the new name
# Hg would otherwise ignore them.
#
try:
except EnvironmentError, e:
except ValueError:
def need_backup(self):
'''Compare backup of uncommitted changes to workspace'''
return True
fd = None
try:
try:
except EnvironmentError, e:
" %s" % (diff, e))
finally:
else:
backdiff = ''
return True
bakrenamed = None
try:
try:
except EnvironmentError, e:
finally:
if currrenamed != bakrenamed:
return True
return False
'''Remove backed up uncommitted changes'''
class CdmMetadataBackup(object):
'''Backup of workspace metadata'''
'''Backup workspace metadata'''
tar = None
try:
try:
'w:gz')
try:
#
# tarfile.TarError doesn't include the tar member or file
# in question, so we have to do so ourselves.
#
else:
" %s" %
error))
finally:
def old_restore(self):
'''Restore workspace metadata from an pre-tar backup'''
try:
except EnvironmentError, e:
" %s" % (bfile, e))
def tar_restore(self):
'''Restore workspace metadata (from a tar-style backup)'''
tar = None
try:
try:
try:
# Make sure the member name is in the exception message.
else:
" %s" %
error))
finally:
'''Restore workspace metadata'''
else:
def need_backup(self):
'''Compare backed up workspace metadata to workspace'''
try:
" %s" %
return True # File in tar, not workspace
continue
return True
else:
tarnames = []
# Directories in tarfile always end with a '/'
mfile += '/'
return True
rpath += '/'
return True # In workspace not tar
else:
return True
return False
'''Remove backed up workspace metadata'''
'''A backup of a given workspace'''
#
# The order of instances here controls the order the various operations
# are run.
#
# There's some inherent dependence, in that on restore we need
# to restore committed changes prior to uncommitted changes
# (as the parent revision of any uncommitted changes is quite
# likely to not exist until committed changes are restored).
# Metadata restore can happen at any point, but happens last
# as a matter of convention.
#
else:
'''Find the path to an appropriate backup directory based on NAME'''
backupdir = None
backupbase = None
return name
else:
home = None
try:
except KeyError:
pass # Handled anyway
if not home:
'find backup path')
# If backupdir exists, it must be a directory.
return backupdir
'''return full path to backup file FILE at GEN'''
'''Update latest symlink to point to the current generation'''
'''Create a new backup generation'''
try:
except EnvironmentError, e:
def need_backup(self):
'''Compare backed up changes to workspace'''
#
# If there's no current backup generation, or the last backup was
# invalid (lacking the dirstate file), we need a backup regardless
# of anything else.
#
if (not self.generation or
return True
if x.need_backup():
return True
return False
'''Take a backup of the current workspace'''
try:
except EnvironmentError, e:
#
# Lock the repo, so the backup can be consistent. We need the
# wlock too to make sure the dirstate parent doesn't change
# underneath us.
#
try:
x.backup()
except Exception, e:
if isinstance(e, KeyboardInterrupt):
else:
#
# If it's not a 'normal' error, we want to print a stack
# trace now in case the attempt to remove the partial
# backup also fails, and raises a second exception.
#
x.cleanup()
else:
'''Restore workspace from backup
Restores from backup generation GEN (defaulting to the latest)
into workspace WS.'''
if gen:
try:
x.restore()
'%s\n'
'Workspace will be partially restored' % e)