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