shelve.py
1286 lines
| 41.6 KiB
| text/x-python
|
PythonLexer
/ mercurial / shelve.py
Navaneeth Suresh
|
r42744 | # shelve.py - save/restore working directory state | ||
# | ||||
# 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. | ||||
"""save and restore changes to the working directory | ||||
The "hg shelve" command saves changes made to the working directory | ||||
and reverts those changes, resetting the working directory to a clean | ||||
state. | ||||
Later on, the "hg unshelve" command restores the changes saved by "hg | ||||
shelve". Changes can be restored even after updating to a different | ||||
parent, in which case Mercurial's merge machinery will resolve any | ||||
conflicts if necessary. | ||||
You can have more than one shelved change outstanding at a time; each | ||||
shelved change has a distinct name. For details, see the help for "hg | ||||
shelve". | ||||
""" | ||||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Navaneeth Suresh
|
r42744 | import collections | ||
Jason R. Coombs
|
r50321 | import io | ||
Navaneeth Suresh
|
r42744 | import itertools | ||
import stat | ||||
Matt Harbison
|
r52709 | from typing import ( | ||
Any, | ||||
Dict, | ||||
Iterable, | ||||
Iterator, | ||||
List, | ||||
Sequence, | ||||
Tuple, | ||||
) | ||||
Navaneeth Suresh
|
r42744 | from .i18n import _ | ||
Joerg Sonnenberger
|
r46729 | from .node import ( | ||
bin, | ||||
hex, | ||||
nullrev, | ||||
) | ||||
Navaneeth Suresh
|
r42744 | from . import ( | ||
bookmarks, | ||||
bundle2, | ||||
changegroup, | ||||
cmdutil, | ||||
Matt Harbison
|
r52709 | context as contextmod, | ||
Navaneeth Suresh
|
r42744 | discovery, | ||
error, | ||||
exchange, | ||||
hg, | ||||
lock as lockmod, | ||||
mdiff, | ||||
merge, | ||||
Augie Fackler
|
r45383 | mergestate as mergestatemod, | ||
Navaneeth Suresh
|
r42744 | patch, | ||
phases, | ||||
pycompat, | ||||
repair, | ||||
scmutil, | ||||
templatefilters, | ||||
util, | ||||
vfs as vfsmod, | ||||
) | ||||
from .utils import ( | ||||
dateutil, | ||||
stringutil, | ||||
) | ||||
Augie Fackler
|
r43347 | backupdir = b'shelve-backup' | ||
shelvedir = b'shelved' | ||||
shelvefileextensions = [b'hg', b'patch', b'shelve'] | ||||
Navaneeth Suresh
|
r42744 | |||
# we never need the user, so we use a | ||||
# generic user for all shelve operations | ||||
Augie Fackler
|
r43347 | shelveuser = b'shelve@localhost' | ||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class ShelfDir: | ||
Matt Harbison
|
r52709 | def __init__(self, repo, for_backups: bool = False) -> None: | ||
Martin von Zweigbergk
|
r47014 | if for_backups: | ||
self.vfs = vfsmod.vfs(repo.vfs.join(backupdir)) | ||||
else: | ||||
self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir)) | ||||
Matt Harbison
|
r52709 | def get(self, name: bytes) -> "Shelf": | ||
Martin von Zweigbergk
|
r47014 | return Shelf(self.vfs, name) | ||
Matt Harbison
|
r52709 | def listshelves(self) -> List[Tuple[float, bytes]]: | ||
Martin von Zweigbergk
|
r47015 | """return all shelves in repo as list of (time, name)""" | ||
try: | ||||
names = self.vfs.listdir() | ||||
Manuel Jacob
|
r50201 | except FileNotFoundError: | ||
Martin von Zweigbergk
|
r47015 | return [] | ||
info = [] | ||||
seen = set() | ||||
for filename in names: | ||||
name = filename.rsplit(b'.', 1)[0] | ||||
if name in seen: | ||||
continue | ||||
seen.add(name) | ||||
shelf = self.get(name) | ||||
if not shelf.exists(): | ||||
continue | ||||
mtime = shelf.mtime() | ||||
info.append((mtime, name)) | ||||
return sorted(info, reverse=True) | ||||
Martin von Zweigbergk
|
r47014 | |||
Matt Harbison
|
r52709 | def _use_internal_phase(repo) -> bool: | ||
Jason R. Coombs
|
r50325 | return ( | ||
phases.supportinternal(repo) | ||||
Jason R. Coombs
|
r50352 | and repo.ui.config(b'shelve', b'store') == b'internal' | ||
Jason R. Coombs
|
r50325 | ) | ||
Matt Harbison
|
r52709 | def _target_phase(repo) -> int: | ||
Jason R. Coombs
|
r50325 | return phases.internal if _use_internal_phase(repo) else phases.secret | ||
Jason R. Coombs
|
r50324 | |||
Gregory Szorc
|
r49801 | class Shelf: | ||
Martin von Zweigbergk
|
r46991 | """Represents a shelf, including possibly multiple files storing it. | ||
Old shelves will have a .patch and a .hg file. Newer shelves will | ||||
also have a .shelve file. This class abstracts away some of the | ||||
differences and lets you work with the shelf as a whole. | ||||
""" | ||||
Matt Harbison
|
r52709 | def __init__(self, vfs: vfsmod.vfs, name: bytes) -> None: | ||
Martin von Zweigbergk
|
r47009 | self.vfs = vfs | ||
Martin von Zweigbergk
|
r46991 | self.name = name | ||
Martin von Zweigbergk
|
r47009 | |||
Matt Harbison
|
r52709 | def exists(self) -> bool: | ||
Jason R. Coombs
|
r50322 | return self._exists(b'.shelve') or self._exists(b'.patch', b'.hg') | ||
Matt Harbison
|
r52709 | def _exists(self, *exts: bytes) -> bool: | ||
Jason R. Coombs
|
r50322 | return all(self.vfs.exists(self.name + ext) for ext in exts) | ||
Martin von Zweigbergk
|
r46991 | |||
Matt Harbison
|
r52709 | def mtime(self) -> float: | ||
Jason R. Coombs
|
r50322 | try: | ||
return self._stat(b'.shelve')[stat.ST_MTIME] | ||||
except FileNotFoundError: | ||||
return self._stat(b'.patch')[stat.ST_MTIME] | ||||
Matt Harbison
|
r52709 | def _stat(self, ext: bytes): | ||
Jason R. Coombs
|
r50322 | return self.vfs.stat(self.name + ext) | ||
Martin von Zweigbergk
|
r46998 | |||
Matt Harbison
|
r52709 | def writeinfo(self, info) -> None: | ||
Martin von Zweigbergk
|
r46992 | scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info) | ||
Matt Harbison
|
r52709 | def hasinfo(self) -> bool: | ||
Martin von Zweigbergk
|
r47004 | return self.vfs.exists(self.name + b'.shelve') | ||
Martin von Zweigbergk
|
r46993 | def readinfo(self): | ||
return scmutil.simplekeyvaluefile( | ||||
self.vfs, self.name + b'.shelve' | ||||
).read() | ||||
Matt Harbison
|
r52709 | def writebundle(self, repo, bases, node) -> None: | ||
Martin von Zweigbergk
|
r47009 | cgversion = changegroup.safeversion(repo) | ||
Martin von Zweigbergk
|
r46994 | if cgversion == b'01': | ||
btype = b'HG10BZ' | ||||
compression = None | ||||
else: | ||||
btype = b'HG20' | ||||
compression = b'BZ' | ||||
Martin von Zweigbergk
|
r47009 | repo = repo.unfiltered() | ||
Martin von Zweigbergk
|
r46994 | |||
outgoing = discovery.outgoing( | ||||
repo, missingroots=bases, ancestorsof=[node] | ||||
) | ||||
cg = changegroup.makechangegroup(repo, outgoing, cgversion, b'shelve') | ||||
bundle_filename = self.vfs.join(self.name + b'.hg') | ||||
bundle2.writebundle( | ||||
Martin von Zweigbergk
|
r47009 | repo.ui, | ||
Martin von Zweigbergk
|
r46994 | cg, | ||
bundle_filename, | ||||
btype, | ||||
self.vfs, | ||||
compression=compression, | ||||
) | ||||
Matt Harbison
|
r52709 | def applybundle(self, repo, tr) -> contextmod.changectx: | ||
Martin von Zweigbergk
|
r46995 | filename = self.name + b'.hg' | ||
fp = self.vfs(filename) | ||||
try: | ||||
Jason R. Coombs
|
r50324 | targetphase = _target_phase(repo) | ||
Martin von Zweigbergk
|
r47009 | gen = exchange.readbundle(repo.ui, fp, filename, self.vfs) | ||
pretip = repo[b'tip'] | ||||
Martin von Zweigbergk
|
r46995 | bundle2.applybundle( | ||
Martin von Zweigbergk
|
r47009 | repo, | ||
Martin von Zweigbergk
|
r46995 | gen, | ||
tr, | ||||
source=b'unshelve', | ||||
url=b'bundle:' + self.vfs.join(filename), | ||||
targetphase=targetphase, | ||||
) | ||||
Martin von Zweigbergk
|
r47009 | shelvectx = repo[b'tip'] | ||
Martin von Zweigbergk
|
r46995 | if pretip == shelvectx: | ||
shelverev = tr.changes[b'revduplicates'][-1] | ||||
Martin von Zweigbergk
|
r47009 | shelvectx = repo[shelverev] | ||
Martin von Zweigbergk
|
r46995 | return shelvectx | ||
finally: | ||||
fp.close() | ||||
Matt Harbison
|
r52709 | def open_patch(self, mode: bytes = b'rb'): | ||
Martin von Zweigbergk
|
r46996 | return self.vfs(self.name + b'.patch', mode) | ||
Matt Harbison
|
r52709 | def patch_from_node(self, repo, node) -> io.BytesIO: | ||
Jason R. Coombs
|
r50321 | repo = repo.unfiltered() | ||
match = _optimized_match(repo, node) | ||||
fp = io.BytesIO() | ||||
cmdutil.exportfile( | ||||
repo, | ||||
[node], | ||||
fp, | ||||
opts=mdiff.diffopts(git=True), | ||||
match=match, | ||||
) | ||||
fp.seek(0) | ||||
return fp | ||||
def load_patch(self, repo): | ||||
try: | ||||
# prefer node-based shelf | ||||
return self.patch_from_node(repo, self.readinfo()[b'node']) | ||||
except (FileNotFoundError, error.RepoLookupError): | ||||
return self.open_patch() | ||||
Matt Harbison
|
r52709 | def _backupfilename(self, backupvfs: vfsmod.vfs, filename: bytes) -> bytes: | ||
def gennames(base: bytes): | ||||
Martin von Zweigbergk
|
r47003 | yield base | ||
base, ext = base.rsplit(b'.', 1) | ||||
for i in itertools.count(1): | ||||
yield b'%s-%d.%s' % (base, i, ext) | ||||
for n in gennames(filename): | ||||
Martin von Zweigbergk
|
r47009 | if not backupvfs.exists(n): | ||
return backupvfs.join(n) | ||||
Martin von Zweigbergk
|
r47003 | |||
Matt Harbison
|
r52709 | # Help pytype- gennames() yields infinitely | ||
raise error.ProgrammingError("unreachable") | ||||
def movetobackup(self, backupvfs: vfsmod.vfs) -> None: | ||||
Martin von Zweigbergk
|
r47009 | if not backupvfs.isdir(): | ||
backupvfs.makedir() | ||||
Martin von Zweigbergk
|
r47003 | for suffix in shelvefileextensions: | ||
filename = self.name + b'.' + suffix | ||||
if self.vfs.exists(filename): | ||||
util.rename( | ||||
Martin von Zweigbergk
|
r47009 | self.vfs.join(filename), | ||
self._backupfilename(backupvfs, filename), | ||||
Martin von Zweigbergk
|
r47003 | ) | ||
Matt Harbison
|
r52709 | def delete(self) -> None: | ||
Martin von Zweigbergk
|
r47013 | for ext in shelvefileextensions: | ||
self.vfs.tryunlink(self.name + b'.' + ext) | ||||
Jason R. Coombs
|
r50481 | def changed_files(self, ui, repo): | ||
try: | ||||
ctx = repo.unfiltered()[self.readinfo()[b'node']] | ||||
return ctx.files() | ||||
except (FileNotFoundError, error.RepoLookupError): | ||||
filename = self.vfs.join(self.name + b'.patch') | ||||
return patch.changedfiles(ui, repo, filename) | ||||
Martin von Zweigbergk
|
r46991 | |||
Matt Harbison
|
r52709 | def _optimized_match(repo, node: bytes): | ||
Jason R. Coombs
|
r50320 | """ | ||
Create a matcher so that prefetch doesn't attempt to fetch | ||||
the entire repository pointlessly, and as an optimisation | ||||
for movedirstate, if needed. | ||||
""" | ||||
return scmutil.matchfiles(repo, repo[node].files()) | ||||
Gregory Szorc
|
r49801 | class shelvedstate: | ||
Navaneeth Suresh
|
r42744 | """Handle persistence during unshelving operations. | ||
Handles saving and restoring a shelved state. Ensures that different | ||||
versions of a shelved state are possible and handles them appropriately. | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | # Class-wide constants | ||
Navaneeth Suresh
|
r42744 | _version = 2 | ||
Augie Fackler
|
r43347 | _filename = b'shelvedstate' | ||
_keep = b'keep' | ||||
_nokeep = b'nokeep' | ||||
Navaneeth Suresh
|
r42744 | # colon is essential to differentiate from a real bookmark name | ||
Augie Fackler
|
r43347 | _noactivebook = b':no-active-bookmark' | ||
_interactive = b'interactive' | ||||
Navaneeth Suresh
|
r42744 | |||
Matt Harbison
|
r52709 | # Per instance attrs | ||
name: bytes | ||||
wctx: contextmod.workingctx | ||||
pendingctx: contextmod.changectx | ||||
parents: List[bytes] | ||||
nodestoremove: List[bytes] | ||||
branchtorestore: bytes | ||||
keep: bool | ||||
activebookmark: bytes | ||||
interactive: bool | ||||
Navaneeth Suresh
|
r42744 | @classmethod | ||
Matt Harbison
|
r52709 | def _verifyandtransform(cls, d: Dict[bytes, Any]) -> None: | ||
Navaneeth Suresh
|
r42744 | """Some basic shelvestate syntactic verification and transformation""" | ||
try: | ||||
Joerg Sonnenberger
|
r46729 | d[b'originalwctx'] = bin(d[b'originalwctx']) | ||
d[b'pendingctx'] = bin(d[b'pendingctx']) | ||||
Jason R. Coombs
|
r50455 | d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ') if h] | ||
Augie Fackler
|
r43347 | d[b'nodestoremove'] = [ | ||
Jason R. Coombs
|
r50455 | bin(h) for h in d[b'nodestoremove'].split(b' ') if h | ||
Augie Fackler
|
r43346 | ] | ||
Manuel Jacob
|
r50143 | except (ValueError, KeyError) as err: | ||
Matt Harbison
|
r47386 | raise error.CorruptedState(stringutil.forcebytestr(err)) | ||
Navaneeth Suresh
|
r42744 | |||
@classmethod | ||||
Matt Harbison
|
r52709 | def _getversion(cls, repo) -> int: | ||
Navaneeth Suresh
|
r42744 | """Read version information from shelvestate file""" | ||
fp = repo.vfs(cls._filename) | ||||
try: | ||||
version = int(fp.readline().strip()) | ||||
except ValueError as err: | ||||
Matt Harbison
|
r47386 | raise error.CorruptedState(stringutil.forcebytestr(err)) | ||
Navaneeth Suresh
|
r42744 | finally: | ||
fp.close() | ||||
return version | ||||
@classmethod | ||||
Matt Harbison
|
r52709 | def _readold(cls, repo) -> Dict[bytes, Any]: | ||
Navaneeth Suresh
|
r42744 | """Read the old position-based version of a shelvestate file""" | ||
# Order is important, because old shelvestate file uses it | ||||
# to detemine values of fields (i.g. name is on the second line, | ||||
# originalwctx is on the third and so forth). Please do not change. | ||||
Augie Fackler
|
r43346 | keys = [ | ||
Augie Fackler
|
r43347 | b'version', | ||
b'name', | ||||
b'originalwctx', | ||||
b'pendingctx', | ||||
b'parents', | ||||
b'nodestoremove', | ||||
b'branchtorestore', | ||||
b'keep', | ||||
b'activebook', | ||||
Augie Fackler
|
r43346 | ] | ||
Navaneeth Suresh
|
r42744 | # this is executed only seldomly, so it is not a big deal | ||
# that we open this file twice | ||||
fp = repo.vfs(cls._filename) | ||||
d = {} | ||||
try: | ||||
for key in keys: | ||||
d[key] = fp.readline().strip() | ||||
finally: | ||||
fp.close() | ||||
return d | ||||
@classmethod | ||||
def load(cls, repo): | ||||
version = cls._getversion(repo) | ||||
if version < cls._version: | ||||
d = cls._readold(repo) | ||||
elif version == cls._version: | ||||
Augie Fackler
|
r43346 | d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename).read( | ||
firstlinenonkeyval=True | ||||
) | ||||
Navaneeth Suresh
|
r42744 | else: | ||
Augie Fackler
|
r43346 | raise error.Abort( | ||
_( | ||||
Augie Fackler
|
r43347 | b'this version of shelve is incompatible ' | ||
b'with the version used in this repo' | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
Navaneeth Suresh
|
r42744 | |||
cls._verifyandtransform(d) | ||||
try: | ||||
obj = cls() | ||||
Augie Fackler
|
r43347 | obj.name = d[b'name'] | ||
obj.wctx = repo[d[b'originalwctx']] | ||||
obj.pendingctx = repo[d[b'pendingctx']] | ||||
obj.parents = d[b'parents'] | ||||
obj.nodestoremove = d[b'nodestoremove'] | ||||
obj.branchtorestore = d.get(b'branchtorestore', b'') | ||||
obj.keep = d.get(b'keep') == cls._keep | ||||
obj.activebookmark = b'' | ||||
if d.get(b'activebook', b'') != cls._noactivebook: | ||||
obj.activebookmark = d.get(b'activebook', b'') | ||||
obj.interactive = d.get(b'interactive') == cls._interactive | ||||
Navaneeth Suresh
|
r42744 | except (error.RepoLookupError, KeyError) as err: | ||
Matt Harbison
|
r52710 | raise error.CorruptedState(stringutil.forcebytestr(err)) | ||
Navaneeth Suresh
|
r42744 | |||
return obj | ||||
@classmethod | ||||
Augie Fackler
|
r43346 | def save( | ||
cls, | ||||
repo, | ||||
Matt Harbison
|
r52709 | name: bytes, | ||
originalwctx: contextmod.workingctx, | ||||
pendingctx: contextmod.changectx, | ||||
nodestoremove: List[bytes], | ||||
branchtorestore: bytes, | ||||
keep: bool = False, | ||||
activebook: bytes = b'', | ||||
interactive: bool = False, | ||||
) -> None: | ||||
Navaneeth Suresh
|
r42744 | info = { | ||
Augie Fackler
|
r43347 | b"name": name, | ||
Joerg Sonnenberger
|
r46729 | b"originalwctx": hex(originalwctx.node()), | ||
b"pendingctx": hex(pendingctx.node()), | ||||
b"parents": b' '.join([hex(p) for p in repo.dirstate.parents()]), | ||||
b"nodestoremove": b' '.join([hex(n) for n in nodestoremove]), | ||||
Augie Fackler
|
r43347 | b"branchtorestore": branchtorestore, | ||
b"keep": cls._keep if keep else cls._nokeep, | ||||
b"activebook": activebook or cls._noactivebook, | ||||
Navaneeth Suresh
|
r42744 | } | ||
Navaneeth Suresh
|
r42889 | if interactive: | ||
Augie Fackler
|
r43347 | info[b'interactive'] = cls._interactive | ||
Augie Fackler
|
r43346 | scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write( | ||
Augie Fackler
|
r43347 | info, firstline=(b"%d" % cls._version) | ||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | |||
@classmethod | ||||
Matt Harbison
|
r52709 | def clear(cls, repo) -> None: | ||
Navaneeth Suresh
|
r42744 | repo.vfs.unlinkpath(cls._filename, ignoremissing=True) | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def cleanupoldbackups(repo) -> None: | ||
Augie Fackler
|
r43347 | maxbackups = repo.ui.configint(b'shelve', b'maxbackups') | ||
Martin von Zweigbergk
|
r47015 | backup_dir = ShelfDir(repo, for_backups=True) | ||
hgfiles = backup_dir.listshelves() | ||||
Navaneeth Suresh
|
r42744 | if maxbackups > 0 and maxbackups < len(hgfiles): | ||
Martin von Zweigbergk
|
r47011 | bordermtime = hgfiles[maxbackups - 1][0] | ||
Navaneeth Suresh
|
r42744 | else: | ||
bordermtime = None | ||||
Martin von Zweigbergk
|
r47011 | for mtime, name in hgfiles[maxbackups:]: | ||
Navaneeth Suresh
|
r42744 | if mtime == bordermtime: | ||
# keep it, because timestamp can't decide exact order of backups | ||||
continue | ||||
Martin von Zweigbergk
|
r47015 | backup_dir.get(name).delete() | ||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _backupactivebookmark(repo) -> bytes: | ||
Navaneeth Suresh
|
r42744 | activebookmark = repo._activebookmark | ||
if activebookmark: | ||||
bookmarks.deactivate(repo) | ||||
return activebookmark | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _restoreactivebookmark(repo, mark) -> None: | ||
Navaneeth Suresh
|
r42744 | if mark: | ||
bookmarks.activate(repo, mark) | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _aborttransaction(repo, tr) -> None: | ||
Augie Fackler
|
r46554 | """Abort current transaction for shelve/unshelve, but keep dirstate""" | ||
r50974 | # disable the transaction invalidation of the dirstate, to preserve the | |||
# current change in memory. | ||||
ds = repo.dirstate | ||||
# The assert below check that nobody else did such wrapping. | ||||
# | ||||
# These is not such other wrapping currently, but if someone try to | ||||
# implement one in the future, this will explicitly break here instead of | ||||
# misbehaving in subtle ways. | ||||
r51090 | current_branch = ds.branch() | |||
r50974 | assert 'invalidate' not in vars(ds) | |||
try: | ||||
# note : we could simply disable the transaction abort callback, but | ||||
# other code also tries to rollback and invalidate this. | ||||
ds.invalidate = lambda: None | ||||
tr.abort() | ||||
finally: | ||||
del ds.invalidate | ||||
# manually write the change in memory since we can no longer rely on the | ||||
# transaction to do so. | ||||
assert repo.currenttransaction() is None | ||||
repo.dirstate.write(None) | ||||
r51159 | ds.setbranch(current_branch, None) | |||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def getshelvename(repo, parent, opts) -> bytes: | ||
Navaneeth Suresh
|
r42744 | """Decide on the name this shelve is going to have""" | ||
Augie Fackler
|
r43346 | |||
Navaneeth Suresh
|
r42744 | def gennames(): | ||
yield label | ||||
for i in itertools.count(1): | ||||
Augie Fackler
|
r43347 | yield b'%s-%02d' % (label, i) | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | name = opts.get(b'name') | ||
label = repo._activebookmark or parent.branch() or b'default' | ||||
Navaneeth Suresh
|
r42744 | # slashes aren't allowed in filenames, therefore we rename it | ||
Augie Fackler
|
r43347 | label = label.replace(b'/', b'_') | ||
label = label.replace(b'\\', b'_') | ||||
Navaneeth Suresh
|
r42744 | # filenames must not start with '.' as it should not be hidden | ||
Augie Fackler
|
r43347 | if label.startswith(b'.'): | ||
label = label.replace(b'.', b'_', 1) | ||||
Navaneeth Suresh
|
r42744 | |||
if name: | ||||
Martin von Zweigbergk
|
r47014 | if ShelfDir(repo).get(name).exists(): | ||
Augie Fackler
|
r43347 | e = _(b"a shelved change named '%s' already exists") % name | ||
Navaneeth Suresh
|
r42744 | raise error.Abort(e) | ||
# ensure we are not creating a subdirectory or a hidden file | ||||
Augie Fackler
|
r43347 | if b'/' in name or b'\\' in name: | ||
raise error.Abort( | ||||
_(b'shelved change names can not contain slashes') | ||||
) | ||||
if name.startswith(b'.'): | ||||
raise error.Abort(_(b"shelved change names can not start with '.'")) | ||||
Navaneeth Suresh
|
r42744 | |||
else: | ||||
Martin von Zweigbergk
|
r47014 | shelf_dir = ShelfDir(repo) | ||
Navaneeth Suresh
|
r42744 | for n in gennames(): | ||
Martin von Zweigbergk
|
r47014 | if not shelf_dir.get(n).exists(): | ||
Navaneeth Suresh
|
r42744 | name = n | ||
break | ||||
return name | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def mutableancestors(ctx) -> Iterator[bytes]: | ||
Navaneeth Suresh
|
r42744 | """return all mutable ancestors for ctx (included) | ||
Much faster than the revset ancestors(ctx) & draft()""" | ||||
Joerg Sonnenberger
|
r46729 | seen = {nullrev} | ||
Navaneeth Suresh
|
r42744 | visit = collections.deque() | ||
visit.append(ctx) | ||||
while visit: | ||||
ctx = visit.popleft() | ||||
yield ctx.node() | ||||
for parent in ctx.parents(): | ||||
rev = parent.rev() | ||||
if rev not in seen: | ||||
seen.add(rev) | ||||
if parent.mutable(): | ||||
visit.append(parent) | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def getcommitfunc(extra, interactive: bool, editor: bool = False): | ||
Navaneeth Suresh
|
r42744 | def commitfunc(ui, repo, message, match, opts): | ||
r51821 | hasmq = hasattr(repo, 'mq') | |||
Navaneeth Suresh
|
r42744 | if hasmq: | ||
saved, repo.mq.checkapplied = repo.mq.checkapplied, False | ||||
Jason R. Coombs
|
r50324 | targetphase = _target_phase(repo) | ||
Augie Fackler
|
r43347 | overrides = {(b'phases', b'new-commit'): targetphase} | ||
Navaneeth Suresh
|
r42744 | try: | ||
editor_ = False | ||||
if editor: | ||||
Augie Fackler
|
r43346 | editor_ = cmdutil.getcommiteditor( | ||
Augie Fackler
|
r43347 | editform=b'shelve.shelve', **pycompat.strkwargs(opts) | ||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | with repo.ui.configoverride(overrides): | ||
Augie Fackler
|
r43346 | return repo.commit( | ||
message, | ||||
shelveuser, | ||||
Augie Fackler
|
r43347 | opts.get(b'date'), | ||
Augie Fackler
|
r43346 | match, | ||
editor=editor_, | ||||
extra=extra, | ||||
) | ||||
Navaneeth Suresh
|
r42744 | finally: | ||
if hasmq: | ||||
repo.mq.checkapplied = saved | ||||
def interactivecommitfunc(ui, repo, *pats, **opts): | ||||
opts = pycompat.byteskwargs(opts) | ||||
Augie Fackler
|
r43347 | match = scmutil.match(repo[b'.'], pats, {}) | ||
message = opts[b'message'] | ||||
Navaneeth Suresh
|
r42744 | return commitfunc(ui, repo, message, match, opts) | ||
return interactivecommitfunc if interactive else commitfunc | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _nothingtoshelvemessaging(ui, repo, pats, opts) -> None: | ||
Navaneeth Suresh
|
r42744 | stat = repo.status(match=scmutil.match(repo[None], pats, opts)) | ||
if stat.deleted: | ||||
Augie Fackler
|
r43346 | ui.status( | ||
Martin von Zweigbergk
|
r43387 | _(b"nothing changed (%d missing files, see 'hg status')\n") | ||
Augie Fackler
|
r43346 | % len(stat.deleted) | ||
) | ||||
Navaneeth Suresh
|
r42744 | else: | ||
Augie Fackler
|
r43347 | ui.status(_(b"nothing changed\n")) | ||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _shelvecreatedcommit(repo, node: bytes, name: bytes, match) -> None: | ||
Joerg Sonnenberger
|
r46729 | info = {b'node': hex(node)} | ||
Martin von Zweigbergk
|
r47014 | shelf = ShelfDir(repo).get(name) | ||
Martin von Zweigbergk
|
r47005 | shelf.writeinfo(info) | ||
Navaneeth Suresh
|
r42744 | bases = list(mutableancestors(repo[node])) | ||
Martin von Zweigbergk
|
r47009 | shelf.writebundle(repo, bases, node) | ||
Martin von Zweigbergk
|
r47005 | with shelf.open_patch(b'wb') as fp: | ||
Augie Fackler
|
r43346 | cmdutil.exportfile( | ||
repo, [node], fp, opts=mdiff.diffopts(git=True), match=match | ||||
) | ||||
Navaneeth Suresh
|
r42744 | |||
Matt Harbison
|
r52709 | def _includeunknownfiles(repo, pats, opts, extra) -> None: | ||
Augie Fackler
|
r43346 | s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True) | ||
Navaneeth Suresh
|
r42744 | if s.unknown: | ||
Augie Fackler
|
r43347 | extra[b'shelve_unknown'] = b'\0'.join(s.unknown) | ||
Navaneeth Suresh
|
r42744 | repo[None].add(s.unknown) | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _finishshelve(repo, tr) -> None: | ||
Jason R. Coombs
|
r50325 | if _use_internal_phase(repo): | ||
Navaneeth Suresh
|
r42744 | tr.close() | ||
else: | ||||
_aborttransaction(repo, tr) | ||||
Augie Fackler
|
r43346 | |||
Navaneeth Suresh
|
r42744 | def createcmd(ui, repo, pats, opts): | ||
"""subcommand that creates a new shelve""" | ||||
with repo.wlock(): | ||||
cmdutil.checkunfinished(repo) | ||||
return _docreatecmd(ui, repo, pats, opts) | ||||
Augie Fackler
|
r43346 | |||
Navaneeth Suresh
|
r42744 | def _docreatecmd(ui, repo, pats, opts): | ||
wctx = repo[None] | ||||
parents = wctx.parents() | ||||
parent = parents[0] | ||||
origbranch = wctx.branch() | ||||
Joerg Sonnenberger
|
r47601 | if parent.rev() != nullrev: | ||
Augie Fackler
|
r43347 | desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0] | ||
Navaneeth Suresh
|
r42744 | else: | ||
Augie Fackler
|
r43347 | desc = b'(changes in empty repository)' | ||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43347 | if not opts.get(b'message'): | ||
opts[b'message'] = desc | ||||
Navaneeth Suresh
|
r42744 | |||
lock = tr = activebookmark = None | ||||
try: | ||||
lock = repo.lock() | ||||
# use an uncommitted transaction to generate the bundle to avoid | ||||
# pull races. ensure we don't print the abort message to stderr. | ||||
Augie Fackler
|
r43347 | tr = repo.transaction(b'shelve', report=lambda x: None) | ||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43347 | interactive = opts.get(b'interactive', False) | ||
includeunknown = opts.get(b'unknown', False) and not opts.get( | ||||
b'addremove', False | ||||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | |||
name = getshelvename(repo, parent, opts) | ||||
activebookmark = _backupactivebookmark(repo) | ||||
Augie Fackler
|
r43347 | extra = {b'internal': b'shelve'} | ||
Navaneeth Suresh
|
r42744 | if includeunknown: | ||
r50949 | with repo.dirstate.changing_files(repo): | |||
_includeunknownfiles(repo, pats, opts, extra) | ||||
Navaneeth Suresh
|
r42744 | |||
if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts): | ||||
# In non-bare shelve we don't store newly created branch | ||||
# at bundled commit | ||||
r51159 | repo.dirstate.setbranch( | |||
repo[b'.'].branch(), repo.currenttransaction() | ||||
) | ||||
Navaneeth Suresh
|
r42744 | |||
commitfunc = getcommitfunc(extra, interactive, editor=True) | ||||
if not interactive: | ||||
node = cmdutil.commit(ui, repo, commitfunc, pats, opts) | ||||
else: | ||||
Augie Fackler
|
r43346 | node = cmdutil.dorecord( | ||
ui, | ||||
repo, | ||||
commitfunc, | ||||
None, | ||||
False, | ||||
cmdutil.recordfilter, | ||||
*pats, | ||||
Matt Harbison
|
r52755 | **pycompat.strkwargs(opts), | ||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | if not node: | ||
_nothingtoshelvemessaging(ui, repo, pats, opts) | ||||
return 1 | ||||
Jason R. Coombs
|
r50320 | match = _optimized_match(repo, node) | ||
Navaneeth Suresh
|
r42744 | _shelvecreatedcommit(repo, node, name, match) | ||
Augie Fackler
|
r43347 | ui.status(_(b'shelved as %s\n') % name) | ||
if opts[b'keep']: | ||||
r50855 | with repo.dirstate.changing_parents(repo): | |||
Navaneeth Suresh
|
r42744 | scmutil.movedirstate(repo, parent, match) | ||
else: | ||||
hg.update(repo, parent.node()) | ||||
Martin von Zweigbergk
|
r46464 | ms = mergestatemod.mergestate.read(repo) | ||
if not ms.unresolvedcount(): | ||||
ms.reset() | ||||
Augie Fackler
|
r43347 | if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts): | ||
r51159 | repo.dirstate.setbranch(origbranch, repo.currenttransaction()) | |||
Navaneeth Suresh
|
r42744 | |||
_finishshelve(repo, tr) | ||||
finally: | ||||
_restoreactivebookmark(repo, activebookmark) | ||||
lockmod.release(tr, lock) | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _isbareshelve(pats, opts) -> bool: | ||
Augie Fackler
|
r43346 | return ( | ||
not pats | ||||
Augie Fackler
|
r43347 | and not opts.get(b'interactive', False) | ||
and not opts.get(b'include', False) | ||||
and not opts.get(b'exclude', False) | ||||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | |||
Matt Harbison
|
r52709 | def _iswctxonnewbranch(repo) -> bool: | ||
Augie Fackler
|
r43347 | return repo[None].branch() != repo[b'.'].branch() | ||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def cleanupcmd(ui, repo) -> None: | ||
Navaneeth Suresh
|
r42744 | """subcommand that deletes all shelves""" | ||
with repo.wlock(): | ||||
Martin von Zweigbergk
|
r47015 | shelf_dir = ShelfDir(repo) | ||
Martin von Zweigbergk
|
r47009 | backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) | ||
Martin von Zweigbergk
|
r47015 | for _mtime, name in shelf_dir.listshelves(): | ||
shelf_dir.get(name).movetobackup(backupvfs) | ||||
Navaneeth Suresh
|
r42744 | cleanupoldbackups(repo) | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def deletecmd(ui, repo, pats) -> None: | ||
Navaneeth Suresh
|
r42744 | """subcommand that deletes a specific shelve""" | ||
if not pats: | ||||
Martin von Zweigbergk
|
r46990 | raise error.InputError(_(b'no shelved changes specified!')) | ||
Navaneeth Suresh
|
r42744 | with repo.wlock(): | ||
Martin von Zweigbergk
|
r47009 | backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) | ||
Matt Harbison
|
r44123 | for name in pats: | ||
Martin von Zweigbergk
|
r47014 | shelf = ShelfDir(repo).get(name) | ||
Martin von Zweigbergk
|
r47005 | if not shelf.exists(): | ||
Martin von Zweigbergk
|
r46990 | raise error.InputError( | ||
_(b"shelved change '%s' not found") % name | ||||
) | ||||
Martin von Zweigbergk
|
r47009 | shelf.movetobackup(backupvfs) | ||
Navaneeth Suresh
|
r42744 | cleanupoldbackups(repo) | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def listcmd(ui, repo, pats: Iterable[bytes], opts) -> None: | ||
Navaneeth Suresh
|
r42744 | """subcommand that displays the list of shelves""" | ||
pats = set(pats) | ||||
width = 80 | ||||
if not ui.plain(): | ||||
width = ui.termwidth() | ||||
Augie Fackler
|
r43347 | namelabel = b'shelve.newest' | ||
ui.pager(b'shelve') | ||||
Martin von Zweigbergk
|
r47014 | shelf_dir = ShelfDir(repo) | ||
Martin von Zweigbergk
|
r47015 | for mtime, name in shelf_dir.listshelves(): | ||
Martin von Zweigbergk
|
r46999 | if pats and name not in pats: | ||
Navaneeth Suresh
|
r42744 | continue | ||
Martin von Zweigbergk
|
r46999 | ui.write(name, label=namelabel) | ||
Augie Fackler
|
r43347 | namelabel = b'shelve.name' | ||
Navaneeth Suresh
|
r42744 | if ui.quiet: | ||
Augie Fackler
|
r43347 | ui.write(b'\n') | ||
Navaneeth Suresh
|
r42744 | continue | ||
Martin von Zweigbergk
|
r46999 | ui.write(b' ' * (16 - len(name))) | ||
Navaneeth Suresh
|
r42744 | used = 16 | ||
date = dateutil.makedate(mtime) | ||||
Augie Fackler
|
r43347 | age = b'(%s)' % templatefilters.age(date, abbrev=True) | ||
ui.write(age, label=b'shelve.age') | ||||
ui.write(b' ' * (12 - len(age))) | ||||
Navaneeth Suresh
|
r42744 | used += 12 | ||
Jason R. Coombs
|
r50321 | with shelf_dir.get(name).load_patch(repo) as fp: | ||
Navaneeth Suresh
|
r42744 | while True: | ||
line = fp.readline() | ||||
if not line: | ||||
break | ||||
Augie Fackler
|
r43347 | if not line.startswith(b'#'): | ||
Navaneeth Suresh
|
r42744 | desc = line.rstrip() | ||
if ui.formatted(): | ||||
desc = stringutil.ellipsis(desc, width - used) | ||||
ui.write(desc) | ||||
break | ||||
Augie Fackler
|
r43347 | ui.write(b'\n') | ||
if not (opts[b'patch'] or opts[b'stat']): | ||||
Navaneeth Suresh
|
r42744 | continue | ||
difflines = fp.readlines() | ||||
Augie Fackler
|
r43347 | if opts[b'patch']: | ||
Navaneeth Suresh
|
r42744 | for chunk, label in patch.difflabel(iter, difflines): | ||
ui.write(chunk, label=label) | ||||
Augie Fackler
|
r43347 | if opts[b'stat']: | ||
Navaneeth Suresh
|
r42744 | for chunk, label in patch.diffstatui(difflines, width=width): | ||
ui.write(chunk, label=label) | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def patchcmds(ui, repo, pats: Sequence[bytes], opts) -> None: | ||
Navaneeth Suresh
|
r42744 | """subcommand that displays shelves""" | ||
Martin von Zweigbergk
|
r47015 | shelf_dir = ShelfDir(repo) | ||
Navaneeth Suresh
|
r42744 | if len(pats) == 0: | ||
Martin von Zweigbergk
|
r47015 | shelves = shelf_dir.listshelves() | ||
Navaneeth Suresh
|
r42744 | if not shelves: | ||
Augie Fackler
|
r43347 | raise error.Abort(_(b"there are no shelves to show")) | ||
Navaneeth Suresh
|
r42744 | mtime, name = shelves[0] | ||
Martin von Zweigbergk
|
r46999 | pats = [name] | ||
Navaneeth Suresh
|
r42744 | |||
for shelfname in pats: | ||||
Martin von Zweigbergk
|
r47014 | if not shelf_dir.get(shelfname).exists(): | ||
Augie Fackler
|
r43347 | raise error.Abort(_(b"cannot find shelf %s") % shelfname) | ||
Navaneeth Suresh
|
r42744 | |||
listcmd(ui, repo, pats, opts) | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def checkparents(repo, state: shelvedstate) -> None: | ||
Navaneeth Suresh
|
r42744 | """check parent while resuming an unshelve""" | ||
if state.parents != repo.dirstate.parents(): | ||||
Augie Fackler
|
r43346 | raise error.Abort( | ||
Martin von Zweigbergk
|
r43387 | _(b'working directory parents do not match unshelve state') | ||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | |||
Matt Harbison
|
r52709 | def _loadshelvedstate(ui, repo, opts) -> shelvedstate: | ||
Taapas Agrawal
|
r42802 | try: | ||
state = shelvedstate.load(repo) | ||||
Augie Fackler
|
r43347 | if opts.get(b'keep') is None: | ||
opts[b'keep'] = state.keep | ||||
Matt Harbison
|
r52706 | return state | ||
Manuel Jacob
|
r50201 | except FileNotFoundError: | ||
Augie Fackler
|
r43347 | cmdutil.wrongtooltocontinue(repo, _(b'unshelve')) | ||
Taapas Agrawal
|
r42802 | except error.CorruptedState as err: | ||
Augie Fackler
|
r43347 | ui.debug(pycompat.bytestr(err) + b'\n') | ||
if opts.get(b'continue'): | ||||
msg = _(b'corrupted shelved state file') | ||||
Augie Fackler
|
r43346 | hint = _( | ||
Augie Fackler
|
r43347 | b'please run hg unshelve --abort to abort unshelve ' | ||
b'operation' | ||||
Augie Fackler
|
r43346 | ) | ||
Taapas Agrawal
|
r42802 | raise error.Abort(msg, hint=hint) | ||
Augie Fackler
|
r43347 | elif opts.get(b'abort'): | ||
Taapas Agrawal
|
r42802 | shelvedstate.clear(repo) | ||
Augie Fackler
|
r43346 | raise error.Abort( | ||
_( | ||||
Augie Fackler
|
r43347 | b'could not read shelved state file, your ' | ||
b'working copy may be in an unexpected state\n' | ||||
b'please update to some commit\n' | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
Matt Harbison
|
r52706 | else: | ||
raise error.ProgrammingError( | ||||
"a corrupted shelvedstate exists without --abort or --continue" | ||||
) | ||||
Taapas Agrawal
|
r42802 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def unshelveabort(ui, repo, state: shelvedstate) -> None: | ||
Navaneeth Suresh
|
r42744 | """subcommand that abort an in-progress unshelve""" | ||
with repo.lock(): | ||||
try: | ||||
checkparents(repo, state) | ||||
Martin von Zweigbergk
|
r44743 | merge.clean_update(state.pendingctx) | ||
Augie Fackler
|
r43346 | if state.activebookmark and state.activebookmark in repo._bookmarks: | ||
Navaneeth Suresh
|
r42744 | bookmarks.activate(repo, state.activebookmark) | ||
mergefiles(ui, repo, state.wctx, state.pendingctx) | ||||
Jason R. Coombs
|
r50325 | if not _use_internal_phase(repo): | ||
Augie Fackler
|
r43346 | repair.strip( | ||
Augie Fackler
|
r43347 | ui, repo, state.nodestoremove, backup=False, topic=b'shelve' | ||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | finally: | ||
shelvedstate.clear(repo) | ||||
Augie Fackler
|
r43347 | ui.warn(_(b"unshelve of '%s' aborted\n") % state.name) | ||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def hgabortunshelve(ui, repo) -> None: | ||
Taapas Agrawal
|
r42802 | """logic to abort unshelve using 'hg abort""" | ||
with repo.wlock(): | ||||
Augie Fackler
|
r43347 | state = _loadshelvedstate(ui, repo, {b'abort': True}) | ||
Taapas Agrawal
|
r42802 | return unshelveabort(ui, repo, state) | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def mergefiles(ui, repo, wctx, shelvectx) -> None: | ||
Navaneeth Suresh
|
r42744 | """updates to wctx and merges the changes from shelvectx into the | ||
dirstate.""" | ||||
Augie Fackler
|
r43347 | with ui.configoverride({(b'ui', b'quiet'): True}): | ||
Navaneeth Suresh
|
r42744 | hg.update(repo, wctx.node()) | ||
Martin von Zweigbergk
|
r45935 | cmdutil.revert(ui, repo, shelvectx) | ||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def restorebranch(ui, repo, branchtorestore: bytes) -> None: | ||
Navaneeth Suresh
|
r42744 | if branchtorestore and branchtorestore != repo.dirstate.branch(): | ||
r51159 | repo.dirstate.setbranch(branchtorestore, repo.currenttransaction()) | |||
Augie Fackler
|
r43346 | ui.status( | ||
Augie Fackler
|
r43347 | _(b'marked working directory as branch %s\n') % branchtorestore | ||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | |||
Matt Harbison
|
r52709 | def unshelvecleanup(ui, repo, name: bytes, opts) -> None: | ||
Navaneeth Suresh
|
r42744 | """remove related files after an unshelve""" | ||
Augie Fackler
|
r43347 | if not opts.get(b'keep'): | ||
Martin von Zweigbergk
|
r47009 | backupvfs = vfsmod.vfs(repo.vfs.join(backupdir)) | ||
Martin von Zweigbergk
|
r47014 | ShelfDir(repo).get(name).movetobackup(backupvfs) | ||
Navaneeth Suresh
|
r42744 | cleanupoldbackups(repo) | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def unshelvecontinue(ui, repo, state: shelvedstate, opts) -> None: | ||
Navaneeth Suresh
|
r42744 | """subcommand to continue an in-progress unshelve""" | ||
# We're finishing off a merge. First parent is our original | ||||
# parent, second is the temporary "fake" commit we're unshelving. | ||||
Navaneeth Suresh
|
r42889 | interactive = state.interactive | ||
basename = state.name | ||||
Navaneeth Suresh
|
r42744 | with repo.lock(): | ||
checkparents(repo, state) | ||||
Augie Fackler
|
r45383 | ms = mergestatemod.mergestate.read(repo) | ||
Augie Fackler
|
r47070 | if ms.unresolvedcount(): | ||
Navaneeth Suresh
|
r42744 | raise error.Abort( | ||
Augie Fackler
|
r43347 | _(b"unresolved conflicts, can't continue"), | ||
hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"), | ||||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | |||
shelvectx = repo[state.parents[1]] | ||||
pendingctx = state.pendingctx | ||||
r50855 | with repo.dirstate.changing_parents(repo): | |||
Joerg Sonnenberger
|
r47771 | repo.setparents(state.pendingctx.node(), repo.nullid) | ||
Navaneeth Suresh
|
r42744 | repo.dirstate.write(repo.currenttransaction()) | ||
Jason R. Coombs
|
r50324 | targetphase = _target_phase(repo) | ||
Augie Fackler
|
r43347 | overrides = {(b'phases', b'new-commit'): targetphase} | ||
with repo.ui.configoverride(overrides, b'unshelve'): | ||||
r50855 | with repo.dirstate.changing_parents(repo): | |||
Joerg Sonnenberger
|
r47771 | repo.setparents(state.parents[0], repo.nullid) | ||
r50947 | newnode, ispartialunshelve = _createunshelvectx( | |||
ui, repo, shelvectx, basename, interactive, opts | ||||
) | ||||
Navaneeth Suresh
|
r42744 | |||
if newnode is None: | ||||
shelvectx = state.pendingctx | ||||
Augie Fackler
|
r43346 | msg = _( | ||
Augie Fackler
|
r43347 | b'note: unshelved changes already existed ' | ||
b'in the working copy\n' | ||||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | ui.status(msg) | ||
else: | ||||
# only strip the shelvectx if we produced one | ||||
state.nodestoremove.append(newnode) | ||||
shelvectx = repo[newnode] | ||||
Martin von Zweigbergk
|
r46151 | merge.update(pendingctx) | ||
Navaneeth Suresh
|
r42744 | mergefiles(ui, repo, state.wctx, shelvectx) | ||
restorebranch(ui, repo, state.branchtorestore) | ||||
Jason R. Coombs
|
r50325 | if not _use_internal_phase(repo): | ||
Augie Fackler
|
r43346 | repair.strip( | ||
Augie Fackler
|
r43347 | ui, repo, state.nodestoremove, backup=False, topic=b'shelve' | ||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42931 | shelvedstate.clear(repo) | ||
Navaneeth Suresh
|
r42835 | if not ispartialunshelve: | ||
unshelvecleanup(ui, repo, state.name, opts) | ||||
Navaneeth Suresh
|
r42744 | _restoreactivebookmark(repo, state.activebookmark) | ||
Augie Fackler
|
r43347 | ui.status(_(b"unshelve of '%s' complete\n") % state.name) | ||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def hgcontinueunshelve(ui, repo) -> None: | ||
Taapas Agrawal
|
r42833 | """logic to resume unshelve using 'hg continue'""" | ||
with repo.wlock(): | ||||
Augie Fackler
|
r43347 | state = _loadshelvedstate(ui, repo, {b'continue': True}) | ||
return unshelvecontinue(ui, repo, state, {b'keep': state.keep}) | ||||
Augie Fackler
|
r43346 | |||
Taapas Agrawal
|
r42833 | |||
Navaneeth Suresh
|
r42744 | def _commitworkingcopychanges(ui, repo, opts, tmpwctx): | ||
"""Temporarily commit working copy changes before moving unshelve commit""" | ||||
# Store pending changes in a commit and remember added in case a shelve | ||||
# contains unknown files that are part of the pending change | ||||
s = repo.status() | ||||
addedbefore = frozenset(s.added) | ||||
if not (s.modified or s.added or s.removed): | ||||
return tmpwctx, addedbefore | ||||
Augie Fackler
|
r43346 | ui.status( | ||
_( | ||||
Augie Fackler
|
r43347 | b"temporarily committing pending changes " | ||
b"(restore with 'hg unshelve --abort')\n" | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
Augie Fackler
|
r43347 | extra = {b'internal': b'shelve'} | ||
Augie Fackler
|
r43346 | commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False) | ||
Navaneeth Suresh
|
r42744 | tempopts = {} | ||
Augie Fackler
|
r43347 | tempopts[b'message'] = b"pending changes temporary commit" | ||
tempopts[b'date'] = opts.get(b'date') | ||||
with ui.configoverride({(b'ui', b'quiet'): True}): | ||||
Navaneeth Suresh
|
r42744 | node = cmdutil.commit(ui, repo, commitfunc, [], tempopts) | ||
tmpwctx = repo[node] | ||||
return tmpwctx, addedbefore | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _unshelverestorecommit(ui, repo, tr, basename: bytes): | ||
Navaneeth Suresh
|
r42744 | """Recreate commit in the repository during the unshelve""" | ||
repo = repo.unfiltered() | ||||
node = None | ||||
Martin von Zweigbergk
|
r47014 | shelf = ShelfDir(repo).get(basename) | ||
Martin von Zweigbergk
|
r47005 | if shelf.hasinfo(): | ||
node = shelf.readinfo()[b'node'] | ||||
Navaneeth Suresh
|
r42744 | if node is None or node not in repo: | ||
Augie Fackler
|
r43347 | with ui.configoverride({(b'ui', b'quiet'): True}): | ||
Martin von Zweigbergk
|
r47009 | shelvectx = shelf.applybundle(repo, tr) | ||
Navaneeth Suresh
|
r42744 | # We might not strip the unbundled changeset, so we should keep track of | ||
# the unshelve node in case we need to reuse it (eg: unshelve --keep) | ||||
if node is None: | ||||
Joerg Sonnenberger
|
r46729 | info = {b'node': hex(shelvectx.node())} | ||
Martin von Zweigbergk
|
r47005 | shelf.writeinfo(info) | ||
Navaneeth Suresh
|
r42744 | else: | ||
shelvectx = repo[node] | ||||
return repo, shelvectx | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _createunshelvectx( | ||
ui, repo, shelvectx, basename: bytes, interactive: bool, opts | ||||
) -> Tuple[bytes, bool]: | ||||
Navaneeth Suresh
|
r42886 | """Handles the creation of unshelve commit and updates the shelve if it | ||
was partially unshelved. | ||||
If interactive is: | ||||
* False: Commits all the changes in the working directory. | ||||
* True: Prompts the user to select changes to unshelve and commit them. | ||||
Update the shelve with remaining changes. | ||||
Returns the node of the new commit formed and a bool indicating whether | ||||
the shelve was partially unshelved.Creates a commit ctx to unshelve | ||||
interactively or non-interactively. | ||||
The user might want to unshelve certain changes only from the stored | ||||
shelve in interactive. So, we would create two commits. One with requested | ||||
changes to unshelve at that time and the latter is shelved for future. | ||||
Here, we return both the newnode which is created interactively and a | ||||
bool to know whether the shelve is partly done or completely done. | ||||
Navaneeth Suresh
|
r42835 | """ | ||
Augie Fackler
|
r43347 | opts[b'message'] = shelvectx.description() | ||
opts[b'interactive-unshelve'] = True | ||||
Navaneeth Suresh
|
r42835 | pats = [] | ||
Navaneeth Suresh
|
r42886 | if not interactive: | ||
Augie Fackler
|
r43346 | newnode = repo.commit( | ||
text=shelvectx.description(), | ||||
extra=shelvectx.extra(), | ||||
user=shelvectx.user(), | ||||
date=shelvectx.date(), | ||||
) | ||||
Navaneeth Suresh
|
r42886 | return newnode, False | ||
Augie Fackler
|
r43346 | commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True) | ||
newnode = cmdutil.dorecord( | ||||
ui, | ||||
repo, | ||||
commitfunc, | ||||
None, | ||||
False, | ||||
cmdutil.recordfilter, | ||||
*pats, | ||||
Matt Harbison
|
r52755 | **pycompat.strkwargs(opts), | ||
Augie Fackler
|
r43346 | ) | ||
snode = repo.commit( | ||||
text=shelvectx.description(), | ||||
extra=shelvectx.extra(), | ||||
user=shelvectx.user(), | ||||
) | ||||
Navaneeth Suresh
|
r42835 | if snode: | ||
Jason R. Coombs
|
r50320 | m = _optimized_match(repo, snode) | ||
Navaneeth Suresh
|
r42835 | _shelvecreatedcommit(repo, snode, basename, m) | ||
return newnode, bool(snode) | ||||
Augie Fackler
|
r43346 | |||
def _rebaserestoredcommit( | ||||
ui, | ||||
repo, | ||||
opts, | ||||
tr, | ||||
oldtiprev, | ||||
Matt Harbison
|
r52709 | basename: bytes, | ||
Augie Fackler
|
r43346 | pctx, | ||
tmpwctx, | ||||
shelvectx, | ||||
branchtorestore, | ||||
activebookmark, | ||||
): | ||||
Navaneeth Suresh
|
r42744 | """Rebase restored commit from its original location to a destination""" | ||
# If the shelve is not immediately on top of the commit | ||||
# we'll be merging with, rebase it to be on top. | ||||
Augie Fackler
|
r43347 | interactive = opts.get(b'interactive') | ||
Navaneeth Suresh
|
r42835 | if tmpwctx.node() == shelvectx.p1().node() and not interactive: | ||
# We won't skip on interactive mode because, the user might want to | ||||
# unshelve certain changes only. | ||||
return shelvectx, False | ||||
Navaneeth Suresh
|
r42744 | |||
overrides = { | ||||
Augie Fackler
|
r43347 | (b'ui', b'forcemerge'): opts.get(b'tool', b''), | ||
(b'phases', b'new-commit'): phases.secret, | ||||
Navaneeth Suresh
|
r42744 | } | ||
Augie Fackler
|
r43347 | with repo.ui.configoverride(overrides, b'unshelve'): | ||
ui.status(_(b'rebasing shelved changes\n')) | ||||
Augie Fackler
|
r43346 | stats = merge.graft( | ||
repo, | ||||
shelvectx, | ||||
Martin von Zweigbergk
|
r49438 | labels=[ | ||
b'working-copy', | ||||
b'shelved change', | ||||
b'parent of shelved change', | ||||
], | ||||
Augie Fackler
|
r43346 | keepconflictparent=True, | ||
) | ||||
Navaneeth Suresh
|
r42744 | if stats.unresolvedcount: | ||
tr.close() | ||||
Augie Fackler
|
r43346 | nodestoremove = [ | ||
Manuel Jacob
|
r50179 | repo.changelog.node(rev) for rev in range(oldtiprev, len(repo)) | ||
Augie Fackler
|
r43346 | ] | ||
shelvedstate.save( | ||||
repo, | ||||
basename, | ||||
pctx, | ||||
tmpwctx, | ||||
nodestoremove, | ||||
branchtorestore, | ||||
Augie Fackler
|
r43347 | opts.get(b'keep'), | ||
Augie Fackler
|
r43346 | activebookmark, | ||
interactive, | ||||
) | ||||
Daniel Ploch
|
r45711 | raise error.ConflictResolutionRequired(b'unshelve') | ||
Navaneeth Suresh
|
r42744 | |||
r50855 | with repo.dirstate.changing_parents(repo): | |||
Joerg Sonnenberger
|
r47771 | repo.setparents(tmpwctx.node(), repo.nullid) | ||
r50947 | newnode, ispartialunshelve = _createunshelvectx( | |||
ui, repo, shelvectx, basename, interactive, opts | ||||
) | ||||
Navaneeth Suresh
|
r42744 | |||
if newnode is None: | ||||
shelvectx = tmpwctx | ||||
Augie Fackler
|
r43346 | msg = _( | ||
Augie Fackler
|
r43347 | b'note: unshelved changes already existed ' | ||
b'in the working copy\n' | ||||
Augie Fackler
|
r43346 | ) | ||
Navaneeth Suresh
|
r42744 | ui.status(msg) | ||
else: | ||||
shelvectx = repo[newnode] | ||||
Martin von Zweigbergk
|
r46151 | merge.update(tmpwctx) | ||
Navaneeth Suresh
|
r42744 | |||
Navaneeth Suresh
|
r42835 | return shelvectx, ispartialunshelve | ||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _forgetunknownfiles(repo, shelvectx, addedbefore) -> None: | ||
Navaneeth Suresh
|
r42744 | # Forget any files that were unknown before the shelve, unknown before | ||
# unshelve started, but are now added. | ||||
Augie Fackler
|
r43347 | shelveunknown = shelvectx.extra().get(b'shelve_unknown') | ||
Navaneeth Suresh
|
r42744 | if not shelveunknown: | ||
return | ||||
Augie Fackler
|
r43347 | shelveunknown = frozenset(shelveunknown.split(b'\0')) | ||
Navaneeth Suresh
|
r42744 | addedafter = frozenset(repo.status().added) | ||
toforget = (addedafter & shelveunknown) - addedbefore | ||||
repo[None].forget(toforget) | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _finishunshelve(repo, oldtiprev, tr, activebookmark) -> None: | ||
Navaneeth Suresh
|
r42744 | _restoreactivebookmark(repo, activebookmark) | ||
r51993 | # We used to manually strip the commit to update inmemory structure and | |||
# prevent some issue around hooks. This no longer seems to be the case, so | ||||
# we simply abort the transaction. | ||||
Navaneeth Suresh
|
r42744 | _aborttransaction(repo, tr) | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def _checkunshelveuntrackedproblems(ui, repo, shelvectx) -> None: | ||
Navaneeth Suresh
|
r42744 | """Check potential problems which may result from working | ||
copy having untracked changes.""" | ||||
wcdeleted = set(repo.status().deleted) | ||||
shelvetouched = set(shelvectx.files()) | ||||
intersection = wcdeleted.intersection(shelvetouched) | ||||
if intersection: | ||||
Augie Fackler
|
r43347 | m = _(b"shelved change touches missing files") | ||
hint = _(b"run hg status to see which files are missing") | ||||
Navaneeth Suresh
|
r42744 | raise error.Abort(m, hint=hint) | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52709 | def unshelvecmd(ui, repo, *shelved, **opts) -> None: | ||
Navaneeth Suresh
|
r42744 | opts = pycompat.byteskwargs(opts) | ||
Augie Fackler
|
r43347 | abortf = opts.get(b'abort') | ||
continuef = opts.get(b'continue') | ||||
interactive = opts.get(b'interactive') | ||||
Navaneeth Suresh
|
r42744 | if not abortf and not continuef: | ||
cmdutil.checkunfinished(repo) | ||||
shelved = list(shelved) | ||||
Augie Fackler
|
r43347 | if opts.get(b"name"): | ||
shelved.append(opts[b"name"]) | ||||
Navaneeth Suresh
|
r42744 | |||
Augie Fackler
|
r43347 | if interactive and opts.get(b'keep'): | ||
Martin von Zweigbergk
|
r46990 | raise error.InputError( | ||
_(b'--keep on --interactive is not yet supported') | ||||
) | ||||
Navaneeth Suresh
|
r42889 | if abortf or continuef: | ||
Navaneeth Suresh
|
r42744 | if abortf and continuef: | ||
Martin von Zweigbergk
|
r46990 | raise error.InputError(_(b'cannot use both abort and continue')) | ||
Navaneeth Suresh
|
r42744 | if shelved: | ||
Martin von Zweigbergk
|
r46990 | raise error.InputError( | ||
Augie Fackler
|
r43346 | _( | ||
Augie Fackler
|
r43347 | b'cannot combine abort/continue with ' | ||
b'naming a shelved change' | ||||
Augie Fackler
|
r43346 | ) | ||
) | ||||
Augie Fackler
|
r43347 | if abortf and opts.get(b'tool', False): | ||
ui.warn(_(b'tool option will be ignored\n')) | ||||
Navaneeth Suresh
|
r42744 | |||
Taapas Agrawal
|
r42802 | state = _loadshelvedstate(ui, repo, opts) | ||
Navaneeth Suresh
|
r42744 | if abortf: | ||
Taapas Agrawal
|
r42802 | return unshelveabort(ui, repo, state) | ||
Navaneeth Suresh
|
r42893 | elif continuef and interactive: | ||
Martin von Zweigbergk
|
r46990 | raise error.InputError( | ||
_(b'cannot use both continue and interactive') | ||||
) | ||||
Navaneeth Suresh
|
r42744 | elif continuef: | ||
return unshelvecontinue(ui, repo, state, opts) | ||||
Matt Harbison
|
r52709 | else: | ||
# Unreachable code, but help type checkers not think that | ||||
# 'basename' may be used before initialization when checking | ||||
# ShelfDir below. | ||||
raise error.ProgrammingError("neither abort nor continue specified") | ||||
Navaneeth Suresh
|
r42744 | elif len(shelved) > 1: | ||
Martin von Zweigbergk
|
r46990 | raise error.InputError(_(b'can only unshelve one change at a time')) | ||
Navaneeth Suresh
|
r42744 | elif not shelved: | ||
Martin von Zweigbergk
|
r47015 | shelved = ShelfDir(repo).listshelves() | ||
Navaneeth Suresh
|
r42744 | if not shelved: | ||
Martin von Zweigbergk
|
r46990 | raise error.StateError(_(b'no shelved changes to apply!')) | ||
Martin von Zweigbergk
|
r46999 | basename = shelved[0][1] | ||
Augie Fackler
|
r43347 | ui.status(_(b"unshelving change '%s'\n") % basename) | ||
Navaneeth Suresh
|
r42889 | else: | ||
Navaneeth Suresh
|
r42744 | basename = shelved[0] | ||
Martin von Zweigbergk
|
r47014 | if not ShelfDir(repo).get(basename).exists(): | ||
Martin von Zweigbergk
|
r46990 | raise error.InputError(_(b"shelved change '%s' not found") % basename) | ||
Navaneeth Suresh
|
r42744 | |||
Martin von Zweigbergk
|
r45086 | return _dounshelve(ui, repo, basename, opts) | ||
Matt Harbison
|
r52709 | def _dounshelve(ui, repo, basename: bytes, opts) -> None: | ||
Navaneeth Suresh
|
r42744 | repo = repo.unfiltered() | ||
lock = tr = None | ||||
try: | ||||
lock = repo.lock() | ||||
Augie Fackler
|
r43347 | tr = repo.transaction(b'unshelve', report=lambda x: None) | ||
Navaneeth Suresh
|
r42744 | oldtiprev = len(repo) | ||
Augie Fackler
|
r43347 | pctx = repo[b'.'] | ||
Navaneeth Suresh
|
r42744 | # The goal is to have a commit structure like so: | ||
# ...-> pctx -> tmpwctx -> shelvectx | ||||
# where tmpwctx is an optional commit with the user's pending changes | ||||
# and shelvectx is the unshelved changes. Then we merge it all down | ||||
# to the original pctx. | ||||
activebookmark = _backupactivebookmark(repo) | ||||
Jason R. Coombs
|
r50417 | tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts, pctx) | ||
Navaneeth Suresh
|
r42744 | repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename) | ||
_checkunshelveuntrackedproblems(ui, repo, shelvectx) | ||||
Augie Fackler
|
r43347 | branchtorestore = b'' | ||
Navaneeth Suresh
|
r42744 | if shelvectx.branch() != shelvectx.p1().branch(): | ||
branchtorestore = shelvectx.branch() | ||||
Augie Fackler
|
r43346 | shelvectx, ispartialunshelve = _rebaserestoredcommit( | ||
ui, | ||||
repo, | ||||
opts, | ||||
tr, | ||||
oldtiprev, | ||||
basename, | ||||
pctx, | ||||
tmpwctx, | ||||
shelvectx, | ||||
branchtorestore, | ||||
activebookmark, | ||||
) | ||||
Augie Fackler
|
r43347 | overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')} | ||
with ui.configoverride(overrides, b'unshelve'): | ||||
Navaneeth Suresh
|
r42744 | mergefiles(ui, repo, pctx, shelvectx) | ||
restorebranch(ui, repo, branchtorestore) | ||||
Navaneeth Suresh
|
r42932 | shelvedstate.clear(repo) | ||
_finishunshelve(repo, oldtiprev, tr, activebookmark) | ||||
r50948 | with repo.dirstate.changing_files(repo): | |||
_forgetunknownfiles(repo, shelvectx, addedbefore) | ||||
Navaneeth Suresh
|
r42835 | if not ispartialunshelve: | ||
unshelvecleanup(ui, repo, basename, opts) | ||||
Navaneeth Suresh
|
r42744 | finally: | ||
if tr: | ||||
tr.release() | ||||
lockmod.release(lock) | ||||