# HG changeset patch # User Pulkit Goyal <7895pulkit@gmail.com> # Date 2020-01-17 18:22:23 # Node ID f82d2d4e71db3290d0b2d0964be3e9ff1842476d # Parent 4ce2330f2d0b9690cc852602a9e055491a408c03 debugbackupbundle: introduce command to interact with strip backups This vendors backups extension from hg-experimental. Listing backups and having some utility to apply them is nice. I know we have obsmarkers now, but this will help a lot of end users who still uses strip until we get evolve out of experimental. Differential Revision: https://phab.mercurial-scm.org/D7932 diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -11,6 +11,7 @@ import codecs import collections import difflib import errno +import glob import operator import os import platform @@ -38,6 +39,7 @@ from .pycompat import ( ) from . import ( bundle2, + bundlerepo, changegroup, cmdutil, color, @@ -3402,6 +3404,143 @@ def debugssl(ui, repo, source=None, **op @command( + b"debugbackupbundle", + [ + ( + b"", + b"recover", + b"", + b"brings the specified changeset back into the repository", + ) + ] + + cmdutil.logopts, + _(b"hg debugbackupbundle [--recover HASH]"), +) +def debugbackupbundle(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. + """ + backups = list( + filter( + os.path.isfile, glob.glob(repo.vfs.join(b"strip-backup") + b"/*.hg") + ) + ) + backups.sort(key=lambda x: os.path.getmtime(x), reverse=True) + + opts = pycompat.byteskwargs(opts) + opts[b"bundle"] = b"" + opts[b"force"] = None + limit = logcmdutil.getlimit(opts) + + def display(other, chlist, displayer): + if opts.get(b"newest_first"): + chlist.reverse() + count = 0 + for n in chlist: + if limit is not None and count >= limit: + break + parents = [True for p in other.changelog.parents(n) if p != nullid] + if opts.get(b"no_merges") and len(parents) == 2: + continue + count += 1 + displayer.show(other[n]) + + recovernode = opts.get(b"recover") + if recovernode: + if scmutil.isrevsymbol(repo, recovernode): + ui.warn(_(b"%s already exists in the repo\n") % recovernode) + return + elif backups: + msg = _( + b"Recover changesets using: hg debugbackupbundle --recover " + b"\n\nAvailable backup changesets:" + ) + ui.status(msg, label=b"status.removed") + else: + ui.status(_(b"no backup changesets found\n")) + return + + for backup in backups: + # Much of this is copied from the hg incoming logic + source = ui.expandpath(os.path.relpath(backup, encoding.getcwd())) + source, branches = hg.parseurl(source, opts.get(b"branch")) + try: + other = hg.peer(repo, opts, source) + except error.LookupError as ex: + msg = _(b"\nwarning: unable to open bundle %s") % source + hint = _(b"\n(missing parent rev %s)\n") % short(ex.name) + ui.warn(msg, hint=hint) + continue + revs, checkout = hg.addbranchrevs( + repo, other, branches, opts.get(b"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[b"bundle"], opts[b"force"] + ) + except error.LookupError: + continue + finally: + ui.quiet = quiet + + try: + if not chlist: + continue + if recovernode: + with repo.lock(), repo.transaction(b"unbundle") as tr: + if scmutil.isrevsymbol(other, recovernode): + ui.status(_(b"Unbundling %s\n") % (recovernode)) + f = hg.openpath(ui, source) + gen = exchange.readbundle(ui, f, source) + if isinstance(gen, bundle2.unbundle20): + bundle2.applybundle( + repo, + gen, + tr, + source=b"unbundle", + url=b"bundle:" + source, + ) + else: + gen.apply(repo, b"unbundle", b"bundle:" + source) + break + else: + backupdate = encoding.strtolocal( + time.strftime( + "%a %H:%M, %Y-%m-%d", + time.localtime(os.path.getmtime(source)), + ) + ) + ui.status(b"\n%s\n" % (backupdate.ljust(50))) + if ui.verbose: + ui.status(b"%s%s\n" % (b"bundle:".ljust(13), source)) + else: + opts[ + b"template" + ] = b"{label('status.modified', node|short)} {desc|firstline}\n" + displayer = logcmdutil.changesetdisplayer( + ui, other, opts, False + ) + display(other, chlist, displayer) + displayer.close() + finally: + cleanupfn() + + +@command( b'debugsub', [(b'r', b'rev', b'', _(b'revision to check'), _(b'REV'))], _(b'[-r REV] [REV]'), diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -75,6 +75,7 @@ Show debug commands if there are no othe $ hg debugcomplete debug debugancestor debugapplystreamclonebundle + debugbackupbundle debugbuilddag debugbundle debugcapabilities @@ -260,6 +261,7 @@ Show all commands + options copy: forget, after, at-rev, force, include, exclude, dry-run debugancestor: debugapplystreamclonebundle: + debugbackupbundle: recover, patch, git, limit, no-merges, stat, graph, style, template debugbuilddag: mergeable-file, overwritten-file, new-file debugbundle: all, part-type, spec debugcapabilities: diff --git a/tests/test-debugbackupbundle.t b/tests/test-debugbackupbundle.t new file mode 100644 --- /dev/null +++ b/tests/test-debugbackupbundle.t @@ -0,0 +1,39 @@ + $ cat >> $HGRCPATH << EOF + > [extensions] + > strip= + > EOF + +Setup repo + + $ hg init repo + $ cd repo + +Test backups list and recover + + $ hg debugbackupbundle + no backup changesets found + + $ mkcommit() { + > echo "$1" > "$1" + > hg add "$1" + > hg ci -l $1 + > } + $ mkcommit a + $ mkcommit b + $ hg strip . + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d2ae7f538514-2953539b-backup.hg (glob) + $ hg debugbackupbundle + Recover changesets using: hg debugbackupbundle --recover + + Available backup changesets: + * (glob) + d2ae7f538514 b + + $ hg debugbackupbundle --recover d2ae7f538514 + Unbundling d2ae7f538514 + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets d2ae7f538514 (1 drafts) diff --git a/tests/test-help.t b/tests/test-help.t --- a/tests/test-help.t +++ b/tests/test-help.t @@ -973,6 +973,8 @@ Test list of internal help commands find the ancestor revision of two revisions in a given index debugapplystreamclonebundle apply a stream clone bundle file + debugbackupbundle + lists the changesets available in backup bundles debugbuilddag builds a repo with a given DAG from scratch in the current empty repo