##// END OF EJS Templates
shelve: move actual created commit shelving to a separate function...
Kostia Balytskyi -
r30383:455f7856 default
parent child Browse files
Show More
@@ -1,937 +1,939
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
28
29 from mercurial.i18n import _
29 from mercurial.i18n import _
30 from mercurial import (
30 from mercurial import (
31 bundle2,
31 bundle2,
32 bundlerepo,
32 bundlerepo,
33 changegroup,
33 changegroup,
34 cmdutil,
34 cmdutil,
35 commands,
35 commands,
36 error,
36 error,
37 exchange,
37 exchange,
38 hg,
38 hg,
39 lock as lockmod,
39 lock as lockmod,
40 mdiff,
40 mdiff,
41 merge,
41 merge,
42 node as nodemod,
42 node as nodemod,
43 patch,
43 patch,
44 phases,
44 phases,
45 repair,
45 repair,
46 scmutil,
46 scmutil,
47 templatefilters,
47 templatefilters,
48 util,
48 util,
49 )
49 )
50
50
51 from . import (
51 from . import (
52 rebase,
52 rebase,
53 )
53 )
54
54
55 cmdtable = {}
55 cmdtable = {}
56 command = cmdutil.command(cmdtable)
56 command = cmdutil.command(cmdtable)
57 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
57 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
58 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
58 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
59 # be specifying the version(s) of Mercurial they are tested with, or
59 # be specifying the version(s) of Mercurial they are tested with, or
60 # leave the attribute unspecified.
60 # leave the attribute unspecified.
61 testedwith = 'ships-with-hg-core'
61 testedwith = 'ships-with-hg-core'
62
62
63 backupdir = 'shelve-backup'
63 backupdir = 'shelve-backup'
64 shelvedir = 'shelved'
64 shelvedir = 'shelved'
65 shelvefileextensions = ['hg', 'patch']
65 shelvefileextensions = ['hg', 'patch']
66
66
67 # we never need the user, so we use a
67 # we never need the user, so we use a
68 # generic user for all shelve operations
68 # generic user for all shelve operations
69 shelveuser = 'shelve@localhost'
69 shelveuser = 'shelve@localhost'
70
70
71 class shelvedfile(object):
71 class shelvedfile(object):
72 """Helper for the file storing a single shelve
72 """Helper for the file storing a single shelve
73
73
74 Handles common functions on shelve files (.hg/.patch) using
74 Handles common functions on shelve files (.hg/.patch) using
75 the vfs layer"""
75 the vfs layer"""
76 def __init__(self, repo, name, filetype=None):
76 def __init__(self, repo, name, filetype=None):
77 self.repo = repo
77 self.repo = repo
78 self.name = name
78 self.name = name
79 self.vfs = scmutil.vfs(repo.join(shelvedir))
79 self.vfs = scmutil.vfs(repo.join(shelvedir))
80 self.backupvfs = scmutil.vfs(repo.join(backupdir))
80 self.backupvfs = scmutil.vfs(repo.join(backupdir))
81 self.ui = self.repo.ui
81 self.ui = self.repo.ui
82 if filetype:
82 if filetype:
83 self.fname = name + '.' + filetype
83 self.fname = name + '.' + filetype
84 else:
84 else:
85 self.fname = name
85 self.fname = name
86
86
87 def exists(self):
87 def exists(self):
88 return self.vfs.exists(self.fname)
88 return self.vfs.exists(self.fname)
89
89
90 def filename(self):
90 def filename(self):
91 return self.vfs.join(self.fname)
91 return self.vfs.join(self.fname)
92
92
93 def backupfilename(self):
93 def backupfilename(self):
94 def gennames(base):
94 def gennames(base):
95 yield base
95 yield base
96 base, ext = base.rsplit('.', 1)
96 base, ext = base.rsplit('.', 1)
97 for i in itertools.count(1):
97 for i in itertools.count(1):
98 yield '%s-%d.%s' % (base, i, ext)
98 yield '%s-%d.%s' % (base, i, ext)
99
99
100 name = self.backupvfs.join(self.fname)
100 name = self.backupvfs.join(self.fname)
101 for n in gennames(name):
101 for n in gennames(name):
102 if not self.backupvfs.exists(n):
102 if not self.backupvfs.exists(n):
103 return n
103 return n
104
104
105 def movetobackup(self):
105 def movetobackup(self):
106 if not self.backupvfs.isdir():
106 if not self.backupvfs.isdir():
107 self.backupvfs.makedir()
107 self.backupvfs.makedir()
108 util.rename(self.filename(), self.backupfilename())
108 util.rename(self.filename(), self.backupfilename())
109
109
110 def stat(self):
110 def stat(self):
111 return self.vfs.stat(self.fname)
111 return self.vfs.stat(self.fname)
112
112
113 def opener(self, mode='rb'):
113 def opener(self, mode='rb'):
114 try:
114 try:
115 return self.vfs(self.fname, mode)
115 return self.vfs(self.fname, mode)
116 except IOError as err:
116 except IOError as err:
117 if err.errno != errno.ENOENT:
117 if err.errno != errno.ENOENT:
118 raise
118 raise
119 raise error.Abort(_("shelved change '%s' not found") % self.name)
119 raise error.Abort(_("shelved change '%s' not found") % self.name)
120
120
121 def applybundle(self):
121 def applybundle(self):
122 fp = self.opener()
122 fp = self.opener()
123 try:
123 try:
124 gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
124 gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
125 if not isinstance(gen, bundle2.unbundle20):
125 if not isinstance(gen, bundle2.unbundle20):
126 gen.apply(self.repo, 'unshelve',
126 gen.apply(self.repo, 'unshelve',
127 'bundle:' + self.vfs.join(self.fname),
127 'bundle:' + self.vfs.join(self.fname),
128 targetphase=phases.secret)
128 targetphase=phases.secret)
129 if isinstance(gen, bundle2.unbundle20):
129 if isinstance(gen, bundle2.unbundle20):
130 bundle2.applybundle(self.repo, gen,
130 bundle2.applybundle(self.repo, gen,
131 self.repo.currenttransaction(),
131 self.repo.currenttransaction(),
132 source='unshelve',
132 source='unshelve',
133 url='bundle:' + self.vfs.join(self.fname))
133 url='bundle:' + self.vfs.join(self.fname))
134 finally:
134 finally:
135 fp.close()
135 fp.close()
136
136
137 def bundlerepo(self):
137 def bundlerepo(self):
138 return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
138 return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
139 self.vfs.join(self.fname))
139 self.vfs.join(self.fname))
140 def writebundle(self, bases, node):
140 def writebundle(self, bases, node):
141 cgversion = changegroup.safeversion(self.repo)
141 cgversion = changegroup.safeversion(self.repo)
142 if cgversion == '01':
142 if cgversion == '01':
143 btype = 'HG10BZ'
143 btype = 'HG10BZ'
144 compression = None
144 compression = None
145 else:
145 else:
146 btype = 'HG20'
146 btype = 'HG20'
147 compression = 'BZ'
147 compression = 'BZ'
148
148
149 cg = changegroup.changegroupsubset(self.repo, bases, [node], 'shelve',
149 cg = changegroup.changegroupsubset(self.repo, bases, [node], 'shelve',
150 version=cgversion)
150 version=cgversion)
151 bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
151 bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
152 compression=compression)
152 compression=compression)
153
153
154 class shelvedstate(object):
154 class shelvedstate(object):
155 """Handle persistence during unshelving operations.
155 """Handle persistence during unshelving operations.
156
156
157 Handles saving and restoring a shelved state. Ensures that different
157 Handles saving and restoring a shelved state. Ensures that different
158 versions of a shelved state are possible and handles them appropriately.
158 versions of a shelved state are possible and handles them appropriately.
159 """
159 """
160 _version = 1
160 _version = 1
161 _filename = 'shelvedstate'
161 _filename = 'shelvedstate'
162
162
163 @classmethod
163 @classmethod
164 def load(cls, repo):
164 def load(cls, repo):
165 fp = repo.vfs(cls._filename)
165 fp = repo.vfs(cls._filename)
166 try:
166 try:
167 version = int(fp.readline().strip())
167 version = int(fp.readline().strip())
168
168
169 if version != cls._version:
169 if version != cls._version:
170 raise error.Abort(_('this version of shelve is incompatible '
170 raise error.Abort(_('this version of shelve is incompatible '
171 'with the version used in this repo'))
171 'with the version used in this repo'))
172 name = fp.readline().strip()
172 name = fp.readline().strip()
173 wctx = nodemod.bin(fp.readline().strip())
173 wctx = nodemod.bin(fp.readline().strip())
174 pendingctx = nodemod.bin(fp.readline().strip())
174 pendingctx = nodemod.bin(fp.readline().strip())
175 parents = [nodemod.bin(h) for h in fp.readline().split()]
175 parents = [nodemod.bin(h) for h in fp.readline().split()]
176 stripnodes = [nodemod.bin(h) for h in fp.readline().split()]
176 stripnodes = [nodemod.bin(h) for h in fp.readline().split()]
177 branchtorestore = fp.readline().strip()
177 branchtorestore = fp.readline().strip()
178 except (ValueError, TypeError) as err:
178 except (ValueError, TypeError) as err:
179 raise error.CorruptedState(str(err))
179 raise error.CorruptedState(str(err))
180 finally:
180 finally:
181 fp.close()
181 fp.close()
182
182
183 try:
183 try:
184 obj = cls()
184 obj = cls()
185 obj.name = name
185 obj.name = name
186 obj.wctx = repo[wctx]
186 obj.wctx = repo[wctx]
187 obj.pendingctx = repo[pendingctx]
187 obj.pendingctx = repo[pendingctx]
188 obj.parents = parents
188 obj.parents = parents
189 obj.stripnodes = stripnodes
189 obj.stripnodes = stripnodes
190 obj.branchtorestore = branchtorestore
190 obj.branchtorestore = branchtorestore
191 except error.RepoLookupError as err:
191 except error.RepoLookupError as err:
192 raise error.CorruptedState(str(err))
192 raise error.CorruptedState(str(err))
193
193
194 return obj
194 return obj
195
195
196 @classmethod
196 @classmethod
197 def save(cls, repo, name, originalwctx, pendingctx, stripnodes,
197 def save(cls, repo, name, originalwctx, pendingctx, stripnodes,
198 branchtorestore):
198 branchtorestore):
199 fp = repo.vfs(cls._filename, 'wb')
199 fp = repo.vfs(cls._filename, 'wb')
200 fp.write('%i\n' % cls._version)
200 fp.write('%i\n' % cls._version)
201 fp.write('%s\n' % name)
201 fp.write('%s\n' % name)
202 fp.write('%s\n' % nodemod.hex(originalwctx.node()))
202 fp.write('%s\n' % nodemod.hex(originalwctx.node()))
203 fp.write('%s\n' % nodemod.hex(pendingctx.node()))
203 fp.write('%s\n' % nodemod.hex(pendingctx.node()))
204 fp.write('%s\n' %
204 fp.write('%s\n' %
205 ' '.join([nodemod.hex(p) for p in repo.dirstate.parents()]))
205 ' '.join([nodemod.hex(p) for p in repo.dirstate.parents()]))
206 fp.write('%s\n' %
206 fp.write('%s\n' %
207 ' '.join([nodemod.hex(n) for n in stripnodes]))
207 ' '.join([nodemod.hex(n) for n in stripnodes]))
208 fp.write('%s\n' % branchtorestore)
208 fp.write('%s\n' % branchtorestore)
209 fp.close()
209 fp.close()
210
210
211 @classmethod
211 @classmethod
212 def clear(cls, repo):
212 def clear(cls, repo):
213 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
213 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
214
214
215 def cleanupoldbackups(repo):
215 def cleanupoldbackups(repo):
216 vfs = scmutil.vfs(repo.join(backupdir))
216 vfs = scmutil.vfs(repo.join(backupdir))
217 maxbackups = repo.ui.configint('shelve', 'maxbackups', 10)
217 maxbackups = repo.ui.configint('shelve', 'maxbackups', 10)
218 hgfiles = [f for f in vfs.listdir() if f.endswith('.hg')]
218 hgfiles = [f for f in vfs.listdir() if f.endswith('.hg')]
219 hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles])
219 hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles])
220 if 0 < maxbackups and maxbackups < len(hgfiles):
220 if 0 < maxbackups and maxbackups < len(hgfiles):
221 bordermtime = hgfiles[-maxbackups][0]
221 bordermtime = hgfiles[-maxbackups][0]
222 else:
222 else:
223 bordermtime = None
223 bordermtime = None
224 for mtime, f in hgfiles[:len(hgfiles) - maxbackups]:
224 for mtime, f in hgfiles[:len(hgfiles) - maxbackups]:
225 if mtime == bordermtime:
225 if mtime == bordermtime:
226 # keep it, because timestamp can't decide exact order of backups
226 # keep it, because timestamp can't decide exact order of backups
227 continue
227 continue
228 base = f[:-3]
228 base = f[:-3]
229 for ext in shelvefileextensions:
229 for ext in shelvefileextensions:
230 try:
230 try:
231 vfs.unlink(base + '.' + ext)
231 vfs.unlink(base + '.' + ext)
232 except OSError as err:
232 except OSError as err:
233 if err.errno != errno.ENOENT:
233 if err.errno != errno.ENOENT:
234 raise
234 raise
235
235
236 def _aborttransaction(repo):
236 def _aborttransaction(repo):
237 '''Abort current transaction for shelve/unshelve, but keep dirstate
237 '''Abort current transaction for shelve/unshelve, but keep dirstate
238 '''
238 '''
239 tr = repo.currenttransaction()
239 tr = repo.currenttransaction()
240 repo.dirstate.savebackup(tr, suffix='.shelve')
240 repo.dirstate.savebackup(tr, suffix='.shelve')
241 tr.abort()
241 tr.abort()
242 repo.dirstate.restorebackup(None, suffix='.shelve')
242 repo.dirstate.restorebackup(None, suffix='.shelve')
243
243
244 def createcmd(ui, repo, pats, opts):
244 def createcmd(ui, repo, pats, opts):
245 """subcommand that creates a new shelve"""
245 """subcommand that creates a new shelve"""
246 with repo.wlock():
246 with repo.wlock():
247 cmdutil.checkunfinished(repo)
247 cmdutil.checkunfinished(repo)
248 return _docreatecmd(ui, repo, pats, opts)
248 return _docreatecmd(ui, repo, pats, opts)
249
249
250 def getshelvename(repo, parent, opts):
250 def getshelvename(repo, parent, opts):
251 """Decide on the name this shelve is going to have"""
251 """Decide on the name this shelve is going to have"""
252 def gennames():
252 def gennames():
253 yield label
253 yield label
254 for i in xrange(1, 100):
254 for i in xrange(1, 100):
255 yield '%s-%02d' % (label, i)
255 yield '%s-%02d' % (label, i)
256 name = opts.get('name')
256 name = opts.get('name')
257 label = repo._activebookmark or parent.branch() or 'default'
257 label = repo._activebookmark or parent.branch() or 'default'
258 # slashes aren't allowed in filenames, therefore we rename it
258 # slashes aren't allowed in filenames, therefore we rename it
259 label = label.replace('/', '_')
259 label = label.replace('/', '_')
260
260
261 if name:
261 if name:
262 if shelvedfile(repo, name, 'hg').exists():
262 if shelvedfile(repo, name, 'hg').exists():
263 e = _("a shelved change named '%s' already exists") % name
263 e = _("a shelved change named '%s' already exists") % name
264 raise error.Abort(e)
264 raise error.Abort(e)
265 else:
265 else:
266 for n in gennames():
266 for n in gennames():
267 if not shelvedfile(repo, n, 'hg').exists():
267 if not shelvedfile(repo, n, 'hg').exists():
268 name = n
268 name = n
269 break
269 break
270 else:
270 else:
271 raise error.Abort(_("too many shelved changes named '%s'") % label)
271 raise error.Abort(_("too many shelved changes named '%s'") % label)
272
272
273 # ensure we are not creating a subdirectory or a hidden file
273 # ensure we are not creating a subdirectory or a hidden file
274 if '/' in name or '\\' in name:
274 if '/' in name or '\\' in name:
275 raise error.Abort(_('shelved change names may not contain slashes'))
275 raise error.Abort(_('shelved change names may not contain slashes'))
276 if name.startswith('.'):
276 if name.startswith('.'):
277 raise error.Abort(_("shelved change names may not start with '.'"))
277 raise error.Abort(_("shelved change names may not start with '.'"))
278 return name
278 return name
279
279
280 def mutableancestors(ctx):
280 def mutableancestors(ctx):
281 """return all mutable ancestors for ctx (included)
281 """return all mutable ancestors for ctx (included)
282
282
283 Much faster than the revset ancestors(ctx) & draft()"""
283 Much faster than the revset ancestors(ctx) & draft()"""
284 seen = set([nodemod.nullrev])
284 seen = set([nodemod.nullrev])
285 visit = collections.deque()
285 visit = collections.deque()
286 visit.append(ctx)
286 visit.append(ctx)
287 while visit:
287 while visit:
288 ctx = visit.popleft()
288 ctx = visit.popleft()
289 yield ctx.node()
289 yield ctx.node()
290 for parent in ctx.parents():
290 for parent in ctx.parents():
291 rev = parent.rev()
291 rev = parent.rev()
292 if rev not in seen:
292 if rev not in seen:
293 seen.add(rev)
293 seen.add(rev)
294 if parent.mutable():
294 if parent.mutable():
295 visit.append(parent)
295 visit.append(parent)
296
296
297 def getcommitfunc(extra, interactive, editor=False):
297 def getcommitfunc(extra, interactive, editor=False):
298 def commitfunc(ui, repo, message, match, opts):
298 def commitfunc(ui, repo, message, match, opts):
299 hasmq = util.safehasattr(repo, 'mq')
299 hasmq = util.safehasattr(repo, 'mq')
300 if hasmq:
300 if hasmq:
301 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
301 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
302 backup = repo.ui.backupconfig('phases', 'new-commit')
302 backup = repo.ui.backupconfig('phases', 'new-commit')
303 try:
303 try:
304 repo.ui.setconfig('phases', 'new-commit', phases.secret)
304 repo.ui.setconfig('phases', 'new-commit', phases.secret)
305 editor_ = False
305 editor_ = False
306 if editor:
306 if editor:
307 editor_ = cmdutil.getcommiteditor(editform='shelve.shelve',
307 editor_ = cmdutil.getcommiteditor(editform='shelve.shelve',
308 **opts)
308 **opts)
309 return repo.commit(message, shelveuser, opts.get('date'), match,
309 return repo.commit(message, shelveuser, opts.get('date'), match,
310 editor=editor_, extra=extra)
310 editor=editor_, extra=extra)
311 finally:
311 finally:
312 repo.ui.restoreconfig(backup)
312 repo.ui.restoreconfig(backup)
313 if hasmq:
313 if hasmq:
314 repo.mq.checkapplied = saved
314 repo.mq.checkapplied = saved
315
315
316 def interactivecommitfunc(ui, repo, *pats, **opts):
316 def interactivecommitfunc(ui, repo, *pats, **opts):
317 match = scmutil.match(repo['.'], pats, {})
317 match = scmutil.match(repo['.'], pats, {})
318 message = opts['message']
318 message = opts['message']
319 return commitfunc(ui, repo, message, match, opts)
319 return commitfunc(ui, repo, message, match, opts)
320
320
321 return interactivecommitfunc if interactive else commitfunc
321 return interactivecommitfunc if interactive else commitfunc
322
322
323 def _nothingtoshelvemessaging(ui, repo, pats, opts):
323 def _nothingtoshelvemessaging(ui, repo, pats, opts):
324 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
324 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
325 if stat.deleted:
325 if stat.deleted:
326 ui.status(_("nothing changed (%d missing files, see "
326 ui.status(_("nothing changed (%d missing files, see "
327 "'hg status')\n") % len(stat.deleted))
327 "'hg status')\n") % len(stat.deleted))
328 else:
328 else:
329 ui.status(_("nothing changed\n"))
329 ui.status(_("nothing changed\n"))
330
330
331 def _shelvecreatedcommit(repo, node, name):
332 bases = list(mutableancestors(repo[node]))
333 shelvedfile(repo, name, 'hg').writebundle(bases, node)
334 cmdutil.export(repo, [node],
335 fp=shelvedfile(repo, name, 'patch').opener('wb'),
336 opts=mdiff.diffopts(git=True))
337
331 def _docreatecmd(ui, repo, pats, opts):
338 def _docreatecmd(ui, repo, pats, opts):
332 wctx = repo[None]
339 wctx = repo[None]
333 parents = wctx.parents()
340 parents = wctx.parents()
334 if len(parents) > 1:
341 if len(parents) > 1:
335 raise error.Abort(_('cannot shelve while merging'))
342 raise error.Abort(_('cannot shelve while merging'))
336 parent = parents[0]
343 parent = parents[0]
337 origbranch = wctx.branch()
344 origbranch = wctx.branch()
338
345
339 if parent.node() != nodemod.nullid:
346 if parent.node() != nodemod.nullid:
340 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
347 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
341 else:
348 else:
342 desc = '(changes in empty repository)'
349 desc = '(changes in empty repository)'
343
350
344 if not opts.get('message'):
351 if not opts.get('message'):
345 opts['message'] = desc
352 opts['message'] = desc
346
353
347 lock = tr = None
354 lock = tr = None
348 try:
355 try:
349 lock = repo.lock()
356 lock = repo.lock()
350
357
351 # use an uncommitted transaction to generate the bundle to avoid
358 # use an uncommitted transaction to generate the bundle to avoid
352 # pull races. ensure we don't print the abort message to stderr.
359 # pull races. ensure we don't print the abort message to stderr.
353 tr = repo.transaction('commit', report=lambda x: None)
360 tr = repo.transaction('commit', report=lambda x: None)
354
361
355 interactive = opts.get('interactive', False)
362 interactive = opts.get('interactive', False)
356 includeunknown = (opts.get('unknown', False) and
363 includeunknown = (opts.get('unknown', False) and
357 not opts.get('addremove', False))
364 not opts.get('addremove', False))
358
365
359 name = getshelvename(repo, parent, opts)
366 name = getshelvename(repo, parent, opts)
360 extra={}
367 extra={}
361 if includeunknown:
368 if includeunknown:
362 s = repo.status(match=scmutil.match(repo[None], pats, opts),
369 s = repo.status(match=scmutil.match(repo[None], pats, opts),
363 unknown=True)
370 unknown=True)
364 if s.unknown:
371 if s.unknown:
365 extra['shelve_unknown'] = '\0'.join(s.unknown)
372 extra['shelve_unknown'] = '\0'.join(s.unknown)
366 repo[None].add(s.unknown)
373 repo[None].add(s.unknown)
367
374
368 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
375 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
369 # In non-bare shelve we don't store newly created branch
376 # In non-bare shelve we don't store newly created branch
370 # at bundled commit
377 # at bundled commit
371 repo.dirstate.setbranch(repo['.'].branch())
378 repo.dirstate.setbranch(repo['.'].branch())
372
379
373 commitfunc = getcommitfunc(extra, interactive, editor=True)
380 commitfunc = getcommitfunc(extra, interactive, editor=True)
374 if not interactive:
381 if not interactive:
375 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
382 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
376 else:
383 else:
377 node = cmdutil.dorecord(ui, repo, commitfunc, None,
384 node = cmdutil.dorecord(ui, repo, commitfunc, None,
378 False, cmdutil.recordfilter, *pats, **opts)
385 False, cmdutil.recordfilter, *pats, **opts)
379 if not node:
386 if not node:
380 _nothingtoshelvemessaging(ui, repo, pats, opts)
387 _nothingtoshelvemessaging(ui, repo, pats, opts)
381 return 1
388 return 1
382
389
383 bases = list(mutableancestors(repo[node]))
390 _shelvecreatedcommit(repo, node, name)
384 shelvedfile(repo, name, 'hg').writebundle(bases, node)
385 cmdutil.export(repo, [node],
386 fp=shelvedfile(repo, name, 'patch').opener('wb'),
387 opts=mdiff.diffopts(git=True))
388
389
391
390 if ui.formatted():
392 if ui.formatted():
391 desc = util.ellipsis(desc, ui.termwidth())
393 desc = util.ellipsis(desc, ui.termwidth())
392 ui.status(_('shelved as %s\n') % name)
394 ui.status(_('shelved as %s\n') % name)
393 hg.update(repo, parent.node())
395 hg.update(repo, parent.node())
394 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
396 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
395 repo.dirstate.setbranch(origbranch)
397 repo.dirstate.setbranch(origbranch)
396
398
397 _aborttransaction(repo)
399 _aborttransaction(repo)
398 finally:
400 finally:
399 lockmod.release(tr, lock)
401 lockmod.release(tr, lock)
400
402
401 def _isbareshelve(pats, opts):
403 def _isbareshelve(pats, opts):
402 return (not pats
404 return (not pats
403 and not opts.get('interactive', False)
405 and not opts.get('interactive', False)
404 and not opts.get('include', False)
406 and not opts.get('include', False)
405 and not opts.get('exclude', False))
407 and not opts.get('exclude', False))
406
408
407 def _iswctxonnewbranch(repo):
409 def _iswctxonnewbranch(repo):
408 return repo[None].branch() != repo['.'].branch()
410 return repo[None].branch() != repo['.'].branch()
409
411
410 def cleanupcmd(ui, repo):
412 def cleanupcmd(ui, repo):
411 """subcommand that deletes all shelves"""
413 """subcommand that deletes all shelves"""
412
414
413 with repo.wlock():
415 with repo.wlock():
414 for (name, _type) in repo.vfs.readdir(shelvedir):
416 for (name, _type) in repo.vfs.readdir(shelvedir):
415 suffix = name.rsplit('.', 1)[-1]
417 suffix = name.rsplit('.', 1)[-1]
416 if suffix in shelvefileextensions:
418 if suffix in shelvefileextensions:
417 shelvedfile(repo, name).movetobackup()
419 shelvedfile(repo, name).movetobackup()
418 cleanupoldbackups(repo)
420 cleanupoldbackups(repo)
419
421
420 def deletecmd(ui, repo, pats):
422 def deletecmd(ui, repo, pats):
421 """subcommand that deletes a specific shelve"""
423 """subcommand that deletes a specific shelve"""
422 if not pats:
424 if not pats:
423 raise error.Abort(_('no shelved changes specified!'))
425 raise error.Abort(_('no shelved changes specified!'))
424 with repo.wlock():
426 with repo.wlock():
425 try:
427 try:
426 for name in pats:
428 for name in pats:
427 for suffix in shelvefileextensions:
429 for suffix in shelvefileextensions:
428 shfile = shelvedfile(repo, name, suffix)
430 shfile = shelvedfile(repo, name, suffix)
429 # patch file is necessary, as it should
431 # patch file is necessary, as it should
430 # be present for any kind of shelve,
432 # be present for any kind of shelve,
431 # but the .hg file is optional as in future we
433 # but the .hg file is optional as in future we
432 # will add obsolete shelve with does not create a
434 # will add obsolete shelve with does not create a
433 # bundle
435 # bundle
434 if shfile.exists() or suffix == 'patch':
436 if shfile.exists() or suffix == 'patch':
435 shfile.movetobackup()
437 shfile.movetobackup()
436 cleanupoldbackups(repo)
438 cleanupoldbackups(repo)
437 except OSError as err:
439 except OSError as err:
438 if err.errno != errno.ENOENT:
440 if err.errno != errno.ENOENT:
439 raise
441 raise
440 raise error.Abort(_("shelved change '%s' not found") % name)
442 raise error.Abort(_("shelved change '%s' not found") % name)
441
443
442 def listshelves(repo):
444 def listshelves(repo):
443 """return all shelves in repo as list of (time, filename)"""
445 """return all shelves in repo as list of (time, filename)"""
444 try:
446 try:
445 names = repo.vfs.readdir(shelvedir)
447 names = repo.vfs.readdir(shelvedir)
446 except OSError as err:
448 except OSError as err:
447 if err.errno != errno.ENOENT:
449 if err.errno != errno.ENOENT:
448 raise
450 raise
449 return []
451 return []
450 info = []
452 info = []
451 for (name, _type) in names:
453 for (name, _type) in names:
452 pfx, sfx = name.rsplit('.', 1)
454 pfx, sfx = name.rsplit('.', 1)
453 if not pfx or sfx != 'patch':
455 if not pfx or sfx != 'patch':
454 continue
456 continue
455 st = shelvedfile(repo, name).stat()
457 st = shelvedfile(repo, name).stat()
456 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
458 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
457 return sorted(info, reverse=True)
459 return sorted(info, reverse=True)
458
460
459 def listcmd(ui, repo, pats, opts):
461 def listcmd(ui, repo, pats, opts):
460 """subcommand that displays the list of shelves"""
462 """subcommand that displays the list of shelves"""
461 pats = set(pats)
463 pats = set(pats)
462 width = 80
464 width = 80
463 if not ui.plain():
465 if not ui.plain():
464 width = ui.termwidth()
466 width = ui.termwidth()
465 namelabel = 'shelve.newest'
467 namelabel = 'shelve.newest'
466 for mtime, name in listshelves(repo):
468 for mtime, name in listshelves(repo):
467 sname = util.split(name)[1]
469 sname = util.split(name)[1]
468 if pats and sname not in pats:
470 if pats and sname not in pats:
469 continue
471 continue
470 ui.write(sname, label=namelabel)
472 ui.write(sname, label=namelabel)
471 namelabel = 'shelve.name'
473 namelabel = 'shelve.name'
472 if ui.quiet:
474 if ui.quiet:
473 ui.write('\n')
475 ui.write('\n')
474 continue
476 continue
475 ui.write(' ' * (16 - len(sname)))
477 ui.write(' ' * (16 - len(sname)))
476 used = 16
478 used = 16
477 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
479 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
478 ui.write(age, label='shelve.age')
480 ui.write(age, label='shelve.age')
479 ui.write(' ' * (12 - len(age)))
481 ui.write(' ' * (12 - len(age)))
480 used += 12
482 used += 12
481 with open(name + '.patch', 'rb') as fp:
483 with open(name + '.patch', 'rb') as fp:
482 while True:
484 while True:
483 line = fp.readline()
485 line = fp.readline()
484 if not line:
486 if not line:
485 break
487 break
486 if not line.startswith('#'):
488 if not line.startswith('#'):
487 desc = line.rstrip()
489 desc = line.rstrip()
488 if ui.formatted():
490 if ui.formatted():
489 desc = util.ellipsis(desc, width - used)
491 desc = util.ellipsis(desc, width - used)
490 ui.write(desc)
492 ui.write(desc)
491 break
493 break
492 ui.write('\n')
494 ui.write('\n')
493 if not (opts['patch'] or opts['stat']):
495 if not (opts['patch'] or opts['stat']):
494 continue
496 continue
495 difflines = fp.readlines()
497 difflines = fp.readlines()
496 if opts['patch']:
498 if opts['patch']:
497 for chunk, label in patch.difflabel(iter, difflines):
499 for chunk, label in patch.difflabel(iter, difflines):
498 ui.write(chunk, label=label)
500 ui.write(chunk, label=label)
499 if opts['stat']:
501 if opts['stat']:
500 for chunk, label in patch.diffstatui(difflines, width=width,
502 for chunk, label in patch.diffstatui(difflines, width=width,
501 git=True):
503 git=True):
502 ui.write(chunk, label=label)
504 ui.write(chunk, label=label)
503
505
504 def singlepatchcmds(ui, repo, pats, opts, subcommand):
506 def singlepatchcmds(ui, repo, pats, opts, subcommand):
505 """subcommand that displays a single shelf"""
507 """subcommand that displays a single shelf"""
506 if len(pats) != 1:
508 if len(pats) != 1:
507 raise error.Abort(_("--%s expects a single shelf") % subcommand)
509 raise error.Abort(_("--%s expects a single shelf") % subcommand)
508 shelfname = pats[0]
510 shelfname = pats[0]
509
511
510 if not shelvedfile(repo, shelfname, 'patch').exists():
512 if not shelvedfile(repo, shelfname, 'patch').exists():
511 raise error.Abort(_("cannot find shelf %s") % shelfname)
513 raise error.Abort(_("cannot find shelf %s") % shelfname)
512
514
513 listcmd(ui, repo, pats, opts)
515 listcmd(ui, repo, pats, opts)
514
516
515 def checkparents(repo, state):
517 def checkparents(repo, state):
516 """check parent while resuming an unshelve"""
518 """check parent while resuming an unshelve"""
517 if state.parents != repo.dirstate.parents():
519 if state.parents != repo.dirstate.parents():
518 raise error.Abort(_('working directory parents do not match unshelve '
520 raise error.Abort(_('working directory parents do not match unshelve '
519 'state'))
521 'state'))
520
522
521 def pathtofiles(repo, files):
523 def pathtofiles(repo, files):
522 cwd = repo.getcwd()
524 cwd = repo.getcwd()
523 return [repo.pathto(f, cwd) for f in files]
525 return [repo.pathto(f, cwd) for f in files]
524
526
525 def unshelveabort(ui, repo, state, opts):
527 def unshelveabort(ui, repo, state, opts):
526 """subcommand that abort an in-progress unshelve"""
528 """subcommand that abort an in-progress unshelve"""
527 with repo.lock():
529 with repo.lock():
528 try:
530 try:
529 checkparents(repo, state)
531 checkparents(repo, state)
530
532
531 util.rename(repo.join('unshelverebasestate'),
533 util.rename(repo.join('unshelverebasestate'),
532 repo.join('rebasestate'))
534 repo.join('rebasestate'))
533 try:
535 try:
534 rebase.rebase(ui, repo, **{
536 rebase.rebase(ui, repo, **{
535 'abort' : True
537 'abort' : True
536 })
538 })
537 except Exception:
539 except Exception:
538 util.rename(repo.join('rebasestate'),
540 util.rename(repo.join('rebasestate'),
539 repo.join('unshelverebasestate'))
541 repo.join('unshelverebasestate'))
540 raise
542 raise
541
543
542 mergefiles(ui, repo, state.wctx, state.pendingctx)
544 mergefiles(ui, repo, state.wctx, state.pendingctx)
543 repair.strip(ui, repo, state.stripnodes, backup=False,
545 repair.strip(ui, repo, state.stripnodes, backup=False,
544 topic='shelve')
546 topic='shelve')
545 finally:
547 finally:
546 shelvedstate.clear(repo)
548 shelvedstate.clear(repo)
547 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
549 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
548
550
549 def mergefiles(ui, repo, wctx, shelvectx):
551 def mergefiles(ui, repo, wctx, shelvectx):
550 """updates to wctx and merges the changes from shelvectx into the
552 """updates to wctx and merges the changes from shelvectx into the
551 dirstate."""
553 dirstate."""
552 oldquiet = ui.quiet
554 oldquiet = ui.quiet
553 try:
555 try:
554 ui.quiet = True
556 ui.quiet = True
555 hg.update(repo, wctx.node())
557 hg.update(repo, wctx.node())
556 files = []
558 files = []
557 files.extend(shelvectx.files())
559 files.extend(shelvectx.files())
558 files.extend(shelvectx.parents()[0].files())
560 files.extend(shelvectx.parents()[0].files())
559
561
560 # revert will overwrite unknown files, so move them out of the way
562 # revert will overwrite unknown files, so move them out of the way
561 for file in repo.status(unknown=True).unknown:
563 for file in repo.status(unknown=True).unknown:
562 if file in files:
564 if file in files:
563 util.rename(file, scmutil.origpath(ui, repo, file))
565 util.rename(file, scmutil.origpath(ui, repo, file))
564 ui.pushbuffer(True)
566 ui.pushbuffer(True)
565 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
567 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
566 *pathtofiles(repo, files),
568 *pathtofiles(repo, files),
567 **{'no_backup': True})
569 **{'no_backup': True})
568 ui.popbuffer()
570 ui.popbuffer()
569 finally:
571 finally:
570 ui.quiet = oldquiet
572 ui.quiet = oldquiet
571
573
572 def restorebranch(ui, repo, branchtorestore):
574 def restorebranch(ui, repo, branchtorestore):
573 if branchtorestore and branchtorestore != repo.dirstate.branch():
575 if branchtorestore and branchtorestore != repo.dirstate.branch():
574 repo.dirstate.setbranch(branchtorestore)
576 repo.dirstate.setbranch(branchtorestore)
575 ui.status(_('marked working directory as branch %s\n')
577 ui.status(_('marked working directory as branch %s\n')
576 % branchtorestore)
578 % branchtorestore)
577
579
578 def unshelvecleanup(ui, repo, name, opts):
580 def unshelvecleanup(ui, repo, name, opts):
579 """remove related files after an unshelve"""
581 """remove related files after an unshelve"""
580 if not opts.get('keep'):
582 if not opts.get('keep'):
581 for filetype in shelvefileextensions:
583 for filetype in shelvefileextensions:
582 shfile = shelvedfile(repo, name, filetype)
584 shfile = shelvedfile(repo, name, filetype)
583 if shfile.exists():
585 if shfile.exists():
584 shfile.movetobackup()
586 shfile.movetobackup()
585 cleanupoldbackups(repo)
587 cleanupoldbackups(repo)
586
588
587 def unshelvecontinue(ui, repo, state, opts):
589 def unshelvecontinue(ui, repo, state, opts):
588 """subcommand to continue an in-progress unshelve"""
590 """subcommand to continue an in-progress unshelve"""
589 # We're finishing off a merge. First parent is our original
591 # We're finishing off a merge. First parent is our original
590 # parent, second is the temporary "fake" commit we're unshelving.
592 # parent, second is the temporary "fake" commit we're unshelving.
591 with repo.lock():
593 with repo.lock():
592 checkparents(repo, state)
594 checkparents(repo, state)
593 ms = merge.mergestate.read(repo)
595 ms = merge.mergestate.read(repo)
594 if [f for f in ms if ms[f] == 'u']:
596 if [f for f in ms if ms[f] == 'u']:
595 raise error.Abort(
597 raise error.Abort(
596 _("unresolved conflicts, can't continue"),
598 _("unresolved conflicts, can't continue"),
597 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
599 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
598
600
599 util.rename(repo.join('unshelverebasestate'),
601 util.rename(repo.join('unshelverebasestate'),
600 repo.join('rebasestate'))
602 repo.join('rebasestate'))
601 try:
603 try:
602 rebase.rebase(ui, repo, **{
604 rebase.rebase(ui, repo, **{
603 'continue' : True
605 'continue' : True
604 })
606 })
605 except Exception:
607 except Exception:
606 util.rename(repo.join('rebasestate'),
608 util.rename(repo.join('rebasestate'),
607 repo.join('unshelverebasestate'))
609 repo.join('unshelverebasestate'))
608 raise
610 raise
609
611
610 shelvectx = repo['tip']
612 shelvectx = repo['tip']
611 if not shelvectx in state.pendingctx.children():
613 if not shelvectx in state.pendingctx.children():
612 # rebase was a no-op, so it produced no child commit
614 # rebase was a no-op, so it produced no child commit
613 shelvectx = state.pendingctx
615 shelvectx = state.pendingctx
614 else:
616 else:
615 # only strip the shelvectx if the rebase produced it
617 # only strip the shelvectx if the rebase produced it
616 state.stripnodes.append(shelvectx.node())
618 state.stripnodes.append(shelvectx.node())
617
619
618 mergefiles(ui, repo, state.wctx, shelvectx)
620 mergefiles(ui, repo, state.wctx, shelvectx)
619 restorebranch(ui, repo, state.branchtorestore)
621 restorebranch(ui, repo, state.branchtorestore)
620
622
621 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
623 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
622 shelvedstate.clear(repo)
624 shelvedstate.clear(repo)
623 unshelvecleanup(ui, repo, state.name, opts)
625 unshelvecleanup(ui, repo, state.name, opts)
624 ui.status(_("unshelve of '%s' complete\n") % state.name)
626 ui.status(_("unshelve of '%s' complete\n") % state.name)
625
627
626 @command('unshelve',
628 @command('unshelve',
627 [('a', 'abort', None,
629 [('a', 'abort', None,
628 _('abort an incomplete unshelve operation')),
630 _('abort an incomplete unshelve operation')),
629 ('c', 'continue', None,
631 ('c', 'continue', None,
630 _('continue an incomplete unshelve operation')),
632 _('continue an incomplete unshelve operation')),
631 ('k', 'keep', None,
633 ('k', 'keep', None,
632 _('keep shelve after unshelving')),
634 _('keep shelve after unshelving')),
633 ('t', 'tool', '', _('specify merge tool')),
635 ('t', 'tool', '', _('specify merge tool')),
634 ('', 'date', '',
636 ('', 'date', '',
635 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
637 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
636 _('hg unshelve [SHELVED]'))
638 _('hg unshelve [SHELVED]'))
637 def unshelve(ui, repo, *shelved, **opts):
639 def unshelve(ui, repo, *shelved, **opts):
638 """restore a shelved change to the working directory
640 """restore a shelved change to the working directory
639
641
640 This command accepts an optional name of a shelved change to
642 This command accepts an optional name of a shelved change to
641 restore. If none is given, the most recent shelved change is used.
643 restore. If none is given, the most recent shelved change is used.
642
644
643 If a shelved change is applied successfully, the bundle that
645 If a shelved change is applied successfully, the bundle that
644 contains the shelved changes is moved to a backup location
646 contains the shelved changes is moved to a backup location
645 (.hg/shelve-backup).
647 (.hg/shelve-backup).
646
648
647 Since you can restore a shelved change on top of an arbitrary
649 Since you can restore a shelved change on top of an arbitrary
648 commit, it is possible that unshelving will result in a conflict
650 commit, it is possible that unshelving will result in a conflict
649 between your changes and the commits you are unshelving onto. If
651 between your changes and the commits you are unshelving onto. If
650 this occurs, you must resolve the conflict, then use
652 this occurs, you must resolve the conflict, then use
651 ``--continue`` to complete the unshelve operation. (The bundle
653 ``--continue`` to complete the unshelve operation. (The bundle
652 will not be moved until you successfully complete the unshelve.)
654 will not be moved until you successfully complete the unshelve.)
653
655
654 (Alternatively, you can use ``--abort`` to abandon an unshelve
656 (Alternatively, you can use ``--abort`` to abandon an unshelve
655 that causes a conflict. This reverts the unshelved changes, and
657 that causes a conflict. This reverts the unshelved changes, and
656 leaves the bundle in place.)
658 leaves the bundle in place.)
657
659
658 If bare shelved change(when no files are specified, without interactive,
660 If bare shelved change(when no files are specified, without interactive,
659 include and exclude option) was done on newly created branch it would
661 include and exclude option) was done on newly created branch it would
660 restore branch information to the working directory.
662 restore branch information to the working directory.
661
663
662 After a successful unshelve, the shelved changes are stored in a
664 After a successful unshelve, the shelved changes are stored in a
663 backup directory. Only the N most recent backups are kept. N
665 backup directory. Only the N most recent backups are kept. N
664 defaults to 10 but can be overridden using the ``shelve.maxbackups``
666 defaults to 10 but can be overridden using the ``shelve.maxbackups``
665 configuration option.
667 configuration option.
666
668
667 .. container:: verbose
669 .. container:: verbose
668
670
669 Timestamp in seconds is used to decide order of backups. More
671 Timestamp in seconds is used to decide order of backups. More
670 than ``maxbackups`` backups are kept, if same timestamp
672 than ``maxbackups`` backups are kept, if same timestamp
671 prevents from deciding exact order of them, for safety.
673 prevents from deciding exact order of them, for safety.
672 """
674 """
673 with repo.wlock():
675 with repo.wlock():
674 return _dounshelve(ui, repo, *shelved, **opts)
676 return _dounshelve(ui, repo, *shelved, **opts)
675
677
676 def _dounshelve(ui, repo, *shelved, **opts):
678 def _dounshelve(ui, repo, *shelved, **opts):
677 abortf = opts.get('abort')
679 abortf = opts.get('abort')
678 continuef = opts.get('continue')
680 continuef = opts.get('continue')
679 if not abortf and not continuef:
681 if not abortf and not continuef:
680 cmdutil.checkunfinished(repo)
682 cmdutil.checkunfinished(repo)
681
683
682 if abortf or continuef:
684 if abortf or continuef:
683 if abortf and continuef:
685 if abortf and continuef:
684 raise error.Abort(_('cannot use both abort and continue'))
686 raise error.Abort(_('cannot use both abort and continue'))
685 if shelved:
687 if shelved:
686 raise error.Abort(_('cannot combine abort/continue with '
688 raise error.Abort(_('cannot combine abort/continue with '
687 'naming a shelved change'))
689 'naming a shelved change'))
688 if abortf and opts.get('tool', False):
690 if abortf and opts.get('tool', False):
689 ui.warn(_('tool option will be ignored\n'))
691 ui.warn(_('tool option will be ignored\n'))
690
692
691 try:
693 try:
692 state = shelvedstate.load(repo)
694 state = shelvedstate.load(repo)
693 except IOError as err:
695 except IOError as err:
694 if err.errno != errno.ENOENT:
696 if err.errno != errno.ENOENT:
695 raise
697 raise
696 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
698 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
697 except error.CorruptedState as err:
699 except error.CorruptedState as err:
698 ui.debug(str(err) + '\n')
700 ui.debug(str(err) + '\n')
699 if continuef:
701 if continuef:
700 msg = _('corrupted shelved state file')
702 msg = _('corrupted shelved state file')
701 hint = _('please run hg unshelve --abort to abort unshelve '
703 hint = _('please run hg unshelve --abort to abort unshelve '
702 'operation')
704 'operation')
703 raise error.Abort(msg, hint=hint)
705 raise error.Abort(msg, hint=hint)
704 elif abortf:
706 elif abortf:
705 msg = _('could not read shelved state file, your working copy '
707 msg = _('could not read shelved state file, your working copy '
706 'may be in an unexpected state\nplease update to some '
708 'may be in an unexpected state\nplease update to some '
707 'commit\n')
709 'commit\n')
708 ui.warn(msg)
710 ui.warn(msg)
709 shelvedstate.clear(repo)
711 shelvedstate.clear(repo)
710 return
712 return
711
713
712 if abortf:
714 if abortf:
713 return unshelveabort(ui, repo, state, opts)
715 return unshelveabort(ui, repo, state, opts)
714 elif continuef:
716 elif continuef:
715 return unshelvecontinue(ui, repo, state, opts)
717 return unshelvecontinue(ui, repo, state, opts)
716 elif len(shelved) > 1:
718 elif len(shelved) > 1:
717 raise error.Abort(_('can only unshelve one change at a time'))
719 raise error.Abort(_('can only unshelve one change at a time'))
718 elif not shelved:
720 elif not shelved:
719 shelved = listshelves(repo)
721 shelved = listshelves(repo)
720 if not shelved:
722 if not shelved:
721 raise error.Abort(_('no shelved changes to apply!'))
723 raise error.Abort(_('no shelved changes to apply!'))
722 basename = util.split(shelved[0][1])[1]
724 basename = util.split(shelved[0][1])[1]
723 ui.status(_("unshelving change '%s'\n") % basename)
725 ui.status(_("unshelving change '%s'\n") % basename)
724 else:
726 else:
725 basename = shelved[0]
727 basename = shelved[0]
726
728
727 if not shelvedfile(repo, basename, 'patch').exists():
729 if not shelvedfile(repo, basename, 'patch').exists():
728 raise error.Abort(_("shelved change '%s' not found") % basename)
730 raise error.Abort(_("shelved change '%s' not found") % basename)
729
731
730 oldquiet = ui.quiet
732 oldquiet = ui.quiet
731 lock = tr = None
733 lock = tr = None
732 forcemerge = ui.backupconfig('ui', 'forcemerge')
734 forcemerge = ui.backupconfig('ui', 'forcemerge')
733 try:
735 try:
734 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
736 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
735 lock = repo.lock()
737 lock = repo.lock()
736
738
737 tr = repo.transaction('unshelve', report=lambda x: None)
739 tr = repo.transaction('unshelve', report=lambda x: None)
738 oldtiprev = len(repo)
740 oldtiprev = len(repo)
739
741
740 pctx = repo['.']
742 pctx = repo['.']
741 tmpwctx = pctx
743 tmpwctx = pctx
742 # The goal is to have a commit structure like so:
744 # The goal is to have a commit structure like so:
743 # ...-> pctx -> tmpwctx -> shelvectx
745 # ...-> pctx -> tmpwctx -> shelvectx
744 # where tmpwctx is an optional commit with the user's pending changes
746 # where tmpwctx is an optional commit with the user's pending changes
745 # and shelvectx is the unshelved changes. Then we merge it all down
747 # and shelvectx is the unshelved changes. Then we merge it all down
746 # to the original pctx.
748 # to the original pctx.
747
749
748 # Store pending changes in a commit and remember added in case a shelve
750 # Store pending changes in a commit and remember added in case a shelve
749 # contains unknown files that are part of the pending change
751 # contains unknown files that are part of the pending change
750 s = repo.status()
752 s = repo.status()
751 addedbefore = frozenset(s.added)
753 addedbefore = frozenset(s.added)
752 if s.modified or s.added or s.removed or s.deleted:
754 if s.modified or s.added or s.removed or s.deleted:
753 ui.status(_("temporarily committing pending changes "
755 ui.status(_("temporarily committing pending changes "
754 "(restore with 'hg unshelve --abort')\n"))
756 "(restore with 'hg unshelve --abort')\n"))
755 commitfunc = getcommitfunc(extra=None, interactive=False,
757 commitfunc = getcommitfunc(extra=None, interactive=False,
756 editor=False)
758 editor=False)
757 tempopts = {}
759 tempopts = {}
758 tempopts['message'] = "pending changes temporary commit"
760 tempopts['message'] = "pending changes temporary commit"
759 tempopts['date'] = opts.get('date')
761 tempopts['date'] = opts.get('date')
760 ui.quiet = True
762 ui.quiet = True
761 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
763 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
762 tmpwctx = repo[node]
764 tmpwctx = repo[node]
763
765
764 ui.quiet = True
766 ui.quiet = True
765 shelvedfile(repo, basename, 'hg').applybundle()
767 shelvedfile(repo, basename, 'hg').applybundle()
766
768
767 ui.quiet = oldquiet
769 ui.quiet = oldquiet
768
770
769 shelvectx = repo['tip']
771 shelvectx = repo['tip']
770
772
771 branchtorestore = ''
773 branchtorestore = ''
772 if shelvectx.branch() != shelvectx.p1().branch():
774 if shelvectx.branch() != shelvectx.p1().branch():
773 branchtorestore = shelvectx.branch()
775 branchtorestore = shelvectx.branch()
774
776
775 # If the shelve is not immediately on top of the commit
777 # If the shelve is not immediately on top of the commit
776 # we'll be merging with, rebase it to be on top.
778 # we'll be merging with, rebase it to be on top.
777 if tmpwctx.node() != shelvectx.parents()[0].node():
779 if tmpwctx.node() != shelvectx.parents()[0].node():
778 ui.status(_('rebasing shelved changes\n'))
780 ui.status(_('rebasing shelved changes\n'))
779 try:
781 try:
780 rebase.rebase(ui, repo, **{
782 rebase.rebase(ui, repo, **{
781 'rev' : [shelvectx.rev()],
783 'rev' : [shelvectx.rev()],
782 'dest' : str(tmpwctx.rev()),
784 'dest' : str(tmpwctx.rev()),
783 'keep' : True,
785 'keep' : True,
784 'tool' : opts.get('tool', ''),
786 'tool' : opts.get('tool', ''),
785 })
787 })
786 except error.InterventionRequired:
788 except error.InterventionRequired:
787 tr.close()
789 tr.close()
788
790
789 stripnodes = [repo.changelog.node(rev)
791 stripnodes = [repo.changelog.node(rev)
790 for rev in xrange(oldtiprev, len(repo))]
792 for rev in xrange(oldtiprev, len(repo))]
791 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes,
793 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes,
792 branchtorestore)
794 branchtorestore)
793
795
794 util.rename(repo.join('rebasestate'),
796 util.rename(repo.join('rebasestate'),
795 repo.join('unshelverebasestate'))
797 repo.join('unshelverebasestate'))
796 raise error.InterventionRequired(
798 raise error.InterventionRequired(
797 _("unresolved conflicts (see 'hg resolve', then "
799 _("unresolved conflicts (see 'hg resolve', then "
798 "'hg unshelve --continue')"))
800 "'hg unshelve --continue')"))
799
801
800 # refresh ctx after rebase completes
802 # refresh ctx after rebase completes
801 shelvectx = repo['tip']
803 shelvectx = repo['tip']
802
804
803 if not shelvectx in tmpwctx.children():
805 if not shelvectx in tmpwctx.children():
804 # rebase was a no-op, so it produced no child commit
806 # rebase was a no-op, so it produced no child commit
805 shelvectx = tmpwctx
807 shelvectx = tmpwctx
806
808
807 mergefiles(ui, repo, pctx, shelvectx)
809 mergefiles(ui, repo, pctx, shelvectx)
808 restorebranch(ui, repo, branchtorestore)
810 restorebranch(ui, repo, branchtorestore)
809
811
810 # Forget any files that were unknown before the shelve, unknown before
812 # Forget any files that were unknown before the shelve, unknown before
811 # unshelve started, but are now added.
813 # unshelve started, but are now added.
812 shelveunknown = shelvectx.extra().get('shelve_unknown')
814 shelveunknown = shelvectx.extra().get('shelve_unknown')
813 if shelveunknown:
815 if shelveunknown:
814 shelveunknown = frozenset(shelveunknown.split('\0'))
816 shelveunknown = frozenset(shelveunknown.split('\0'))
815 addedafter = frozenset(repo.status().added)
817 addedafter = frozenset(repo.status().added)
816 toforget = (addedafter & shelveunknown) - addedbefore
818 toforget = (addedafter & shelveunknown) - addedbefore
817 repo[None].forget(toforget)
819 repo[None].forget(toforget)
818
820
819 shelvedstate.clear(repo)
821 shelvedstate.clear(repo)
820
822
821 # The transaction aborting will strip all the commits for us,
823 # The transaction aborting will strip all the commits for us,
822 # but it doesn't update the inmemory structures, so addchangegroup
824 # but it doesn't update the inmemory structures, so addchangegroup
823 # hooks still fire and try to operate on the missing commits.
825 # hooks still fire and try to operate on the missing commits.
824 # Clean up manually to prevent this.
826 # Clean up manually to prevent this.
825 repo.unfiltered().changelog.strip(oldtiprev, tr)
827 repo.unfiltered().changelog.strip(oldtiprev, tr)
826
828
827 unshelvecleanup(ui, repo, basename, opts)
829 unshelvecleanup(ui, repo, basename, opts)
828
830
829 _aborttransaction(repo)
831 _aborttransaction(repo)
830 finally:
832 finally:
831 ui.quiet = oldquiet
833 ui.quiet = oldquiet
832 if tr:
834 if tr:
833 tr.release()
835 tr.release()
834 lockmod.release(lock)
836 lockmod.release(lock)
835 ui.restoreconfig(forcemerge)
837 ui.restoreconfig(forcemerge)
836
838
837 @command('shelve',
839 @command('shelve',
838 [('A', 'addremove', None,
840 [('A', 'addremove', None,
839 _('mark new/missing files as added/removed before shelving')),
841 _('mark new/missing files as added/removed before shelving')),
840 ('u', 'unknown', None,
842 ('u', 'unknown', None,
841 _('store unknown files in the shelve')),
843 _('store unknown files in the shelve')),
842 ('', 'cleanup', None,
844 ('', 'cleanup', None,
843 _('delete all shelved changes')),
845 _('delete all shelved changes')),
844 ('', 'date', '',
846 ('', 'date', '',
845 _('shelve with the specified commit date'), _('DATE')),
847 _('shelve with the specified commit date'), _('DATE')),
846 ('d', 'delete', None,
848 ('d', 'delete', None,
847 _('delete the named shelved change(s)')),
849 _('delete the named shelved change(s)')),
848 ('e', 'edit', False,
850 ('e', 'edit', False,
849 _('invoke editor on commit messages')),
851 _('invoke editor on commit messages')),
850 ('l', 'list', None,
852 ('l', 'list', None,
851 _('list current shelves')),
853 _('list current shelves')),
852 ('m', 'message', '',
854 ('m', 'message', '',
853 _('use text as shelve message'), _('TEXT')),
855 _('use text as shelve message'), _('TEXT')),
854 ('n', 'name', '',
856 ('n', 'name', '',
855 _('use the given name for the shelved commit'), _('NAME')),
857 _('use the given name for the shelved commit'), _('NAME')),
856 ('p', 'patch', None,
858 ('p', 'patch', None,
857 _('show patch')),
859 _('show patch')),
858 ('i', 'interactive', None,
860 ('i', 'interactive', None,
859 _('interactive mode, only works while creating a shelve')),
861 _('interactive mode, only works while creating a shelve')),
860 ('', 'stat', None,
862 ('', 'stat', None,
861 _('output diffstat-style summary of changes'))] + commands.walkopts,
863 _('output diffstat-style summary of changes'))] + commands.walkopts,
862 _('hg shelve [OPTION]... [FILE]...'))
864 _('hg shelve [OPTION]... [FILE]...'))
863 def shelvecmd(ui, repo, *pats, **opts):
865 def shelvecmd(ui, repo, *pats, **opts):
864 '''save and set aside changes from the working directory
866 '''save and set aside changes from the working directory
865
867
866 Shelving takes files that "hg status" reports as not clean, saves
868 Shelving takes files that "hg status" reports as not clean, saves
867 the modifications to a bundle (a shelved change), and reverts the
869 the modifications to a bundle (a shelved change), and reverts the
868 files so that their state in the working directory becomes clean.
870 files so that their state in the working directory becomes clean.
869
871
870 To restore these changes to the working directory, using "hg
872 To restore these changes to the working directory, using "hg
871 unshelve"; this will work even if you switch to a different
873 unshelve"; this will work even if you switch to a different
872 commit.
874 commit.
873
875
874 When no files are specified, "hg shelve" saves all not-clean
876 When no files are specified, "hg shelve" saves all not-clean
875 files. If specific files or directories are named, only changes to
877 files. If specific files or directories are named, only changes to
876 those files are shelved.
878 those files are shelved.
877
879
878 In bare shelve(when no files are specified, without interactive,
880 In bare shelve(when no files are specified, without interactive,
879 include and exclude option), shelving remembers information if the
881 include and exclude option), shelving remembers information if the
880 working directory was on newly created branch, in other words working
882 working directory was on newly created branch, in other words working
881 directory was on different branch than its first parent. In this
883 directory was on different branch than its first parent. In this
882 situation unshelving restores branch information to the working directory.
884 situation unshelving restores branch information to the working directory.
883
885
884 Each shelved change has a name that makes it easier to find later.
886 Each shelved change has a name that makes it easier to find later.
885 The name of a shelved change defaults to being based on the active
887 The name of a shelved change defaults to being based on the active
886 bookmark, or if there is no active bookmark, the current named
888 bookmark, or if there is no active bookmark, the current named
887 branch. To specify a different name, use ``--name``.
889 branch. To specify a different name, use ``--name``.
888
890
889 To see a list of existing shelved changes, use the ``--list``
891 To see a list of existing shelved changes, use the ``--list``
890 option. For each shelved change, this will print its name, age,
892 option. For each shelved change, this will print its name, age,
891 and description; use ``--patch`` or ``--stat`` for more details.
893 and description; use ``--patch`` or ``--stat`` for more details.
892
894
893 To delete specific shelved changes, use ``--delete``. To delete
895 To delete specific shelved changes, use ``--delete``. To delete
894 all shelved changes, use ``--cleanup``.
896 all shelved changes, use ``--cleanup``.
895 '''
897 '''
896 allowables = [
898 allowables = [
897 ('addremove', set(['create'])), # 'create' is pseudo action
899 ('addremove', set(['create'])), # 'create' is pseudo action
898 ('unknown', set(['create'])),
900 ('unknown', set(['create'])),
899 ('cleanup', set(['cleanup'])),
901 ('cleanup', set(['cleanup'])),
900 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
902 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
901 ('delete', set(['delete'])),
903 ('delete', set(['delete'])),
902 ('edit', set(['create'])),
904 ('edit', set(['create'])),
903 ('list', set(['list'])),
905 ('list', set(['list'])),
904 ('message', set(['create'])),
906 ('message', set(['create'])),
905 ('name', set(['create'])),
907 ('name', set(['create'])),
906 ('patch', set(['patch', 'list'])),
908 ('patch', set(['patch', 'list'])),
907 ('stat', set(['stat', 'list'])),
909 ('stat', set(['stat', 'list'])),
908 ]
910 ]
909 def checkopt(opt):
911 def checkopt(opt):
910 if opts.get(opt):
912 if opts.get(opt):
911 for i, allowable in allowables:
913 for i, allowable in allowables:
912 if opts[i] and opt not in allowable:
914 if opts[i] and opt not in allowable:
913 raise error.Abort(_("options '--%s' and '--%s' may not be "
915 raise error.Abort(_("options '--%s' and '--%s' may not be "
914 "used together") % (opt, i))
916 "used together") % (opt, i))
915 return True
917 return True
916 if checkopt('cleanup'):
918 if checkopt('cleanup'):
917 if pats:
919 if pats:
918 raise error.Abort(_("cannot specify names when using '--cleanup'"))
920 raise error.Abort(_("cannot specify names when using '--cleanup'"))
919 return cleanupcmd(ui, repo)
921 return cleanupcmd(ui, repo)
920 elif checkopt('delete'):
922 elif checkopt('delete'):
921 return deletecmd(ui, repo, pats)
923 return deletecmd(ui, repo, pats)
922 elif checkopt('list'):
924 elif checkopt('list'):
923 return listcmd(ui, repo, pats, opts)
925 return listcmd(ui, repo, pats, opts)
924 elif checkopt('patch'):
926 elif checkopt('patch'):
925 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
927 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
926 elif checkopt('stat'):
928 elif checkopt('stat'):
927 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
929 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
928 else:
930 else:
929 return createcmd(ui, repo, pats, opts)
931 return createcmd(ui, repo, pats, opts)
930
932
931 def extsetup(ui):
933 def extsetup(ui):
932 cmdutil.unfinishedstates.append(
934 cmdutil.unfinishedstates.append(
933 [shelvedstate._filename, False, False,
935 [shelvedstate._filename, False, False,
934 _('unshelve already in progress'),
936 _('unshelve already in progress'),
935 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
937 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
936 cmdutil.afterresolvedstates.append(
938 cmdutil.afterresolvedstates.append(
937 [shelvedstate._filename, _('hg unshelve --continue')])
939 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now