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