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