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