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