diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -11,6 +11,7 @@ import collections import difflib import errno +import glob import operator import os import platform @@ -39,6 +40,7 @@ ) from . import ( bundle2, + bundlerepo, changegroup, cmdutil, color, @@ -130,6 +132,7 @@ gen.apply(repo) + @command( b'debugbuilddag', [ @@ -3361,6 +3364,131 @@ @command( + "debugstripbackups", + [ + ( + "", + "recover", + "", + "brings the specified changeset back into the repository", + ) + ] + + cmdutil.logopts, + _("hg debugstripbackups [--recover HASH]"), +) +def debugstripbackups(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 = filter(os.path.isfile, glob.glob(repo.vfs.join("strip-backup") + "/*.hg")) + backups.sort(key=lambda x: os.path.getmtime(x), reverse=True) + + opts["bundle"] = "" + opts["force"] = None + limit = logcmdutil.getlimit(opts) + + def display(other, chlist, displayer): + if opts.get("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("no_merges") and len(parents) == 2: + continue + count += 1 + displayer.show(other[n]) + + recovernode = opts.get("recover") + if recovernode: + if scmutil.isrevsymbol(repo, recovernode): + ui.warn(_("%s already exists in the repo\n") % recovernode) + return + elif backups: + msg = _( + "Recover changesets using: hg debugstripbackups --recover " + "\n\nAvailable backup changesets:" + ) + ui.status(msg, label="status.removed") + else: + ui.status(_("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("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, hint=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 not chlist: + continue + if recovernode: + with repo.lock(), repo.transaction("unbundle") as tr: + if scmutil.isrevsymbol(other, recovernode): + ui.status(_("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="unbundle", + url="bundle:" + source, + ) + else: + gen.apply(repo, "unbundle", "bundle:" + source) + break + else: + backupdate = time.strftime( + "%a %H:%M, %Y-%m-%d", time.localtime(os.path.getmtime(source)) + ) + ui.status("\n%s\n" % (backupdate.ljust(50))) + if ui.verbose: + ui.status("%s%s\n" % ("bundle:".ljust(13), source)) + else: + opts["template"] = "{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-debugstripbackup.t b/tests/test-debugstripbackup.t new file mode 100644 --- /dev/null +++ b/tests/test-debugstripbackup.t @@ -0,0 +1,39 @@ + $ cat >> $HGRCPATH << EOF + > [extensions] + > strip= + > EOF + +Setup repo + + $ hg init repo + $ cd repo + +Test backups list and recover + + $ hg debugstripbackups + 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 debugstripbackups + Recover changesets using: hg debugstripbackups --recover + + Available backup changesets: + * (glob) + d2ae7f538514 b + + $ hg debugstripbackups --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)