mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 09:17:30 +03:00
9fea0c3f0c
Summary: "changeset" is a more official term and let's use it. Note that this patch only changes documentation / i18n messages visible to the users and header comment blocks to developers. Other places like comments in the code are untouched. With the "dialect" extension enabled, users will still see the more friendly term - "commit". Test Plan: `arc unit`. Note the remotefilelog failure is probably unrelated - seems related to ongoing / upcoming manifest refactoring upstream. Reviewers: #sourcecontrol, rmcelroy Reviewed By: rmcelroy Subscribers: mjpieters Differential Revision: https://phabricator.intern.facebook.com/D3900394 Signature: t1:3900394:1474470348:6a1b5691e2599cc47df18b227d56d1f9d3c7c906
147 lines
5.8 KiB
Python
147 lines
5.8 KiB
Python
# backups.py
|
|
#
|
|
# Copyright 2013 Facebook, Inc.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2 or any later version.
|
|
"""display recently made backups to recover stripped changesets"""
|
|
|
|
from mercurial import extensions, cmdutil, commands, error, bundlerepo
|
|
from mercurial import hg, changegroup, exchange, obsolete
|
|
from mercurial import bundle2
|
|
from mercurial import lock as lockmod
|
|
from hgext import pager
|
|
from mercurial.node import nullid, short
|
|
from mercurial.i18n import _
|
|
import os, glob, time
|
|
|
|
pager.attended.append('backups')
|
|
|
|
cmdtable = {}
|
|
command = cmdutil.command(cmdtable)
|
|
testedwith = 'internal'
|
|
msgwithcreatermarkers = """Marker creation is enabled so no changeset should be
|
|
stripped unless you explicitly called hg strip. hg backups will show you the
|
|
stripped changesets. If you are trying to recover a changeset hidden from a
|
|
previous command, use hg journal to get its sha1 and you will be able to access
|
|
it directly without recovering a backup."""
|
|
verbosetemplate = "{label('status.modified', node|short)} {desc|firstline}\n"
|
|
|
|
@command('^backups', [
|
|
('', 'recover', '',
|
|
'brings the specified changeset back into the repository')
|
|
] + commands.logopts, _('hg backups [--recover HASH]'))
|
|
def backups(ui, repo, *pats, **opts):
|
|
'''lists the changesets available in backup bundles
|
|
|
|
Without any arguments, this command prints a list of the changesets in each
|
|
backup bundle.
|
|
|
|
--recover takes a changeset hash and unbundles the first bundle that
|
|
contains that hash, which puts that changeset back in your repository.
|
|
|
|
--verbose will print the entire commit message and the bundle path for that
|
|
backup.
|
|
'''
|
|
supportsmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
|
|
if supportsmarkers:
|
|
# Warn users of obsolescence markers that they probably don't want to
|
|
# use backups but reflog instead
|
|
ui.warn(msgwithcreatermarkers)
|
|
backuppath = repo.join("strip-backup")
|
|
backups = filter(os.path.isfile, glob.glob(backuppath + "/*.hg"))
|
|
backups.sort(key=lambda x: os.path.getmtime(x), reverse=True)
|
|
|
|
opts['bundle'] = ''
|
|
opts['force'] = None
|
|
|
|
def display(other, chlist, displayer):
|
|
limit = cmdutil.loglimit(opts)
|
|
if opts.get('newest_first'):
|
|
chlist.reverse()
|
|
count = 0
|
|
for n in chlist:
|
|
if limit is not None and count >= limit:
|
|
break
|
|
parents = [p for p in other.changelog.parents(n) if p != nullid]
|
|
if opts.get('no_merges') and len(parents) == 2:
|
|
continue
|
|
count += 1
|
|
displayer.show(other[n])
|
|
|
|
recovernode = opts.get('recover')
|
|
if recovernode:
|
|
if recovernode in repo:
|
|
ui.warn(_("%s already exists in the repo\n") % recovernode)
|
|
return
|
|
else:
|
|
msg = _('Recover changesets using: hg backups --recover '
|
|
'<changeset hash>\n')
|
|
ui.status(msg, label="status.removed")
|
|
|
|
for backup in backups:
|
|
# Much of this is copied from the hg incoming logic
|
|
source = os.path.relpath(backup, os.getcwd())
|
|
source = ui.expandpath(source)
|
|
source, branches = hg.parseurl(source, opts.get('branch'))
|
|
try:
|
|
other = hg.peer(repo, opts, source)
|
|
except error.LookupError as ex:
|
|
msg = _("\nwarning: unable to open bundle %s") % source
|
|
hint = _("\n(missing parent rev %s)\n") % short(ex.name)
|
|
ui.warn(msg)
|
|
ui.warn(hint)
|
|
continue
|
|
revs, checkout = hg.addbranchrevs(repo, other, branches,
|
|
opts.get('rev'))
|
|
|
|
if revs:
|
|
revs = [other.lookup(rev) for rev in revs]
|
|
|
|
quiet = ui.quiet
|
|
try:
|
|
ui.quiet = True
|
|
other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo,
|
|
other, revs, opts["bundle"],
|
|
opts["force"])
|
|
except error.LookupError:
|
|
continue
|
|
finally:
|
|
ui.quiet = quiet
|
|
|
|
try:
|
|
if chlist:
|
|
if recovernode:
|
|
tr = lock = None
|
|
try:
|
|
lock = repo.lock()
|
|
if recovernode in other:
|
|
ui.status(_("Unbundling %s\n") % (recovernode))
|
|
f = hg.openpath(ui, source)
|
|
gen = exchange.readbundle(ui, f, source)
|
|
tr = repo.transaction("unbundle")
|
|
if not isinstance(gen, bundle2.unbundle20):
|
|
gen.apply(repo, 'unbundle', 'bundle:' + source)
|
|
if isinstance(gen, bundle2.unbundle20):
|
|
bundle2.applybundle(repo, gen, tr,
|
|
source='unbundle',
|
|
url='bundle:' + source)
|
|
tr.close()
|
|
break
|
|
finally:
|
|
lockmod.release(lock, tr)
|
|
else:
|
|
backupdate = os.path.getmtime(source)
|
|
backupdate = time.strftime('%a %H:%M, %Y-%m-%d',
|
|
time.localtime(backupdate))
|
|
ui.status("\n%s\n" % (backupdate.ljust(50)))
|
|
if not ui.verbose:
|
|
opts['template'] = verbosetemplate
|
|
else:
|
|
ui.status("%s%s\n" % ("bundle:".ljust(13), source))
|
|
displayer = cmdutil.show_changeset(ui, other, opts, False)
|
|
display(other, chlist, displayer)
|
|
displayer.close()
|
|
finally:
|
|
cleanupfn()
|