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