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