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