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