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