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