##// END OF EJS Templates
shelve: fix conversion of exceptions to strings flagged by pytype...
Matt Harbison -
r47386:e571fec5 default
parent child Browse files
Show More
@@ -1,1188 +1,1188 b''
1 # shelve.py - save/restore working directory state
1 # shelve.py - save/restore working directory state
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """save and restore changes to the working directory
8 """save and restore changes to the working directory
9
9
10 The "hg shelve" command saves changes made to the working directory
10 The "hg shelve" command saves changes made to the working directory
11 and reverts those changes, resetting the working directory to a clean
11 and reverts those changes, resetting the working directory to a clean
12 state.
12 state.
13
13
14 Later on, the "hg unshelve" command restores the changes saved by "hg
14 Later on, the "hg unshelve" command restores the changes saved by "hg
15 shelve". Changes can be restored even after updating to a different
15 shelve". Changes can be restored even after updating to a different
16 parent, in which case Mercurial's merge machinery will resolve any
16 parent, in which case Mercurial's merge machinery will resolve any
17 conflicts if necessary.
17 conflicts if necessary.
18
18
19 You can have more than one shelved change outstanding at a time; each
19 You can have more than one shelved change outstanding at a time; each
20 shelved change has a distinct name. For details, see the help for "hg
20 shelved change has a distinct name. For details, see the help for "hg
21 shelve".
21 shelve".
22 """
22 """
23 from __future__ import absolute_import
23 from __future__ import absolute_import
24
24
25 import collections
25 import collections
26 import errno
26 import errno
27 import itertools
27 import itertools
28 import stat
28 import stat
29
29
30 from .i18n import _
30 from .i18n import _
31 from .node import (
31 from .node import (
32 bin,
32 bin,
33 hex,
33 hex,
34 nullid,
34 nullid,
35 nullrev,
35 nullrev,
36 )
36 )
37 from . import (
37 from . import (
38 bookmarks,
38 bookmarks,
39 bundle2,
39 bundle2,
40 changegroup,
40 changegroup,
41 cmdutil,
41 cmdutil,
42 discovery,
42 discovery,
43 error,
43 error,
44 exchange,
44 exchange,
45 hg,
45 hg,
46 lock as lockmod,
46 lock as lockmod,
47 mdiff,
47 mdiff,
48 merge,
48 merge,
49 mergestate as mergestatemod,
49 mergestate as mergestatemod,
50 patch,
50 patch,
51 phases,
51 phases,
52 pycompat,
52 pycompat,
53 repair,
53 repair,
54 scmutil,
54 scmutil,
55 templatefilters,
55 templatefilters,
56 util,
56 util,
57 vfs as vfsmod,
57 vfs as vfsmod,
58 )
58 )
59 from .utils import (
59 from .utils import (
60 dateutil,
60 dateutil,
61 stringutil,
61 stringutil,
62 )
62 )
63
63
64 backupdir = b'shelve-backup'
64 backupdir = b'shelve-backup'
65 shelvedir = b'shelved'
65 shelvedir = b'shelved'
66 shelvefileextensions = [b'hg', b'patch', b'shelve']
66 shelvefileextensions = [b'hg', b'patch', b'shelve']
67
67
68 # we never need the user, so we use a
68 # we never need the user, so we use a
69 # generic user for all shelve operations
69 # generic user for all shelve operations
70 shelveuser = b'shelve@localhost'
70 shelveuser = b'shelve@localhost'
71
71
72
72
73 class ShelfDir(object):
73 class ShelfDir(object):
74 def __init__(self, repo, for_backups=False):
74 def __init__(self, repo, for_backups=False):
75 if for_backups:
75 if for_backups:
76 self.vfs = vfsmod.vfs(repo.vfs.join(backupdir))
76 self.vfs = vfsmod.vfs(repo.vfs.join(backupdir))
77 else:
77 else:
78 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
78 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
79
79
80 def get(self, name):
80 def get(self, name):
81 return Shelf(self.vfs, name)
81 return Shelf(self.vfs, name)
82
82
83 def listshelves(self):
83 def listshelves(self):
84 """return all shelves in repo as list of (time, name)"""
84 """return all shelves in repo as list of (time, name)"""
85 try:
85 try:
86 names = self.vfs.listdir()
86 names = self.vfs.listdir()
87 except OSError as err:
87 except OSError as err:
88 if err.errno != errno.ENOENT:
88 if err.errno != errno.ENOENT:
89 raise
89 raise
90 return []
90 return []
91 info = []
91 info = []
92 seen = set()
92 seen = set()
93 for filename in names:
93 for filename in names:
94 name = filename.rsplit(b'.', 1)[0]
94 name = filename.rsplit(b'.', 1)[0]
95 if name in seen:
95 if name in seen:
96 continue
96 continue
97 seen.add(name)
97 seen.add(name)
98 shelf = self.get(name)
98 shelf = self.get(name)
99 if not shelf.exists():
99 if not shelf.exists():
100 continue
100 continue
101 mtime = shelf.mtime()
101 mtime = shelf.mtime()
102 info.append((mtime, name))
102 info.append((mtime, name))
103 return sorted(info, reverse=True)
103 return sorted(info, reverse=True)
104
104
105
105
106 class Shelf(object):
106 class Shelf(object):
107 """Represents a shelf, including possibly multiple files storing it.
107 """Represents a shelf, including possibly multiple files storing it.
108
108
109 Old shelves will have a .patch and a .hg file. Newer shelves will
109 Old shelves will have a .patch and a .hg file. Newer shelves will
110 also have a .shelve file. This class abstracts away some of the
110 also have a .shelve file. This class abstracts away some of the
111 differences and lets you work with the shelf as a whole.
111 differences and lets you work with the shelf as a whole.
112 """
112 """
113
113
114 def __init__(self, vfs, name):
114 def __init__(self, vfs, name):
115 self.vfs = vfs
115 self.vfs = vfs
116 self.name = name
116 self.name = name
117
117
118 def exists(self):
118 def exists(self):
119 return self.vfs.exists(self.name + b'.patch') and self.vfs.exists(
119 return self.vfs.exists(self.name + b'.patch') and self.vfs.exists(
120 self.name + b'.hg'
120 self.name + b'.hg'
121 )
121 )
122
122
123 def mtime(self):
123 def mtime(self):
124 return self.vfs.stat(self.name + b'.patch')[stat.ST_MTIME]
124 return self.vfs.stat(self.name + b'.patch')[stat.ST_MTIME]
125
125
126 def writeinfo(self, info):
126 def writeinfo(self, info):
127 scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info)
127 scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info)
128
128
129 def hasinfo(self):
129 def hasinfo(self):
130 return self.vfs.exists(self.name + b'.shelve')
130 return self.vfs.exists(self.name + b'.shelve')
131
131
132 def readinfo(self):
132 def readinfo(self):
133 return scmutil.simplekeyvaluefile(
133 return scmutil.simplekeyvaluefile(
134 self.vfs, self.name + b'.shelve'
134 self.vfs, self.name + b'.shelve'
135 ).read()
135 ).read()
136
136
137 def writebundle(self, repo, bases, node):
137 def writebundle(self, repo, bases, node):
138 cgversion = changegroup.safeversion(repo)
138 cgversion = changegroup.safeversion(repo)
139 if cgversion == b'01':
139 if cgversion == b'01':
140 btype = b'HG10BZ'
140 btype = b'HG10BZ'
141 compression = None
141 compression = None
142 else:
142 else:
143 btype = b'HG20'
143 btype = b'HG20'
144 compression = b'BZ'
144 compression = b'BZ'
145
145
146 repo = repo.unfiltered()
146 repo = repo.unfiltered()
147
147
148 outgoing = discovery.outgoing(
148 outgoing = discovery.outgoing(
149 repo, missingroots=bases, ancestorsof=[node]
149 repo, missingroots=bases, ancestorsof=[node]
150 )
150 )
151 cg = changegroup.makechangegroup(repo, outgoing, cgversion, b'shelve')
151 cg = changegroup.makechangegroup(repo, outgoing, cgversion, b'shelve')
152
152
153 bundle_filename = self.vfs.join(self.name + b'.hg')
153 bundle_filename = self.vfs.join(self.name + b'.hg')
154 bundle2.writebundle(
154 bundle2.writebundle(
155 repo.ui,
155 repo.ui,
156 cg,
156 cg,
157 bundle_filename,
157 bundle_filename,
158 btype,
158 btype,
159 self.vfs,
159 self.vfs,
160 compression=compression,
160 compression=compression,
161 )
161 )
162
162
163 def applybundle(self, repo, tr):
163 def applybundle(self, repo, tr):
164 filename = self.name + b'.hg'
164 filename = self.name + b'.hg'
165 fp = self.vfs(filename)
165 fp = self.vfs(filename)
166 try:
166 try:
167 targetphase = phases.internal
167 targetphase = phases.internal
168 if not phases.supportinternal(repo):
168 if not phases.supportinternal(repo):
169 targetphase = phases.secret
169 targetphase = phases.secret
170 gen = exchange.readbundle(repo.ui, fp, filename, self.vfs)
170 gen = exchange.readbundle(repo.ui, fp, filename, self.vfs)
171 pretip = repo[b'tip']
171 pretip = repo[b'tip']
172 bundle2.applybundle(
172 bundle2.applybundle(
173 repo,
173 repo,
174 gen,
174 gen,
175 tr,
175 tr,
176 source=b'unshelve',
176 source=b'unshelve',
177 url=b'bundle:' + self.vfs.join(filename),
177 url=b'bundle:' + self.vfs.join(filename),
178 targetphase=targetphase,
178 targetphase=targetphase,
179 )
179 )
180 shelvectx = repo[b'tip']
180 shelvectx = repo[b'tip']
181 if pretip == shelvectx:
181 if pretip == shelvectx:
182 shelverev = tr.changes[b'revduplicates'][-1]
182 shelverev = tr.changes[b'revduplicates'][-1]
183 shelvectx = repo[shelverev]
183 shelvectx = repo[shelverev]
184 return shelvectx
184 return shelvectx
185 finally:
185 finally:
186 fp.close()
186 fp.close()
187
187
188 def open_patch(self, mode=b'rb'):
188 def open_patch(self, mode=b'rb'):
189 return self.vfs(self.name + b'.patch', mode)
189 return self.vfs(self.name + b'.patch', mode)
190
190
191 def _backupfilename(self, backupvfs, filename):
191 def _backupfilename(self, backupvfs, filename):
192 def gennames(base):
192 def gennames(base):
193 yield base
193 yield base
194 base, ext = base.rsplit(b'.', 1)
194 base, ext = base.rsplit(b'.', 1)
195 for i in itertools.count(1):
195 for i in itertools.count(1):
196 yield b'%s-%d.%s' % (base, i, ext)
196 yield b'%s-%d.%s' % (base, i, ext)
197
197
198 for n in gennames(filename):
198 for n in gennames(filename):
199 if not backupvfs.exists(n):
199 if not backupvfs.exists(n):
200 return backupvfs.join(n)
200 return backupvfs.join(n)
201
201
202 def movetobackup(self, backupvfs):
202 def movetobackup(self, backupvfs):
203 if not backupvfs.isdir():
203 if not backupvfs.isdir():
204 backupvfs.makedir()
204 backupvfs.makedir()
205 for suffix in shelvefileextensions:
205 for suffix in shelvefileextensions:
206 filename = self.name + b'.' + suffix
206 filename = self.name + b'.' + suffix
207 if self.vfs.exists(filename):
207 if self.vfs.exists(filename):
208 util.rename(
208 util.rename(
209 self.vfs.join(filename),
209 self.vfs.join(filename),
210 self._backupfilename(backupvfs, filename),
210 self._backupfilename(backupvfs, filename),
211 )
211 )
212
212
213 def delete(self):
213 def delete(self):
214 for ext in shelvefileextensions:
214 for ext in shelvefileextensions:
215 self.vfs.tryunlink(self.name + b'.' + ext)
215 self.vfs.tryunlink(self.name + b'.' + ext)
216
216
217
217
218 class shelvedstate(object):
218 class shelvedstate(object):
219 """Handle persistence during unshelving operations.
219 """Handle persistence during unshelving operations.
220
220
221 Handles saving and restoring a shelved state. Ensures that different
221 Handles saving and restoring a shelved state. Ensures that different
222 versions of a shelved state are possible and handles them appropriately.
222 versions of a shelved state are possible and handles them appropriately.
223 """
223 """
224
224
225 _version = 2
225 _version = 2
226 _filename = b'shelvedstate'
226 _filename = b'shelvedstate'
227 _keep = b'keep'
227 _keep = b'keep'
228 _nokeep = b'nokeep'
228 _nokeep = b'nokeep'
229 # colon is essential to differentiate from a real bookmark name
229 # colon is essential to differentiate from a real bookmark name
230 _noactivebook = b':no-active-bookmark'
230 _noactivebook = b':no-active-bookmark'
231 _interactive = b'interactive'
231 _interactive = b'interactive'
232
232
233 @classmethod
233 @classmethod
234 def _verifyandtransform(cls, d):
234 def _verifyandtransform(cls, d):
235 """Some basic shelvestate syntactic verification and transformation"""
235 """Some basic shelvestate syntactic verification and transformation"""
236 try:
236 try:
237 d[b'originalwctx'] = bin(d[b'originalwctx'])
237 d[b'originalwctx'] = bin(d[b'originalwctx'])
238 d[b'pendingctx'] = bin(d[b'pendingctx'])
238 d[b'pendingctx'] = bin(d[b'pendingctx'])
239 d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ')]
239 d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ')]
240 d[b'nodestoremove'] = [
240 d[b'nodestoremove'] = [
241 bin(h) for h in d[b'nodestoremove'].split(b' ')
241 bin(h) for h in d[b'nodestoremove'].split(b' ')
242 ]
242 ]
243 except (ValueError, TypeError, KeyError) as err:
243 except (ValueError, TypeError, KeyError) as err:
244 raise error.CorruptedState(pycompat.bytestr(err))
244 raise error.CorruptedState(stringutil.forcebytestr(err))
245
245
246 @classmethod
246 @classmethod
247 def _getversion(cls, repo):
247 def _getversion(cls, repo):
248 """Read version information from shelvestate file"""
248 """Read version information from shelvestate file"""
249 fp = repo.vfs(cls._filename)
249 fp = repo.vfs(cls._filename)
250 try:
250 try:
251 version = int(fp.readline().strip())
251 version = int(fp.readline().strip())
252 except ValueError as err:
252 except ValueError as err:
253 raise error.CorruptedState(pycompat.bytestr(err))
253 raise error.CorruptedState(stringutil.forcebytestr(err))
254 finally:
254 finally:
255 fp.close()
255 fp.close()
256 return version
256 return version
257
257
258 @classmethod
258 @classmethod
259 def _readold(cls, repo):
259 def _readold(cls, repo):
260 """Read the old position-based version of a shelvestate file"""
260 """Read the old position-based version of a shelvestate file"""
261 # Order is important, because old shelvestate file uses it
261 # Order is important, because old shelvestate file uses it
262 # to detemine values of fields (i.g. name is on the second line,
262 # to detemine values of fields (i.g. name is on the second line,
263 # originalwctx is on the third and so forth). Please do not change.
263 # originalwctx is on the third and so forth). Please do not change.
264 keys = [
264 keys = [
265 b'version',
265 b'version',
266 b'name',
266 b'name',
267 b'originalwctx',
267 b'originalwctx',
268 b'pendingctx',
268 b'pendingctx',
269 b'parents',
269 b'parents',
270 b'nodestoremove',
270 b'nodestoremove',
271 b'branchtorestore',
271 b'branchtorestore',
272 b'keep',
272 b'keep',
273 b'activebook',
273 b'activebook',
274 ]
274 ]
275 # this is executed only seldomly, so it is not a big deal
275 # this is executed only seldomly, so it is not a big deal
276 # that we open this file twice
276 # that we open this file twice
277 fp = repo.vfs(cls._filename)
277 fp = repo.vfs(cls._filename)
278 d = {}
278 d = {}
279 try:
279 try:
280 for key in keys:
280 for key in keys:
281 d[key] = fp.readline().strip()
281 d[key] = fp.readline().strip()
282 finally:
282 finally:
283 fp.close()
283 fp.close()
284 return d
284 return d
285
285
286 @classmethod
286 @classmethod
287 def load(cls, repo):
287 def load(cls, repo):
288 version = cls._getversion(repo)
288 version = cls._getversion(repo)
289 if version < cls._version:
289 if version < cls._version:
290 d = cls._readold(repo)
290 d = cls._readold(repo)
291 elif version == cls._version:
291 elif version == cls._version:
292 d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename).read(
292 d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename).read(
293 firstlinenonkeyval=True
293 firstlinenonkeyval=True
294 )
294 )
295 else:
295 else:
296 raise error.Abort(
296 raise error.Abort(
297 _(
297 _(
298 b'this version of shelve is incompatible '
298 b'this version of shelve is incompatible '
299 b'with the version used in this repo'
299 b'with the version used in this repo'
300 )
300 )
301 )
301 )
302
302
303 cls._verifyandtransform(d)
303 cls._verifyandtransform(d)
304 try:
304 try:
305 obj = cls()
305 obj = cls()
306 obj.name = d[b'name']
306 obj.name = d[b'name']
307 obj.wctx = repo[d[b'originalwctx']]
307 obj.wctx = repo[d[b'originalwctx']]
308 obj.pendingctx = repo[d[b'pendingctx']]
308 obj.pendingctx = repo[d[b'pendingctx']]
309 obj.parents = d[b'parents']
309 obj.parents = d[b'parents']
310 obj.nodestoremove = d[b'nodestoremove']
310 obj.nodestoremove = d[b'nodestoremove']
311 obj.branchtorestore = d.get(b'branchtorestore', b'')
311 obj.branchtorestore = d.get(b'branchtorestore', b'')
312 obj.keep = d.get(b'keep') == cls._keep
312 obj.keep = d.get(b'keep') == cls._keep
313 obj.activebookmark = b''
313 obj.activebookmark = b''
314 if d.get(b'activebook', b'') != cls._noactivebook:
314 if d.get(b'activebook', b'') != cls._noactivebook:
315 obj.activebookmark = d.get(b'activebook', b'')
315 obj.activebookmark = d.get(b'activebook', b'')
316 obj.interactive = d.get(b'interactive') == cls._interactive
316 obj.interactive = d.get(b'interactive') == cls._interactive
317 except (error.RepoLookupError, KeyError) as err:
317 except (error.RepoLookupError, KeyError) as err:
318 raise error.CorruptedState(pycompat.bytestr(err))
318 raise error.CorruptedState(pycompat.bytestr(err))
319
319
320 return obj
320 return obj
321
321
322 @classmethod
322 @classmethod
323 def save(
323 def save(
324 cls,
324 cls,
325 repo,
325 repo,
326 name,
326 name,
327 originalwctx,
327 originalwctx,
328 pendingctx,
328 pendingctx,
329 nodestoremove,
329 nodestoremove,
330 branchtorestore,
330 branchtorestore,
331 keep=False,
331 keep=False,
332 activebook=b'',
332 activebook=b'',
333 interactive=False,
333 interactive=False,
334 ):
334 ):
335 info = {
335 info = {
336 b"name": name,
336 b"name": name,
337 b"originalwctx": hex(originalwctx.node()),
337 b"originalwctx": hex(originalwctx.node()),
338 b"pendingctx": hex(pendingctx.node()),
338 b"pendingctx": hex(pendingctx.node()),
339 b"parents": b' '.join([hex(p) for p in repo.dirstate.parents()]),
339 b"parents": b' '.join([hex(p) for p in repo.dirstate.parents()]),
340 b"nodestoremove": b' '.join([hex(n) for n in nodestoremove]),
340 b"nodestoremove": b' '.join([hex(n) for n in nodestoremove]),
341 b"branchtorestore": branchtorestore,
341 b"branchtorestore": branchtorestore,
342 b"keep": cls._keep if keep else cls._nokeep,
342 b"keep": cls._keep if keep else cls._nokeep,
343 b"activebook": activebook or cls._noactivebook,
343 b"activebook": activebook or cls._noactivebook,
344 }
344 }
345 if interactive:
345 if interactive:
346 info[b'interactive'] = cls._interactive
346 info[b'interactive'] = cls._interactive
347 scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write(
347 scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write(
348 info, firstline=(b"%d" % cls._version)
348 info, firstline=(b"%d" % cls._version)
349 )
349 )
350
350
351 @classmethod
351 @classmethod
352 def clear(cls, repo):
352 def clear(cls, repo):
353 repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
353 repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
354
354
355
355
356 def cleanupoldbackups(repo):
356 def cleanupoldbackups(repo):
357 maxbackups = repo.ui.configint(b'shelve', b'maxbackups')
357 maxbackups = repo.ui.configint(b'shelve', b'maxbackups')
358 backup_dir = ShelfDir(repo, for_backups=True)
358 backup_dir = ShelfDir(repo, for_backups=True)
359 hgfiles = backup_dir.listshelves()
359 hgfiles = backup_dir.listshelves()
360 if maxbackups > 0 and maxbackups < len(hgfiles):
360 if maxbackups > 0 and maxbackups < len(hgfiles):
361 bordermtime = hgfiles[maxbackups - 1][0]
361 bordermtime = hgfiles[maxbackups - 1][0]
362 else:
362 else:
363 bordermtime = None
363 bordermtime = None
364 for mtime, name in hgfiles[maxbackups:]:
364 for mtime, name in hgfiles[maxbackups:]:
365 if mtime == bordermtime:
365 if mtime == bordermtime:
366 # keep it, because timestamp can't decide exact order of backups
366 # keep it, because timestamp can't decide exact order of backups
367 continue
367 continue
368 backup_dir.get(name).delete()
368 backup_dir.get(name).delete()
369
369
370
370
371 def _backupactivebookmark(repo):
371 def _backupactivebookmark(repo):
372 activebookmark = repo._activebookmark
372 activebookmark = repo._activebookmark
373 if activebookmark:
373 if activebookmark:
374 bookmarks.deactivate(repo)
374 bookmarks.deactivate(repo)
375 return activebookmark
375 return activebookmark
376
376
377
377
378 def _restoreactivebookmark(repo, mark):
378 def _restoreactivebookmark(repo, mark):
379 if mark:
379 if mark:
380 bookmarks.activate(repo, mark)
380 bookmarks.activate(repo, mark)
381
381
382
382
383 def _aborttransaction(repo, tr):
383 def _aborttransaction(repo, tr):
384 """Abort current transaction for shelve/unshelve, but keep dirstate"""
384 """Abort current transaction for shelve/unshelve, but keep dirstate"""
385 dirstatebackupname = b'dirstate.shelve'
385 dirstatebackupname = b'dirstate.shelve'
386 repo.dirstate.savebackup(tr, dirstatebackupname)
386 repo.dirstate.savebackup(tr, dirstatebackupname)
387 tr.abort()
387 tr.abort()
388 repo.dirstate.restorebackup(None, dirstatebackupname)
388 repo.dirstate.restorebackup(None, dirstatebackupname)
389
389
390
390
391 def getshelvename(repo, parent, opts):
391 def getshelvename(repo, parent, opts):
392 """Decide on the name this shelve is going to have"""
392 """Decide on the name this shelve is going to have"""
393
393
394 def gennames():
394 def gennames():
395 yield label
395 yield label
396 for i in itertools.count(1):
396 for i in itertools.count(1):
397 yield b'%s-%02d' % (label, i)
397 yield b'%s-%02d' % (label, i)
398
398
399 name = opts.get(b'name')
399 name = opts.get(b'name')
400 label = repo._activebookmark or parent.branch() or b'default'
400 label = repo._activebookmark or parent.branch() or b'default'
401 # slashes aren't allowed in filenames, therefore we rename it
401 # slashes aren't allowed in filenames, therefore we rename it
402 label = label.replace(b'/', b'_')
402 label = label.replace(b'/', b'_')
403 label = label.replace(b'\\', b'_')
403 label = label.replace(b'\\', b'_')
404 # filenames must not start with '.' as it should not be hidden
404 # filenames must not start with '.' as it should not be hidden
405 if label.startswith(b'.'):
405 if label.startswith(b'.'):
406 label = label.replace(b'.', b'_', 1)
406 label = label.replace(b'.', b'_', 1)
407
407
408 if name:
408 if name:
409 if ShelfDir(repo).get(name).exists():
409 if ShelfDir(repo).get(name).exists():
410 e = _(b"a shelved change named '%s' already exists") % name
410 e = _(b"a shelved change named '%s' already exists") % name
411 raise error.Abort(e)
411 raise error.Abort(e)
412
412
413 # ensure we are not creating a subdirectory or a hidden file
413 # ensure we are not creating a subdirectory or a hidden file
414 if b'/' in name or b'\\' in name:
414 if b'/' in name or b'\\' in name:
415 raise error.Abort(
415 raise error.Abort(
416 _(b'shelved change names can not contain slashes')
416 _(b'shelved change names can not contain slashes')
417 )
417 )
418 if name.startswith(b'.'):
418 if name.startswith(b'.'):
419 raise error.Abort(_(b"shelved change names can not start with '.'"))
419 raise error.Abort(_(b"shelved change names can not start with '.'"))
420
420
421 else:
421 else:
422 shelf_dir = ShelfDir(repo)
422 shelf_dir = ShelfDir(repo)
423 for n in gennames():
423 for n in gennames():
424 if not shelf_dir.get(n).exists():
424 if not shelf_dir.get(n).exists():
425 name = n
425 name = n
426 break
426 break
427
427
428 return name
428 return name
429
429
430
430
431 def mutableancestors(ctx):
431 def mutableancestors(ctx):
432 """return all mutable ancestors for ctx (included)
432 """return all mutable ancestors for ctx (included)
433
433
434 Much faster than the revset ancestors(ctx) & draft()"""
434 Much faster than the revset ancestors(ctx) & draft()"""
435 seen = {nullrev}
435 seen = {nullrev}
436 visit = collections.deque()
436 visit = collections.deque()
437 visit.append(ctx)
437 visit.append(ctx)
438 while visit:
438 while visit:
439 ctx = visit.popleft()
439 ctx = visit.popleft()
440 yield ctx.node()
440 yield ctx.node()
441 for parent in ctx.parents():
441 for parent in ctx.parents():
442 rev = parent.rev()
442 rev = parent.rev()
443 if rev not in seen:
443 if rev not in seen:
444 seen.add(rev)
444 seen.add(rev)
445 if parent.mutable():
445 if parent.mutable():
446 visit.append(parent)
446 visit.append(parent)
447
447
448
448
449 def getcommitfunc(extra, interactive, editor=False):
449 def getcommitfunc(extra, interactive, editor=False):
450 def commitfunc(ui, repo, message, match, opts):
450 def commitfunc(ui, repo, message, match, opts):
451 hasmq = util.safehasattr(repo, b'mq')
451 hasmq = util.safehasattr(repo, b'mq')
452 if hasmq:
452 if hasmq:
453 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
453 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
454
454
455 targetphase = phases.internal
455 targetphase = phases.internal
456 if not phases.supportinternal(repo):
456 if not phases.supportinternal(repo):
457 targetphase = phases.secret
457 targetphase = phases.secret
458 overrides = {(b'phases', b'new-commit'): targetphase}
458 overrides = {(b'phases', b'new-commit'): targetphase}
459 try:
459 try:
460 editor_ = False
460 editor_ = False
461 if editor:
461 if editor:
462 editor_ = cmdutil.getcommiteditor(
462 editor_ = cmdutil.getcommiteditor(
463 editform=b'shelve.shelve', **pycompat.strkwargs(opts)
463 editform=b'shelve.shelve', **pycompat.strkwargs(opts)
464 )
464 )
465 with repo.ui.configoverride(overrides):
465 with repo.ui.configoverride(overrides):
466 return repo.commit(
466 return repo.commit(
467 message,
467 message,
468 shelveuser,
468 shelveuser,
469 opts.get(b'date'),
469 opts.get(b'date'),
470 match,
470 match,
471 editor=editor_,
471 editor=editor_,
472 extra=extra,
472 extra=extra,
473 )
473 )
474 finally:
474 finally:
475 if hasmq:
475 if hasmq:
476 repo.mq.checkapplied = saved
476 repo.mq.checkapplied = saved
477
477
478 def interactivecommitfunc(ui, repo, *pats, **opts):
478 def interactivecommitfunc(ui, repo, *pats, **opts):
479 opts = pycompat.byteskwargs(opts)
479 opts = pycompat.byteskwargs(opts)
480 match = scmutil.match(repo[b'.'], pats, {})
480 match = scmutil.match(repo[b'.'], pats, {})
481 message = opts[b'message']
481 message = opts[b'message']
482 return commitfunc(ui, repo, message, match, opts)
482 return commitfunc(ui, repo, message, match, opts)
483
483
484 return interactivecommitfunc if interactive else commitfunc
484 return interactivecommitfunc if interactive else commitfunc
485
485
486
486
487 def _nothingtoshelvemessaging(ui, repo, pats, opts):
487 def _nothingtoshelvemessaging(ui, repo, pats, opts):
488 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
488 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
489 if stat.deleted:
489 if stat.deleted:
490 ui.status(
490 ui.status(
491 _(b"nothing changed (%d missing files, see 'hg status')\n")
491 _(b"nothing changed (%d missing files, see 'hg status')\n")
492 % len(stat.deleted)
492 % len(stat.deleted)
493 )
493 )
494 else:
494 else:
495 ui.status(_(b"nothing changed\n"))
495 ui.status(_(b"nothing changed\n"))
496
496
497
497
498 def _shelvecreatedcommit(repo, node, name, match):
498 def _shelvecreatedcommit(repo, node, name, match):
499 info = {b'node': hex(node)}
499 info = {b'node': hex(node)}
500 shelf = ShelfDir(repo).get(name)
500 shelf = ShelfDir(repo).get(name)
501 shelf.writeinfo(info)
501 shelf.writeinfo(info)
502 bases = list(mutableancestors(repo[node]))
502 bases = list(mutableancestors(repo[node]))
503 shelf.writebundle(repo, bases, node)
503 shelf.writebundle(repo, bases, node)
504 with shelf.open_patch(b'wb') as fp:
504 with shelf.open_patch(b'wb') as fp:
505 cmdutil.exportfile(
505 cmdutil.exportfile(
506 repo, [node], fp, opts=mdiff.diffopts(git=True), match=match
506 repo, [node], fp, opts=mdiff.diffopts(git=True), match=match
507 )
507 )
508
508
509
509
510 def _includeunknownfiles(repo, pats, opts, extra):
510 def _includeunknownfiles(repo, pats, opts, extra):
511 s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True)
511 s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True)
512 if s.unknown:
512 if s.unknown:
513 extra[b'shelve_unknown'] = b'\0'.join(s.unknown)
513 extra[b'shelve_unknown'] = b'\0'.join(s.unknown)
514 repo[None].add(s.unknown)
514 repo[None].add(s.unknown)
515
515
516
516
517 def _finishshelve(repo, tr):
517 def _finishshelve(repo, tr):
518 if phases.supportinternal(repo):
518 if phases.supportinternal(repo):
519 tr.close()
519 tr.close()
520 else:
520 else:
521 _aborttransaction(repo, tr)
521 _aborttransaction(repo, tr)
522
522
523
523
524 def createcmd(ui, repo, pats, opts):
524 def createcmd(ui, repo, pats, opts):
525 """subcommand that creates a new shelve"""
525 """subcommand that creates a new shelve"""
526 with repo.wlock():
526 with repo.wlock():
527 cmdutil.checkunfinished(repo)
527 cmdutil.checkunfinished(repo)
528 return _docreatecmd(ui, repo, pats, opts)
528 return _docreatecmd(ui, repo, pats, opts)
529
529
530
530
531 def _docreatecmd(ui, repo, pats, opts):
531 def _docreatecmd(ui, repo, pats, opts):
532 wctx = repo[None]
532 wctx = repo[None]
533 parents = wctx.parents()
533 parents = wctx.parents()
534 parent = parents[0]
534 parent = parents[0]
535 origbranch = wctx.branch()
535 origbranch = wctx.branch()
536
536
537 if parent.node() != nullid:
537 if parent.node() != nullid:
538 desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0]
538 desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0]
539 else:
539 else:
540 desc = b'(changes in empty repository)'
540 desc = b'(changes in empty repository)'
541
541
542 if not opts.get(b'message'):
542 if not opts.get(b'message'):
543 opts[b'message'] = desc
543 opts[b'message'] = desc
544
544
545 lock = tr = activebookmark = None
545 lock = tr = activebookmark = None
546 try:
546 try:
547 lock = repo.lock()
547 lock = repo.lock()
548
548
549 # use an uncommitted transaction to generate the bundle to avoid
549 # use an uncommitted transaction to generate the bundle to avoid
550 # pull races. ensure we don't print the abort message to stderr.
550 # pull races. ensure we don't print the abort message to stderr.
551 tr = repo.transaction(b'shelve', report=lambda x: None)
551 tr = repo.transaction(b'shelve', report=lambda x: None)
552
552
553 interactive = opts.get(b'interactive', False)
553 interactive = opts.get(b'interactive', False)
554 includeunknown = opts.get(b'unknown', False) and not opts.get(
554 includeunknown = opts.get(b'unknown', False) and not opts.get(
555 b'addremove', False
555 b'addremove', False
556 )
556 )
557
557
558 name = getshelvename(repo, parent, opts)
558 name = getshelvename(repo, parent, opts)
559 activebookmark = _backupactivebookmark(repo)
559 activebookmark = _backupactivebookmark(repo)
560 extra = {b'internal': b'shelve'}
560 extra = {b'internal': b'shelve'}
561 if includeunknown:
561 if includeunknown:
562 _includeunknownfiles(repo, pats, opts, extra)
562 _includeunknownfiles(repo, pats, opts, extra)
563
563
564 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
564 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
565 # In non-bare shelve we don't store newly created branch
565 # In non-bare shelve we don't store newly created branch
566 # at bundled commit
566 # at bundled commit
567 repo.dirstate.setbranch(repo[b'.'].branch())
567 repo.dirstate.setbranch(repo[b'.'].branch())
568
568
569 commitfunc = getcommitfunc(extra, interactive, editor=True)
569 commitfunc = getcommitfunc(extra, interactive, editor=True)
570 if not interactive:
570 if not interactive:
571 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
571 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
572 else:
572 else:
573 node = cmdutil.dorecord(
573 node = cmdutil.dorecord(
574 ui,
574 ui,
575 repo,
575 repo,
576 commitfunc,
576 commitfunc,
577 None,
577 None,
578 False,
578 False,
579 cmdutil.recordfilter,
579 cmdutil.recordfilter,
580 *pats,
580 *pats,
581 **pycompat.strkwargs(opts)
581 **pycompat.strkwargs(opts)
582 )
582 )
583 if not node:
583 if not node:
584 _nothingtoshelvemessaging(ui, repo, pats, opts)
584 _nothingtoshelvemessaging(ui, repo, pats, opts)
585 return 1
585 return 1
586
586
587 # Create a matcher so that prefetch doesn't attempt to fetch
587 # Create a matcher so that prefetch doesn't attempt to fetch
588 # the entire repository pointlessly, and as an optimisation
588 # the entire repository pointlessly, and as an optimisation
589 # for movedirstate, if needed.
589 # for movedirstate, if needed.
590 match = scmutil.matchfiles(repo, repo[node].files())
590 match = scmutil.matchfiles(repo, repo[node].files())
591 _shelvecreatedcommit(repo, node, name, match)
591 _shelvecreatedcommit(repo, node, name, match)
592
592
593 ui.status(_(b'shelved as %s\n') % name)
593 ui.status(_(b'shelved as %s\n') % name)
594 if opts[b'keep']:
594 if opts[b'keep']:
595 with repo.dirstate.parentchange():
595 with repo.dirstate.parentchange():
596 scmutil.movedirstate(repo, parent, match)
596 scmutil.movedirstate(repo, parent, match)
597 else:
597 else:
598 hg.update(repo, parent.node())
598 hg.update(repo, parent.node())
599 ms = mergestatemod.mergestate.read(repo)
599 ms = mergestatemod.mergestate.read(repo)
600 if not ms.unresolvedcount():
600 if not ms.unresolvedcount():
601 ms.reset()
601 ms.reset()
602
602
603 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
603 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
604 repo.dirstate.setbranch(origbranch)
604 repo.dirstate.setbranch(origbranch)
605
605
606 _finishshelve(repo, tr)
606 _finishshelve(repo, tr)
607 finally:
607 finally:
608 _restoreactivebookmark(repo, activebookmark)
608 _restoreactivebookmark(repo, activebookmark)
609 lockmod.release(tr, lock)
609 lockmod.release(tr, lock)
610
610
611
611
612 def _isbareshelve(pats, opts):
612 def _isbareshelve(pats, opts):
613 return (
613 return (
614 not pats
614 not pats
615 and not opts.get(b'interactive', False)
615 and not opts.get(b'interactive', False)
616 and not opts.get(b'include', False)
616 and not opts.get(b'include', False)
617 and not opts.get(b'exclude', False)
617 and not opts.get(b'exclude', False)
618 )
618 )
619
619
620
620
621 def _iswctxonnewbranch(repo):
621 def _iswctxonnewbranch(repo):
622 return repo[None].branch() != repo[b'.'].branch()
622 return repo[None].branch() != repo[b'.'].branch()
623
623
624
624
625 def cleanupcmd(ui, repo):
625 def cleanupcmd(ui, repo):
626 """subcommand that deletes all shelves"""
626 """subcommand that deletes all shelves"""
627
627
628 with repo.wlock():
628 with repo.wlock():
629 shelf_dir = ShelfDir(repo)
629 shelf_dir = ShelfDir(repo)
630 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
630 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
631 for _mtime, name in shelf_dir.listshelves():
631 for _mtime, name in shelf_dir.listshelves():
632 shelf_dir.get(name).movetobackup(backupvfs)
632 shelf_dir.get(name).movetobackup(backupvfs)
633 cleanupoldbackups(repo)
633 cleanupoldbackups(repo)
634
634
635
635
636 def deletecmd(ui, repo, pats):
636 def deletecmd(ui, repo, pats):
637 """subcommand that deletes a specific shelve"""
637 """subcommand that deletes a specific shelve"""
638 if not pats:
638 if not pats:
639 raise error.InputError(_(b'no shelved changes specified!'))
639 raise error.InputError(_(b'no shelved changes specified!'))
640 with repo.wlock():
640 with repo.wlock():
641 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
641 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
642 for name in pats:
642 for name in pats:
643 shelf = ShelfDir(repo).get(name)
643 shelf = ShelfDir(repo).get(name)
644 if not shelf.exists():
644 if not shelf.exists():
645 raise error.InputError(
645 raise error.InputError(
646 _(b"shelved change '%s' not found") % name
646 _(b"shelved change '%s' not found") % name
647 )
647 )
648 shelf.movetobackup(backupvfs)
648 shelf.movetobackup(backupvfs)
649 cleanupoldbackups(repo)
649 cleanupoldbackups(repo)
650
650
651
651
652 def listcmd(ui, repo, pats, opts):
652 def listcmd(ui, repo, pats, opts):
653 """subcommand that displays the list of shelves"""
653 """subcommand that displays the list of shelves"""
654 pats = set(pats)
654 pats = set(pats)
655 width = 80
655 width = 80
656 if not ui.plain():
656 if not ui.plain():
657 width = ui.termwidth()
657 width = ui.termwidth()
658 namelabel = b'shelve.newest'
658 namelabel = b'shelve.newest'
659 ui.pager(b'shelve')
659 ui.pager(b'shelve')
660 shelf_dir = ShelfDir(repo)
660 shelf_dir = ShelfDir(repo)
661 for mtime, name in shelf_dir.listshelves():
661 for mtime, name in shelf_dir.listshelves():
662 if pats and name not in pats:
662 if pats and name not in pats:
663 continue
663 continue
664 ui.write(name, label=namelabel)
664 ui.write(name, label=namelabel)
665 namelabel = b'shelve.name'
665 namelabel = b'shelve.name'
666 if ui.quiet:
666 if ui.quiet:
667 ui.write(b'\n')
667 ui.write(b'\n')
668 continue
668 continue
669 ui.write(b' ' * (16 - len(name)))
669 ui.write(b' ' * (16 - len(name)))
670 used = 16
670 used = 16
671 date = dateutil.makedate(mtime)
671 date = dateutil.makedate(mtime)
672 age = b'(%s)' % templatefilters.age(date, abbrev=True)
672 age = b'(%s)' % templatefilters.age(date, abbrev=True)
673 ui.write(age, label=b'shelve.age')
673 ui.write(age, label=b'shelve.age')
674 ui.write(b' ' * (12 - len(age)))
674 ui.write(b' ' * (12 - len(age)))
675 used += 12
675 used += 12
676 with shelf_dir.get(name).open_patch() as fp:
676 with shelf_dir.get(name).open_patch() as fp:
677 while True:
677 while True:
678 line = fp.readline()
678 line = fp.readline()
679 if not line:
679 if not line:
680 break
680 break
681 if not line.startswith(b'#'):
681 if not line.startswith(b'#'):
682 desc = line.rstrip()
682 desc = line.rstrip()
683 if ui.formatted():
683 if ui.formatted():
684 desc = stringutil.ellipsis(desc, width - used)
684 desc = stringutil.ellipsis(desc, width - used)
685 ui.write(desc)
685 ui.write(desc)
686 break
686 break
687 ui.write(b'\n')
687 ui.write(b'\n')
688 if not (opts[b'patch'] or opts[b'stat']):
688 if not (opts[b'patch'] or opts[b'stat']):
689 continue
689 continue
690 difflines = fp.readlines()
690 difflines = fp.readlines()
691 if opts[b'patch']:
691 if opts[b'patch']:
692 for chunk, label in patch.difflabel(iter, difflines):
692 for chunk, label in patch.difflabel(iter, difflines):
693 ui.write(chunk, label=label)
693 ui.write(chunk, label=label)
694 if opts[b'stat']:
694 if opts[b'stat']:
695 for chunk, label in patch.diffstatui(difflines, width=width):
695 for chunk, label in patch.diffstatui(difflines, width=width):
696 ui.write(chunk, label=label)
696 ui.write(chunk, label=label)
697
697
698
698
699 def patchcmds(ui, repo, pats, opts):
699 def patchcmds(ui, repo, pats, opts):
700 """subcommand that displays shelves"""
700 """subcommand that displays shelves"""
701 shelf_dir = ShelfDir(repo)
701 shelf_dir = ShelfDir(repo)
702 if len(pats) == 0:
702 if len(pats) == 0:
703 shelves = shelf_dir.listshelves()
703 shelves = shelf_dir.listshelves()
704 if not shelves:
704 if not shelves:
705 raise error.Abort(_(b"there are no shelves to show"))
705 raise error.Abort(_(b"there are no shelves to show"))
706 mtime, name = shelves[0]
706 mtime, name = shelves[0]
707 pats = [name]
707 pats = [name]
708
708
709 for shelfname in pats:
709 for shelfname in pats:
710 if not shelf_dir.get(shelfname).exists():
710 if not shelf_dir.get(shelfname).exists():
711 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
711 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
712
712
713 listcmd(ui, repo, pats, opts)
713 listcmd(ui, repo, pats, opts)
714
714
715
715
716 def checkparents(repo, state):
716 def checkparents(repo, state):
717 """check parent while resuming an unshelve"""
717 """check parent while resuming an unshelve"""
718 if state.parents != repo.dirstate.parents():
718 if state.parents != repo.dirstate.parents():
719 raise error.Abort(
719 raise error.Abort(
720 _(b'working directory parents do not match unshelve state')
720 _(b'working directory parents do not match unshelve state')
721 )
721 )
722
722
723
723
724 def _loadshelvedstate(ui, repo, opts):
724 def _loadshelvedstate(ui, repo, opts):
725 try:
725 try:
726 state = shelvedstate.load(repo)
726 state = shelvedstate.load(repo)
727 if opts.get(b'keep') is None:
727 if opts.get(b'keep') is None:
728 opts[b'keep'] = state.keep
728 opts[b'keep'] = state.keep
729 except IOError as err:
729 except IOError as err:
730 if err.errno != errno.ENOENT:
730 if err.errno != errno.ENOENT:
731 raise
731 raise
732 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
732 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
733 except error.CorruptedState as err:
733 except error.CorruptedState as err:
734 ui.debug(pycompat.bytestr(err) + b'\n')
734 ui.debug(pycompat.bytestr(err) + b'\n')
735 if opts.get(b'continue'):
735 if opts.get(b'continue'):
736 msg = _(b'corrupted shelved state file')
736 msg = _(b'corrupted shelved state file')
737 hint = _(
737 hint = _(
738 b'please run hg unshelve --abort to abort unshelve '
738 b'please run hg unshelve --abort to abort unshelve '
739 b'operation'
739 b'operation'
740 )
740 )
741 raise error.Abort(msg, hint=hint)
741 raise error.Abort(msg, hint=hint)
742 elif opts.get(b'abort'):
742 elif opts.get(b'abort'):
743 shelvedstate.clear(repo)
743 shelvedstate.clear(repo)
744 raise error.Abort(
744 raise error.Abort(
745 _(
745 _(
746 b'could not read shelved state file, your '
746 b'could not read shelved state file, your '
747 b'working copy may be in an unexpected state\n'
747 b'working copy may be in an unexpected state\n'
748 b'please update to some commit\n'
748 b'please update to some commit\n'
749 )
749 )
750 )
750 )
751 return state
751 return state
752
752
753
753
754 def unshelveabort(ui, repo, state):
754 def unshelveabort(ui, repo, state):
755 """subcommand that abort an in-progress unshelve"""
755 """subcommand that abort an in-progress unshelve"""
756 with repo.lock():
756 with repo.lock():
757 try:
757 try:
758 checkparents(repo, state)
758 checkparents(repo, state)
759
759
760 merge.clean_update(state.pendingctx)
760 merge.clean_update(state.pendingctx)
761 if state.activebookmark and state.activebookmark in repo._bookmarks:
761 if state.activebookmark and state.activebookmark in repo._bookmarks:
762 bookmarks.activate(repo, state.activebookmark)
762 bookmarks.activate(repo, state.activebookmark)
763 mergefiles(ui, repo, state.wctx, state.pendingctx)
763 mergefiles(ui, repo, state.wctx, state.pendingctx)
764 if not phases.supportinternal(repo):
764 if not phases.supportinternal(repo):
765 repair.strip(
765 repair.strip(
766 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
766 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
767 )
767 )
768 finally:
768 finally:
769 shelvedstate.clear(repo)
769 shelvedstate.clear(repo)
770 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
770 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
771
771
772
772
773 def hgabortunshelve(ui, repo):
773 def hgabortunshelve(ui, repo):
774 """logic to abort unshelve using 'hg abort"""
774 """logic to abort unshelve using 'hg abort"""
775 with repo.wlock():
775 with repo.wlock():
776 state = _loadshelvedstate(ui, repo, {b'abort': True})
776 state = _loadshelvedstate(ui, repo, {b'abort': True})
777 return unshelveabort(ui, repo, state)
777 return unshelveabort(ui, repo, state)
778
778
779
779
780 def mergefiles(ui, repo, wctx, shelvectx):
780 def mergefiles(ui, repo, wctx, shelvectx):
781 """updates to wctx and merges the changes from shelvectx into the
781 """updates to wctx and merges the changes from shelvectx into the
782 dirstate."""
782 dirstate."""
783 with ui.configoverride({(b'ui', b'quiet'): True}):
783 with ui.configoverride({(b'ui', b'quiet'): True}):
784 hg.update(repo, wctx.node())
784 hg.update(repo, wctx.node())
785 ui.pushbuffer(True)
785 ui.pushbuffer(True)
786 cmdutil.revert(ui, repo, shelvectx)
786 cmdutil.revert(ui, repo, shelvectx)
787 ui.popbuffer()
787 ui.popbuffer()
788
788
789
789
790 def restorebranch(ui, repo, branchtorestore):
790 def restorebranch(ui, repo, branchtorestore):
791 if branchtorestore and branchtorestore != repo.dirstate.branch():
791 if branchtorestore and branchtorestore != repo.dirstate.branch():
792 repo.dirstate.setbranch(branchtorestore)
792 repo.dirstate.setbranch(branchtorestore)
793 ui.status(
793 ui.status(
794 _(b'marked working directory as branch %s\n') % branchtorestore
794 _(b'marked working directory as branch %s\n') % branchtorestore
795 )
795 )
796
796
797
797
798 def unshelvecleanup(ui, repo, name, opts):
798 def unshelvecleanup(ui, repo, name, opts):
799 """remove related files after an unshelve"""
799 """remove related files after an unshelve"""
800 if not opts.get(b'keep'):
800 if not opts.get(b'keep'):
801 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
801 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
802 ShelfDir(repo).get(name).movetobackup(backupvfs)
802 ShelfDir(repo).get(name).movetobackup(backupvfs)
803 cleanupoldbackups(repo)
803 cleanupoldbackups(repo)
804
804
805
805
806 def unshelvecontinue(ui, repo, state, opts):
806 def unshelvecontinue(ui, repo, state, opts):
807 """subcommand to continue an in-progress unshelve"""
807 """subcommand to continue an in-progress unshelve"""
808 # We're finishing off a merge. First parent is our original
808 # We're finishing off a merge. First parent is our original
809 # parent, second is the temporary "fake" commit we're unshelving.
809 # parent, second is the temporary "fake" commit we're unshelving.
810 interactive = state.interactive
810 interactive = state.interactive
811 basename = state.name
811 basename = state.name
812 with repo.lock():
812 with repo.lock():
813 checkparents(repo, state)
813 checkparents(repo, state)
814 ms = mergestatemod.mergestate.read(repo)
814 ms = mergestatemod.mergestate.read(repo)
815 if ms.unresolvedcount():
815 if ms.unresolvedcount():
816 raise error.Abort(
816 raise error.Abort(
817 _(b"unresolved conflicts, can't continue"),
817 _(b"unresolved conflicts, can't continue"),
818 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
818 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
819 )
819 )
820
820
821 shelvectx = repo[state.parents[1]]
821 shelvectx = repo[state.parents[1]]
822 pendingctx = state.pendingctx
822 pendingctx = state.pendingctx
823
823
824 with repo.dirstate.parentchange():
824 with repo.dirstate.parentchange():
825 repo.setparents(state.pendingctx.node(), nullid)
825 repo.setparents(state.pendingctx.node(), nullid)
826 repo.dirstate.write(repo.currenttransaction())
826 repo.dirstate.write(repo.currenttransaction())
827
827
828 targetphase = phases.internal
828 targetphase = phases.internal
829 if not phases.supportinternal(repo):
829 if not phases.supportinternal(repo):
830 targetphase = phases.secret
830 targetphase = phases.secret
831 overrides = {(b'phases', b'new-commit'): targetphase}
831 overrides = {(b'phases', b'new-commit'): targetphase}
832 with repo.ui.configoverride(overrides, b'unshelve'):
832 with repo.ui.configoverride(overrides, b'unshelve'):
833 with repo.dirstate.parentchange():
833 with repo.dirstate.parentchange():
834 repo.setparents(state.parents[0], nullid)
834 repo.setparents(state.parents[0], nullid)
835 newnode, ispartialunshelve = _createunshelvectx(
835 newnode, ispartialunshelve = _createunshelvectx(
836 ui, repo, shelvectx, basename, interactive, opts
836 ui, repo, shelvectx, basename, interactive, opts
837 )
837 )
838
838
839 if newnode is None:
839 if newnode is None:
840 shelvectx = state.pendingctx
840 shelvectx = state.pendingctx
841 msg = _(
841 msg = _(
842 b'note: unshelved changes already existed '
842 b'note: unshelved changes already existed '
843 b'in the working copy\n'
843 b'in the working copy\n'
844 )
844 )
845 ui.status(msg)
845 ui.status(msg)
846 else:
846 else:
847 # only strip the shelvectx if we produced one
847 # only strip the shelvectx if we produced one
848 state.nodestoremove.append(newnode)
848 state.nodestoremove.append(newnode)
849 shelvectx = repo[newnode]
849 shelvectx = repo[newnode]
850
850
851 merge.update(pendingctx)
851 merge.update(pendingctx)
852 mergefiles(ui, repo, state.wctx, shelvectx)
852 mergefiles(ui, repo, state.wctx, shelvectx)
853 restorebranch(ui, repo, state.branchtorestore)
853 restorebranch(ui, repo, state.branchtorestore)
854
854
855 if not phases.supportinternal(repo):
855 if not phases.supportinternal(repo):
856 repair.strip(
856 repair.strip(
857 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
857 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
858 )
858 )
859 shelvedstate.clear(repo)
859 shelvedstate.clear(repo)
860 if not ispartialunshelve:
860 if not ispartialunshelve:
861 unshelvecleanup(ui, repo, state.name, opts)
861 unshelvecleanup(ui, repo, state.name, opts)
862 _restoreactivebookmark(repo, state.activebookmark)
862 _restoreactivebookmark(repo, state.activebookmark)
863 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
863 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
864
864
865
865
866 def hgcontinueunshelve(ui, repo):
866 def hgcontinueunshelve(ui, repo):
867 """logic to resume unshelve using 'hg continue'"""
867 """logic to resume unshelve using 'hg continue'"""
868 with repo.wlock():
868 with repo.wlock():
869 state = _loadshelvedstate(ui, repo, {b'continue': True})
869 state = _loadshelvedstate(ui, repo, {b'continue': True})
870 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
870 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
871
871
872
872
873 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
873 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
874 """Temporarily commit working copy changes before moving unshelve commit"""
874 """Temporarily commit working copy changes before moving unshelve commit"""
875 # Store pending changes in a commit and remember added in case a shelve
875 # Store pending changes in a commit and remember added in case a shelve
876 # contains unknown files that are part of the pending change
876 # contains unknown files that are part of the pending change
877 s = repo.status()
877 s = repo.status()
878 addedbefore = frozenset(s.added)
878 addedbefore = frozenset(s.added)
879 if not (s.modified or s.added or s.removed):
879 if not (s.modified or s.added or s.removed):
880 return tmpwctx, addedbefore
880 return tmpwctx, addedbefore
881 ui.status(
881 ui.status(
882 _(
882 _(
883 b"temporarily committing pending changes "
883 b"temporarily committing pending changes "
884 b"(restore with 'hg unshelve --abort')\n"
884 b"(restore with 'hg unshelve --abort')\n"
885 )
885 )
886 )
886 )
887 extra = {b'internal': b'shelve'}
887 extra = {b'internal': b'shelve'}
888 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
888 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
889 tempopts = {}
889 tempopts = {}
890 tempopts[b'message'] = b"pending changes temporary commit"
890 tempopts[b'message'] = b"pending changes temporary commit"
891 tempopts[b'date'] = opts.get(b'date')
891 tempopts[b'date'] = opts.get(b'date')
892 with ui.configoverride({(b'ui', b'quiet'): True}):
892 with ui.configoverride({(b'ui', b'quiet'): True}):
893 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
893 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
894 tmpwctx = repo[node]
894 tmpwctx = repo[node]
895 return tmpwctx, addedbefore
895 return tmpwctx, addedbefore
896
896
897
897
898 def _unshelverestorecommit(ui, repo, tr, basename):
898 def _unshelverestorecommit(ui, repo, tr, basename):
899 """Recreate commit in the repository during the unshelve"""
899 """Recreate commit in the repository during the unshelve"""
900 repo = repo.unfiltered()
900 repo = repo.unfiltered()
901 node = None
901 node = None
902 shelf = ShelfDir(repo).get(basename)
902 shelf = ShelfDir(repo).get(basename)
903 if shelf.hasinfo():
903 if shelf.hasinfo():
904 node = shelf.readinfo()[b'node']
904 node = shelf.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.applybundle(repo, tr)
907 shelvectx = shelf.applybundle(repo, 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.writeinfo(info)
912 shelf.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 = ShelfDir(repo).listshelves()
1123 shelved = ShelfDir(repo).listshelves()
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 = shelved[0][1]
1126 basename = shelved[0][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 ShelfDir(repo).get(basename).exists():
1131 if not ShelfDir(repo).get(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