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