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