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