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