mercurial/shelve.py
branchstable
changeset 49527 a3356ab610fc
parent 49507 f0a3aaa07d6a
parent 49522 52dd7a43ad5c
child 49560 5f778b3a94ca
equal deleted inserted replaced
49526:192949b68159 49527:a3356ab610fc
    20 shelved change has a distinct name. For details, see the help for "hg
    20 shelved change has a distinct name. For details, see the help for "hg
    21 shelve".
    21 shelve".
    22 """
    22 """
    23 
    23 
    24 import collections
    24 import collections
       
    25 import io
    25 import itertools
    26 import itertools
    26 import stat
    27 import stat
    27 
    28 
    28 from .i18n import _
    29 from .i18n import _
    29 from .node import (
    30 from .node import (
    96             mtime = shelf.mtime()
    97             mtime = shelf.mtime()
    97             info.append((mtime, name))
    98             info.append((mtime, name))
    98         return sorted(info, reverse=True)
    99         return sorted(info, reverse=True)
    99 
   100 
   100 
   101 
       
   102 def _use_internal_phase(repo):
       
   103     return (
       
   104         phases.supportinternal(repo)
       
   105         and repo.ui.config(b'shelve', b'store') == b'internal'
       
   106     )
       
   107 
       
   108 
       
   109 def _target_phase(repo):
       
   110     return phases.internal if _use_internal_phase(repo) else phases.secret
       
   111 
       
   112 
   101 class Shelf:
   113 class Shelf:
   102     """Represents a shelf, including possibly multiple files storing it.
   114     """Represents a shelf, including possibly multiple files storing it.
   103 
   115 
   104     Old shelves will have a .patch and a .hg file. Newer shelves will
   116     Old shelves will have a .patch and a .hg file. Newer shelves will
   105     also have a .shelve file. This class abstracts away some of the
   117     also have a .shelve file. This class abstracts away some of the
   109     def __init__(self, vfs, name):
   121     def __init__(self, vfs, name):
   110         self.vfs = vfs
   122         self.vfs = vfs
   111         self.name = name
   123         self.name = name
   112 
   124 
   113     def exists(self):
   125     def exists(self):
   114         return self.vfs.exists(self.name + b'.patch') and self.vfs.exists(
   126         return self._exists(b'.shelve') or self._exists(b'.patch', b'.hg')
   115             self.name + b'.hg'
   127 
   116         )
   128     def _exists(self, *exts):
       
   129         return all(self.vfs.exists(self.name + ext) for ext in exts)
   117 
   130 
   118     def mtime(self):
   131     def mtime(self):
   119         return self.vfs.stat(self.name + b'.patch')[stat.ST_MTIME]
   132         try:
       
   133             return self._stat(b'.shelve')[stat.ST_MTIME]
       
   134         except FileNotFoundError:
       
   135             return self._stat(b'.patch')[stat.ST_MTIME]
       
   136 
       
   137     def _stat(self, ext):
       
   138         return self.vfs.stat(self.name + ext)
   120 
   139 
   121     def writeinfo(self, info):
   140     def writeinfo(self, info):
   122         scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info)
   141         scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info)
   123 
   142 
   124     def hasinfo(self):
   143     def hasinfo(self):
   157 
   176 
   158     def applybundle(self, repo, tr):
   177     def applybundle(self, repo, tr):
   159         filename = self.name + b'.hg'
   178         filename = self.name + b'.hg'
   160         fp = self.vfs(filename)
   179         fp = self.vfs(filename)
   161         try:
   180         try:
   162             targetphase = phases.internal
   181             targetphase = _target_phase(repo)
   163             if not phases.supportinternal(repo):
       
   164                 targetphase = phases.secret
       
   165             gen = exchange.readbundle(repo.ui, fp, filename, self.vfs)
   182             gen = exchange.readbundle(repo.ui, fp, filename, self.vfs)
   166             pretip = repo[b'tip']
   183             pretip = repo[b'tip']
   167             bundle2.applybundle(
   184             bundle2.applybundle(
   168                 repo,
   185                 repo,
   169                 gen,
   186                 gen,
   180         finally:
   197         finally:
   181             fp.close()
   198             fp.close()
   182 
   199 
   183     def open_patch(self, mode=b'rb'):
   200     def open_patch(self, mode=b'rb'):
   184         return self.vfs(self.name + b'.patch', mode)
   201         return self.vfs(self.name + b'.patch', mode)
       
   202 
       
   203     def patch_from_node(self, repo, node):
       
   204         repo = repo.unfiltered()
       
   205         match = _optimized_match(repo, node)
       
   206         fp = io.BytesIO()
       
   207         cmdutil.exportfile(
       
   208             repo,
       
   209             [node],
       
   210             fp,
       
   211             opts=mdiff.diffopts(git=True),
       
   212             match=match,
       
   213         )
       
   214         fp.seek(0)
       
   215         return fp
       
   216 
       
   217     def load_patch(self, repo):
       
   218         try:
       
   219             # prefer node-based shelf
       
   220             return self.patch_from_node(repo, self.readinfo()[b'node'])
       
   221         except (FileNotFoundError, error.RepoLookupError):
       
   222             return self.open_patch()
   185 
   223 
   186     def _backupfilename(self, backupvfs, filename):
   224     def _backupfilename(self, backupvfs, filename):
   187         def gennames(base):
   225         def gennames(base):
   188             yield base
   226             yield base
   189             base, ext = base.rsplit(b'.', 1)
   227             base, ext = base.rsplit(b'.', 1)
   206                 )
   244                 )
   207 
   245 
   208     def delete(self):
   246     def delete(self):
   209         for ext in shelvefileextensions:
   247         for ext in shelvefileextensions:
   210             self.vfs.tryunlink(self.name + b'.' + ext)
   248             self.vfs.tryunlink(self.name + b'.' + ext)
       
   249 
       
   250 
       
   251 def _optimized_match(repo, node):
       
   252     """
       
   253     Create a matcher so that prefetch doesn't attempt to fetch
       
   254     the entire repository pointlessly, and as an optimisation
       
   255     for movedirstate, if needed.
       
   256     """
       
   257     return scmutil.matchfiles(repo, repo[node].files())
   211 
   258 
   212 
   259 
   213 class shelvedstate:
   260 class shelvedstate:
   214     """Handle persistence during unshelving operations.
   261     """Handle persistence during unshelving operations.
   215 
   262 
   445     def commitfunc(ui, repo, message, match, opts):
   492     def commitfunc(ui, repo, message, match, opts):
   446         hasmq = util.safehasattr(repo, b'mq')
   493         hasmq = util.safehasattr(repo, b'mq')
   447         if hasmq:
   494         if hasmq:
   448             saved, repo.mq.checkapplied = repo.mq.checkapplied, False
   495             saved, repo.mq.checkapplied = repo.mq.checkapplied, False
   449 
   496 
   450         targetphase = phases.internal
   497         targetphase = _target_phase(repo)
   451         if not phases.supportinternal(repo):
       
   452             targetphase = phases.secret
       
   453         overrides = {(b'phases', b'new-commit'): targetphase}
   498         overrides = {(b'phases', b'new-commit'): targetphase}
   454         try:
   499         try:
   455             editor_ = False
   500             editor_ = False
   456             if editor:
   501             if editor:
   457                 editor_ = cmdutil.getcommiteditor(
   502                 editor_ = cmdutil.getcommiteditor(
   508         extra[b'shelve_unknown'] = b'\0'.join(s.unknown)
   553         extra[b'shelve_unknown'] = b'\0'.join(s.unknown)
   509         repo[None].add(s.unknown)
   554         repo[None].add(s.unknown)
   510 
   555 
   511 
   556 
   512 def _finishshelve(repo, tr):
   557 def _finishshelve(repo, tr):
   513     if phases.supportinternal(repo):
   558     if _use_internal_phase(repo):
   514         tr.close()
   559         tr.close()
   515     else:
   560     else:
   516         _aborttransaction(repo, tr)
   561         _aborttransaction(repo, tr)
   517 
   562 
   518 
   563 
   577             )
   622             )
   578         if not node:
   623         if not node:
   579             _nothingtoshelvemessaging(ui, repo, pats, opts)
   624             _nothingtoshelvemessaging(ui, repo, pats, opts)
   580             return 1
   625             return 1
   581 
   626 
   582         # Create a matcher so that prefetch doesn't attempt to fetch
   627         match = _optimized_match(repo, node)
   583         # the entire repository pointlessly, and as an optimisation
       
   584         # for movedirstate, if needed.
       
   585         match = scmutil.matchfiles(repo, repo[node].files())
       
   586         _shelvecreatedcommit(repo, node, name, match)
   628         _shelvecreatedcommit(repo, node, name, match)
   587 
   629 
   588         ui.status(_(b'shelved as %s\n') % name)
   630         ui.status(_(b'shelved as %s\n') % name)
   589         if opts[b'keep']:
   631         if opts[b'keep']:
   590             with repo.dirstate.parentchange():
   632             with repo.dirstate.parentchange():
   666         date = dateutil.makedate(mtime)
   708         date = dateutil.makedate(mtime)
   667         age = b'(%s)' % templatefilters.age(date, abbrev=True)
   709         age = b'(%s)' % templatefilters.age(date, abbrev=True)
   668         ui.write(age, label=b'shelve.age')
   710         ui.write(age, label=b'shelve.age')
   669         ui.write(b' ' * (12 - len(age)))
   711         ui.write(b' ' * (12 - len(age)))
   670         used += 12
   712         used += 12
   671         with shelf_dir.get(name).open_patch() as fp:
   713         with shelf_dir.get(name).load_patch(repo) as fp:
   672             while True:
   714             while True:
   673                 line = fp.readline()
   715                 line = fp.readline()
   674                 if not line:
   716                 if not line:
   675                     break
   717                     break
   676                 if not line.startswith(b'#'):
   718                 if not line.startswith(b'#'):
   752 
   794 
   753             merge.clean_update(state.pendingctx)
   795             merge.clean_update(state.pendingctx)
   754             if state.activebookmark and state.activebookmark in repo._bookmarks:
   796             if state.activebookmark and state.activebookmark in repo._bookmarks:
   755                 bookmarks.activate(repo, state.activebookmark)
   797                 bookmarks.activate(repo, state.activebookmark)
   756             mergefiles(ui, repo, state.wctx, state.pendingctx)
   798             mergefiles(ui, repo, state.wctx, state.pendingctx)
   757             if not phases.supportinternal(repo):
   799             if not _use_internal_phase(repo):
   758                 repair.strip(
   800                 repair.strip(
   759                     ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
   801                     ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
   760                 )
   802                 )
   761         finally:
   803         finally:
   762             shelvedstate.clear(repo)
   804             shelvedstate.clear(repo)
   814 
   856 
   815         with repo.dirstate.parentchange():
   857         with repo.dirstate.parentchange():
   816             repo.setparents(state.pendingctx.node(), repo.nullid)
   858             repo.setparents(state.pendingctx.node(), repo.nullid)
   817             repo.dirstate.write(repo.currenttransaction())
   859             repo.dirstate.write(repo.currenttransaction())
   818 
   860 
   819         targetphase = phases.internal
   861         targetphase = _target_phase(repo)
   820         if not phases.supportinternal(repo):
       
   821             targetphase = phases.secret
       
   822         overrides = {(b'phases', b'new-commit'): targetphase}
   862         overrides = {(b'phases', b'new-commit'): targetphase}
   823         with repo.ui.configoverride(overrides, b'unshelve'):
   863         with repo.ui.configoverride(overrides, b'unshelve'):
   824             with repo.dirstate.parentchange():
   864             with repo.dirstate.parentchange():
   825                 repo.setparents(state.parents[0], repo.nullid)
   865                 repo.setparents(state.parents[0], repo.nullid)
   826                 newnode, ispartialunshelve = _createunshelvectx(
   866                 newnode, ispartialunshelve = _createunshelvectx(
   841 
   881 
   842         merge.update(pendingctx)
   882         merge.update(pendingctx)
   843         mergefiles(ui, repo, state.wctx, shelvectx)
   883         mergefiles(ui, repo, state.wctx, shelvectx)
   844         restorebranch(ui, repo, state.branchtorestore)
   884         restorebranch(ui, repo, state.branchtorestore)
   845 
   885 
   846         if not phases.supportinternal(repo):
   886         if not _use_internal_phase(repo):
   847             repair.strip(
   887             repair.strip(
   848                 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
   888                 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
   849             )
   889             )
   850         shelvedstate.clear(repo)
   890         shelvedstate.clear(repo)
   851         if not ispartialunshelve:
   891         if not ispartialunshelve:
   955         text=shelvectx.description(),
   995         text=shelvectx.description(),
   956         extra=shelvectx.extra(),
   996         extra=shelvectx.extra(),
   957         user=shelvectx.user(),
   997         user=shelvectx.user(),
   958     )
   998     )
   959     if snode:
   999     if snode:
   960         m = scmutil.matchfiles(repo, repo[snode].files())
  1000         m = _optimized_match(repo, snode)
   961         _shelvecreatedcommit(repo, snode, basename, m)
  1001         _shelvecreatedcommit(repo, snode, basename, m)
   962 
  1002 
   963     return newnode, bool(snode)
  1003     return newnode, bool(snode)
   964 
  1004 
   965 
  1005 
  1135         lock = repo.lock()
  1175         lock = repo.lock()
  1136         tr = repo.transaction(b'unshelve', report=lambda x: None)
  1176         tr = repo.transaction(b'unshelve', report=lambda x: None)
  1137         oldtiprev = len(repo)
  1177         oldtiprev = len(repo)
  1138 
  1178 
  1139         pctx = repo[b'.']
  1179         pctx = repo[b'.']
  1140         tmpwctx = pctx
       
  1141         # The goal is to have a commit structure like so:
  1180         # The goal is to have a commit structure like so:
  1142         # ...-> pctx -> tmpwctx -> shelvectx
  1181         # ...-> pctx -> tmpwctx -> shelvectx
  1143         # where tmpwctx is an optional commit with the user's pending changes
  1182         # where tmpwctx is an optional commit with the user's pending changes
  1144         # and shelvectx is the unshelved changes. Then we merge it all down
  1183         # and shelvectx is the unshelved changes. Then we merge it all down
  1145         # to the original pctx.
  1184         # to the original pctx.
  1146 
  1185 
  1147         activebookmark = _backupactivebookmark(repo)
  1186         activebookmark = _backupactivebookmark(repo)
  1148         tmpwctx, addedbefore = _commitworkingcopychanges(
  1187         tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts, pctx)
  1149             ui, repo, opts, tmpwctx
       
  1150         )
       
  1151         repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
  1188         repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
  1152         _checkunshelveuntrackedproblems(ui, repo, shelvectx)
  1189         _checkunshelveuntrackedproblems(ui, repo, shelvectx)
  1153         branchtorestore = b''
  1190         branchtorestore = b''
  1154         if shelvectx.branch() != shelvectx.p1().branch():
  1191         if shelvectx.branch() != shelvectx.p1().branch():
  1155             branchtorestore = shelvectx.branch()
  1192             branchtorestore = shelvectx.branch()