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