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