##// END OF EJS Templates
shelve: drop an unused variable assignment...
Matt Harbison -
r44433:414cb20e default
parent child Browse files
Show More
@@ -1,1185 +1,1183 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), False
149 self.repo.baseui, b'bundle://%s+%s' % (self.repo.root, path), False
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():
561 desc = stringutil.ellipsis(desc, ui.termwidth())
562 ui.status(_(b'shelved as %s\n') % name)
560 ui.status(_(b'shelved as %s\n') % name)
563 if opts[b'keep']:
561 if opts[b'keep']:
564 with repo.dirstate.parentchange():
562 with repo.dirstate.parentchange():
565 scmutil.movedirstate(repo, parent, match)
563 scmutil.movedirstate(repo, parent, match)
566 else:
564 else:
567 hg.update(repo, parent.node())
565 hg.update(repo, parent.node())
568 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
566 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
569 repo.dirstate.setbranch(origbranch)
567 repo.dirstate.setbranch(origbranch)
570
568
571 _finishshelve(repo, tr)
569 _finishshelve(repo, tr)
572 finally:
570 finally:
573 _restoreactivebookmark(repo, activebookmark)
571 _restoreactivebookmark(repo, activebookmark)
574 lockmod.release(tr, lock)
572 lockmod.release(tr, lock)
575
573
576
574
577 def _isbareshelve(pats, opts):
575 def _isbareshelve(pats, opts):
578 return (
576 return (
579 not pats
577 not pats
580 and not opts.get(b'interactive', False)
578 and not opts.get(b'interactive', False)
581 and not opts.get(b'include', False)
579 and not opts.get(b'include', False)
582 and not opts.get(b'exclude', False)
580 and not opts.get(b'exclude', False)
583 )
581 )
584
582
585
583
586 def _iswctxonnewbranch(repo):
584 def _iswctxonnewbranch(repo):
587 return repo[None].branch() != repo[b'.'].branch()
585 return repo[None].branch() != repo[b'.'].branch()
588
586
589
587
590 def cleanupcmd(ui, repo):
588 def cleanupcmd(ui, repo):
591 """subcommand that deletes all shelves"""
589 """subcommand that deletes all shelves"""
592
590
593 with repo.wlock():
591 with repo.wlock():
594 for (name, _type) in repo.vfs.readdir(shelvedir):
592 for (name, _type) in repo.vfs.readdir(shelvedir):
595 suffix = name.rsplit(b'.', 1)[-1]
593 suffix = name.rsplit(b'.', 1)[-1]
596 if suffix in shelvefileextensions:
594 if suffix in shelvefileextensions:
597 shelvedfile(repo, name).movetobackup()
595 shelvedfile(repo, name).movetobackup()
598 cleanupoldbackups(repo)
596 cleanupoldbackups(repo)
599
597
600
598
601 def deletecmd(ui, repo, pats):
599 def deletecmd(ui, repo, pats):
602 """subcommand that deletes a specific shelve"""
600 """subcommand that deletes a specific shelve"""
603 if not pats:
601 if not pats:
604 raise error.Abort(_(b'no shelved changes specified!'))
602 raise error.Abort(_(b'no shelved changes specified!'))
605 with repo.wlock():
603 with repo.wlock():
606 for name in pats:
604 for name in pats:
607 try:
605 try:
608 for suffix in shelvefileextensions:
606 for suffix in shelvefileextensions:
609 shfile = shelvedfile(repo, name, suffix)
607 shfile = shelvedfile(repo, name, suffix)
610 # patch file is necessary, as it should
608 # patch file is necessary, as it should
611 # be present for any kind of shelve,
609 # be present for any kind of shelve,
612 # but the .hg file is optional as in future we
610 # but the .hg file is optional as in future we
613 # will add obsolete shelve with does not create a
611 # will add obsolete shelve with does not create a
614 # bundle
612 # bundle
615 if shfile.exists() or suffix == patchextension:
613 if shfile.exists() or suffix == patchextension:
616 shfile.movetobackup()
614 shfile.movetobackup()
617 except OSError as err:
615 except OSError as err:
618 if err.errno != errno.ENOENT:
616 if err.errno != errno.ENOENT:
619 raise
617 raise
620 raise error.Abort(_(b"shelved change '%s' not found") % name)
618 raise error.Abort(_(b"shelved change '%s' not found") % name)
621 cleanupoldbackups(repo)
619 cleanupoldbackups(repo)
622
620
623
621
624 def listshelves(repo):
622 def listshelves(repo):
625 """return all shelves in repo as list of (time, filename)"""
623 """return all shelves in repo as list of (time, filename)"""
626 try:
624 try:
627 names = repo.vfs.readdir(shelvedir)
625 names = repo.vfs.readdir(shelvedir)
628 except OSError as err:
626 except OSError as err:
629 if err.errno != errno.ENOENT:
627 if err.errno != errno.ENOENT:
630 raise
628 raise
631 return []
629 return []
632 info = []
630 info = []
633 for (name, _type) in names:
631 for (name, _type) in names:
634 pfx, sfx = name.rsplit(b'.', 1)
632 pfx, sfx = name.rsplit(b'.', 1)
635 if not pfx or sfx != patchextension:
633 if not pfx or sfx != patchextension:
636 continue
634 continue
637 st = shelvedfile(repo, name).stat()
635 st = shelvedfile(repo, name).stat()
638 info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename()))
636 info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename()))
639 return sorted(info, reverse=True)
637 return sorted(info, reverse=True)
640
638
641
639
642 def listcmd(ui, repo, pats, opts):
640 def listcmd(ui, repo, pats, opts):
643 """subcommand that displays the list of shelves"""
641 """subcommand that displays the list of shelves"""
644 pats = set(pats)
642 pats = set(pats)
645 width = 80
643 width = 80
646 if not ui.plain():
644 if not ui.plain():
647 width = ui.termwidth()
645 width = ui.termwidth()
648 namelabel = b'shelve.newest'
646 namelabel = b'shelve.newest'
649 ui.pager(b'shelve')
647 ui.pager(b'shelve')
650 for mtime, name in listshelves(repo):
648 for mtime, name in listshelves(repo):
651 sname = util.split(name)[1]
649 sname = util.split(name)[1]
652 if pats and sname not in pats:
650 if pats and sname not in pats:
653 continue
651 continue
654 ui.write(sname, label=namelabel)
652 ui.write(sname, label=namelabel)
655 namelabel = b'shelve.name'
653 namelabel = b'shelve.name'
656 if ui.quiet:
654 if ui.quiet:
657 ui.write(b'\n')
655 ui.write(b'\n')
658 continue
656 continue
659 ui.write(b' ' * (16 - len(sname)))
657 ui.write(b' ' * (16 - len(sname)))
660 used = 16
658 used = 16
661 date = dateutil.makedate(mtime)
659 date = dateutil.makedate(mtime)
662 age = b'(%s)' % templatefilters.age(date, abbrev=True)
660 age = b'(%s)' % templatefilters.age(date, abbrev=True)
663 ui.write(age, label=b'shelve.age')
661 ui.write(age, label=b'shelve.age')
664 ui.write(b' ' * (12 - len(age)))
662 ui.write(b' ' * (12 - len(age)))
665 used += 12
663 used += 12
666 with open(name + b'.' + patchextension, b'rb') as fp:
664 with open(name + b'.' + patchextension, b'rb') as fp:
667 while True:
665 while True:
668 line = fp.readline()
666 line = fp.readline()
669 if not line:
667 if not line:
670 break
668 break
671 if not line.startswith(b'#'):
669 if not line.startswith(b'#'):
672 desc = line.rstrip()
670 desc = line.rstrip()
673 if ui.formatted():
671 if ui.formatted():
674 desc = stringutil.ellipsis(desc, width - used)
672 desc = stringutil.ellipsis(desc, width - used)
675 ui.write(desc)
673 ui.write(desc)
676 break
674 break
677 ui.write(b'\n')
675 ui.write(b'\n')
678 if not (opts[b'patch'] or opts[b'stat']):
676 if not (opts[b'patch'] or opts[b'stat']):
679 continue
677 continue
680 difflines = fp.readlines()
678 difflines = fp.readlines()
681 if opts[b'patch']:
679 if opts[b'patch']:
682 for chunk, label in patch.difflabel(iter, difflines):
680 for chunk, label in patch.difflabel(iter, difflines):
683 ui.write(chunk, label=label)
681 ui.write(chunk, label=label)
684 if opts[b'stat']:
682 if opts[b'stat']:
685 for chunk, label in patch.diffstatui(difflines, width=width):
683 for chunk, label in patch.diffstatui(difflines, width=width):
686 ui.write(chunk, label=label)
684 ui.write(chunk, label=label)
687
685
688
686
689 def patchcmds(ui, repo, pats, opts):
687 def patchcmds(ui, repo, pats, opts):
690 """subcommand that displays shelves"""
688 """subcommand that displays shelves"""
691 if len(pats) == 0:
689 if len(pats) == 0:
692 shelves = listshelves(repo)
690 shelves = listshelves(repo)
693 if not shelves:
691 if not shelves:
694 raise error.Abort(_(b"there are no shelves to show"))
692 raise error.Abort(_(b"there are no shelves to show"))
695 mtime, name = shelves[0]
693 mtime, name = shelves[0]
696 sname = util.split(name)[1]
694 sname = util.split(name)[1]
697 pats = [sname]
695 pats = [sname]
698
696
699 for shelfname in pats:
697 for shelfname in pats:
700 if not shelvedfile(repo, shelfname, patchextension).exists():
698 if not shelvedfile(repo, shelfname, patchextension).exists():
701 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
699 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
702
700
703 listcmd(ui, repo, pats, opts)
701 listcmd(ui, repo, pats, opts)
704
702
705
703
706 def checkparents(repo, state):
704 def checkparents(repo, state):
707 """check parent while resuming an unshelve"""
705 """check parent while resuming an unshelve"""
708 if state.parents != repo.dirstate.parents():
706 if state.parents != repo.dirstate.parents():
709 raise error.Abort(
707 raise error.Abort(
710 _(b'working directory parents do not match unshelve state')
708 _(b'working directory parents do not match unshelve state')
711 )
709 )
712
710
713
711
714 def _loadshelvedstate(ui, repo, opts):
712 def _loadshelvedstate(ui, repo, opts):
715 try:
713 try:
716 state = shelvedstate.load(repo)
714 state = shelvedstate.load(repo)
717 if opts.get(b'keep') is None:
715 if opts.get(b'keep') is None:
718 opts[b'keep'] = state.keep
716 opts[b'keep'] = state.keep
719 except IOError as err:
717 except IOError as err:
720 if err.errno != errno.ENOENT:
718 if err.errno != errno.ENOENT:
721 raise
719 raise
722 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
720 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
723 except error.CorruptedState as err:
721 except error.CorruptedState as err:
724 ui.debug(pycompat.bytestr(err) + b'\n')
722 ui.debug(pycompat.bytestr(err) + b'\n')
725 if opts.get(b'continue'):
723 if opts.get(b'continue'):
726 msg = _(b'corrupted shelved state file')
724 msg = _(b'corrupted shelved state file')
727 hint = _(
725 hint = _(
728 b'please run hg unshelve --abort to abort unshelve '
726 b'please run hg unshelve --abort to abort unshelve '
729 b'operation'
727 b'operation'
730 )
728 )
731 raise error.Abort(msg, hint=hint)
729 raise error.Abort(msg, hint=hint)
732 elif opts.get(b'abort'):
730 elif opts.get(b'abort'):
733 shelvedstate.clear(repo)
731 shelvedstate.clear(repo)
734 raise error.Abort(
732 raise error.Abort(
735 _(
733 _(
736 b'could not read shelved state file, your '
734 b'could not read shelved state file, your '
737 b'working copy may be in an unexpected state\n'
735 b'working copy may be in an unexpected state\n'
738 b'please update to some commit\n'
736 b'please update to some commit\n'
739 )
737 )
740 )
738 )
741 return state
739 return state
742
740
743
741
744 def unshelveabort(ui, repo, state):
742 def unshelveabort(ui, repo, state):
745 """subcommand that abort an in-progress unshelve"""
743 """subcommand that abort an in-progress unshelve"""
746 with repo.lock():
744 with repo.lock():
747 try:
745 try:
748 checkparents(repo, state)
746 checkparents(repo, state)
749
747
750 merge.update(repo, state.pendingctx, branchmerge=False, force=True)
748 merge.update(repo, state.pendingctx, branchmerge=False, force=True)
751 if state.activebookmark and state.activebookmark in repo._bookmarks:
749 if state.activebookmark and state.activebookmark in repo._bookmarks:
752 bookmarks.activate(repo, state.activebookmark)
750 bookmarks.activate(repo, state.activebookmark)
753 mergefiles(ui, repo, state.wctx, state.pendingctx)
751 mergefiles(ui, repo, state.wctx, state.pendingctx)
754 if not phases.supportinternal(repo):
752 if not phases.supportinternal(repo):
755 repair.strip(
753 repair.strip(
756 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
754 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
757 )
755 )
758 finally:
756 finally:
759 shelvedstate.clear(repo)
757 shelvedstate.clear(repo)
760 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
758 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
761
759
762
760
763 def hgabortunshelve(ui, repo):
761 def hgabortunshelve(ui, repo):
764 """logic to abort unshelve using 'hg abort"""
762 """logic to abort unshelve using 'hg abort"""
765 with repo.wlock():
763 with repo.wlock():
766 state = _loadshelvedstate(ui, repo, {b'abort': True})
764 state = _loadshelvedstate(ui, repo, {b'abort': True})
767 return unshelveabort(ui, repo, state)
765 return unshelveabort(ui, repo, state)
768
766
769
767
770 def mergefiles(ui, repo, wctx, shelvectx):
768 def mergefiles(ui, repo, wctx, shelvectx):
771 """updates to wctx and merges the changes from shelvectx into the
769 """updates to wctx and merges the changes from shelvectx into the
772 dirstate."""
770 dirstate."""
773 with ui.configoverride({(b'ui', b'quiet'): True}):
771 with ui.configoverride({(b'ui', b'quiet'): True}):
774 hg.update(repo, wctx.node())
772 hg.update(repo, wctx.node())
775 ui.pushbuffer(True)
773 ui.pushbuffer(True)
776 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents())
774 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents())
777 ui.popbuffer()
775 ui.popbuffer()
778
776
779
777
780 def restorebranch(ui, repo, branchtorestore):
778 def restorebranch(ui, repo, branchtorestore):
781 if branchtorestore and branchtorestore != repo.dirstate.branch():
779 if branchtorestore and branchtorestore != repo.dirstate.branch():
782 repo.dirstate.setbranch(branchtorestore)
780 repo.dirstate.setbranch(branchtorestore)
783 ui.status(
781 ui.status(
784 _(b'marked working directory as branch %s\n') % branchtorestore
782 _(b'marked working directory as branch %s\n') % branchtorestore
785 )
783 )
786
784
787
785
788 def unshelvecleanup(ui, repo, name, opts):
786 def unshelvecleanup(ui, repo, name, opts):
789 """remove related files after an unshelve"""
787 """remove related files after an unshelve"""
790 if not opts.get(b'keep'):
788 if not opts.get(b'keep'):
791 for filetype in shelvefileextensions:
789 for filetype in shelvefileextensions:
792 shfile = shelvedfile(repo, name, filetype)
790 shfile = shelvedfile(repo, name, filetype)
793 if shfile.exists():
791 if shfile.exists():
794 shfile.movetobackup()
792 shfile.movetobackup()
795 cleanupoldbackups(repo)
793 cleanupoldbackups(repo)
796
794
797
795
798 def unshelvecontinue(ui, repo, state, opts):
796 def unshelvecontinue(ui, repo, state, opts):
799 """subcommand to continue an in-progress unshelve"""
797 """subcommand to continue an in-progress unshelve"""
800 # We're finishing off a merge. First parent is our original
798 # We're finishing off a merge. First parent is our original
801 # parent, second is the temporary "fake" commit we're unshelving.
799 # parent, second is the temporary "fake" commit we're unshelving.
802 interactive = state.interactive
800 interactive = state.interactive
803 basename = state.name
801 basename = state.name
804 with repo.lock():
802 with repo.lock():
805 checkparents(repo, state)
803 checkparents(repo, state)
806 ms = merge.mergestate.read(repo)
804 ms = merge.mergestate.read(repo)
807 if list(ms.unresolved()):
805 if list(ms.unresolved()):
808 raise error.Abort(
806 raise error.Abort(
809 _(b"unresolved conflicts, can't continue"),
807 _(b"unresolved conflicts, can't continue"),
810 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
808 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
811 )
809 )
812
810
813 shelvectx = repo[state.parents[1]]
811 shelvectx = repo[state.parents[1]]
814 pendingctx = state.pendingctx
812 pendingctx = state.pendingctx
815
813
816 with repo.dirstate.parentchange():
814 with repo.dirstate.parentchange():
817 repo.setparents(state.pendingctx.node(), nodemod.nullid)
815 repo.setparents(state.pendingctx.node(), nodemod.nullid)
818 repo.dirstate.write(repo.currenttransaction())
816 repo.dirstate.write(repo.currenttransaction())
819
817
820 targetphase = phases.internal
818 targetphase = phases.internal
821 if not phases.supportinternal(repo):
819 if not phases.supportinternal(repo):
822 targetphase = phases.secret
820 targetphase = phases.secret
823 overrides = {(b'phases', b'new-commit'): targetphase}
821 overrides = {(b'phases', b'new-commit'): targetphase}
824 with repo.ui.configoverride(overrides, b'unshelve'):
822 with repo.ui.configoverride(overrides, b'unshelve'):
825 with repo.dirstate.parentchange():
823 with repo.dirstate.parentchange():
826 repo.setparents(state.parents[0], nodemod.nullid)
824 repo.setparents(state.parents[0], nodemod.nullid)
827 newnode, ispartialunshelve = _createunshelvectx(
825 newnode, ispartialunshelve = _createunshelvectx(
828 ui, repo, shelvectx, basename, interactive, opts
826 ui, repo, shelvectx, basename, interactive, opts
829 )
827 )
830
828
831 if newnode is None:
829 if newnode is None:
832 # If it ended up being a no-op commit, then the normal
830 # If it ended up being a no-op commit, then the normal
833 # merge state clean-up path doesn't happen, so do it
831 # merge state clean-up path doesn't happen, so do it
834 # here. Fix issue5494
832 # here. Fix issue5494
835 merge.mergestate.clean(repo)
833 merge.mergestate.clean(repo)
836 shelvectx = state.pendingctx
834 shelvectx = state.pendingctx
837 msg = _(
835 msg = _(
838 b'note: unshelved changes already existed '
836 b'note: unshelved changes already existed '
839 b'in the working copy\n'
837 b'in the working copy\n'
840 )
838 )
841 ui.status(msg)
839 ui.status(msg)
842 else:
840 else:
843 # only strip the shelvectx if we produced one
841 # only strip the shelvectx if we produced one
844 state.nodestoremove.append(newnode)
842 state.nodestoremove.append(newnode)
845 shelvectx = repo[newnode]
843 shelvectx = repo[newnode]
846
844
847 hg.updaterepo(repo, pendingctx.node(), overwrite=False)
845 hg.updaterepo(repo, pendingctx.node(), overwrite=False)
848 mergefiles(ui, repo, state.wctx, shelvectx)
846 mergefiles(ui, repo, state.wctx, shelvectx)
849 restorebranch(ui, repo, state.branchtorestore)
847 restorebranch(ui, repo, state.branchtorestore)
850
848
851 if not phases.supportinternal(repo):
849 if not phases.supportinternal(repo):
852 repair.strip(
850 repair.strip(
853 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
851 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
854 )
852 )
855 shelvedstate.clear(repo)
853 shelvedstate.clear(repo)
856 if not ispartialunshelve:
854 if not ispartialunshelve:
857 unshelvecleanup(ui, repo, state.name, opts)
855 unshelvecleanup(ui, repo, state.name, opts)
858 _restoreactivebookmark(repo, state.activebookmark)
856 _restoreactivebookmark(repo, state.activebookmark)
859 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
857 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
860
858
861
859
862 def hgcontinueunshelve(ui, repo):
860 def hgcontinueunshelve(ui, repo):
863 """logic to resume unshelve using 'hg continue'"""
861 """logic to resume unshelve using 'hg continue'"""
864 with repo.wlock():
862 with repo.wlock():
865 state = _loadshelvedstate(ui, repo, {b'continue': True})
863 state = _loadshelvedstate(ui, repo, {b'continue': True})
866 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
864 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
867
865
868
866
869 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
867 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
870 """Temporarily commit working copy changes before moving unshelve commit"""
868 """Temporarily commit working copy changes before moving unshelve commit"""
871 # Store pending changes in a commit and remember added in case a shelve
869 # Store pending changes in a commit and remember added in case a shelve
872 # contains unknown files that are part of the pending change
870 # contains unknown files that are part of the pending change
873 s = repo.status()
871 s = repo.status()
874 addedbefore = frozenset(s.added)
872 addedbefore = frozenset(s.added)
875 if not (s.modified or s.added or s.removed):
873 if not (s.modified or s.added or s.removed):
876 return tmpwctx, addedbefore
874 return tmpwctx, addedbefore
877 ui.status(
875 ui.status(
878 _(
876 _(
879 b"temporarily committing pending changes "
877 b"temporarily committing pending changes "
880 b"(restore with 'hg unshelve --abort')\n"
878 b"(restore with 'hg unshelve --abort')\n"
881 )
879 )
882 )
880 )
883 extra = {b'internal': b'shelve'}
881 extra = {b'internal': b'shelve'}
884 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
882 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
885 tempopts = {}
883 tempopts = {}
886 tempopts[b'message'] = b"pending changes temporary commit"
884 tempopts[b'message'] = b"pending changes temporary commit"
887 tempopts[b'date'] = opts.get(b'date')
885 tempopts[b'date'] = opts.get(b'date')
888 with ui.configoverride({(b'ui', b'quiet'): True}):
886 with ui.configoverride({(b'ui', b'quiet'): True}):
889 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
887 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
890 tmpwctx = repo[node]
888 tmpwctx = repo[node]
891 return tmpwctx, addedbefore
889 return tmpwctx, addedbefore
892
890
893
891
894 def _unshelverestorecommit(ui, repo, tr, basename):
892 def _unshelverestorecommit(ui, repo, tr, basename):
895 """Recreate commit in the repository during the unshelve"""
893 """Recreate commit in the repository during the unshelve"""
896 repo = repo.unfiltered()
894 repo = repo.unfiltered()
897 node = None
895 node = None
898 if shelvedfile(repo, basename, b'shelve').exists():
896 if shelvedfile(repo, basename, b'shelve').exists():
899 node = shelvedfile(repo, basename, b'shelve').readinfo()[b'node']
897 node = shelvedfile(repo, basename, b'shelve').readinfo()[b'node']
900 if node is None or node not in repo:
898 if node is None or node not in repo:
901 with ui.configoverride({(b'ui', b'quiet'): True}):
899 with ui.configoverride({(b'ui', b'quiet'): True}):
902 shelvectx = shelvedfile(repo, basename, b'hg').applybundle(tr)
900 shelvectx = shelvedfile(repo, basename, b'hg').applybundle(tr)
903 # We might not strip the unbundled changeset, so we should keep track of
901 # 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)
902 # the unshelve node in case we need to reuse it (eg: unshelve --keep)
905 if node is None:
903 if node is None:
906 info = {b'node': nodemod.hex(shelvectx.node())}
904 info = {b'node': nodemod.hex(shelvectx.node())}
907 shelvedfile(repo, basename, b'shelve').writeinfo(info)
905 shelvedfile(repo, basename, b'shelve').writeinfo(info)
908 else:
906 else:
909 shelvectx = repo[node]
907 shelvectx = repo[node]
910
908
911 return repo, shelvectx
909 return repo, shelvectx
912
910
913
911
914 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
912 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
915 """Handles the creation of unshelve commit and updates the shelve if it
913 """Handles the creation of unshelve commit and updates the shelve if it
916 was partially unshelved.
914 was partially unshelved.
917
915
918 If interactive is:
916 If interactive is:
919
917
920 * False: Commits all the changes in the working directory.
918 * False: Commits all the changes in the working directory.
921 * True: Prompts the user to select changes to unshelve and commit them.
919 * True: Prompts the user to select changes to unshelve and commit them.
922 Update the shelve with remaining changes.
920 Update the shelve with remaining changes.
923
921
924 Returns the node of the new commit formed and a bool indicating whether
922 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
923 the shelve was partially unshelved.Creates a commit ctx to unshelve
926 interactively or non-interactively.
924 interactively or non-interactively.
927
925
928 The user might want to unshelve certain changes only from the stored
926 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
927 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.
928 changes to unshelve at that time and the latter is shelved for future.
931
929
932 Here, we return both the newnode which is created interactively and a
930 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.
931 bool to know whether the shelve is partly done or completely done.
934 """
932 """
935 opts[b'message'] = shelvectx.description()
933 opts[b'message'] = shelvectx.description()
936 opts[b'interactive-unshelve'] = True
934 opts[b'interactive-unshelve'] = True
937 pats = []
935 pats = []
938 if not interactive:
936 if not interactive:
939 newnode = repo.commit(
937 newnode = repo.commit(
940 text=shelvectx.description(),
938 text=shelvectx.description(),
941 extra=shelvectx.extra(),
939 extra=shelvectx.extra(),
942 user=shelvectx.user(),
940 user=shelvectx.user(),
943 date=shelvectx.date(),
941 date=shelvectx.date(),
944 )
942 )
945 return newnode, False
943 return newnode, False
946
944
947 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True)
945 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True)
948 newnode = cmdutil.dorecord(
946 newnode = cmdutil.dorecord(
949 ui,
947 ui,
950 repo,
948 repo,
951 commitfunc,
949 commitfunc,
952 None,
950 None,
953 False,
951 False,
954 cmdutil.recordfilter,
952 cmdutil.recordfilter,
955 *pats,
953 *pats,
956 **pycompat.strkwargs(opts)
954 **pycompat.strkwargs(opts)
957 )
955 )
958 snode = repo.commit(
956 snode = repo.commit(
959 text=shelvectx.description(),
957 text=shelvectx.description(),
960 extra=shelvectx.extra(),
958 extra=shelvectx.extra(),
961 user=shelvectx.user(),
959 user=shelvectx.user(),
962 )
960 )
963 if snode:
961 if snode:
964 m = scmutil.matchfiles(repo, repo[snode].files())
962 m = scmutil.matchfiles(repo, repo[snode].files())
965 _shelvecreatedcommit(repo, snode, basename, m)
963 _shelvecreatedcommit(repo, snode, basename, m)
966
964
967 return newnode, bool(snode)
965 return newnode, bool(snode)
968
966
969
967
970 def _rebaserestoredcommit(
968 def _rebaserestoredcommit(
971 ui,
969 ui,
972 repo,
970 repo,
973 opts,
971 opts,
974 tr,
972 tr,
975 oldtiprev,
973 oldtiprev,
976 basename,
974 basename,
977 pctx,
975 pctx,
978 tmpwctx,
976 tmpwctx,
979 shelvectx,
977 shelvectx,
980 branchtorestore,
978 branchtorestore,
981 activebookmark,
979 activebookmark,
982 ):
980 ):
983 """Rebase restored commit from its original location to a destination"""
981 """Rebase restored commit from its original location to a destination"""
984 # If the shelve is not immediately on top of the commit
982 # If the shelve is not immediately on top of the commit
985 # we'll be merging with, rebase it to be on top.
983 # we'll be merging with, rebase it to be on top.
986 interactive = opts.get(b'interactive')
984 interactive = opts.get(b'interactive')
987 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
985 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
988 # We won't skip on interactive mode because, the user might want to
986 # We won't skip on interactive mode because, the user might want to
989 # unshelve certain changes only.
987 # unshelve certain changes only.
990 return shelvectx, False
988 return shelvectx, False
991
989
992 overrides = {
990 overrides = {
993 (b'ui', b'forcemerge'): opts.get(b'tool', b''),
991 (b'ui', b'forcemerge'): opts.get(b'tool', b''),
994 (b'phases', b'new-commit'): phases.secret,
992 (b'phases', b'new-commit'): phases.secret,
995 }
993 }
996 with repo.ui.configoverride(overrides, b'unshelve'):
994 with repo.ui.configoverride(overrides, b'unshelve'):
997 ui.status(_(b'rebasing shelved changes\n'))
995 ui.status(_(b'rebasing shelved changes\n'))
998 stats = merge.graft(
996 stats = merge.graft(
999 repo,
997 repo,
1000 shelvectx,
998 shelvectx,
1001 shelvectx.p1(),
999 shelvectx.p1(),
1002 labels=[b'shelve', b'working-copy'],
1000 labels=[b'shelve', b'working-copy'],
1003 keepconflictparent=True,
1001 keepconflictparent=True,
1004 )
1002 )
1005 if stats.unresolvedcount:
1003 if stats.unresolvedcount:
1006 tr.close()
1004 tr.close()
1007
1005
1008 nodestoremove = [
1006 nodestoremove = [
1009 repo.changelog.node(rev)
1007 repo.changelog.node(rev)
1010 for rev in pycompat.xrange(oldtiprev, len(repo))
1008 for rev in pycompat.xrange(oldtiprev, len(repo))
1011 ]
1009 ]
1012 shelvedstate.save(
1010 shelvedstate.save(
1013 repo,
1011 repo,
1014 basename,
1012 basename,
1015 pctx,
1013 pctx,
1016 tmpwctx,
1014 tmpwctx,
1017 nodestoremove,
1015 nodestoremove,
1018 branchtorestore,
1016 branchtorestore,
1019 opts.get(b'keep'),
1017 opts.get(b'keep'),
1020 activebookmark,
1018 activebookmark,
1021 interactive,
1019 interactive,
1022 )
1020 )
1023 raise error.InterventionRequired(
1021 raise error.InterventionRequired(
1024 _(
1022 _(
1025 b"unresolved conflicts (see 'hg resolve', then "
1023 b"unresolved conflicts (see 'hg resolve', then "
1026 b"'hg unshelve --continue')"
1024 b"'hg unshelve --continue')"
1027 )
1025 )
1028 )
1026 )
1029
1027
1030 with repo.dirstate.parentchange():
1028 with repo.dirstate.parentchange():
1031 repo.setparents(tmpwctx.node(), nodemod.nullid)
1029 repo.setparents(tmpwctx.node(), nodemod.nullid)
1032 newnode, ispartialunshelve = _createunshelvectx(
1030 newnode, ispartialunshelve = _createunshelvectx(
1033 ui, repo, shelvectx, basename, interactive, opts
1031 ui, repo, shelvectx, basename, interactive, opts
1034 )
1032 )
1035
1033
1036 if newnode is None:
1034 if newnode is None:
1037 # If it ended up being a no-op commit, then the normal
1035 # If it ended up being a no-op commit, then the normal
1038 # merge state clean-up path doesn't happen, so do it
1036 # merge state clean-up path doesn't happen, so do it
1039 # here. Fix issue5494
1037 # here. Fix issue5494
1040 merge.mergestate.clean(repo)
1038 merge.mergestate.clean(repo)
1041 shelvectx = tmpwctx
1039 shelvectx = tmpwctx
1042 msg = _(
1040 msg = _(
1043 b'note: unshelved changes already existed '
1041 b'note: unshelved changes already existed '
1044 b'in the working copy\n'
1042 b'in the working copy\n'
1045 )
1043 )
1046 ui.status(msg)
1044 ui.status(msg)
1047 else:
1045 else:
1048 shelvectx = repo[newnode]
1046 shelvectx = repo[newnode]
1049 hg.updaterepo(repo, tmpwctx.node(), False)
1047 hg.updaterepo(repo, tmpwctx.node(), False)
1050
1048
1051 return shelvectx, ispartialunshelve
1049 return shelvectx, ispartialunshelve
1052
1050
1053
1051
1054 def _forgetunknownfiles(repo, shelvectx, addedbefore):
1052 def _forgetunknownfiles(repo, shelvectx, addedbefore):
1055 # Forget any files that were unknown before the shelve, unknown before
1053 # Forget any files that were unknown before the shelve, unknown before
1056 # unshelve started, but are now added.
1054 # unshelve started, but are now added.
1057 shelveunknown = shelvectx.extra().get(b'shelve_unknown')
1055 shelveunknown = shelvectx.extra().get(b'shelve_unknown')
1058 if not shelveunknown:
1056 if not shelveunknown:
1059 return
1057 return
1060 shelveunknown = frozenset(shelveunknown.split(b'\0'))
1058 shelveunknown = frozenset(shelveunknown.split(b'\0'))
1061 addedafter = frozenset(repo.status().added)
1059 addedafter = frozenset(repo.status().added)
1062 toforget = (addedafter & shelveunknown) - addedbefore
1060 toforget = (addedafter & shelveunknown) - addedbefore
1063 repo[None].forget(toforget)
1061 repo[None].forget(toforget)
1064
1062
1065
1063
1066 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
1064 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
1067 _restoreactivebookmark(repo, activebookmark)
1065 _restoreactivebookmark(repo, activebookmark)
1068 # The transaction aborting will strip all the commits for us,
1066 # The transaction aborting will strip all the commits for us,
1069 # but it doesn't update the inmemory structures, so addchangegroup
1067 # but it doesn't update the inmemory structures, so addchangegroup
1070 # hooks still fire and try to operate on the missing commits.
1068 # hooks still fire and try to operate on the missing commits.
1071 # Clean up manually to prevent this.
1069 # Clean up manually to prevent this.
1072 repo.unfiltered().changelog.strip(oldtiprev, tr)
1070 repo.unfiltered().changelog.strip(oldtiprev, tr)
1073 _aborttransaction(repo, tr)
1071 _aborttransaction(repo, tr)
1074
1072
1075
1073
1076 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
1074 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
1077 """Check potential problems which may result from working
1075 """Check potential problems which may result from working
1078 copy having untracked changes."""
1076 copy having untracked changes."""
1079 wcdeleted = set(repo.status().deleted)
1077 wcdeleted = set(repo.status().deleted)
1080 shelvetouched = set(shelvectx.files())
1078 shelvetouched = set(shelvectx.files())
1081 intersection = wcdeleted.intersection(shelvetouched)
1079 intersection = wcdeleted.intersection(shelvetouched)
1082 if intersection:
1080 if intersection:
1083 m = _(b"shelved change touches missing files")
1081 m = _(b"shelved change touches missing files")
1084 hint = _(b"run hg status to see which files are missing")
1082 hint = _(b"run hg status to see which files are missing")
1085 raise error.Abort(m, hint=hint)
1083 raise error.Abort(m, hint=hint)
1086
1084
1087
1085
1088 def dounshelve(ui, repo, *shelved, **opts):
1086 def dounshelve(ui, repo, *shelved, **opts):
1089 opts = pycompat.byteskwargs(opts)
1087 opts = pycompat.byteskwargs(opts)
1090 abortf = opts.get(b'abort')
1088 abortf = opts.get(b'abort')
1091 continuef = opts.get(b'continue')
1089 continuef = opts.get(b'continue')
1092 interactive = opts.get(b'interactive')
1090 interactive = opts.get(b'interactive')
1093 if not abortf and not continuef:
1091 if not abortf and not continuef:
1094 cmdutil.checkunfinished(repo)
1092 cmdutil.checkunfinished(repo)
1095 shelved = list(shelved)
1093 shelved = list(shelved)
1096 if opts.get(b"name"):
1094 if opts.get(b"name"):
1097 shelved.append(opts[b"name"])
1095 shelved.append(opts[b"name"])
1098
1096
1099 if interactive and opts.get(b'keep'):
1097 if interactive and opts.get(b'keep'):
1100 raise error.Abort(_(b'--keep on --interactive is not yet supported'))
1098 raise error.Abort(_(b'--keep on --interactive is not yet supported'))
1101 if abortf or continuef:
1099 if abortf or continuef:
1102 if abortf and continuef:
1100 if abortf and continuef:
1103 raise error.Abort(_(b'cannot use both abort and continue'))
1101 raise error.Abort(_(b'cannot use both abort and continue'))
1104 if shelved:
1102 if shelved:
1105 raise error.Abort(
1103 raise error.Abort(
1106 _(
1104 _(
1107 b'cannot combine abort/continue with '
1105 b'cannot combine abort/continue with '
1108 b'naming a shelved change'
1106 b'naming a shelved change'
1109 )
1107 )
1110 )
1108 )
1111 if abortf and opts.get(b'tool', False):
1109 if abortf and opts.get(b'tool', False):
1112 ui.warn(_(b'tool option will be ignored\n'))
1110 ui.warn(_(b'tool option will be ignored\n'))
1113
1111
1114 state = _loadshelvedstate(ui, repo, opts)
1112 state = _loadshelvedstate(ui, repo, opts)
1115 if abortf:
1113 if abortf:
1116 return unshelveabort(ui, repo, state)
1114 return unshelveabort(ui, repo, state)
1117 elif continuef and interactive:
1115 elif continuef and interactive:
1118 raise error.Abort(_(b'cannot use both continue and interactive'))
1116 raise error.Abort(_(b'cannot use both continue and interactive'))
1119 elif continuef:
1117 elif continuef:
1120 return unshelvecontinue(ui, repo, state, opts)
1118 return unshelvecontinue(ui, repo, state, opts)
1121 elif len(shelved) > 1:
1119 elif len(shelved) > 1:
1122 raise error.Abort(_(b'can only unshelve one change at a time'))
1120 raise error.Abort(_(b'can only unshelve one change at a time'))
1123 elif not shelved:
1121 elif not shelved:
1124 shelved = listshelves(repo)
1122 shelved = listshelves(repo)
1125 if not shelved:
1123 if not shelved:
1126 raise error.Abort(_(b'no shelved changes to apply!'))
1124 raise error.Abort(_(b'no shelved changes to apply!'))
1127 basename = util.split(shelved[0][1])[1]
1125 basename = util.split(shelved[0][1])[1]
1128 ui.status(_(b"unshelving change '%s'\n") % basename)
1126 ui.status(_(b"unshelving change '%s'\n") % basename)
1129 else:
1127 else:
1130 basename = shelved[0]
1128 basename = shelved[0]
1131
1129
1132 if not shelvedfile(repo, basename, patchextension).exists():
1130 if not shelvedfile(repo, basename, patchextension).exists():
1133 raise error.Abort(_(b"shelved change '%s' not found") % basename)
1131 raise error.Abort(_(b"shelved change '%s' not found") % basename)
1134
1132
1135 repo = repo.unfiltered()
1133 repo = repo.unfiltered()
1136 lock = tr = None
1134 lock = tr = None
1137 try:
1135 try:
1138 lock = repo.lock()
1136 lock = repo.lock()
1139 tr = repo.transaction(b'unshelve', report=lambda x: None)
1137 tr = repo.transaction(b'unshelve', report=lambda x: None)
1140 oldtiprev = len(repo)
1138 oldtiprev = len(repo)
1141
1139
1142 pctx = repo[b'.']
1140 pctx = repo[b'.']
1143 tmpwctx = pctx
1141 tmpwctx = pctx
1144 # The goal is to have a commit structure like so:
1142 # The goal is to have a commit structure like so:
1145 # ...-> pctx -> tmpwctx -> shelvectx
1143 # ...-> pctx -> tmpwctx -> shelvectx
1146 # where tmpwctx is an optional commit with the user's pending changes
1144 # 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
1145 # and shelvectx is the unshelved changes. Then we merge it all down
1148 # to the original pctx.
1146 # to the original pctx.
1149
1147
1150 activebookmark = _backupactivebookmark(repo)
1148 activebookmark = _backupactivebookmark(repo)
1151 tmpwctx, addedbefore = _commitworkingcopychanges(
1149 tmpwctx, addedbefore = _commitworkingcopychanges(
1152 ui, repo, opts, tmpwctx
1150 ui, repo, opts, tmpwctx
1153 )
1151 )
1154 repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
1152 repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
1155 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1153 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1156 branchtorestore = b''
1154 branchtorestore = b''
1157 if shelvectx.branch() != shelvectx.p1().branch():
1155 if shelvectx.branch() != shelvectx.p1().branch():
1158 branchtorestore = shelvectx.branch()
1156 branchtorestore = shelvectx.branch()
1159
1157
1160 shelvectx, ispartialunshelve = _rebaserestoredcommit(
1158 shelvectx, ispartialunshelve = _rebaserestoredcommit(
1161 ui,
1159 ui,
1162 repo,
1160 repo,
1163 opts,
1161 opts,
1164 tr,
1162 tr,
1165 oldtiprev,
1163 oldtiprev,
1166 basename,
1164 basename,
1167 pctx,
1165 pctx,
1168 tmpwctx,
1166 tmpwctx,
1169 shelvectx,
1167 shelvectx,
1170 branchtorestore,
1168 branchtorestore,
1171 activebookmark,
1169 activebookmark,
1172 )
1170 )
1173 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
1171 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
1174 with ui.configoverride(overrides, b'unshelve'):
1172 with ui.configoverride(overrides, b'unshelve'):
1175 mergefiles(ui, repo, pctx, shelvectx)
1173 mergefiles(ui, repo, pctx, shelvectx)
1176 restorebranch(ui, repo, branchtorestore)
1174 restorebranch(ui, repo, branchtorestore)
1177 shelvedstate.clear(repo)
1175 shelvedstate.clear(repo)
1178 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1176 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1179 _forgetunknownfiles(repo, shelvectx, addedbefore)
1177 _forgetunknownfiles(repo, shelvectx, addedbefore)
1180 if not ispartialunshelve:
1178 if not ispartialunshelve:
1181 unshelvecleanup(ui, repo, basename, opts)
1179 unshelvecleanup(ui, repo, basename, opts)
1182 finally:
1180 finally:
1183 if tr:
1181 if tr:
1184 tr.release()
1182 tr.release()
1185 lockmod.release(lock)
1183 lockmod.release(lock)
General Comments 0
You need to be logged in to leave comments. Login now