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