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