##// END OF EJS Templates
shelve: drop unnecessary backup of dirstate for phase-based case...
Martin von Zweigbergk -
r40888:d7e44da4 default
parent child Browse files
Show More
@@ -1,1156 +1,1153
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
433 # Create a matcher so that prefetch doesn't attempt to fetch the entire
434 # repository pointlessly.
434 # repository pointlessly.
435 match = scmutil.matchfiles(repo, repo[node].files())
435 match = scmutil.matchfiles(repo, repo[node].files())
436 with shelvedfile(repo, name, patchextension).opener('wb') as fp:
436 with shelvedfile(repo, name, patchextension).opener('wb') as fp:
437 cmdutil.exportfile(repo, [node], fp, opts=mdiff.diffopts(git=True),
437 cmdutil.exportfile(repo, [node], fp, opts=mdiff.diffopts(git=True),
438 match=match)
438 match=match)
439
439
440 def _includeunknownfiles(repo, pats, opts, extra):
440 def _includeunknownfiles(repo, pats, opts, extra):
441 s = repo.status(match=scmutil.match(repo[None], pats, opts),
441 s = repo.status(match=scmutil.match(repo[None], pats, opts),
442 unknown=True)
442 unknown=True)
443 if s.unknown:
443 if s.unknown:
444 extra['shelve_unknown'] = '\0'.join(s.unknown)
444 extra['shelve_unknown'] = '\0'.join(s.unknown)
445 repo[None].add(s.unknown)
445 repo[None].add(s.unknown)
446
446
447 def _finishshelve(repo):
447 def _finishshelve(repo):
448 if phases.supportinternal(repo):
448 if phases.supportinternal(repo):
449 backupname = 'dirstate.shelve'
450 tr = repo.currenttransaction()
449 tr = repo.currenttransaction()
451 repo.dirstate.savebackup(tr, backupname)
452 tr.close()
450 tr.close()
453 repo.dirstate.restorebackup(None, backupname)
454 else:
451 else:
455 _aborttransaction(repo)
452 _aborttransaction(repo)
456
453
457 def createcmd(ui, repo, pats, opts):
454 def createcmd(ui, repo, pats, opts):
458 """subcommand that creates a new shelve"""
455 """subcommand that creates a new shelve"""
459 with repo.wlock():
456 with repo.wlock():
460 cmdutil.checkunfinished(repo)
457 cmdutil.checkunfinished(repo)
461 return _docreatecmd(ui, repo, pats, opts)
458 return _docreatecmd(ui, repo, pats, opts)
462
459
463 def _docreatecmd(ui, repo, pats, opts):
460 def _docreatecmd(ui, repo, pats, opts):
464 wctx = repo[None]
461 wctx = repo[None]
465 parents = wctx.parents()
462 parents = wctx.parents()
466 if len(parents) > 1:
463 if len(parents) > 1:
467 raise error.Abort(_('cannot shelve while merging'))
464 raise error.Abort(_('cannot shelve while merging'))
468 parent = parents[0]
465 parent = parents[0]
469 origbranch = wctx.branch()
466 origbranch = wctx.branch()
470
467
471 if parent.node() != nodemod.nullid:
468 if parent.node() != nodemod.nullid:
472 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
469 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
473 else:
470 else:
474 desc = '(changes in empty repository)'
471 desc = '(changes in empty repository)'
475
472
476 if not opts.get('message'):
473 if not opts.get('message'):
477 opts['message'] = desc
474 opts['message'] = desc
478
475
479 lock = tr = activebookmark = None
476 lock = tr = activebookmark = None
480 try:
477 try:
481 lock = repo.lock()
478 lock = repo.lock()
482
479
483 # use an uncommitted transaction to generate the bundle to avoid
480 # use an uncommitted transaction to generate the bundle to avoid
484 # pull races. ensure we don't print the abort message to stderr.
481 # pull races. ensure we don't print the abort message to stderr.
485 tr = repo.transaction('commit', report=lambda x: None)
482 tr = repo.transaction('commit', report=lambda x: None)
486
483
487 interactive = opts.get('interactive', False)
484 interactive = opts.get('interactive', False)
488 includeunknown = (opts.get('unknown', False) and
485 includeunknown = (opts.get('unknown', False) and
489 not opts.get('addremove', False))
486 not opts.get('addremove', False))
490
487
491 name = getshelvename(repo, parent, opts)
488 name = getshelvename(repo, parent, opts)
492 activebookmark = _backupactivebookmark(repo)
489 activebookmark = _backupactivebookmark(repo)
493 extra = {'internal': 'shelve'}
490 extra = {'internal': 'shelve'}
494 if includeunknown:
491 if includeunknown:
495 _includeunknownfiles(repo, pats, opts, extra)
492 _includeunknownfiles(repo, pats, opts, extra)
496
493
497 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
494 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
498 # In non-bare shelve we don't store newly created branch
495 # In non-bare shelve we don't store newly created branch
499 # at bundled commit
496 # at bundled commit
500 repo.dirstate.setbranch(repo['.'].branch())
497 repo.dirstate.setbranch(repo['.'].branch())
501
498
502 commitfunc = getcommitfunc(extra, interactive, editor=True)
499 commitfunc = getcommitfunc(extra, interactive, editor=True)
503 if not interactive:
500 if not interactive:
504 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
501 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
505 else:
502 else:
506 node = cmdutil.dorecord(ui, repo, commitfunc, None,
503 node = cmdutil.dorecord(ui, repo, commitfunc, None,
507 False, cmdutil.recordfilter, *pats,
504 False, cmdutil.recordfilter, *pats,
508 **pycompat.strkwargs(opts))
505 **pycompat.strkwargs(opts))
509 if not node:
506 if not node:
510 _nothingtoshelvemessaging(ui, repo, pats, opts)
507 _nothingtoshelvemessaging(ui, repo, pats, opts)
511 return 1
508 return 1
512
509
513 _shelvecreatedcommit(repo, node, name)
510 _shelvecreatedcommit(repo, node, name)
514
511
515 if ui.formatted():
512 if ui.formatted():
516 desc = stringutil.ellipsis(desc, ui.termwidth())
513 desc = stringutil.ellipsis(desc, ui.termwidth())
517 ui.status(_('shelved as %s\n') % name)
514 ui.status(_('shelved as %s\n') % name)
518 hg.update(repo, parent.node())
515 hg.update(repo, parent.node())
519 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
516 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
520 repo.dirstate.setbranch(origbranch)
517 repo.dirstate.setbranch(origbranch)
521
518
522 _finishshelve(repo)
519 _finishshelve(repo)
523 finally:
520 finally:
524 _restoreactivebookmark(repo, activebookmark)
521 _restoreactivebookmark(repo, activebookmark)
525 lockmod.release(tr, lock)
522 lockmod.release(tr, lock)
526
523
527 def _isbareshelve(pats, opts):
524 def _isbareshelve(pats, opts):
528 return (not pats
525 return (not pats
529 and not opts.get('interactive', False)
526 and not opts.get('interactive', False)
530 and not opts.get('include', False)
527 and not opts.get('include', False)
531 and not opts.get('exclude', False))
528 and not opts.get('exclude', False))
532
529
533 def _iswctxonnewbranch(repo):
530 def _iswctxonnewbranch(repo):
534 return repo[None].branch() != repo['.'].branch()
531 return repo[None].branch() != repo['.'].branch()
535
532
536 def cleanupcmd(ui, repo):
533 def cleanupcmd(ui, repo):
537 """subcommand that deletes all shelves"""
534 """subcommand that deletes all shelves"""
538
535
539 with repo.wlock():
536 with repo.wlock():
540 for (name, _type) in repo.vfs.readdir(shelvedir):
537 for (name, _type) in repo.vfs.readdir(shelvedir):
541 suffix = name.rsplit('.', 1)[-1]
538 suffix = name.rsplit('.', 1)[-1]
542 if suffix in shelvefileextensions:
539 if suffix in shelvefileextensions:
543 shelvedfile(repo, name).movetobackup()
540 shelvedfile(repo, name).movetobackup()
544 cleanupoldbackups(repo)
541 cleanupoldbackups(repo)
545
542
546 def deletecmd(ui, repo, pats):
543 def deletecmd(ui, repo, pats):
547 """subcommand that deletes a specific shelve"""
544 """subcommand that deletes a specific shelve"""
548 if not pats:
545 if not pats:
549 raise error.Abort(_('no shelved changes specified!'))
546 raise error.Abort(_('no shelved changes specified!'))
550 with repo.wlock():
547 with repo.wlock():
551 try:
548 try:
552 for name in pats:
549 for name in pats:
553 for suffix in shelvefileextensions:
550 for suffix in shelvefileextensions:
554 shfile = shelvedfile(repo, name, suffix)
551 shfile = shelvedfile(repo, name, suffix)
555 # patch file is necessary, as it should
552 # patch file is necessary, as it should
556 # be present for any kind of shelve,
553 # be present for any kind of shelve,
557 # but the .hg file is optional as in future we
554 # but the .hg file is optional as in future we
558 # will add obsolete shelve with does not create a
555 # will add obsolete shelve with does not create a
559 # bundle
556 # bundle
560 if shfile.exists() or suffix == patchextension:
557 if shfile.exists() or suffix == patchextension:
561 shfile.movetobackup()
558 shfile.movetobackup()
562 cleanupoldbackups(repo)
559 cleanupoldbackups(repo)
563 except OSError as err:
560 except OSError as err:
564 if err.errno != errno.ENOENT:
561 if err.errno != errno.ENOENT:
565 raise
562 raise
566 raise error.Abort(_("shelved change '%s' not found") % name)
563 raise error.Abort(_("shelved change '%s' not found") % name)
567
564
568 def listshelves(repo):
565 def listshelves(repo):
569 """return all shelves in repo as list of (time, filename)"""
566 """return all shelves in repo as list of (time, filename)"""
570 try:
567 try:
571 names = repo.vfs.readdir(shelvedir)
568 names = repo.vfs.readdir(shelvedir)
572 except OSError as err:
569 except OSError as err:
573 if err.errno != errno.ENOENT:
570 if err.errno != errno.ENOENT:
574 raise
571 raise
575 return []
572 return []
576 info = []
573 info = []
577 for (name, _type) in names:
574 for (name, _type) in names:
578 pfx, sfx = name.rsplit('.', 1)
575 pfx, sfx = name.rsplit('.', 1)
579 if not pfx or sfx != patchextension:
576 if not pfx or sfx != patchextension:
580 continue
577 continue
581 st = shelvedfile(repo, name).stat()
578 st = shelvedfile(repo, name).stat()
582 info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename()))
579 info.append((st[stat.ST_MTIME], shelvedfile(repo, pfx).filename()))
583 return sorted(info, reverse=True)
580 return sorted(info, reverse=True)
584
581
585 def listcmd(ui, repo, pats, opts):
582 def listcmd(ui, repo, pats, opts):
586 """subcommand that displays the list of shelves"""
583 """subcommand that displays the list of shelves"""
587 pats = set(pats)
584 pats = set(pats)
588 width = 80
585 width = 80
589 if not ui.plain():
586 if not ui.plain():
590 width = ui.termwidth()
587 width = ui.termwidth()
591 namelabel = 'shelve.newest'
588 namelabel = 'shelve.newest'
592 ui.pager('shelve')
589 ui.pager('shelve')
593 for mtime, name in listshelves(repo):
590 for mtime, name in listshelves(repo):
594 sname = util.split(name)[1]
591 sname = util.split(name)[1]
595 if pats and sname not in pats:
592 if pats and sname not in pats:
596 continue
593 continue
597 ui.write(sname, label=namelabel)
594 ui.write(sname, label=namelabel)
598 namelabel = 'shelve.name'
595 namelabel = 'shelve.name'
599 if ui.quiet:
596 if ui.quiet:
600 ui.write('\n')
597 ui.write('\n')
601 continue
598 continue
602 ui.write(' ' * (16 - len(sname)))
599 ui.write(' ' * (16 - len(sname)))
603 used = 16
600 used = 16
604 date = dateutil.makedate(mtime)
601 date = dateutil.makedate(mtime)
605 age = '(%s)' % templatefilters.age(date, abbrev=True)
602 age = '(%s)' % templatefilters.age(date, abbrev=True)
606 ui.write(age, label='shelve.age')
603 ui.write(age, label='shelve.age')
607 ui.write(' ' * (12 - len(age)))
604 ui.write(' ' * (12 - len(age)))
608 used += 12
605 used += 12
609 with open(name + '.' + patchextension, 'rb') as fp:
606 with open(name + '.' + patchextension, 'rb') as fp:
610 while True:
607 while True:
611 line = fp.readline()
608 line = fp.readline()
612 if not line:
609 if not line:
613 break
610 break
614 if not line.startswith('#'):
611 if not line.startswith('#'):
615 desc = line.rstrip()
612 desc = line.rstrip()
616 if ui.formatted():
613 if ui.formatted():
617 desc = stringutil.ellipsis(desc, width - used)
614 desc = stringutil.ellipsis(desc, width - used)
618 ui.write(desc)
615 ui.write(desc)
619 break
616 break
620 ui.write('\n')
617 ui.write('\n')
621 if not (opts['patch'] or opts['stat']):
618 if not (opts['patch'] or opts['stat']):
622 continue
619 continue
623 difflines = fp.readlines()
620 difflines = fp.readlines()
624 if opts['patch']:
621 if opts['patch']:
625 for chunk, label in patch.difflabel(iter, difflines):
622 for chunk, label in patch.difflabel(iter, difflines):
626 ui.write(chunk, label=label)
623 ui.write(chunk, label=label)
627 if opts['stat']:
624 if opts['stat']:
628 for chunk, label in patch.diffstatui(difflines, width=width):
625 for chunk, label in patch.diffstatui(difflines, width=width):
629 ui.write(chunk, label=label)
626 ui.write(chunk, label=label)
630
627
631 def patchcmds(ui, repo, pats, opts):
628 def patchcmds(ui, repo, pats, opts):
632 """subcommand that displays shelves"""
629 """subcommand that displays shelves"""
633 if len(pats) == 0:
630 if len(pats) == 0:
634 shelves = listshelves(repo)
631 shelves = listshelves(repo)
635 if not shelves:
632 if not shelves:
636 raise error.Abort(_("there are no shelves to show"))
633 raise error.Abort(_("there are no shelves to show"))
637 mtime, name = shelves[0]
634 mtime, name = shelves[0]
638 sname = util.split(name)[1]
635 sname = util.split(name)[1]
639 pats = [sname]
636 pats = [sname]
640
637
641 for shelfname in pats:
638 for shelfname in pats:
642 if not shelvedfile(repo, shelfname, patchextension).exists():
639 if not shelvedfile(repo, shelfname, patchextension).exists():
643 raise error.Abort(_("cannot find shelf %s") % shelfname)
640 raise error.Abort(_("cannot find shelf %s") % shelfname)
644
641
645 listcmd(ui, repo, pats, opts)
642 listcmd(ui, repo, pats, opts)
646
643
647 def checkparents(repo, state):
644 def checkparents(repo, state):
648 """check parent while resuming an unshelve"""
645 """check parent while resuming an unshelve"""
649 if state.parents != repo.dirstate.parents():
646 if state.parents != repo.dirstate.parents():
650 raise error.Abort(_('working directory parents do not match unshelve '
647 raise error.Abort(_('working directory parents do not match unshelve '
651 'state'))
648 'state'))
652
649
653 def pathtofiles(repo, files):
650 def pathtofiles(repo, files):
654 cwd = repo.getcwd()
651 cwd = repo.getcwd()
655 return [repo.pathto(f, cwd) for f in files]
652 return [repo.pathto(f, cwd) for f in files]
656
653
657 def unshelveabort(ui, repo, state, opts):
654 def unshelveabort(ui, repo, state, opts):
658 """subcommand that abort an in-progress unshelve"""
655 """subcommand that abort an in-progress unshelve"""
659 with repo.lock():
656 with repo.lock():
660 try:
657 try:
661 checkparents(repo, state)
658 checkparents(repo, state)
662
659
663 merge.update(repo, state.pendingctx, branchmerge=False, force=True)
660 merge.update(repo, state.pendingctx, branchmerge=False, force=True)
664 if (state.activebookmark
661 if (state.activebookmark
665 and state.activebookmark in repo._bookmarks):
662 and state.activebookmark in repo._bookmarks):
666 bookmarks.activate(repo, state.activebookmark)
663 bookmarks.activate(repo, state.activebookmark)
667
664
668 if repo.vfs.exists('unshelverebasestate'):
665 if repo.vfs.exists('unshelverebasestate'):
669 repo.vfs.rename('unshelverebasestate', 'rebasestate')
666 repo.vfs.rename('unshelverebasestate', 'rebasestate')
670 rebase.clearstatus(repo)
667 rebase.clearstatus(repo)
671
668
672 mergefiles(ui, repo, state.wctx, state.pendingctx)
669 mergefiles(ui, repo, state.wctx, state.pendingctx)
673 if not phases.supportinternal(repo):
670 if not phases.supportinternal(repo):
674 repair.strip(ui, repo, state.nodestoremove, backup=False,
671 repair.strip(ui, repo, state.nodestoremove, backup=False,
675 topic='shelve')
672 topic='shelve')
676 finally:
673 finally:
677 shelvedstate.clear(repo)
674 shelvedstate.clear(repo)
678 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
675 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
679
676
680 def mergefiles(ui, repo, wctx, shelvectx):
677 def mergefiles(ui, repo, wctx, shelvectx):
681 """updates to wctx and merges the changes from shelvectx into the
678 """updates to wctx and merges the changes from shelvectx into the
682 dirstate."""
679 dirstate."""
683 with ui.configoverride({('ui', 'quiet'): True}):
680 with ui.configoverride({('ui', 'quiet'): True}):
684 hg.update(repo, wctx.node())
681 hg.update(repo, wctx.node())
685 files = []
682 files = []
686 files.extend(shelvectx.files())
683 files.extend(shelvectx.files())
687 files.extend(shelvectx.parents()[0].files())
684 files.extend(shelvectx.parents()[0].files())
688
685
689 # revert will overwrite unknown files, so move them out of the way
686 # revert will overwrite unknown files, so move them out of the way
690 for file in repo.status(unknown=True).unknown:
687 for file in repo.status(unknown=True).unknown:
691 if file in files:
688 if file in files:
692 util.rename(file, scmutil.origpath(ui, repo, file))
689 util.rename(file, scmutil.origpath(ui, repo, file))
693 ui.pushbuffer(True)
690 ui.pushbuffer(True)
694 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
691 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
695 *pathtofiles(repo, files),
692 *pathtofiles(repo, files),
696 **{r'no_backup': True})
693 **{r'no_backup': True})
697 ui.popbuffer()
694 ui.popbuffer()
698
695
699 def restorebranch(ui, repo, branchtorestore):
696 def restorebranch(ui, repo, branchtorestore):
700 if branchtorestore and branchtorestore != repo.dirstate.branch():
697 if branchtorestore and branchtorestore != repo.dirstate.branch():
701 repo.dirstate.setbranch(branchtorestore)
698 repo.dirstate.setbranch(branchtorestore)
702 ui.status(_('marked working directory as branch %s\n')
699 ui.status(_('marked working directory as branch %s\n')
703 % branchtorestore)
700 % branchtorestore)
704
701
705 def unshelvecleanup(ui, repo, name, opts):
702 def unshelvecleanup(ui, repo, name, opts):
706 """remove related files after an unshelve"""
703 """remove related files after an unshelve"""
707 if not opts.get('keep'):
704 if not opts.get('keep'):
708 for filetype in shelvefileextensions:
705 for filetype in shelvefileextensions:
709 shfile = shelvedfile(repo, name, filetype)
706 shfile = shelvedfile(repo, name, filetype)
710 if shfile.exists():
707 if shfile.exists():
711 shfile.movetobackup()
708 shfile.movetobackup()
712 cleanupoldbackups(repo)
709 cleanupoldbackups(repo)
713
710
714 def unshelvecontinue(ui, repo, state, opts):
711 def unshelvecontinue(ui, repo, state, opts):
715 """subcommand to continue an in-progress unshelve"""
712 """subcommand to continue an in-progress unshelve"""
716 # We're finishing off a merge. First parent is our original
713 # We're finishing off a merge. First parent is our original
717 # parent, second is the temporary "fake" commit we're unshelving.
714 # parent, second is the temporary "fake" commit we're unshelving.
718 with repo.lock():
715 with repo.lock():
719 checkparents(repo, state)
716 checkparents(repo, state)
720 ms = merge.mergestate.read(repo)
717 ms = merge.mergestate.read(repo)
721 if list(ms.unresolved()):
718 if list(ms.unresolved()):
722 raise error.Abort(
719 raise error.Abort(
723 _("unresolved conflicts, can't continue"),
720 _("unresolved conflicts, can't continue"),
724 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
721 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
725
722
726 shelvectx = repo[state.parents[1]]
723 shelvectx = repo[state.parents[1]]
727 pendingctx = state.pendingctx
724 pendingctx = state.pendingctx
728
725
729 with repo.dirstate.parentchange():
726 with repo.dirstate.parentchange():
730 repo.setparents(state.pendingctx.node(), nodemod.nullid)
727 repo.setparents(state.pendingctx.node(), nodemod.nullid)
731 repo.dirstate.write(repo.currenttransaction())
728 repo.dirstate.write(repo.currenttransaction())
732
729
733 targetphase = phases.internal
730 targetphase = phases.internal
734 if not phases.supportinternal(repo):
731 if not phases.supportinternal(repo):
735 targetphase = phases.secret
732 targetphase = phases.secret
736 overrides = {('phases', 'new-commit'): targetphase}
733 overrides = {('phases', 'new-commit'): targetphase}
737 with repo.ui.configoverride(overrides, 'unshelve'):
734 with repo.ui.configoverride(overrides, 'unshelve'):
738 with repo.dirstate.parentchange():
735 with repo.dirstate.parentchange():
739 repo.setparents(state.parents[0], nodemod.nullid)
736 repo.setparents(state.parents[0], nodemod.nullid)
740 newnode = repo.commit(text=shelvectx.description(),
737 newnode = repo.commit(text=shelvectx.description(),
741 extra=shelvectx.extra(),
738 extra=shelvectx.extra(),
742 user=shelvectx.user(),
739 user=shelvectx.user(),
743 date=shelvectx.date())
740 date=shelvectx.date())
744
741
745 if newnode is None:
742 if newnode is None:
746 # If it ended up being a no-op commit, then the normal
743 # If it ended up being a no-op commit, then the normal
747 # merge state clean-up path doesn't happen, so do it
744 # merge state clean-up path doesn't happen, so do it
748 # here. Fix issue5494
745 # here. Fix issue5494
749 merge.mergestate.clean(repo)
746 merge.mergestate.clean(repo)
750 shelvectx = state.pendingctx
747 shelvectx = state.pendingctx
751 msg = _('note: unshelved changes already existed '
748 msg = _('note: unshelved changes already existed '
752 'in the working copy\n')
749 'in the working copy\n')
753 ui.status(msg)
750 ui.status(msg)
754 else:
751 else:
755 # only strip the shelvectx if we produced one
752 # only strip the shelvectx if we produced one
756 state.nodestoremove.append(newnode)
753 state.nodestoremove.append(newnode)
757 shelvectx = repo[newnode]
754 shelvectx = repo[newnode]
758
755
759 hg.updaterepo(repo, pendingctx.node(), overwrite=False)
756 hg.updaterepo(repo, pendingctx.node(), overwrite=False)
760
757
761 if repo.vfs.exists('unshelverebasestate'):
758 if repo.vfs.exists('unshelverebasestate'):
762 repo.vfs.rename('unshelverebasestate', 'rebasestate')
759 repo.vfs.rename('unshelverebasestate', 'rebasestate')
763 rebase.clearstatus(repo)
760 rebase.clearstatus(repo)
764
761
765 mergefiles(ui, repo, state.wctx, shelvectx)
762 mergefiles(ui, repo, state.wctx, shelvectx)
766 restorebranch(ui, repo, state.branchtorestore)
763 restorebranch(ui, repo, state.branchtorestore)
767
764
768 if not phases.supportinternal(repo):
765 if not phases.supportinternal(repo):
769 repair.strip(ui, repo, state.nodestoremove, backup=False,
766 repair.strip(ui, repo, state.nodestoremove, backup=False,
770 topic='shelve')
767 topic='shelve')
771 _restoreactivebookmark(repo, state.activebookmark)
768 _restoreactivebookmark(repo, state.activebookmark)
772 shelvedstate.clear(repo)
769 shelvedstate.clear(repo)
773 unshelvecleanup(ui, repo, state.name, opts)
770 unshelvecleanup(ui, repo, state.name, opts)
774 ui.status(_("unshelve of '%s' complete\n") % state.name)
771 ui.status(_("unshelve of '%s' complete\n") % state.name)
775
772
776 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
773 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
777 """Temporarily commit working copy changes before moving unshelve commit"""
774 """Temporarily commit working copy changes before moving unshelve commit"""
778 # Store pending changes in a commit and remember added in case a shelve
775 # Store pending changes in a commit and remember added in case a shelve
779 # contains unknown files that are part of the pending change
776 # contains unknown files that are part of the pending change
780 s = repo.status()
777 s = repo.status()
781 addedbefore = frozenset(s.added)
778 addedbefore = frozenset(s.added)
782 if not (s.modified or s.added or s.removed):
779 if not (s.modified or s.added or s.removed):
783 return tmpwctx, addedbefore
780 return tmpwctx, addedbefore
784 ui.status(_("temporarily committing pending changes "
781 ui.status(_("temporarily committing pending changes "
785 "(restore with 'hg unshelve --abort')\n"))
782 "(restore with 'hg unshelve --abort')\n"))
786 extra = {'internal': 'shelve'}
783 extra = {'internal': 'shelve'}
787 commitfunc = getcommitfunc(extra=extra, interactive=False,
784 commitfunc = getcommitfunc(extra=extra, interactive=False,
788 editor=False)
785 editor=False)
789 tempopts = {}
786 tempopts = {}
790 tempopts['message'] = "pending changes temporary commit"
787 tempopts['message'] = "pending changes temporary commit"
791 tempopts['date'] = opts.get('date')
788 tempopts['date'] = opts.get('date')
792 with ui.configoverride({('ui', 'quiet'): True}):
789 with ui.configoverride({('ui', 'quiet'): True}):
793 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
790 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
794 tmpwctx = repo[node]
791 tmpwctx = repo[node]
795 return tmpwctx, addedbefore
792 return tmpwctx, addedbefore
796
793
797 def _unshelverestorecommit(ui, repo, basename):
794 def _unshelverestorecommit(ui, repo, basename):
798 """Recreate commit in the repository during the unshelve"""
795 """Recreate commit in the repository during the unshelve"""
799 repo = repo.unfiltered()
796 repo = repo.unfiltered()
800 node = None
797 node = None
801 if shelvedfile(repo, basename, 'shelve').exists():
798 if shelvedfile(repo, basename, 'shelve').exists():
802 node = shelvedfile(repo, basename, 'shelve').readinfo()['node']
799 node = shelvedfile(repo, basename, 'shelve').readinfo()['node']
803 if node is None or node not in repo:
800 if node is None or node not in repo:
804 with ui.configoverride({('ui', 'quiet'): True}):
801 with ui.configoverride({('ui', 'quiet'): True}):
805 shelvectx = shelvedfile(repo, basename, 'hg').applybundle()
802 shelvectx = shelvedfile(repo, basename, 'hg').applybundle()
806 # We might not strip the unbundled changeset, so we should keep track of
803 # We might not strip the unbundled changeset, so we should keep track of
807 # the unshelve node in case we need to reuse it (eg: unshelve --keep)
804 # the unshelve node in case we need to reuse it (eg: unshelve --keep)
808 if node is None:
805 if node is None:
809 info = {'node': nodemod.hex(shelvectx.node())}
806 info = {'node': nodemod.hex(shelvectx.node())}
810 shelvedfile(repo, basename, 'shelve').writeinfo(info)
807 shelvedfile(repo, basename, 'shelve').writeinfo(info)
811 else:
808 else:
812 shelvectx = repo[node]
809 shelvectx = repo[node]
813
810
814 return repo, shelvectx
811 return repo, shelvectx
815
812
816 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
813 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
817 tmpwctx, shelvectx, branchtorestore,
814 tmpwctx, shelvectx, branchtorestore,
818 activebookmark):
815 activebookmark):
819 """Rebase restored commit from its original location to a destination"""
816 """Rebase restored commit from its original location to a destination"""
820 # If the shelve is not immediately on top of the commit
817 # If the shelve is not immediately on top of the commit
821 # we'll be merging with, rebase it to be on top.
818 # we'll be merging with, rebase it to be on top.
822 if tmpwctx.node() == shelvectx.parents()[0].node():
819 if tmpwctx.node() == shelvectx.parents()[0].node():
823 return shelvectx
820 return shelvectx
824
821
825 overrides = {
822 overrides = {
826 ('ui', 'forcemerge'): opts.get('tool', ''),
823 ('ui', 'forcemerge'): opts.get('tool', ''),
827 ('phases', 'new-commit'): phases.secret,
824 ('phases', 'new-commit'): phases.secret,
828 }
825 }
829 with repo.ui.configoverride(overrides, 'unshelve'):
826 with repo.ui.configoverride(overrides, 'unshelve'):
830 ui.status(_('rebasing shelved changes\n'))
827 ui.status(_('rebasing shelved changes\n'))
831 stats = merge.graft(repo, shelvectx, shelvectx.p1(),
828 stats = merge.graft(repo, shelvectx, shelvectx.p1(),
832 labels=['shelve', 'working-copy'],
829 labels=['shelve', 'working-copy'],
833 keepconflictparent=True)
830 keepconflictparent=True)
834 if stats.unresolvedcount:
831 if stats.unresolvedcount:
835 tr.close()
832 tr.close()
836
833
837 nodestoremove = [repo.changelog.node(rev)
834 nodestoremove = [repo.changelog.node(rev)
838 for rev in pycompat.xrange(oldtiprev, len(repo))]
835 for rev in pycompat.xrange(oldtiprev, len(repo))]
839 shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
836 shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
840 branchtorestore, opts.get('keep'), activebookmark)
837 branchtorestore, opts.get('keep'), activebookmark)
841 raise error.InterventionRequired(
838 raise error.InterventionRequired(
842 _("unresolved conflicts (see 'hg resolve', then "
839 _("unresolved conflicts (see 'hg resolve', then "
843 "'hg unshelve --continue')"))
840 "'hg unshelve --continue')"))
844
841
845 with repo.dirstate.parentchange():
842 with repo.dirstate.parentchange():
846 repo.setparents(tmpwctx.node(), nodemod.nullid)
843 repo.setparents(tmpwctx.node(), nodemod.nullid)
847 newnode = repo.commit(text=shelvectx.description(),
844 newnode = repo.commit(text=shelvectx.description(),
848 extra=shelvectx.extra(),
845 extra=shelvectx.extra(),
849 user=shelvectx.user(),
846 user=shelvectx.user(),
850 date=shelvectx.date())
847 date=shelvectx.date())
851
848
852 if newnode is None:
849 if newnode is None:
853 # If it ended up being a no-op commit, then the normal
850 # If it ended up being a no-op commit, then the normal
854 # merge state clean-up path doesn't happen, so do it
851 # merge state clean-up path doesn't happen, so do it
855 # here. Fix issue5494
852 # here. Fix issue5494
856 merge.mergestate.clean(repo)
853 merge.mergestate.clean(repo)
857 shelvectx = tmpwctx
854 shelvectx = tmpwctx
858 msg = _('note: unshelved changes already existed '
855 msg = _('note: unshelved changes already existed '
859 'in the working copy\n')
856 'in the working copy\n')
860 ui.status(msg)
857 ui.status(msg)
861 else:
858 else:
862 shelvectx = repo[newnode]
859 shelvectx = repo[newnode]
863 hg.updaterepo(repo, tmpwctx.node(), False)
860 hg.updaterepo(repo, tmpwctx.node(), False)
864
861
865 return shelvectx
862 return shelvectx
866
863
867 def _forgetunknownfiles(repo, shelvectx, addedbefore):
864 def _forgetunknownfiles(repo, shelvectx, addedbefore):
868 # Forget any files that were unknown before the shelve, unknown before
865 # Forget any files that were unknown before the shelve, unknown before
869 # unshelve started, but are now added.
866 # unshelve started, but are now added.
870 shelveunknown = shelvectx.extra().get('shelve_unknown')
867 shelveunknown = shelvectx.extra().get('shelve_unknown')
871 if not shelveunknown:
868 if not shelveunknown:
872 return
869 return
873 shelveunknown = frozenset(shelveunknown.split('\0'))
870 shelveunknown = frozenset(shelveunknown.split('\0'))
874 addedafter = frozenset(repo.status().added)
871 addedafter = frozenset(repo.status().added)
875 toforget = (addedafter & shelveunknown) - addedbefore
872 toforget = (addedafter & shelveunknown) - addedbefore
876 repo[None].forget(toforget)
873 repo[None].forget(toforget)
877
874
878 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
875 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
879 _restoreactivebookmark(repo, activebookmark)
876 _restoreactivebookmark(repo, activebookmark)
880 # The transaction aborting will strip all the commits for us,
877 # The transaction aborting will strip all the commits for us,
881 # but it doesn't update the inmemory structures, so addchangegroup
878 # but it doesn't update the inmemory structures, so addchangegroup
882 # hooks still fire and try to operate on the missing commits.
879 # hooks still fire and try to operate on the missing commits.
883 # Clean up manually to prevent this.
880 # Clean up manually to prevent this.
884 repo.unfiltered().changelog.strip(oldtiprev, tr)
881 repo.unfiltered().changelog.strip(oldtiprev, tr)
885 _aborttransaction(repo)
882 _aborttransaction(repo)
886
883
887 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
884 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
888 """Check potential problems which may result from working
885 """Check potential problems which may result from working
889 copy having untracked changes."""
886 copy having untracked changes."""
890 wcdeleted = set(repo.status().deleted)
887 wcdeleted = set(repo.status().deleted)
891 shelvetouched = set(shelvectx.files())
888 shelvetouched = set(shelvectx.files())
892 intersection = wcdeleted.intersection(shelvetouched)
889 intersection = wcdeleted.intersection(shelvetouched)
893 if intersection:
890 if intersection:
894 m = _("shelved change touches missing files")
891 m = _("shelved change touches missing files")
895 hint = _("run hg status to see which files are missing")
892 hint = _("run hg status to see which files are missing")
896 raise error.Abort(m, hint=hint)
893 raise error.Abort(m, hint=hint)
897
894
898 @command('unshelve',
895 @command('unshelve',
899 [('a', 'abort', None,
896 [('a', 'abort', None,
900 _('abort an incomplete unshelve operation')),
897 _('abort an incomplete unshelve operation')),
901 ('c', 'continue', None,
898 ('c', 'continue', None,
902 _('continue an incomplete unshelve operation')),
899 _('continue an incomplete unshelve operation')),
903 ('k', 'keep', None,
900 ('k', 'keep', None,
904 _('keep shelve after unshelving')),
901 _('keep shelve after unshelving')),
905 ('n', 'name', '',
902 ('n', 'name', '',
906 _('restore shelved change with given name'), _('NAME')),
903 _('restore shelved change with given name'), _('NAME')),
907 ('t', 'tool', '', _('specify merge tool')),
904 ('t', 'tool', '', _('specify merge tool')),
908 ('', 'date', '',
905 ('', 'date', '',
909 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
906 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
910 _('hg unshelve [[-n] SHELVED]'),
907 _('hg unshelve [[-n] SHELVED]'),
911 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
908 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
912 def unshelve(ui, repo, *shelved, **opts):
909 def unshelve(ui, repo, *shelved, **opts):
913 """restore a shelved change to the working directory
910 """restore a shelved change to the working directory
914
911
915 This command accepts an optional name of a shelved change to
912 This command accepts an optional name of a shelved change to
916 restore. If none is given, the most recent shelved change is used.
913 restore. If none is given, the most recent shelved change is used.
917
914
918 If a shelved change is applied successfully, the bundle that
915 If a shelved change is applied successfully, the bundle that
919 contains the shelved changes is moved to a backup location
916 contains the shelved changes is moved to a backup location
920 (.hg/shelve-backup).
917 (.hg/shelve-backup).
921
918
922 Since you can restore a shelved change on top of an arbitrary
919 Since you can restore a shelved change on top of an arbitrary
923 commit, it is possible that unshelving will result in a conflict
920 commit, it is possible that unshelving will result in a conflict
924 between your changes and the commits you are unshelving onto. If
921 between your changes and the commits you are unshelving onto. If
925 this occurs, you must resolve the conflict, then use
922 this occurs, you must resolve the conflict, then use
926 ``--continue`` to complete the unshelve operation. (The bundle
923 ``--continue`` to complete the unshelve operation. (The bundle
927 will not be moved until you successfully complete the unshelve.)
924 will not be moved until you successfully complete the unshelve.)
928
925
929 (Alternatively, you can use ``--abort`` to abandon an unshelve
926 (Alternatively, you can use ``--abort`` to abandon an unshelve
930 that causes a conflict. This reverts the unshelved changes, and
927 that causes a conflict. This reverts the unshelved changes, and
931 leaves the bundle in place.)
928 leaves the bundle in place.)
932
929
933 If bare shelved change(when no files are specified, without interactive,
930 If bare shelved change(when no files are specified, without interactive,
934 include and exclude option) was done on newly created branch it would
931 include and exclude option) was done on newly created branch it would
935 restore branch information to the working directory.
932 restore branch information to the working directory.
936
933
937 After a successful unshelve, the shelved changes are stored in a
934 After a successful unshelve, the shelved changes are stored in a
938 backup directory. Only the N most recent backups are kept. N
935 backup directory. Only the N most recent backups are kept. N
939 defaults to 10 but can be overridden using the ``shelve.maxbackups``
936 defaults to 10 but can be overridden using the ``shelve.maxbackups``
940 configuration option.
937 configuration option.
941
938
942 .. container:: verbose
939 .. container:: verbose
943
940
944 Timestamp in seconds is used to decide order of backups. More
941 Timestamp in seconds is used to decide order of backups. More
945 than ``maxbackups`` backups are kept, if same timestamp
942 than ``maxbackups`` backups are kept, if same timestamp
946 prevents from deciding exact order of them, for safety.
943 prevents from deciding exact order of them, for safety.
947 """
944 """
948 with repo.wlock():
945 with repo.wlock():
949 return _dounshelve(ui, repo, *shelved, **opts)
946 return _dounshelve(ui, repo, *shelved, **opts)
950
947
951 def _dounshelve(ui, repo, *shelved, **opts):
948 def _dounshelve(ui, repo, *shelved, **opts):
952 opts = pycompat.byteskwargs(opts)
949 opts = pycompat.byteskwargs(opts)
953 abortf = opts.get('abort')
950 abortf = opts.get('abort')
954 continuef = opts.get('continue')
951 continuef = opts.get('continue')
955 if not abortf and not continuef:
952 if not abortf and not continuef:
956 cmdutil.checkunfinished(repo)
953 cmdutil.checkunfinished(repo)
957 shelved = list(shelved)
954 shelved = list(shelved)
958 if opts.get("name"):
955 if opts.get("name"):
959 shelved.append(opts["name"])
956 shelved.append(opts["name"])
960
957
961 if abortf or continuef:
958 if abortf or continuef:
962 if abortf and continuef:
959 if abortf and continuef:
963 raise error.Abort(_('cannot use both abort and continue'))
960 raise error.Abort(_('cannot use both abort and continue'))
964 if shelved:
961 if shelved:
965 raise error.Abort(_('cannot combine abort/continue with '
962 raise error.Abort(_('cannot combine abort/continue with '
966 'naming a shelved change'))
963 'naming a shelved change'))
967 if abortf and opts.get('tool', False):
964 if abortf and opts.get('tool', False):
968 ui.warn(_('tool option will be ignored\n'))
965 ui.warn(_('tool option will be ignored\n'))
969
966
970 try:
967 try:
971 state = shelvedstate.load(repo)
968 state = shelvedstate.load(repo)
972 if opts.get('keep') is None:
969 if opts.get('keep') is None:
973 opts['keep'] = state.keep
970 opts['keep'] = state.keep
974 except IOError as err:
971 except IOError as err:
975 if err.errno != errno.ENOENT:
972 if err.errno != errno.ENOENT:
976 raise
973 raise
977 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
974 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
978 except error.CorruptedState as err:
975 except error.CorruptedState as err:
979 ui.debug(pycompat.bytestr(err) + '\n')
976 ui.debug(pycompat.bytestr(err) + '\n')
980 if continuef:
977 if continuef:
981 msg = _('corrupted shelved state file')
978 msg = _('corrupted shelved state file')
982 hint = _('please run hg unshelve --abort to abort unshelve '
979 hint = _('please run hg unshelve --abort to abort unshelve '
983 'operation')
980 'operation')
984 raise error.Abort(msg, hint=hint)
981 raise error.Abort(msg, hint=hint)
985 elif abortf:
982 elif abortf:
986 msg = _('could not read shelved state file, your working copy '
983 msg = _('could not read shelved state file, your working copy '
987 'may be in an unexpected state\nplease update to some '
984 'may be in an unexpected state\nplease update to some '
988 'commit\n')
985 'commit\n')
989 ui.warn(msg)
986 ui.warn(msg)
990 shelvedstate.clear(repo)
987 shelvedstate.clear(repo)
991 return
988 return
992
989
993 if abortf:
990 if abortf:
994 return unshelveabort(ui, repo, state, opts)
991 return unshelveabort(ui, repo, state, opts)
995 elif continuef:
992 elif continuef:
996 return unshelvecontinue(ui, repo, state, opts)
993 return unshelvecontinue(ui, repo, state, opts)
997 elif len(shelved) > 1:
994 elif len(shelved) > 1:
998 raise error.Abort(_('can only unshelve one change at a time'))
995 raise error.Abort(_('can only unshelve one change at a time'))
999 elif not shelved:
996 elif not shelved:
1000 shelved = listshelves(repo)
997 shelved = listshelves(repo)
1001 if not shelved:
998 if not shelved:
1002 raise error.Abort(_('no shelved changes to apply!'))
999 raise error.Abort(_('no shelved changes to apply!'))
1003 basename = util.split(shelved[0][1])[1]
1000 basename = util.split(shelved[0][1])[1]
1004 ui.status(_("unshelving change '%s'\n") % basename)
1001 ui.status(_("unshelving change '%s'\n") % basename)
1005 else:
1002 else:
1006 basename = shelved[0]
1003 basename = shelved[0]
1007
1004
1008 if not shelvedfile(repo, basename, patchextension).exists():
1005 if not shelvedfile(repo, basename, patchextension).exists():
1009 raise error.Abort(_("shelved change '%s' not found") % basename)
1006 raise error.Abort(_("shelved change '%s' not found") % basename)
1010
1007
1011 repo = repo.unfiltered()
1008 repo = repo.unfiltered()
1012 lock = tr = None
1009 lock = tr = None
1013 try:
1010 try:
1014 lock = repo.lock()
1011 lock = repo.lock()
1015 tr = repo.transaction('unshelve', report=lambda x: None)
1012 tr = repo.transaction('unshelve', report=lambda x: None)
1016 oldtiprev = len(repo)
1013 oldtiprev = len(repo)
1017
1014
1018 pctx = repo['.']
1015 pctx = repo['.']
1019 tmpwctx = pctx
1016 tmpwctx = pctx
1020 # The goal is to have a commit structure like so:
1017 # The goal is to have a commit structure like so:
1021 # ...-> pctx -> tmpwctx -> shelvectx
1018 # ...-> pctx -> tmpwctx -> shelvectx
1022 # where tmpwctx is an optional commit with the user's pending changes
1019 # where tmpwctx is an optional commit with the user's pending changes
1023 # and shelvectx is the unshelved changes. Then we merge it all down
1020 # and shelvectx is the unshelved changes. Then we merge it all down
1024 # to the original pctx.
1021 # to the original pctx.
1025
1022
1026 activebookmark = _backupactivebookmark(repo)
1023 activebookmark = _backupactivebookmark(repo)
1027 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
1024 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
1028 tmpwctx)
1025 tmpwctx)
1029 repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
1026 repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
1030 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1027 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1031 branchtorestore = ''
1028 branchtorestore = ''
1032 if shelvectx.branch() != shelvectx.p1().branch():
1029 if shelvectx.branch() != shelvectx.p1().branch():
1033 branchtorestore = shelvectx.branch()
1030 branchtorestore = shelvectx.branch()
1034
1031
1035 shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
1032 shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
1036 basename, pctx, tmpwctx,
1033 basename, pctx, tmpwctx,
1037 shelvectx, branchtorestore,
1034 shelvectx, branchtorestore,
1038 activebookmark)
1035 activebookmark)
1039 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
1036 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
1040 with ui.configoverride(overrides, 'unshelve'):
1037 with ui.configoverride(overrides, 'unshelve'):
1041 mergefiles(ui, repo, pctx, shelvectx)
1038 mergefiles(ui, repo, pctx, shelvectx)
1042 restorebranch(ui, repo, branchtorestore)
1039 restorebranch(ui, repo, branchtorestore)
1043 _forgetunknownfiles(repo, shelvectx, addedbefore)
1040 _forgetunknownfiles(repo, shelvectx, addedbefore)
1044
1041
1045 shelvedstate.clear(repo)
1042 shelvedstate.clear(repo)
1046 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1043 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1047 unshelvecleanup(ui, repo, basename, opts)
1044 unshelvecleanup(ui, repo, basename, opts)
1048 finally:
1045 finally:
1049 if tr:
1046 if tr:
1050 tr.release()
1047 tr.release()
1051 lockmod.release(lock)
1048 lockmod.release(lock)
1052
1049
1053 @command('shelve',
1050 @command('shelve',
1054 [('A', 'addremove', None,
1051 [('A', 'addremove', None,
1055 _('mark new/missing files as added/removed before shelving')),
1052 _('mark new/missing files as added/removed before shelving')),
1056 ('u', 'unknown', None,
1053 ('u', 'unknown', None,
1057 _('store unknown files in the shelve')),
1054 _('store unknown files in the shelve')),
1058 ('', 'cleanup', None,
1055 ('', 'cleanup', None,
1059 _('delete all shelved changes')),
1056 _('delete all shelved changes')),
1060 ('', 'date', '',
1057 ('', 'date', '',
1061 _('shelve with the specified commit date'), _('DATE')),
1058 _('shelve with the specified commit date'), _('DATE')),
1062 ('d', 'delete', None,
1059 ('d', 'delete', None,
1063 _('delete the named shelved change(s)')),
1060 _('delete the named shelved change(s)')),
1064 ('e', 'edit', False,
1061 ('e', 'edit', False,
1065 _('invoke editor on commit messages')),
1062 _('invoke editor on commit messages')),
1066 ('l', 'list', None,
1063 ('l', 'list', None,
1067 _('list current shelves')),
1064 _('list current shelves')),
1068 ('m', 'message', '',
1065 ('m', 'message', '',
1069 _('use text as shelve message'), _('TEXT')),
1066 _('use text as shelve message'), _('TEXT')),
1070 ('n', 'name', '',
1067 ('n', 'name', '',
1071 _('use the given name for the shelved commit'), _('NAME')),
1068 _('use the given name for the shelved commit'), _('NAME')),
1072 ('p', 'patch', None,
1069 ('p', 'patch', None,
1073 _('output patches for changes (provide the names of the shelved '
1070 _('output patches for changes (provide the names of the shelved '
1074 'changes as positional arguments)')),
1071 'changes as positional arguments)')),
1075 ('i', 'interactive', None,
1072 ('i', 'interactive', None,
1076 _('interactive mode, only works while creating a shelve')),
1073 _('interactive mode, only works while creating a shelve')),
1077 ('', 'stat', None,
1074 ('', 'stat', None,
1078 _('output diffstat-style summary of changes (provide the names of '
1075 _('output diffstat-style summary of changes (provide the names of '
1079 'the shelved changes as positional arguments)')
1076 'the shelved changes as positional arguments)')
1080 )] + cmdutil.walkopts,
1077 )] + cmdutil.walkopts,
1081 _('hg shelve [OPTION]... [FILE]...'),
1078 _('hg shelve [OPTION]... [FILE]...'),
1082 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
1079 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
1083 def shelvecmd(ui, repo, *pats, **opts):
1080 def shelvecmd(ui, repo, *pats, **opts):
1084 '''save and set aside changes from the working directory
1081 '''save and set aside changes from the working directory
1085
1082
1086 Shelving takes files that "hg status" reports as not clean, saves
1083 Shelving takes files that "hg status" reports as not clean, saves
1087 the modifications to a bundle (a shelved change), and reverts the
1084 the modifications to a bundle (a shelved change), and reverts the
1088 files so that their state in the working directory becomes clean.
1085 files so that their state in the working directory becomes clean.
1089
1086
1090 To restore these changes to the working directory, using "hg
1087 To restore these changes to the working directory, using "hg
1091 unshelve"; this will work even if you switch to a different
1088 unshelve"; this will work even if you switch to a different
1092 commit.
1089 commit.
1093
1090
1094 When no files are specified, "hg shelve" saves all not-clean
1091 When no files are specified, "hg shelve" saves all not-clean
1095 files. If specific files or directories are named, only changes to
1092 files. If specific files or directories are named, only changes to
1096 those files are shelved.
1093 those files are shelved.
1097
1094
1098 In bare shelve (when no files are specified, without interactive,
1095 In bare shelve (when no files are specified, without interactive,
1099 include and exclude option), shelving remembers information if the
1096 include and exclude option), shelving remembers information if the
1100 working directory was on newly created branch, in other words working
1097 working directory was on newly created branch, in other words working
1101 directory was on different branch than its first parent. In this
1098 directory was on different branch than its first parent. In this
1102 situation unshelving restores branch information to the working directory.
1099 situation unshelving restores branch information to the working directory.
1103
1100
1104 Each shelved change has a name that makes it easier to find later.
1101 Each shelved change has a name that makes it easier to find later.
1105 The name of a shelved change defaults to being based on the active
1102 The name of a shelved change defaults to being based on the active
1106 bookmark, or if there is no active bookmark, the current named
1103 bookmark, or if there is no active bookmark, the current named
1107 branch. To specify a different name, use ``--name``.
1104 branch. To specify a different name, use ``--name``.
1108
1105
1109 To see a list of existing shelved changes, use the ``--list``
1106 To see a list of existing shelved changes, use the ``--list``
1110 option. For each shelved change, this will print its name, age,
1107 option. For each shelved change, this will print its name, age,
1111 and description; use ``--patch`` or ``--stat`` for more details.
1108 and description; use ``--patch`` or ``--stat`` for more details.
1112
1109
1113 To delete specific shelved changes, use ``--delete``. To delete
1110 To delete specific shelved changes, use ``--delete``. To delete
1114 all shelved changes, use ``--cleanup``.
1111 all shelved changes, use ``--cleanup``.
1115 '''
1112 '''
1116 opts = pycompat.byteskwargs(opts)
1113 opts = pycompat.byteskwargs(opts)
1117 allowables = [
1114 allowables = [
1118 ('addremove', {'create'}), # 'create' is pseudo action
1115 ('addremove', {'create'}), # 'create' is pseudo action
1119 ('unknown', {'create'}),
1116 ('unknown', {'create'}),
1120 ('cleanup', {'cleanup'}),
1117 ('cleanup', {'cleanup'}),
1121 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
1118 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
1122 ('delete', {'delete'}),
1119 ('delete', {'delete'}),
1123 ('edit', {'create'}),
1120 ('edit', {'create'}),
1124 ('list', {'list'}),
1121 ('list', {'list'}),
1125 ('message', {'create'}),
1122 ('message', {'create'}),
1126 ('name', {'create'}),
1123 ('name', {'create'}),
1127 ('patch', {'patch', 'list'}),
1124 ('patch', {'patch', 'list'}),
1128 ('stat', {'stat', 'list'}),
1125 ('stat', {'stat', 'list'}),
1129 ]
1126 ]
1130 def checkopt(opt):
1127 def checkopt(opt):
1131 if opts.get(opt):
1128 if opts.get(opt):
1132 for i, allowable in allowables:
1129 for i, allowable in allowables:
1133 if opts[i] and opt not in allowable:
1130 if opts[i] and opt not in allowable:
1134 raise error.Abort(_("options '--%s' and '--%s' may not be "
1131 raise error.Abort(_("options '--%s' and '--%s' may not be "
1135 "used together") % (opt, i))
1132 "used together") % (opt, i))
1136 return True
1133 return True
1137 if checkopt('cleanup'):
1134 if checkopt('cleanup'):
1138 if pats:
1135 if pats:
1139 raise error.Abort(_("cannot specify names when using '--cleanup'"))
1136 raise error.Abort(_("cannot specify names when using '--cleanup'"))
1140 return cleanupcmd(ui, repo)
1137 return cleanupcmd(ui, repo)
1141 elif checkopt('delete'):
1138 elif checkopt('delete'):
1142 return deletecmd(ui, repo, pats)
1139 return deletecmd(ui, repo, pats)
1143 elif checkopt('list'):
1140 elif checkopt('list'):
1144 return listcmd(ui, repo, pats, opts)
1141 return listcmd(ui, repo, pats, opts)
1145 elif checkopt('patch') or checkopt('stat'):
1142 elif checkopt('patch') or checkopt('stat'):
1146 return patchcmds(ui, repo, pats, opts)
1143 return patchcmds(ui, repo, pats, opts)
1147 else:
1144 else:
1148 return createcmd(ui, repo, pats, opts)
1145 return createcmd(ui, repo, pats, opts)
1149
1146
1150 def extsetup(ui):
1147 def extsetup(ui):
1151 cmdutil.unfinishedstates.append(
1148 cmdutil.unfinishedstates.append(
1152 [shelvedstate._filename, False, False,
1149 [shelvedstate._filename, False, False,
1153 _('unshelve already in progress'),
1150 _('unshelve already in progress'),
1154 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
1151 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
1155 cmdutil.afterresolvedstates.append(
1152 cmdutil.afterresolvedstates.append(
1156 [shelvedstate._filename, _('hg unshelve --continue')])
1153 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now