##// END OF EJS Templates
shelve: use listdir() instead of readdir() when we don't need stat information...
Martin von Zweigbergk -
r47008:e33266b9 default
parent child Browse files
Show More
@@ -1,1173 +1,1173 b''
1 # shelve.py - save/restore working directory state
1 # shelve.py - save/restore working directory state
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """save and restore changes to the working directory
8 """save and restore changes to the working directory
9
9
10 The "hg shelve" command saves changes made to the working directory
10 The "hg shelve" command saves changes made to the working directory
11 and reverts those changes, resetting the working directory to a clean
11 and reverts those changes, resetting the working directory to a clean
12 state.
12 state.
13
13
14 Later on, the "hg unshelve" command restores the changes saved by "hg
14 Later on, the "hg unshelve" command restores the changes saved by "hg
15 shelve". Changes can be restored even after updating to a different
15 shelve". Changes can be restored even after updating to a different
16 parent, in which case Mercurial's merge machinery will resolve any
16 parent, in which case Mercurial's merge machinery will resolve any
17 conflicts if necessary.
17 conflicts if necessary.
18
18
19 You can have more than one shelved change outstanding at a time; each
19 You can have more than one shelved change outstanding at a time; each
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 from __future__ import absolute_import
23 from __future__ import absolute_import
24
24
25 import collections
25 import collections
26 import errno
26 import errno
27 import itertools
27 import itertools
28 import stat
28 import stat
29
29
30 from .i18n import _
30 from .i18n import _
31 from .node import (
31 from .node import (
32 bin,
32 bin,
33 hex,
33 hex,
34 nullid,
34 nullid,
35 nullrev,
35 nullrev,
36 )
36 )
37 from . import (
37 from . import (
38 bookmarks,
38 bookmarks,
39 bundle2,
39 bundle2,
40 changegroup,
40 changegroup,
41 cmdutil,
41 cmdutil,
42 discovery,
42 discovery,
43 error,
43 error,
44 exchange,
44 exchange,
45 hg,
45 hg,
46 lock as lockmod,
46 lock as lockmod,
47 mdiff,
47 mdiff,
48 merge,
48 merge,
49 mergestate as mergestatemod,
49 mergestate as mergestatemod,
50 patch,
50 patch,
51 phases,
51 phases,
52 pycompat,
52 pycompat,
53 repair,
53 repair,
54 scmutil,
54 scmutil,
55 templatefilters,
55 templatefilters,
56 util,
56 util,
57 vfs as vfsmod,
57 vfs as vfsmod,
58 )
58 )
59 from .utils import (
59 from .utils import (
60 dateutil,
60 dateutil,
61 stringutil,
61 stringutil,
62 )
62 )
63
63
64 backupdir = b'shelve-backup'
64 backupdir = b'shelve-backup'
65 shelvedir = b'shelved'
65 shelvedir = b'shelved'
66 shelvefileextensions = [b'hg', b'patch', b'shelve']
66 shelvefileextensions = [b'hg', b'patch', b'shelve']
67 # universal extension is present in all types of shelves
67 # universal extension is present in all types of shelves
68 patchextension = b'patch'
68 patchextension = b'patch'
69
69
70 # we never need the user, so we use a
70 # we never need the user, so we use a
71 # generic user for all shelve operations
71 # generic user for all shelve operations
72 shelveuser = b'shelve@localhost'
72 shelveuser = b'shelve@localhost'
73
73
74
74
75 class Shelf(object):
75 class Shelf(object):
76 """Represents a shelf, including possibly multiple files storing it.
76 """Represents a shelf, including possibly multiple files storing it.
77
77
78 Old shelves will have a .patch and a .hg file. Newer shelves will
78 Old shelves will have a .patch and a .hg file. Newer shelves will
79 also have a .shelve file. This class abstracts away some of the
79 also have a .shelve file. This class abstracts away some of the
80 differences and lets you work with the shelf as a whole.
80 differences and lets you work with the shelf as a whole.
81 """
81 """
82
82
83 def __init__(self, repo, name):
83 def __init__(self, repo, name):
84 self.repo = repo
84 self.repo = repo
85 self.name = name
85 self.name = name
86 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
86 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
87 self.backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
87 self.backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
88
88
89 def exists(self):
89 def exists(self):
90 return self.vfs.exists(
90 return self.vfs.exists(
91 self.name + b'.' + patchextension
91 self.name + b'.' + patchextension
92 ) and self.vfs.exists(self.name + b'.hg')
92 ) and self.vfs.exists(self.name + b'.hg')
93
93
94 def mtime(self):
94 def mtime(self):
95 return self.vfs.stat(self.name + b'.' + patchextension)[stat.ST_MTIME]
95 return self.vfs.stat(self.name + b'.' + patchextension)[stat.ST_MTIME]
96
96
97 def writeinfo(self, info):
97 def writeinfo(self, info):
98 scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info)
98 scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info)
99
99
100 def hasinfo(self):
100 def hasinfo(self):
101 return self.vfs.exists(self.name + b'.shelve')
101 return self.vfs.exists(self.name + b'.shelve')
102
102
103 def readinfo(self):
103 def readinfo(self):
104 return scmutil.simplekeyvaluefile(
104 return scmutil.simplekeyvaluefile(
105 self.vfs, self.name + b'.shelve'
105 self.vfs, self.name + b'.shelve'
106 ).read()
106 ).read()
107
107
108 def writebundle(self, bases, node):
108 def writebundle(self, bases, node):
109 cgversion = changegroup.safeversion(self.repo)
109 cgversion = changegroup.safeversion(self.repo)
110 if cgversion == b'01':
110 if cgversion == b'01':
111 btype = b'HG10BZ'
111 btype = b'HG10BZ'
112 compression = None
112 compression = None
113 else:
113 else:
114 btype = b'HG20'
114 btype = b'HG20'
115 compression = b'BZ'
115 compression = b'BZ'
116
116
117 repo = self.repo.unfiltered()
117 repo = self.repo.unfiltered()
118
118
119 outgoing = discovery.outgoing(
119 outgoing = discovery.outgoing(
120 repo, missingroots=bases, ancestorsof=[node]
120 repo, missingroots=bases, ancestorsof=[node]
121 )
121 )
122 cg = changegroup.makechangegroup(repo, outgoing, cgversion, b'shelve')
122 cg = changegroup.makechangegroup(repo, outgoing, cgversion, b'shelve')
123
123
124 bundle_filename = self.vfs.join(self.name + b'.hg')
124 bundle_filename = self.vfs.join(self.name + b'.hg')
125 bundle2.writebundle(
125 bundle2.writebundle(
126 self.repo.ui,
126 self.repo.ui,
127 cg,
127 cg,
128 bundle_filename,
128 bundle_filename,
129 btype,
129 btype,
130 self.vfs,
130 self.vfs,
131 compression=compression,
131 compression=compression,
132 )
132 )
133
133
134 def applybundle(self, tr):
134 def applybundle(self, tr):
135 filename = self.name + b'.hg'
135 filename = self.name + b'.hg'
136 fp = self.vfs(filename)
136 fp = self.vfs(filename)
137 try:
137 try:
138 targetphase = phases.internal
138 targetphase = phases.internal
139 if not phases.supportinternal(self.repo):
139 if not phases.supportinternal(self.repo):
140 targetphase = phases.secret
140 targetphase = phases.secret
141 gen = exchange.readbundle(self.repo.ui, fp, filename, self.vfs)
141 gen = exchange.readbundle(self.repo.ui, fp, filename, self.vfs)
142 pretip = self.repo[b'tip']
142 pretip = self.repo[b'tip']
143 bundle2.applybundle(
143 bundle2.applybundle(
144 self.repo,
144 self.repo,
145 gen,
145 gen,
146 tr,
146 tr,
147 source=b'unshelve',
147 source=b'unshelve',
148 url=b'bundle:' + self.vfs.join(filename),
148 url=b'bundle:' + self.vfs.join(filename),
149 targetphase=targetphase,
149 targetphase=targetphase,
150 )
150 )
151 shelvectx = self.repo[b'tip']
151 shelvectx = self.repo[b'tip']
152 if pretip == shelvectx:
152 if pretip == shelvectx:
153 shelverev = tr.changes[b'revduplicates'][-1]
153 shelverev = tr.changes[b'revduplicates'][-1]
154 shelvectx = self.repo[shelverev]
154 shelvectx = self.repo[shelverev]
155 return shelvectx
155 return shelvectx
156 finally:
156 finally:
157 fp.close()
157 fp.close()
158
158
159 def open_patch(self, mode=b'rb'):
159 def open_patch(self, mode=b'rb'):
160 return self.vfs(self.name + b'.patch', mode)
160 return self.vfs(self.name + b'.patch', mode)
161
161
162 def _backupfilename(self, filename):
162 def _backupfilename(self, filename):
163 def gennames(base):
163 def gennames(base):
164 yield base
164 yield base
165 base, ext = base.rsplit(b'.', 1)
165 base, ext = base.rsplit(b'.', 1)
166 for i in itertools.count(1):
166 for i in itertools.count(1):
167 yield b'%s-%d.%s' % (base, i, ext)
167 yield b'%s-%d.%s' % (base, i, ext)
168
168
169 for n in gennames(filename):
169 for n in gennames(filename):
170 if not self.backupvfs.exists(n):
170 if not self.backupvfs.exists(n):
171 return self.backupvfs.join(n)
171 return self.backupvfs.join(n)
172
172
173 def movetobackup(self):
173 def movetobackup(self):
174 if not self.backupvfs.isdir():
174 if not self.backupvfs.isdir():
175 self.backupvfs.makedir()
175 self.backupvfs.makedir()
176 for suffix in shelvefileextensions:
176 for suffix in shelvefileextensions:
177 filename = self.name + b'.' + suffix
177 filename = self.name + b'.' + suffix
178 if self.vfs.exists(filename):
178 if self.vfs.exists(filename):
179 util.rename(
179 util.rename(
180 self.vfs.join(filename), self._backupfilename(filename)
180 self.vfs.join(filename), self._backupfilename(filename)
181 )
181 )
182
182
183
183
184 class shelvedstate(object):
184 class shelvedstate(object):
185 """Handle persistence during unshelving operations.
185 """Handle persistence during unshelving operations.
186
186
187 Handles saving and restoring a shelved state. Ensures that different
187 Handles saving and restoring a shelved state. Ensures that different
188 versions of a shelved state are possible and handles them appropriately.
188 versions of a shelved state are possible and handles them appropriately.
189 """
189 """
190
190
191 _version = 2
191 _version = 2
192 _filename = b'shelvedstate'
192 _filename = b'shelvedstate'
193 _keep = b'keep'
193 _keep = b'keep'
194 _nokeep = b'nokeep'
194 _nokeep = b'nokeep'
195 # colon is essential to differentiate from a real bookmark name
195 # colon is essential to differentiate from a real bookmark name
196 _noactivebook = b':no-active-bookmark'
196 _noactivebook = b':no-active-bookmark'
197 _interactive = b'interactive'
197 _interactive = b'interactive'
198
198
199 @classmethod
199 @classmethod
200 def _verifyandtransform(cls, d):
200 def _verifyandtransform(cls, d):
201 """Some basic shelvestate syntactic verification and transformation"""
201 """Some basic shelvestate syntactic verification and transformation"""
202 try:
202 try:
203 d[b'originalwctx'] = bin(d[b'originalwctx'])
203 d[b'originalwctx'] = bin(d[b'originalwctx'])
204 d[b'pendingctx'] = bin(d[b'pendingctx'])
204 d[b'pendingctx'] = bin(d[b'pendingctx'])
205 d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ')]
205 d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ')]
206 d[b'nodestoremove'] = [
206 d[b'nodestoremove'] = [
207 bin(h) for h in d[b'nodestoremove'].split(b' ')
207 bin(h) for h in d[b'nodestoremove'].split(b' ')
208 ]
208 ]
209 except (ValueError, TypeError, KeyError) as err:
209 except (ValueError, TypeError, KeyError) as err:
210 raise error.CorruptedState(pycompat.bytestr(err))
210 raise error.CorruptedState(pycompat.bytestr(err))
211
211
212 @classmethod
212 @classmethod
213 def _getversion(cls, repo):
213 def _getversion(cls, repo):
214 """Read version information from shelvestate file"""
214 """Read version information from shelvestate file"""
215 fp = repo.vfs(cls._filename)
215 fp = repo.vfs(cls._filename)
216 try:
216 try:
217 version = int(fp.readline().strip())
217 version = int(fp.readline().strip())
218 except ValueError as err:
218 except ValueError as err:
219 raise error.CorruptedState(pycompat.bytestr(err))
219 raise error.CorruptedState(pycompat.bytestr(err))
220 finally:
220 finally:
221 fp.close()
221 fp.close()
222 return version
222 return version
223
223
224 @classmethod
224 @classmethod
225 def _readold(cls, repo):
225 def _readold(cls, repo):
226 """Read the old position-based version of a shelvestate file"""
226 """Read the old position-based version of a shelvestate file"""
227 # Order is important, because old shelvestate file uses it
227 # Order is important, because old shelvestate file uses it
228 # to detemine values of fields (i.g. name is on the second line,
228 # to detemine values of fields (i.g. name is on the second line,
229 # originalwctx is on the third and so forth). Please do not change.
229 # originalwctx is on the third and so forth). Please do not change.
230 keys = [
230 keys = [
231 b'version',
231 b'version',
232 b'name',
232 b'name',
233 b'originalwctx',
233 b'originalwctx',
234 b'pendingctx',
234 b'pendingctx',
235 b'parents',
235 b'parents',
236 b'nodestoremove',
236 b'nodestoremove',
237 b'branchtorestore',
237 b'branchtorestore',
238 b'keep',
238 b'keep',
239 b'activebook',
239 b'activebook',
240 ]
240 ]
241 # this is executed only seldomly, so it is not a big deal
241 # this is executed only seldomly, so it is not a big deal
242 # that we open this file twice
242 # that we open this file twice
243 fp = repo.vfs(cls._filename)
243 fp = repo.vfs(cls._filename)
244 d = {}
244 d = {}
245 try:
245 try:
246 for key in keys:
246 for key in keys:
247 d[key] = fp.readline().strip()
247 d[key] = fp.readline().strip()
248 finally:
248 finally:
249 fp.close()
249 fp.close()
250 return d
250 return d
251
251
252 @classmethod
252 @classmethod
253 def load(cls, repo):
253 def load(cls, repo):
254 version = cls._getversion(repo)
254 version = cls._getversion(repo)
255 if version < cls._version:
255 if version < cls._version:
256 d = cls._readold(repo)
256 d = cls._readold(repo)
257 elif version == cls._version:
257 elif version == cls._version:
258 d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename).read(
258 d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename).read(
259 firstlinenonkeyval=True
259 firstlinenonkeyval=True
260 )
260 )
261 else:
261 else:
262 raise error.Abort(
262 raise error.Abort(
263 _(
263 _(
264 b'this version of shelve is incompatible '
264 b'this version of shelve is incompatible '
265 b'with the version used in this repo'
265 b'with the version used in this repo'
266 )
266 )
267 )
267 )
268
268
269 cls._verifyandtransform(d)
269 cls._verifyandtransform(d)
270 try:
270 try:
271 obj = cls()
271 obj = cls()
272 obj.name = d[b'name']
272 obj.name = d[b'name']
273 obj.wctx = repo[d[b'originalwctx']]
273 obj.wctx = repo[d[b'originalwctx']]
274 obj.pendingctx = repo[d[b'pendingctx']]
274 obj.pendingctx = repo[d[b'pendingctx']]
275 obj.parents = d[b'parents']
275 obj.parents = d[b'parents']
276 obj.nodestoremove = d[b'nodestoremove']
276 obj.nodestoremove = d[b'nodestoremove']
277 obj.branchtorestore = d.get(b'branchtorestore', b'')
277 obj.branchtorestore = d.get(b'branchtorestore', b'')
278 obj.keep = d.get(b'keep') == cls._keep
278 obj.keep = d.get(b'keep') == cls._keep
279 obj.activebookmark = b''
279 obj.activebookmark = b''
280 if d.get(b'activebook', b'') != cls._noactivebook:
280 if d.get(b'activebook', b'') != cls._noactivebook:
281 obj.activebookmark = d.get(b'activebook', b'')
281 obj.activebookmark = d.get(b'activebook', b'')
282 obj.interactive = d.get(b'interactive') == cls._interactive
282 obj.interactive = d.get(b'interactive') == cls._interactive
283 except (error.RepoLookupError, KeyError) as err:
283 except (error.RepoLookupError, KeyError) as err:
284 raise error.CorruptedState(pycompat.bytestr(err))
284 raise error.CorruptedState(pycompat.bytestr(err))
285
285
286 return obj
286 return obj
287
287
288 @classmethod
288 @classmethod
289 def save(
289 def save(
290 cls,
290 cls,
291 repo,
291 repo,
292 name,
292 name,
293 originalwctx,
293 originalwctx,
294 pendingctx,
294 pendingctx,
295 nodestoremove,
295 nodestoremove,
296 branchtorestore,
296 branchtorestore,
297 keep=False,
297 keep=False,
298 activebook=b'',
298 activebook=b'',
299 interactive=False,
299 interactive=False,
300 ):
300 ):
301 info = {
301 info = {
302 b"name": name,
302 b"name": name,
303 b"originalwctx": hex(originalwctx.node()),
303 b"originalwctx": hex(originalwctx.node()),
304 b"pendingctx": hex(pendingctx.node()),
304 b"pendingctx": hex(pendingctx.node()),
305 b"parents": b' '.join([hex(p) for p in repo.dirstate.parents()]),
305 b"parents": b' '.join([hex(p) for p in repo.dirstate.parents()]),
306 b"nodestoremove": b' '.join([hex(n) for n in nodestoremove]),
306 b"nodestoremove": b' '.join([hex(n) for n in nodestoremove]),
307 b"branchtorestore": branchtorestore,
307 b"branchtorestore": branchtorestore,
308 b"keep": cls._keep if keep else cls._nokeep,
308 b"keep": cls._keep if keep else cls._nokeep,
309 b"activebook": activebook or cls._noactivebook,
309 b"activebook": activebook or cls._noactivebook,
310 }
310 }
311 if interactive:
311 if interactive:
312 info[b'interactive'] = cls._interactive
312 info[b'interactive'] = cls._interactive
313 scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write(
313 scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write(
314 info, firstline=(b"%d" % cls._version)
314 info, firstline=(b"%d" % cls._version)
315 )
315 )
316
316
317 @classmethod
317 @classmethod
318 def clear(cls, repo):
318 def clear(cls, repo):
319 repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
319 repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
320
320
321
321
322 def cleanupoldbackups(repo):
322 def cleanupoldbackups(repo):
323 vfs = vfsmod.vfs(repo.vfs.join(backupdir))
323 vfs = vfsmod.vfs(repo.vfs.join(backupdir))
324 maxbackups = repo.ui.configint(b'shelve', b'maxbackups')
324 maxbackups = repo.ui.configint(b'shelve', b'maxbackups')
325 hgfiles = [f for f in vfs.listdir() if f.endswith(b'.' + patchextension)]
325 hgfiles = [f for f in vfs.listdir() if f.endswith(b'.' + patchextension)]
326 hgfiles = sorted([(vfs.stat(f)[stat.ST_MTIME], f) for f in hgfiles])
326 hgfiles = sorted([(vfs.stat(f)[stat.ST_MTIME], f) for f in hgfiles])
327 if maxbackups > 0 and maxbackups < len(hgfiles):
327 if maxbackups > 0 and maxbackups < len(hgfiles):
328 bordermtime = hgfiles[-maxbackups][0]
328 bordermtime = hgfiles[-maxbackups][0]
329 else:
329 else:
330 bordermtime = None
330 bordermtime = None
331 for mtime, f in hgfiles[: len(hgfiles) - maxbackups]:
331 for mtime, f in hgfiles[: len(hgfiles) - maxbackups]:
332 if mtime == bordermtime:
332 if mtime == bordermtime:
333 # keep it, because timestamp can't decide exact order of backups
333 # keep it, because timestamp can't decide exact order of backups
334 continue
334 continue
335 base = f[: -(1 + len(patchextension))]
335 base = f[: -(1 + len(patchextension))]
336 for ext in shelvefileextensions:
336 for ext in shelvefileextensions:
337 vfs.tryunlink(base + b'.' + ext)
337 vfs.tryunlink(base + b'.' + ext)
338
338
339
339
340 def _backupactivebookmark(repo):
340 def _backupactivebookmark(repo):
341 activebookmark = repo._activebookmark
341 activebookmark = repo._activebookmark
342 if activebookmark:
342 if activebookmark:
343 bookmarks.deactivate(repo)
343 bookmarks.deactivate(repo)
344 return activebookmark
344 return activebookmark
345
345
346
346
347 def _restoreactivebookmark(repo, mark):
347 def _restoreactivebookmark(repo, mark):
348 if mark:
348 if mark:
349 bookmarks.activate(repo, mark)
349 bookmarks.activate(repo, mark)
350
350
351
351
352 def _aborttransaction(repo, tr):
352 def _aborttransaction(repo, tr):
353 """Abort current transaction for shelve/unshelve, but keep dirstate"""
353 """Abort current transaction for shelve/unshelve, but keep dirstate"""
354 dirstatebackupname = b'dirstate.shelve'
354 dirstatebackupname = b'dirstate.shelve'
355 repo.dirstate.savebackup(tr, dirstatebackupname)
355 repo.dirstate.savebackup(tr, dirstatebackupname)
356 tr.abort()
356 tr.abort()
357 repo.dirstate.restorebackup(None, dirstatebackupname)
357 repo.dirstate.restorebackup(None, dirstatebackupname)
358
358
359
359
360 def getshelvename(repo, parent, opts):
360 def getshelvename(repo, parent, opts):
361 """Decide on the name this shelve is going to have"""
361 """Decide on the name this shelve is going to have"""
362
362
363 def gennames():
363 def gennames():
364 yield label
364 yield label
365 for i in itertools.count(1):
365 for i in itertools.count(1):
366 yield b'%s-%02d' % (label, i)
366 yield b'%s-%02d' % (label, i)
367
367
368 name = opts.get(b'name')
368 name = opts.get(b'name')
369 label = repo._activebookmark or parent.branch() or b'default'
369 label = repo._activebookmark or parent.branch() or b'default'
370 # slashes aren't allowed in filenames, therefore we rename it
370 # slashes aren't allowed in filenames, therefore we rename it
371 label = label.replace(b'/', b'_')
371 label = label.replace(b'/', b'_')
372 label = label.replace(b'\\', b'_')
372 label = label.replace(b'\\', b'_')
373 # filenames must not start with '.' as it should not be hidden
373 # filenames must not start with '.' as it should not be hidden
374 if label.startswith(b'.'):
374 if label.startswith(b'.'):
375 label = label.replace(b'.', b'_', 1)
375 label = label.replace(b'.', b'_', 1)
376
376
377 if name:
377 if name:
378 if Shelf(repo, name).exists():
378 if Shelf(repo, name).exists():
379 e = _(b"a shelved change named '%s' already exists") % name
379 e = _(b"a shelved change named '%s' already exists") % name
380 raise error.Abort(e)
380 raise error.Abort(e)
381
381
382 # ensure we are not creating a subdirectory or a hidden file
382 # ensure we are not creating a subdirectory or a hidden file
383 if b'/' in name or b'\\' in name:
383 if b'/' in name or b'\\' in name:
384 raise error.Abort(
384 raise error.Abort(
385 _(b'shelved change names can not contain slashes')
385 _(b'shelved change names can not contain slashes')
386 )
386 )
387 if name.startswith(b'.'):
387 if name.startswith(b'.'):
388 raise error.Abort(_(b"shelved change names can not start with '.'"))
388 raise error.Abort(_(b"shelved change names can not start with '.'"))
389
389
390 else:
390 else:
391 for n in gennames():
391 for n in gennames():
392 if not Shelf(repo, n).exists():
392 if not Shelf(repo, n).exists():
393 name = n
393 name = n
394 break
394 break
395
395
396 return name
396 return name
397
397
398
398
399 def mutableancestors(ctx):
399 def mutableancestors(ctx):
400 """return all mutable ancestors for ctx (included)
400 """return all mutable ancestors for ctx (included)
401
401
402 Much faster than the revset ancestors(ctx) & draft()"""
402 Much faster than the revset ancestors(ctx) & draft()"""
403 seen = {nullrev}
403 seen = {nullrev}
404 visit = collections.deque()
404 visit = collections.deque()
405 visit.append(ctx)
405 visit.append(ctx)
406 while visit:
406 while visit:
407 ctx = visit.popleft()
407 ctx = visit.popleft()
408 yield ctx.node()
408 yield ctx.node()
409 for parent in ctx.parents():
409 for parent in ctx.parents():
410 rev = parent.rev()
410 rev = parent.rev()
411 if rev not in seen:
411 if rev not in seen:
412 seen.add(rev)
412 seen.add(rev)
413 if parent.mutable():
413 if parent.mutable():
414 visit.append(parent)
414 visit.append(parent)
415
415
416
416
417 def getcommitfunc(extra, interactive, editor=False):
417 def getcommitfunc(extra, interactive, editor=False):
418 def commitfunc(ui, repo, message, match, opts):
418 def commitfunc(ui, repo, message, match, opts):
419 hasmq = util.safehasattr(repo, b'mq')
419 hasmq = util.safehasattr(repo, b'mq')
420 if hasmq:
420 if hasmq:
421 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
421 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
422
422
423 targetphase = phases.internal
423 targetphase = phases.internal
424 if not phases.supportinternal(repo):
424 if not phases.supportinternal(repo):
425 targetphase = phases.secret
425 targetphase = phases.secret
426 overrides = {(b'phases', b'new-commit'): targetphase}
426 overrides = {(b'phases', b'new-commit'): targetphase}
427 try:
427 try:
428 editor_ = False
428 editor_ = False
429 if editor:
429 if editor:
430 editor_ = cmdutil.getcommiteditor(
430 editor_ = cmdutil.getcommiteditor(
431 editform=b'shelve.shelve', **pycompat.strkwargs(opts)
431 editform=b'shelve.shelve', **pycompat.strkwargs(opts)
432 )
432 )
433 with repo.ui.configoverride(overrides):
433 with repo.ui.configoverride(overrides):
434 return repo.commit(
434 return repo.commit(
435 message,
435 message,
436 shelveuser,
436 shelveuser,
437 opts.get(b'date'),
437 opts.get(b'date'),
438 match,
438 match,
439 editor=editor_,
439 editor=editor_,
440 extra=extra,
440 extra=extra,
441 )
441 )
442 finally:
442 finally:
443 if hasmq:
443 if hasmq:
444 repo.mq.checkapplied = saved
444 repo.mq.checkapplied = saved
445
445
446 def interactivecommitfunc(ui, repo, *pats, **opts):
446 def interactivecommitfunc(ui, repo, *pats, **opts):
447 opts = pycompat.byteskwargs(opts)
447 opts = pycompat.byteskwargs(opts)
448 match = scmutil.match(repo[b'.'], pats, {})
448 match = scmutil.match(repo[b'.'], pats, {})
449 message = opts[b'message']
449 message = opts[b'message']
450 return commitfunc(ui, repo, message, match, opts)
450 return commitfunc(ui, repo, message, match, opts)
451
451
452 return interactivecommitfunc if interactive else commitfunc
452 return interactivecommitfunc if interactive else commitfunc
453
453
454
454
455 def _nothingtoshelvemessaging(ui, repo, pats, opts):
455 def _nothingtoshelvemessaging(ui, repo, pats, opts):
456 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
456 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
457 if stat.deleted:
457 if stat.deleted:
458 ui.status(
458 ui.status(
459 _(b"nothing changed (%d missing files, see 'hg status')\n")
459 _(b"nothing changed (%d missing files, see 'hg status')\n")
460 % len(stat.deleted)
460 % len(stat.deleted)
461 )
461 )
462 else:
462 else:
463 ui.status(_(b"nothing changed\n"))
463 ui.status(_(b"nothing changed\n"))
464
464
465
465
466 def _shelvecreatedcommit(repo, node, name, match):
466 def _shelvecreatedcommit(repo, node, name, match):
467 info = {b'node': hex(node)}
467 info = {b'node': hex(node)}
468 shelf = Shelf(repo, name)
468 shelf = Shelf(repo, name)
469 shelf.writeinfo(info)
469 shelf.writeinfo(info)
470 bases = list(mutableancestors(repo[node]))
470 bases = list(mutableancestors(repo[node]))
471 shelf.writebundle(bases, node)
471 shelf.writebundle(bases, node)
472 with shelf.open_patch(b'wb') as fp:
472 with shelf.open_patch(b'wb') as fp:
473 cmdutil.exportfile(
473 cmdutil.exportfile(
474 repo, [node], fp, opts=mdiff.diffopts(git=True), match=match
474 repo, [node], fp, opts=mdiff.diffopts(git=True), match=match
475 )
475 )
476
476
477
477
478 def _includeunknownfiles(repo, pats, opts, extra):
478 def _includeunknownfiles(repo, pats, opts, extra):
479 s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True)
479 s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True)
480 if s.unknown:
480 if s.unknown:
481 extra[b'shelve_unknown'] = b'\0'.join(s.unknown)
481 extra[b'shelve_unknown'] = b'\0'.join(s.unknown)
482 repo[None].add(s.unknown)
482 repo[None].add(s.unknown)
483
483
484
484
485 def _finishshelve(repo, tr):
485 def _finishshelve(repo, tr):
486 if phases.supportinternal(repo):
486 if phases.supportinternal(repo):
487 tr.close()
487 tr.close()
488 else:
488 else:
489 _aborttransaction(repo, tr)
489 _aborttransaction(repo, tr)
490
490
491
491
492 def createcmd(ui, repo, pats, opts):
492 def createcmd(ui, repo, pats, opts):
493 """subcommand that creates a new shelve"""
493 """subcommand that creates a new shelve"""
494 with repo.wlock():
494 with repo.wlock():
495 cmdutil.checkunfinished(repo)
495 cmdutil.checkunfinished(repo)
496 return _docreatecmd(ui, repo, pats, opts)
496 return _docreatecmd(ui, repo, pats, opts)
497
497
498
498
499 def _docreatecmd(ui, repo, pats, opts):
499 def _docreatecmd(ui, repo, pats, opts):
500 wctx = repo[None]
500 wctx = repo[None]
501 parents = wctx.parents()
501 parents = wctx.parents()
502 parent = parents[0]
502 parent = parents[0]
503 origbranch = wctx.branch()
503 origbranch = wctx.branch()
504
504
505 if parent.node() != nullid:
505 if parent.node() != nullid:
506 desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0]
506 desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0]
507 else:
507 else:
508 desc = b'(changes in empty repository)'
508 desc = b'(changes in empty repository)'
509
509
510 if not opts.get(b'message'):
510 if not opts.get(b'message'):
511 opts[b'message'] = desc
511 opts[b'message'] = desc
512
512
513 lock = tr = activebookmark = None
513 lock = tr = activebookmark = None
514 try:
514 try:
515 lock = repo.lock()
515 lock = repo.lock()
516
516
517 # use an uncommitted transaction to generate the bundle to avoid
517 # use an uncommitted transaction to generate the bundle to avoid
518 # pull races. ensure we don't print the abort message to stderr.
518 # pull races. ensure we don't print the abort message to stderr.
519 tr = repo.transaction(b'shelve', report=lambda x: None)
519 tr = repo.transaction(b'shelve', report=lambda x: None)
520
520
521 interactive = opts.get(b'interactive', False)
521 interactive = opts.get(b'interactive', False)
522 includeunknown = opts.get(b'unknown', False) and not opts.get(
522 includeunknown = opts.get(b'unknown', False) and not opts.get(
523 b'addremove', False
523 b'addremove', False
524 )
524 )
525
525
526 name = getshelvename(repo, parent, opts)
526 name = getshelvename(repo, parent, opts)
527 activebookmark = _backupactivebookmark(repo)
527 activebookmark = _backupactivebookmark(repo)
528 extra = {b'internal': b'shelve'}
528 extra = {b'internal': b'shelve'}
529 if includeunknown:
529 if includeunknown:
530 _includeunknownfiles(repo, pats, opts, extra)
530 _includeunknownfiles(repo, pats, opts, extra)
531
531
532 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
532 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
533 # In non-bare shelve we don't store newly created branch
533 # In non-bare shelve we don't store newly created branch
534 # at bundled commit
534 # at bundled commit
535 repo.dirstate.setbranch(repo[b'.'].branch())
535 repo.dirstate.setbranch(repo[b'.'].branch())
536
536
537 commitfunc = getcommitfunc(extra, interactive, editor=True)
537 commitfunc = getcommitfunc(extra, interactive, editor=True)
538 if not interactive:
538 if not interactive:
539 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
539 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
540 else:
540 else:
541 node = cmdutil.dorecord(
541 node = cmdutil.dorecord(
542 ui,
542 ui,
543 repo,
543 repo,
544 commitfunc,
544 commitfunc,
545 None,
545 None,
546 False,
546 False,
547 cmdutil.recordfilter,
547 cmdutil.recordfilter,
548 *pats,
548 *pats,
549 **pycompat.strkwargs(opts)
549 **pycompat.strkwargs(opts)
550 )
550 )
551 if not node:
551 if not node:
552 _nothingtoshelvemessaging(ui, repo, pats, opts)
552 _nothingtoshelvemessaging(ui, repo, pats, opts)
553 return 1
553 return 1
554
554
555 # Create a matcher so that prefetch doesn't attempt to fetch
555 # Create a matcher so that prefetch doesn't attempt to fetch
556 # the entire repository pointlessly, and as an optimisation
556 # the entire repository pointlessly, and as an optimisation
557 # for movedirstate, if needed.
557 # for movedirstate, if needed.
558 match = scmutil.matchfiles(repo, repo[node].files())
558 match = scmutil.matchfiles(repo, repo[node].files())
559 _shelvecreatedcommit(repo, node, name, match)
559 _shelvecreatedcommit(repo, node, name, match)
560
560
561 ui.status(_(b'shelved as %s\n') % name)
561 ui.status(_(b'shelved as %s\n') % name)
562 if opts[b'keep']:
562 if opts[b'keep']:
563 with repo.dirstate.parentchange():
563 with repo.dirstate.parentchange():
564 scmutil.movedirstate(repo, parent, match)
564 scmutil.movedirstate(repo, parent, match)
565 else:
565 else:
566 hg.update(repo, parent.node())
566 hg.update(repo, parent.node())
567 ms = mergestatemod.mergestate.read(repo)
567 ms = mergestatemod.mergestate.read(repo)
568 if not ms.unresolvedcount():
568 if not ms.unresolvedcount():
569 ms.reset()
569 ms.reset()
570
570
571 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
571 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
572 repo.dirstate.setbranch(origbranch)
572 repo.dirstate.setbranch(origbranch)
573
573
574 _finishshelve(repo, tr)
574 _finishshelve(repo, tr)
575 finally:
575 finally:
576 _restoreactivebookmark(repo, activebookmark)
576 _restoreactivebookmark(repo, activebookmark)
577 lockmod.release(tr, lock)
577 lockmod.release(tr, lock)
578
578
579
579
580 def _isbareshelve(pats, opts):
580 def _isbareshelve(pats, opts):
581 return (
581 return (
582 not pats
582 not pats
583 and not opts.get(b'interactive', False)
583 and not opts.get(b'interactive', False)
584 and not opts.get(b'include', False)
584 and not opts.get(b'include', False)
585 and not opts.get(b'exclude', False)
585 and not opts.get(b'exclude', False)
586 )
586 )
587
587
588
588
589 def _iswctxonnewbranch(repo):
589 def _iswctxonnewbranch(repo):
590 return repo[None].branch() != repo[b'.'].branch()
590 return repo[None].branch() != repo[b'.'].branch()
591
591
592
592
593 def cleanupcmd(ui, repo):
593 def cleanupcmd(ui, repo):
594 """subcommand that deletes all shelves"""
594 """subcommand that deletes all shelves"""
595
595
596 with repo.wlock():
596 with repo.wlock():
597 for _mtime, name in listshelves(repo):
597 for _mtime, name in listshelves(repo):
598 Shelf(repo, name).movetobackup()
598 Shelf(repo, name).movetobackup()
599 cleanupoldbackups(repo)
599 cleanupoldbackups(repo)
600
600
601
601
602 def deletecmd(ui, repo, pats):
602 def deletecmd(ui, repo, pats):
603 """subcommand that deletes a specific shelve"""
603 """subcommand that deletes a specific shelve"""
604 if not pats:
604 if not pats:
605 raise error.InputError(_(b'no shelved changes specified!'))
605 raise error.InputError(_(b'no shelved changes specified!'))
606 with repo.wlock():
606 with repo.wlock():
607 for name in pats:
607 for name in pats:
608 shelf = Shelf(repo, name)
608 shelf = Shelf(repo, name)
609 if not shelf.exists():
609 if not shelf.exists():
610 raise error.InputError(
610 raise error.InputError(
611 _(b"shelved change '%s' not found") % name
611 _(b"shelved change '%s' not found") % name
612 )
612 )
613 shelf.movetobackup()
613 shelf.movetobackup()
614 cleanupoldbackups(repo)
614 cleanupoldbackups(repo)
615
615
616
616
617 def listshelves(repo):
617 def listshelves(repo):
618 """return all shelves in repo as list of (time, name)"""
618 """return all shelves in repo as list of (time, name)"""
619 try:
619 try:
620 names = repo.vfs.readdir(shelvedir)
620 names = repo.vfs.listdir(shelvedir)
621 except OSError as err:
621 except OSError as err:
622 if err.errno != errno.ENOENT:
622 if err.errno != errno.ENOENT:
623 raise
623 raise
624 return []
624 return []
625 info = []
625 info = []
626 seen = set()
626 seen = set()
627 for (filename, _type) in names:
627 for filename in names:
628 name = filename.rsplit(b'.', 1)[0]
628 name = filename.rsplit(b'.', 1)[0]
629 if name in seen:
629 if name in seen:
630 continue
630 continue
631 seen.add(name)
631 seen.add(name)
632 shelf = Shelf(repo, name)
632 shelf = Shelf(repo, name)
633 if not shelf.exists():
633 if not shelf.exists():
634 continue
634 continue
635 mtime = shelf.mtime()
635 mtime = shelf.mtime()
636 info.append((mtime, name))
636 info.append((mtime, name))
637 return sorted(info, reverse=True)
637 return sorted(info, reverse=True)
638
638
639
639
640 def listcmd(ui, repo, pats, opts):
640 def listcmd(ui, repo, pats, opts):
641 """subcommand that displays the list of shelves"""
641 """subcommand that displays the list of shelves"""
642 pats = set(pats)
642 pats = set(pats)
643 width = 80
643 width = 80
644 if not ui.plain():
644 if not ui.plain():
645 width = ui.termwidth()
645 width = ui.termwidth()
646 namelabel = b'shelve.newest'
646 namelabel = b'shelve.newest'
647 ui.pager(b'shelve')
647 ui.pager(b'shelve')
648 for mtime, name in listshelves(repo):
648 for mtime, name in listshelves(repo):
649 if pats and name not in pats:
649 if pats and name not in pats:
650 continue
650 continue
651 ui.write(name, label=namelabel)
651 ui.write(name, label=namelabel)
652 namelabel = b'shelve.name'
652 namelabel = b'shelve.name'
653 if ui.quiet:
653 if ui.quiet:
654 ui.write(b'\n')
654 ui.write(b'\n')
655 continue
655 continue
656 ui.write(b' ' * (16 - len(name)))
656 ui.write(b' ' * (16 - len(name)))
657 used = 16
657 used = 16
658 date = dateutil.makedate(mtime)
658 date = dateutil.makedate(mtime)
659 age = b'(%s)' % templatefilters.age(date, abbrev=True)
659 age = b'(%s)' % templatefilters.age(date, abbrev=True)
660 ui.write(age, label=b'shelve.age')
660 ui.write(age, label=b'shelve.age')
661 ui.write(b' ' * (12 - len(age)))
661 ui.write(b' ' * (12 - len(age)))
662 used += 12
662 used += 12
663 with Shelf(repo, name).open_patch() as fp:
663 with Shelf(repo, name).open_patch() as fp:
664 while True:
664 while True:
665 line = fp.readline()
665 line = fp.readline()
666 if not line:
666 if not line:
667 break
667 break
668 if not line.startswith(b'#'):
668 if not line.startswith(b'#'):
669 desc = line.rstrip()
669 desc = line.rstrip()
670 if ui.formatted():
670 if ui.formatted():
671 desc = stringutil.ellipsis(desc, width - used)
671 desc = stringutil.ellipsis(desc, width - used)
672 ui.write(desc)
672 ui.write(desc)
673 break
673 break
674 ui.write(b'\n')
674 ui.write(b'\n')
675 if not (opts[b'patch'] or opts[b'stat']):
675 if not (opts[b'patch'] or opts[b'stat']):
676 continue
676 continue
677 difflines = fp.readlines()
677 difflines = fp.readlines()
678 if opts[b'patch']:
678 if opts[b'patch']:
679 for chunk, label in patch.difflabel(iter, difflines):
679 for chunk, label in patch.difflabel(iter, difflines):
680 ui.write(chunk, label=label)
680 ui.write(chunk, label=label)
681 if opts[b'stat']:
681 if opts[b'stat']:
682 for chunk, label in patch.diffstatui(difflines, width=width):
682 for chunk, label in patch.diffstatui(difflines, width=width):
683 ui.write(chunk, label=label)
683 ui.write(chunk, label=label)
684
684
685
685
686 def patchcmds(ui, repo, pats, opts):
686 def patchcmds(ui, repo, pats, opts):
687 """subcommand that displays shelves"""
687 """subcommand that displays shelves"""
688 if len(pats) == 0:
688 if len(pats) == 0:
689 shelves = listshelves(repo)
689 shelves = listshelves(repo)
690 if not shelves:
690 if not shelves:
691 raise error.Abort(_(b"there are no shelves to show"))
691 raise error.Abort(_(b"there are no shelves to show"))
692 mtime, name = shelves[0]
692 mtime, name = shelves[0]
693 pats = [name]
693 pats = [name]
694
694
695 for shelfname in pats:
695 for shelfname in pats:
696 if not Shelf(repo, shelfname).exists():
696 if not Shelf(repo, shelfname).exists():
697 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
697 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
698
698
699 listcmd(ui, repo, pats, opts)
699 listcmd(ui, repo, pats, opts)
700
700
701
701
702 def checkparents(repo, state):
702 def checkparents(repo, state):
703 """check parent while resuming an unshelve"""
703 """check parent while resuming an unshelve"""
704 if state.parents != repo.dirstate.parents():
704 if state.parents != repo.dirstate.parents():
705 raise error.Abort(
705 raise error.Abort(
706 _(b'working directory parents do not match unshelve state')
706 _(b'working directory parents do not match unshelve state')
707 )
707 )
708
708
709
709
710 def _loadshelvedstate(ui, repo, opts):
710 def _loadshelvedstate(ui, repo, opts):
711 try:
711 try:
712 state = shelvedstate.load(repo)
712 state = shelvedstate.load(repo)
713 if opts.get(b'keep') is None:
713 if opts.get(b'keep') is None:
714 opts[b'keep'] = state.keep
714 opts[b'keep'] = state.keep
715 except IOError as err:
715 except IOError as err:
716 if err.errno != errno.ENOENT:
716 if err.errno != errno.ENOENT:
717 raise
717 raise
718 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
718 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
719 except error.CorruptedState as err:
719 except error.CorruptedState as err:
720 ui.debug(pycompat.bytestr(err) + b'\n')
720 ui.debug(pycompat.bytestr(err) + b'\n')
721 if opts.get(b'continue'):
721 if opts.get(b'continue'):
722 msg = _(b'corrupted shelved state file')
722 msg = _(b'corrupted shelved state file')
723 hint = _(
723 hint = _(
724 b'please run hg unshelve --abort to abort unshelve '
724 b'please run hg unshelve --abort to abort unshelve '
725 b'operation'
725 b'operation'
726 )
726 )
727 raise error.Abort(msg, hint=hint)
727 raise error.Abort(msg, hint=hint)
728 elif opts.get(b'abort'):
728 elif opts.get(b'abort'):
729 shelvedstate.clear(repo)
729 shelvedstate.clear(repo)
730 raise error.Abort(
730 raise error.Abort(
731 _(
731 _(
732 b'could not read shelved state file, your '
732 b'could not read shelved state file, your '
733 b'working copy may be in an unexpected state\n'
733 b'working copy may be in an unexpected state\n'
734 b'please update to some commit\n'
734 b'please update to some commit\n'
735 )
735 )
736 )
736 )
737 return state
737 return state
738
738
739
739
740 def unshelveabort(ui, repo, state):
740 def unshelveabort(ui, repo, state):
741 """subcommand that abort an in-progress unshelve"""
741 """subcommand that abort an in-progress unshelve"""
742 with repo.lock():
742 with repo.lock():
743 try:
743 try:
744 checkparents(repo, state)
744 checkparents(repo, state)
745
745
746 merge.clean_update(state.pendingctx)
746 merge.clean_update(state.pendingctx)
747 if state.activebookmark and state.activebookmark in repo._bookmarks:
747 if state.activebookmark and state.activebookmark in repo._bookmarks:
748 bookmarks.activate(repo, state.activebookmark)
748 bookmarks.activate(repo, state.activebookmark)
749 mergefiles(ui, repo, state.wctx, state.pendingctx)
749 mergefiles(ui, repo, state.wctx, state.pendingctx)
750 if not phases.supportinternal(repo):
750 if not phases.supportinternal(repo):
751 repair.strip(
751 repair.strip(
752 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
752 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
753 )
753 )
754 finally:
754 finally:
755 shelvedstate.clear(repo)
755 shelvedstate.clear(repo)
756 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
756 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
757
757
758
758
759 def hgabortunshelve(ui, repo):
759 def hgabortunshelve(ui, repo):
760 """logic to abort unshelve using 'hg abort"""
760 """logic to abort unshelve using 'hg abort"""
761 with repo.wlock():
761 with repo.wlock():
762 state = _loadshelvedstate(ui, repo, {b'abort': True})
762 state = _loadshelvedstate(ui, repo, {b'abort': True})
763 return unshelveabort(ui, repo, state)
763 return unshelveabort(ui, repo, state)
764
764
765
765
766 def mergefiles(ui, repo, wctx, shelvectx):
766 def mergefiles(ui, repo, wctx, shelvectx):
767 """updates to wctx and merges the changes from shelvectx into the
767 """updates to wctx and merges the changes from shelvectx into the
768 dirstate."""
768 dirstate."""
769 with ui.configoverride({(b'ui', b'quiet'): True}):
769 with ui.configoverride({(b'ui', b'quiet'): True}):
770 hg.update(repo, wctx.node())
770 hg.update(repo, wctx.node())
771 ui.pushbuffer(True)
771 ui.pushbuffer(True)
772 cmdutil.revert(ui, repo, shelvectx)
772 cmdutil.revert(ui, repo, shelvectx)
773 ui.popbuffer()
773 ui.popbuffer()
774
774
775
775
776 def restorebranch(ui, repo, branchtorestore):
776 def restorebranch(ui, repo, branchtorestore):
777 if branchtorestore and branchtorestore != repo.dirstate.branch():
777 if branchtorestore and branchtorestore != repo.dirstate.branch():
778 repo.dirstate.setbranch(branchtorestore)
778 repo.dirstate.setbranch(branchtorestore)
779 ui.status(
779 ui.status(
780 _(b'marked working directory as branch %s\n') % branchtorestore
780 _(b'marked working directory as branch %s\n') % branchtorestore
781 )
781 )
782
782
783
783
784 def unshelvecleanup(ui, repo, name, opts):
784 def unshelvecleanup(ui, repo, name, opts):
785 """remove related files after an unshelve"""
785 """remove related files after an unshelve"""
786 if not opts.get(b'keep'):
786 if not opts.get(b'keep'):
787 Shelf(repo, name).movetobackup()
787 Shelf(repo, name).movetobackup()
788 cleanupoldbackups(repo)
788 cleanupoldbackups(repo)
789
789
790
790
791 def unshelvecontinue(ui, repo, state, opts):
791 def unshelvecontinue(ui, repo, state, opts):
792 """subcommand to continue an in-progress unshelve"""
792 """subcommand to continue an in-progress unshelve"""
793 # We're finishing off a merge. First parent is our original
793 # We're finishing off a merge. First parent is our original
794 # parent, second is the temporary "fake" commit we're unshelving.
794 # parent, second is the temporary "fake" commit we're unshelving.
795 interactive = state.interactive
795 interactive = state.interactive
796 basename = state.name
796 basename = state.name
797 with repo.lock():
797 with repo.lock():
798 checkparents(repo, state)
798 checkparents(repo, state)
799 ms = mergestatemod.mergestate.read(repo)
799 ms = mergestatemod.mergestate.read(repo)
800 if list(ms.unresolved()):
800 if list(ms.unresolved()):
801 raise error.Abort(
801 raise error.Abort(
802 _(b"unresolved conflicts, can't continue"),
802 _(b"unresolved conflicts, can't continue"),
803 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
803 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
804 )
804 )
805
805
806 shelvectx = repo[state.parents[1]]
806 shelvectx = repo[state.parents[1]]
807 pendingctx = state.pendingctx
807 pendingctx = state.pendingctx
808
808
809 with repo.dirstate.parentchange():
809 with repo.dirstate.parentchange():
810 repo.setparents(state.pendingctx.node(), nullid)
810 repo.setparents(state.pendingctx.node(), nullid)
811 repo.dirstate.write(repo.currenttransaction())
811 repo.dirstate.write(repo.currenttransaction())
812
812
813 targetphase = phases.internal
813 targetphase = phases.internal
814 if not phases.supportinternal(repo):
814 if not phases.supportinternal(repo):
815 targetphase = phases.secret
815 targetphase = phases.secret
816 overrides = {(b'phases', b'new-commit'): targetphase}
816 overrides = {(b'phases', b'new-commit'): targetphase}
817 with repo.ui.configoverride(overrides, b'unshelve'):
817 with repo.ui.configoverride(overrides, b'unshelve'):
818 with repo.dirstate.parentchange():
818 with repo.dirstate.parentchange():
819 repo.setparents(state.parents[0], nullid)
819 repo.setparents(state.parents[0], nullid)
820 newnode, ispartialunshelve = _createunshelvectx(
820 newnode, ispartialunshelve = _createunshelvectx(
821 ui, repo, shelvectx, basename, interactive, opts
821 ui, repo, shelvectx, basename, interactive, opts
822 )
822 )
823
823
824 if newnode is None:
824 if newnode is None:
825 shelvectx = state.pendingctx
825 shelvectx = state.pendingctx
826 msg = _(
826 msg = _(
827 b'note: unshelved changes already existed '
827 b'note: unshelved changes already existed '
828 b'in the working copy\n'
828 b'in the working copy\n'
829 )
829 )
830 ui.status(msg)
830 ui.status(msg)
831 else:
831 else:
832 # only strip the shelvectx if we produced one
832 # only strip the shelvectx if we produced one
833 state.nodestoremove.append(newnode)
833 state.nodestoremove.append(newnode)
834 shelvectx = repo[newnode]
834 shelvectx = repo[newnode]
835
835
836 merge.update(pendingctx)
836 merge.update(pendingctx)
837 mergefiles(ui, repo, state.wctx, shelvectx)
837 mergefiles(ui, repo, state.wctx, shelvectx)
838 restorebranch(ui, repo, state.branchtorestore)
838 restorebranch(ui, repo, state.branchtorestore)
839
839
840 if not phases.supportinternal(repo):
840 if not phases.supportinternal(repo):
841 repair.strip(
841 repair.strip(
842 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
842 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
843 )
843 )
844 shelvedstate.clear(repo)
844 shelvedstate.clear(repo)
845 if not ispartialunshelve:
845 if not ispartialunshelve:
846 unshelvecleanup(ui, repo, state.name, opts)
846 unshelvecleanup(ui, repo, state.name, opts)
847 _restoreactivebookmark(repo, state.activebookmark)
847 _restoreactivebookmark(repo, state.activebookmark)
848 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
848 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
849
849
850
850
851 def hgcontinueunshelve(ui, repo):
851 def hgcontinueunshelve(ui, repo):
852 """logic to resume unshelve using 'hg continue'"""
852 """logic to resume unshelve using 'hg continue'"""
853 with repo.wlock():
853 with repo.wlock():
854 state = _loadshelvedstate(ui, repo, {b'continue': True})
854 state = _loadshelvedstate(ui, repo, {b'continue': True})
855 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
855 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
856
856
857
857
858 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
858 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
859 """Temporarily commit working copy changes before moving unshelve commit"""
859 """Temporarily commit working copy changes before moving unshelve commit"""
860 # Store pending changes in a commit and remember added in case a shelve
860 # Store pending changes in a commit and remember added in case a shelve
861 # contains unknown files that are part of the pending change
861 # contains unknown files that are part of the pending change
862 s = repo.status()
862 s = repo.status()
863 addedbefore = frozenset(s.added)
863 addedbefore = frozenset(s.added)
864 if not (s.modified or s.added or s.removed):
864 if not (s.modified or s.added or s.removed):
865 return tmpwctx, addedbefore
865 return tmpwctx, addedbefore
866 ui.status(
866 ui.status(
867 _(
867 _(
868 b"temporarily committing pending changes "
868 b"temporarily committing pending changes "
869 b"(restore with 'hg unshelve --abort')\n"
869 b"(restore with 'hg unshelve --abort')\n"
870 )
870 )
871 )
871 )
872 extra = {b'internal': b'shelve'}
872 extra = {b'internal': b'shelve'}
873 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
873 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
874 tempopts = {}
874 tempopts = {}
875 tempopts[b'message'] = b"pending changes temporary commit"
875 tempopts[b'message'] = b"pending changes temporary commit"
876 tempopts[b'date'] = opts.get(b'date')
876 tempopts[b'date'] = opts.get(b'date')
877 with ui.configoverride({(b'ui', b'quiet'): True}):
877 with ui.configoverride({(b'ui', b'quiet'): True}):
878 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
878 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
879 tmpwctx = repo[node]
879 tmpwctx = repo[node]
880 return tmpwctx, addedbefore
880 return tmpwctx, addedbefore
881
881
882
882
883 def _unshelverestorecommit(ui, repo, tr, basename):
883 def _unshelverestorecommit(ui, repo, tr, basename):
884 """Recreate commit in the repository during the unshelve"""
884 """Recreate commit in the repository during the unshelve"""
885 repo = repo.unfiltered()
885 repo = repo.unfiltered()
886 node = None
886 node = None
887 shelf = Shelf(repo, basename)
887 shelf = Shelf(repo, basename)
888 if shelf.hasinfo():
888 if shelf.hasinfo():
889 node = shelf.readinfo()[b'node']
889 node = shelf.readinfo()[b'node']
890 if node is None or node not in repo:
890 if node is None or node not in repo:
891 with ui.configoverride({(b'ui', b'quiet'): True}):
891 with ui.configoverride({(b'ui', b'quiet'): True}):
892 shelvectx = shelf.applybundle(tr)
892 shelvectx = shelf.applybundle(tr)
893 # We might not strip the unbundled changeset, so we should keep track of
893 # We might not strip the unbundled changeset, so we should keep track of
894 # the unshelve node in case we need to reuse it (eg: unshelve --keep)
894 # the unshelve node in case we need to reuse it (eg: unshelve --keep)
895 if node is None:
895 if node is None:
896 info = {b'node': hex(shelvectx.node())}
896 info = {b'node': hex(shelvectx.node())}
897 shelf.writeinfo(info)
897 shelf.writeinfo(info)
898 else:
898 else:
899 shelvectx = repo[node]
899 shelvectx = repo[node]
900
900
901 return repo, shelvectx
901 return repo, shelvectx
902
902
903
903
904 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
904 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
905 """Handles the creation of unshelve commit and updates the shelve if it
905 """Handles the creation of unshelve commit and updates the shelve if it
906 was partially unshelved.
906 was partially unshelved.
907
907
908 If interactive is:
908 If interactive is:
909
909
910 * False: Commits all the changes in the working directory.
910 * False: Commits all the changes in the working directory.
911 * True: Prompts the user to select changes to unshelve and commit them.
911 * True: Prompts the user to select changes to unshelve and commit them.
912 Update the shelve with remaining changes.
912 Update the shelve with remaining changes.
913
913
914 Returns the node of the new commit formed and a bool indicating whether
914 Returns the node of the new commit formed and a bool indicating whether
915 the shelve was partially unshelved.Creates a commit ctx to unshelve
915 the shelve was partially unshelved.Creates a commit ctx to unshelve
916 interactively or non-interactively.
916 interactively or non-interactively.
917
917
918 The user might want to unshelve certain changes only from the stored
918 The user might want to unshelve certain changes only from the stored
919 shelve in interactive. So, we would create two commits. One with requested
919 shelve in interactive. So, we would create two commits. One with requested
920 changes to unshelve at that time and the latter is shelved for future.
920 changes to unshelve at that time and the latter is shelved for future.
921
921
922 Here, we return both the newnode which is created interactively and a
922 Here, we return both the newnode which is created interactively and a
923 bool to know whether the shelve is partly done or completely done.
923 bool to know whether the shelve is partly done or completely done.
924 """
924 """
925 opts[b'message'] = shelvectx.description()
925 opts[b'message'] = shelvectx.description()
926 opts[b'interactive-unshelve'] = True
926 opts[b'interactive-unshelve'] = True
927 pats = []
927 pats = []
928 if not interactive:
928 if not interactive:
929 newnode = repo.commit(
929 newnode = repo.commit(
930 text=shelvectx.description(),
930 text=shelvectx.description(),
931 extra=shelvectx.extra(),
931 extra=shelvectx.extra(),
932 user=shelvectx.user(),
932 user=shelvectx.user(),
933 date=shelvectx.date(),
933 date=shelvectx.date(),
934 )
934 )
935 return newnode, False
935 return newnode, False
936
936
937 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True)
937 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True)
938 newnode = cmdutil.dorecord(
938 newnode = cmdutil.dorecord(
939 ui,
939 ui,
940 repo,
940 repo,
941 commitfunc,
941 commitfunc,
942 None,
942 None,
943 False,
943 False,
944 cmdutil.recordfilter,
944 cmdutil.recordfilter,
945 *pats,
945 *pats,
946 **pycompat.strkwargs(opts)
946 **pycompat.strkwargs(opts)
947 )
947 )
948 snode = repo.commit(
948 snode = repo.commit(
949 text=shelvectx.description(),
949 text=shelvectx.description(),
950 extra=shelvectx.extra(),
950 extra=shelvectx.extra(),
951 user=shelvectx.user(),
951 user=shelvectx.user(),
952 )
952 )
953 if snode:
953 if snode:
954 m = scmutil.matchfiles(repo, repo[snode].files())
954 m = scmutil.matchfiles(repo, repo[snode].files())
955 _shelvecreatedcommit(repo, snode, basename, m)
955 _shelvecreatedcommit(repo, snode, basename, m)
956
956
957 return newnode, bool(snode)
957 return newnode, bool(snode)
958
958
959
959
960 def _rebaserestoredcommit(
960 def _rebaserestoredcommit(
961 ui,
961 ui,
962 repo,
962 repo,
963 opts,
963 opts,
964 tr,
964 tr,
965 oldtiprev,
965 oldtiprev,
966 basename,
966 basename,
967 pctx,
967 pctx,
968 tmpwctx,
968 tmpwctx,
969 shelvectx,
969 shelvectx,
970 branchtorestore,
970 branchtorestore,
971 activebookmark,
971 activebookmark,
972 ):
972 ):
973 """Rebase restored commit from its original location to a destination"""
973 """Rebase restored commit from its original location to a destination"""
974 # If the shelve is not immediately on top of the commit
974 # If the shelve is not immediately on top of the commit
975 # we'll be merging with, rebase it to be on top.
975 # we'll be merging with, rebase it to be on top.
976 interactive = opts.get(b'interactive')
976 interactive = opts.get(b'interactive')
977 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
977 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
978 # We won't skip on interactive mode because, the user might want to
978 # We won't skip on interactive mode because, the user might want to
979 # unshelve certain changes only.
979 # unshelve certain changes only.
980 return shelvectx, False
980 return shelvectx, False
981
981
982 overrides = {
982 overrides = {
983 (b'ui', b'forcemerge'): opts.get(b'tool', b''),
983 (b'ui', b'forcemerge'): opts.get(b'tool', b''),
984 (b'phases', b'new-commit'): phases.secret,
984 (b'phases', b'new-commit'): phases.secret,
985 }
985 }
986 with repo.ui.configoverride(overrides, b'unshelve'):
986 with repo.ui.configoverride(overrides, b'unshelve'):
987 ui.status(_(b'rebasing shelved changes\n'))
987 ui.status(_(b'rebasing shelved changes\n'))
988 stats = merge.graft(
988 stats = merge.graft(
989 repo,
989 repo,
990 shelvectx,
990 shelvectx,
991 labels=[b'working-copy', b'shelve'],
991 labels=[b'working-copy', b'shelve'],
992 keepconflictparent=True,
992 keepconflictparent=True,
993 )
993 )
994 if stats.unresolvedcount:
994 if stats.unresolvedcount:
995 tr.close()
995 tr.close()
996
996
997 nodestoremove = [
997 nodestoremove = [
998 repo.changelog.node(rev)
998 repo.changelog.node(rev)
999 for rev in pycompat.xrange(oldtiprev, len(repo))
999 for rev in pycompat.xrange(oldtiprev, len(repo))
1000 ]
1000 ]
1001 shelvedstate.save(
1001 shelvedstate.save(
1002 repo,
1002 repo,
1003 basename,
1003 basename,
1004 pctx,
1004 pctx,
1005 tmpwctx,
1005 tmpwctx,
1006 nodestoremove,
1006 nodestoremove,
1007 branchtorestore,
1007 branchtorestore,
1008 opts.get(b'keep'),
1008 opts.get(b'keep'),
1009 activebookmark,
1009 activebookmark,
1010 interactive,
1010 interactive,
1011 )
1011 )
1012 raise error.ConflictResolutionRequired(b'unshelve')
1012 raise error.ConflictResolutionRequired(b'unshelve')
1013
1013
1014 with repo.dirstate.parentchange():
1014 with repo.dirstate.parentchange():
1015 repo.setparents(tmpwctx.node(), nullid)
1015 repo.setparents(tmpwctx.node(), nullid)
1016 newnode, ispartialunshelve = _createunshelvectx(
1016 newnode, ispartialunshelve = _createunshelvectx(
1017 ui, repo, shelvectx, basename, interactive, opts
1017 ui, repo, shelvectx, basename, interactive, opts
1018 )
1018 )
1019
1019
1020 if newnode is None:
1020 if newnode is None:
1021 shelvectx = tmpwctx
1021 shelvectx = tmpwctx
1022 msg = _(
1022 msg = _(
1023 b'note: unshelved changes already existed '
1023 b'note: unshelved changes already existed '
1024 b'in the working copy\n'
1024 b'in the working copy\n'
1025 )
1025 )
1026 ui.status(msg)
1026 ui.status(msg)
1027 else:
1027 else:
1028 shelvectx = repo[newnode]
1028 shelvectx = repo[newnode]
1029 merge.update(tmpwctx)
1029 merge.update(tmpwctx)
1030
1030
1031 return shelvectx, ispartialunshelve
1031 return shelvectx, ispartialunshelve
1032
1032
1033
1033
1034 def _forgetunknownfiles(repo, shelvectx, addedbefore):
1034 def _forgetunknownfiles(repo, shelvectx, addedbefore):
1035 # Forget any files that were unknown before the shelve, unknown before
1035 # Forget any files that were unknown before the shelve, unknown before
1036 # unshelve started, but are now added.
1036 # unshelve started, but are now added.
1037 shelveunknown = shelvectx.extra().get(b'shelve_unknown')
1037 shelveunknown = shelvectx.extra().get(b'shelve_unknown')
1038 if not shelveunknown:
1038 if not shelveunknown:
1039 return
1039 return
1040 shelveunknown = frozenset(shelveunknown.split(b'\0'))
1040 shelveunknown = frozenset(shelveunknown.split(b'\0'))
1041 addedafter = frozenset(repo.status().added)
1041 addedafter = frozenset(repo.status().added)
1042 toforget = (addedafter & shelveunknown) - addedbefore
1042 toforget = (addedafter & shelveunknown) - addedbefore
1043 repo[None].forget(toforget)
1043 repo[None].forget(toforget)
1044
1044
1045
1045
1046 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
1046 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
1047 _restoreactivebookmark(repo, activebookmark)
1047 _restoreactivebookmark(repo, activebookmark)
1048 # The transaction aborting will strip all the commits for us,
1048 # The transaction aborting will strip all the commits for us,
1049 # but it doesn't update the inmemory structures, so addchangegroup
1049 # but it doesn't update the inmemory structures, so addchangegroup
1050 # hooks still fire and try to operate on the missing commits.
1050 # hooks still fire and try to operate on the missing commits.
1051 # Clean up manually to prevent this.
1051 # Clean up manually to prevent this.
1052 repo.unfiltered().changelog.strip(oldtiprev, tr)
1052 repo.unfiltered().changelog.strip(oldtiprev, tr)
1053 _aborttransaction(repo, tr)
1053 _aborttransaction(repo, tr)
1054
1054
1055
1055
1056 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
1056 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
1057 """Check potential problems which may result from working
1057 """Check potential problems which may result from working
1058 copy having untracked changes."""
1058 copy having untracked changes."""
1059 wcdeleted = set(repo.status().deleted)
1059 wcdeleted = set(repo.status().deleted)
1060 shelvetouched = set(shelvectx.files())
1060 shelvetouched = set(shelvectx.files())
1061 intersection = wcdeleted.intersection(shelvetouched)
1061 intersection = wcdeleted.intersection(shelvetouched)
1062 if intersection:
1062 if intersection:
1063 m = _(b"shelved change touches missing files")
1063 m = _(b"shelved change touches missing files")
1064 hint = _(b"run hg status to see which files are missing")
1064 hint = _(b"run hg status to see which files are missing")
1065 raise error.Abort(m, hint=hint)
1065 raise error.Abort(m, hint=hint)
1066
1066
1067
1067
1068 def unshelvecmd(ui, repo, *shelved, **opts):
1068 def unshelvecmd(ui, repo, *shelved, **opts):
1069 opts = pycompat.byteskwargs(opts)
1069 opts = pycompat.byteskwargs(opts)
1070 abortf = opts.get(b'abort')
1070 abortf = opts.get(b'abort')
1071 continuef = opts.get(b'continue')
1071 continuef = opts.get(b'continue')
1072 interactive = opts.get(b'interactive')
1072 interactive = opts.get(b'interactive')
1073 if not abortf and not continuef:
1073 if not abortf and not continuef:
1074 cmdutil.checkunfinished(repo)
1074 cmdutil.checkunfinished(repo)
1075 shelved = list(shelved)
1075 shelved = list(shelved)
1076 if opts.get(b"name"):
1076 if opts.get(b"name"):
1077 shelved.append(opts[b"name"])
1077 shelved.append(opts[b"name"])
1078
1078
1079 if interactive and opts.get(b'keep'):
1079 if interactive and opts.get(b'keep'):
1080 raise error.InputError(
1080 raise error.InputError(
1081 _(b'--keep on --interactive is not yet supported')
1081 _(b'--keep on --interactive is not yet supported')
1082 )
1082 )
1083 if abortf or continuef:
1083 if abortf or continuef:
1084 if abortf and continuef:
1084 if abortf and continuef:
1085 raise error.InputError(_(b'cannot use both abort and continue'))
1085 raise error.InputError(_(b'cannot use both abort and continue'))
1086 if shelved:
1086 if shelved:
1087 raise error.InputError(
1087 raise error.InputError(
1088 _(
1088 _(
1089 b'cannot combine abort/continue with '
1089 b'cannot combine abort/continue with '
1090 b'naming a shelved change'
1090 b'naming a shelved change'
1091 )
1091 )
1092 )
1092 )
1093 if abortf and opts.get(b'tool', False):
1093 if abortf and opts.get(b'tool', False):
1094 ui.warn(_(b'tool option will be ignored\n'))
1094 ui.warn(_(b'tool option will be ignored\n'))
1095
1095
1096 state = _loadshelvedstate(ui, repo, opts)
1096 state = _loadshelvedstate(ui, repo, opts)
1097 if abortf:
1097 if abortf:
1098 return unshelveabort(ui, repo, state)
1098 return unshelveabort(ui, repo, state)
1099 elif continuef and interactive:
1099 elif continuef and interactive:
1100 raise error.InputError(
1100 raise error.InputError(
1101 _(b'cannot use both continue and interactive')
1101 _(b'cannot use both continue and interactive')
1102 )
1102 )
1103 elif continuef:
1103 elif continuef:
1104 return unshelvecontinue(ui, repo, state, opts)
1104 return unshelvecontinue(ui, repo, state, opts)
1105 elif len(shelved) > 1:
1105 elif len(shelved) > 1:
1106 raise error.InputError(_(b'can only unshelve one change at a time'))
1106 raise error.InputError(_(b'can only unshelve one change at a time'))
1107 elif not shelved:
1107 elif not shelved:
1108 shelved = listshelves(repo)
1108 shelved = listshelves(repo)
1109 if not shelved:
1109 if not shelved:
1110 raise error.StateError(_(b'no shelved changes to apply!'))
1110 raise error.StateError(_(b'no shelved changes to apply!'))
1111 basename = shelved[0][1]
1111 basename = shelved[0][1]
1112 ui.status(_(b"unshelving change '%s'\n") % basename)
1112 ui.status(_(b"unshelving change '%s'\n") % basename)
1113 else:
1113 else:
1114 basename = shelved[0]
1114 basename = shelved[0]
1115
1115
1116 if not Shelf(repo, basename).exists():
1116 if not Shelf(repo, basename).exists():
1117 raise error.InputError(_(b"shelved change '%s' not found") % basename)
1117 raise error.InputError(_(b"shelved change '%s' not found") % basename)
1118
1118
1119 return _dounshelve(ui, repo, basename, opts)
1119 return _dounshelve(ui, repo, basename, opts)
1120
1120
1121
1121
1122 def _dounshelve(ui, repo, basename, opts):
1122 def _dounshelve(ui, repo, basename, opts):
1123 repo = repo.unfiltered()
1123 repo = repo.unfiltered()
1124 lock = tr = None
1124 lock = tr = None
1125 try:
1125 try:
1126 lock = repo.lock()
1126 lock = repo.lock()
1127 tr = repo.transaction(b'unshelve', report=lambda x: None)
1127 tr = repo.transaction(b'unshelve', report=lambda x: None)
1128 oldtiprev = len(repo)
1128 oldtiprev = len(repo)
1129
1129
1130 pctx = repo[b'.']
1130 pctx = repo[b'.']
1131 tmpwctx = pctx
1131 tmpwctx = pctx
1132 # The goal is to have a commit structure like so:
1132 # The goal is to have a commit structure like so:
1133 # ...-> pctx -> tmpwctx -> shelvectx
1133 # ...-> pctx -> tmpwctx -> shelvectx
1134 # where tmpwctx is an optional commit with the user's pending changes
1134 # where tmpwctx is an optional commit with the user's pending changes
1135 # and shelvectx is the unshelved changes. Then we merge it all down
1135 # and shelvectx is the unshelved changes. Then we merge it all down
1136 # to the original pctx.
1136 # to the original pctx.
1137
1137
1138 activebookmark = _backupactivebookmark(repo)
1138 activebookmark = _backupactivebookmark(repo)
1139 tmpwctx, addedbefore = _commitworkingcopychanges(
1139 tmpwctx, addedbefore = _commitworkingcopychanges(
1140 ui, repo, opts, tmpwctx
1140 ui, repo, opts, tmpwctx
1141 )
1141 )
1142 repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
1142 repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
1143 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1143 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1144 branchtorestore = b''
1144 branchtorestore = b''
1145 if shelvectx.branch() != shelvectx.p1().branch():
1145 if shelvectx.branch() != shelvectx.p1().branch():
1146 branchtorestore = shelvectx.branch()
1146 branchtorestore = shelvectx.branch()
1147
1147
1148 shelvectx, ispartialunshelve = _rebaserestoredcommit(
1148 shelvectx, ispartialunshelve = _rebaserestoredcommit(
1149 ui,
1149 ui,
1150 repo,
1150 repo,
1151 opts,
1151 opts,
1152 tr,
1152 tr,
1153 oldtiprev,
1153 oldtiprev,
1154 basename,
1154 basename,
1155 pctx,
1155 pctx,
1156 tmpwctx,
1156 tmpwctx,
1157 shelvectx,
1157 shelvectx,
1158 branchtorestore,
1158 branchtorestore,
1159 activebookmark,
1159 activebookmark,
1160 )
1160 )
1161 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
1161 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
1162 with ui.configoverride(overrides, b'unshelve'):
1162 with ui.configoverride(overrides, b'unshelve'):
1163 mergefiles(ui, repo, pctx, shelvectx)
1163 mergefiles(ui, repo, pctx, shelvectx)
1164 restorebranch(ui, repo, branchtorestore)
1164 restorebranch(ui, repo, branchtorestore)
1165 shelvedstate.clear(repo)
1165 shelvedstate.clear(repo)
1166 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1166 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1167 _forgetunknownfiles(repo, shelvectx, addedbefore)
1167 _forgetunknownfiles(repo, shelvectx, addedbefore)
1168 if not ispartialunshelve:
1168 if not ispartialunshelve:
1169 unshelvecleanup(ui, repo, basename, opts)
1169 unshelvecleanup(ui, repo, basename, opts)
1170 finally:
1170 finally:
1171 if tr:
1171 if tr:
1172 tr.release()
1172 tr.release()
1173 lockmod.release(lock)
1173 lockmod.release(lock)
General Comments 0
You need to be logged in to leave comments. Login now