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