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