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