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