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