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