##// END OF EJS Templates
shelve: move shelve-finishing logic to a separate function...
Kostia Balytskyi -
r30385:b573d7ca default
parent child Browse files
Show More
@@ -1,942 +1,945 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
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):
331 def _shelvecreatedcommit(repo, node, name):
332 bases = list(mutableancestors(repo[node]))
332 bases = list(mutableancestors(repo[node]))
333 shelvedfile(repo, name, 'hg').writebundle(bases, node)
333 shelvedfile(repo, name, 'hg').writebundle(bases, node)
334 cmdutil.export(repo, [node],
334 cmdutil.export(repo, [node],
335 fp=shelvedfile(repo, name, 'patch').opener('wb'),
335 fp=shelvedfile(repo, name, 'patch').opener('wb'),
336 opts=mdiff.diffopts(git=True))
336 opts=mdiff.diffopts(git=True))
337
337
338 def _includeunknownfiles(repo, pats, opts, extra):
338 def _includeunknownfiles(repo, pats, opts, extra):
339 s = repo.status(match=scmutil.match(repo[None], pats, opts),
339 s = repo.status(match=scmutil.match(repo[None], pats, opts),
340 unknown=True)
340 unknown=True)
341 if s.unknown:
341 if s.unknown:
342 extra['shelve_unknown'] = '\0'.join(s.unknown)
342 extra['shelve_unknown'] = '\0'.join(s.unknown)
343 repo[None].add(s.unknown)
343 repo[None].add(s.unknown)
344
344
345 def _finishshelve(repo):
346 _aborttransaction(repo)
347
345 def _docreatecmd(ui, repo, pats, opts):
348 def _docreatecmd(ui, repo, pats, opts):
346 wctx = repo[None]
349 wctx = repo[None]
347 parents = wctx.parents()
350 parents = wctx.parents()
348 if len(parents) > 1:
351 if len(parents) > 1:
349 raise error.Abort(_('cannot shelve while merging'))
352 raise error.Abort(_('cannot shelve while merging'))
350 parent = parents[0]
353 parent = parents[0]
351 origbranch = wctx.branch()
354 origbranch = wctx.branch()
352
355
353 if parent.node() != nodemod.nullid:
356 if parent.node() != nodemod.nullid:
354 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
357 desc = "changes to: %s" % parent.description().split('\n', 1)[0]
355 else:
358 else:
356 desc = '(changes in empty repository)'
359 desc = '(changes in empty repository)'
357
360
358 if not opts.get('message'):
361 if not opts.get('message'):
359 opts['message'] = desc
362 opts['message'] = desc
360
363
361 lock = tr = None
364 lock = tr = None
362 try:
365 try:
363 lock = repo.lock()
366 lock = repo.lock()
364
367
365 # use an uncommitted transaction to generate the bundle to avoid
368 # use an uncommitted transaction to generate the bundle to avoid
366 # pull races. ensure we don't print the abort message to stderr.
369 # pull races. ensure we don't print the abort message to stderr.
367 tr = repo.transaction('commit', report=lambda x: None)
370 tr = repo.transaction('commit', report=lambda x: None)
368
371
369 interactive = opts.get('interactive', False)
372 interactive = opts.get('interactive', False)
370 includeunknown = (opts.get('unknown', False) and
373 includeunknown = (opts.get('unknown', False) and
371 not opts.get('addremove', False))
374 not opts.get('addremove', False))
372
375
373 name = getshelvename(repo, parent, opts)
376 name = getshelvename(repo, parent, opts)
374 extra = {}
377 extra = {}
375 if includeunknown:
378 if includeunknown:
376 _includeunknownfiles(repo, pats, opts, extra)
379 _includeunknownfiles(repo, pats, opts, extra)
377
380
378 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
381 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
379 # In non-bare shelve we don't store newly created branch
382 # In non-bare shelve we don't store newly created branch
380 # at bundled commit
383 # at bundled commit
381 repo.dirstate.setbranch(repo['.'].branch())
384 repo.dirstate.setbranch(repo['.'].branch())
382
385
383 commitfunc = getcommitfunc(extra, interactive, editor=True)
386 commitfunc = getcommitfunc(extra, interactive, editor=True)
384 if not interactive:
387 if not interactive:
385 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
388 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
386 else:
389 else:
387 node = cmdutil.dorecord(ui, repo, commitfunc, None,
390 node = cmdutil.dorecord(ui, repo, commitfunc, None,
388 False, cmdutil.recordfilter, *pats, **opts)
391 False, cmdutil.recordfilter, *pats, **opts)
389 if not node:
392 if not node:
390 _nothingtoshelvemessaging(ui, repo, pats, opts)
393 _nothingtoshelvemessaging(ui, repo, pats, opts)
391 return 1
394 return 1
392
395
393 _shelvecreatedcommit(repo, node, name)
396 _shelvecreatedcommit(repo, node, name)
394
397
395 if ui.formatted():
398 if ui.formatted():
396 desc = util.ellipsis(desc, ui.termwidth())
399 desc = util.ellipsis(desc, ui.termwidth())
397 ui.status(_('shelved as %s\n') % name)
400 ui.status(_('shelved as %s\n') % name)
398 hg.update(repo, parent.node())
401 hg.update(repo, parent.node())
399 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
402 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
400 repo.dirstate.setbranch(origbranch)
403 repo.dirstate.setbranch(origbranch)
401
404
402 _aborttransaction(repo)
405 _finishshelve(repo)
403 finally:
406 finally:
404 lockmod.release(tr, lock)
407 lockmod.release(tr, lock)
405
408
406 def _isbareshelve(pats, opts):
409 def _isbareshelve(pats, opts):
407 return (not pats
410 return (not pats
408 and not opts.get('interactive', False)
411 and not opts.get('interactive', False)
409 and not opts.get('include', False)
412 and not opts.get('include', False)
410 and not opts.get('exclude', False))
413 and not opts.get('exclude', False))
411
414
412 def _iswctxonnewbranch(repo):
415 def _iswctxonnewbranch(repo):
413 return repo[None].branch() != repo['.'].branch()
416 return repo[None].branch() != repo['.'].branch()
414
417
415 def cleanupcmd(ui, repo):
418 def cleanupcmd(ui, repo):
416 """subcommand that deletes all shelves"""
419 """subcommand that deletes all shelves"""
417
420
418 with repo.wlock():
421 with repo.wlock():
419 for (name, _type) in repo.vfs.readdir(shelvedir):
422 for (name, _type) in repo.vfs.readdir(shelvedir):
420 suffix = name.rsplit('.', 1)[-1]
423 suffix = name.rsplit('.', 1)[-1]
421 if suffix in shelvefileextensions:
424 if suffix in shelvefileextensions:
422 shelvedfile(repo, name).movetobackup()
425 shelvedfile(repo, name).movetobackup()
423 cleanupoldbackups(repo)
426 cleanupoldbackups(repo)
424
427
425 def deletecmd(ui, repo, pats):
428 def deletecmd(ui, repo, pats):
426 """subcommand that deletes a specific shelve"""
429 """subcommand that deletes a specific shelve"""
427 if not pats:
430 if not pats:
428 raise error.Abort(_('no shelved changes specified!'))
431 raise error.Abort(_('no shelved changes specified!'))
429 with repo.wlock():
432 with repo.wlock():
430 try:
433 try:
431 for name in pats:
434 for name in pats:
432 for suffix in shelvefileextensions:
435 for suffix in shelvefileextensions:
433 shfile = shelvedfile(repo, name, suffix)
436 shfile = shelvedfile(repo, name, suffix)
434 # patch file is necessary, as it should
437 # patch file is necessary, as it should
435 # be present for any kind of shelve,
438 # be present for any kind of shelve,
436 # but the .hg file is optional as in future we
439 # but the .hg file is optional as in future we
437 # will add obsolete shelve with does not create a
440 # will add obsolete shelve with does not create a
438 # bundle
441 # bundle
439 if shfile.exists() or suffix == 'patch':
442 if shfile.exists() or suffix == 'patch':
440 shfile.movetobackup()
443 shfile.movetobackup()
441 cleanupoldbackups(repo)
444 cleanupoldbackups(repo)
442 except OSError as err:
445 except OSError as err:
443 if err.errno != errno.ENOENT:
446 if err.errno != errno.ENOENT:
444 raise
447 raise
445 raise error.Abort(_("shelved change '%s' not found") % name)
448 raise error.Abort(_("shelved change '%s' not found") % name)
446
449
447 def listshelves(repo):
450 def listshelves(repo):
448 """return all shelves in repo as list of (time, filename)"""
451 """return all shelves in repo as list of (time, filename)"""
449 try:
452 try:
450 names = repo.vfs.readdir(shelvedir)
453 names = repo.vfs.readdir(shelvedir)
451 except OSError as err:
454 except OSError as err:
452 if err.errno != errno.ENOENT:
455 if err.errno != errno.ENOENT:
453 raise
456 raise
454 return []
457 return []
455 info = []
458 info = []
456 for (name, _type) in names:
459 for (name, _type) in names:
457 pfx, sfx = name.rsplit('.', 1)
460 pfx, sfx = name.rsplit('.', 1)
458 if not pfx or sfx != 'patch':
461 if not pfx or sfx != 'patch':
459 continue
462 continue
460 st = shelvedfile(repo, name).stat()
463 st = shelvedfile(repo, name).stat()
461 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
464 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
462 return sorted(info, reverse=True)
465 return sorted(info, reverse=True)
463
466
464 def listcmd(ui, repo, pats, opts):
467 def listcmd(ui, repo, pats, opts):
465 """subcommand that displays the list of shelves"""
468 """subcommand that displays the list of shelves"""
466 pats = set(pats)
469 pats = set(pats)
467 width = 80
470 width = 80
468 if not ui.plain():
471 if not ui.plain():
469 width = ui.termwidth()
472 width = ui.termwidth()
470 namelabel = 'shelve.newest'
473 namelabel = 'shelve.newest'
471 for mtime, name in listshelves(repo):
474 for mtime, name in listshelves(repo):
472 sname = util.split(name)[1]
475 sname = util.split(name)[1]
473 if pats and sname not in pats:
476 if pats and sname not in pats:
474 continue
477 continue
475 ui.write(sname, label=namelabel)
478 ui.write(sname, label=namelabel)
476 namelabel = 'shelve.name'
479 namelabel = 'shelve.name'
477 if ui.quiet:
480 if ui.quiet:
478 ui.write('\n')
481 ui.write('\n')
479 continue
482 continue
480 ui.write(' ' * (16 - len(sname)))
483 ui.write(' ' * (16 - len(sname)))
481 used = 16
484 used = 16
482 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
485 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
483 ui.write(age, label='shelve.age')
486 ui.write(age, label='shelve.age')
484 ui.write(' ' * (12 - len(age)))
487 ui.write(' ' * (12 - len(age)))
485 used += 12
488 used += 12
486 with open(name + '.patch', 'rb') as fp:
489 with open(name + '.patch', 'rb') as fp:
487 while True:
490 while True:
488 line = fp.readline()
491 line = fp.readline()
489 if not line:
492 if not line:
490 break
493 break
491 if not line.startswith('#'):
494 if not line.startswith('#'):
492 desc = line.rstrip()
495 desc = line.rstrip()
493 if ui.formatted():
496 if ui.formatted():
494 desc = util.ellipsis(desc, width - used)
497 desc = util.ellipsis(desc, width - used)
495 ui.write(desc)
498 ui.write(desc)
496 break
499 break
497 ui.write('\n')
500 ui.write('\n')
498 if not (opts['patch'] or opts['stat']):
501 if not (opts['patch'] or opts['stat']):
499 continue
502 continue
500 difflines = fp.readlines()
503 difflines = fp.readlines()
501 if opts['patch']:
504 if opts['patch']:
502 for chunk, label in patch.difflabel(iter, difflines):
505 for chunk, label in patch.difflabel(iter, difflines):
503 ui.write(chunk, label=label)
506 ui.write(chunk, label=label)
504 if opts['stat']:
507 if opts['stat']:
505 for chunk, label in patch.diffstatui(difflines, width=width,
508 for chunk, label in patch.diffstatui(difflines, width=width,
506 git=True):
509 git=True):
507 ui.write(chunk, label=label)
510 ui.write(chunk, label=label)
508
511
509 def singlepatchcmds(ui, repo, pats, opts, subcommand):
512 def singlepatchcmds(ui, repo, pats, opts, subcommand):
510 """subcommand that displays a single shelf"""
513 """subcommand that displays a single shelf"""
511 if len(pats) != 1:
514 if len(pats) != 1:
512 raise error.Abort(_("--%s expects a single shelf") % subcommand)
515 raise error.Abort(_("--%s expects a single shelf") % subcommand)
513 shelfname = pats[0]
516 shelfname = pats[0]
514
517
515 if not shelvedfile(repo, shelfname, 'patch').exists():
518 if not shelvedfile(repo, shelfname, 'patch').exists():
516 raise error.Abort(_("cannot find shelf %s") % shelfname)
519 raise error.Abort(_("cannot find shelf %s") % shelfname)
517
520
518 listcmd(ui, repo, pats, opts)
521 listcmd(ui, repo, pats, opts)
519
522
520 def checkparents(repo, state):
523 def checkparents(repo, state):
521 """check parent while resuming an unshelve"""
524 """check parent while resuming an unshelve"""
522 if state.parents != repo.dirstate.parents():
525 if state.parents != repo.dirstate.parents():
523 raise error.Abort(_('working directory parents do not match unshelve '
526 raise error.Abort(_('working directory parents do not match unshelve '
524 'state'))
527 'state'))
525
528
526 def pathtofiles(repo, files):
529 def pathtofiles(repo, files):
527 cwd = repo.getcwd()
530 cwd = repo.getcwd()
528 return [repo.pathto(f, cwd) for f in files]
531 return [repo.pathto(f, cwd) for f in files]
529
532
530 def unshelveabort(ui, repo, state, opts):
533 def unshelveabort(ui, repo, state, opts):
531 """subcommand that abort an in-progress unshelve"""
534 """subcommand that abort an in-progress unshelve"""
532 with repo.lock():
535 with repo.lock():
533 try:
536 try:
534 checkparents(repo, state)
537 checkparents(repo, state)
535
538
536 util.rename(repo.join('unshelverebasestate'),
539 util.rename(repo.join('unshelverebasestate'),
537 repo.join('rebasestate'))
540 repo.join('rebasestate'))
538 try:
541 try:
539 rebase.rebase(ui, repo, **{
542 rebase.rebase(ui, repo, **{
540 'abort' : True
543 'abort' : True
541 })
544 })
542 except Exception:
545 except Exception:
543 util.rename(repo.join('rebasestate'),
546 util.rename(repo.join('rebasestate'),
544 repo.join('unshelverebasestate'))
547 repo.join('unshelverebasestate'))
545 raise
548 raise
546
549
547 mergefiles(ui, repo, state.wctx, state.pendingctx)
550 mergefiles(ui, repo, state.wctx, state.pendingctx)
548 repair.strip(ui, repo, state.stripnodes, backup=False,
551 repair.strip(ui, repo, state.stripnodes, backup=False,
549 topic='shelve')
552 topic='shelve')
550 finally:
553 finally:
551 shelvedstate.clear(repo)
554 shelvedstate.clear(repo)
552 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
555 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
553
556
554 def mergefiles(ui, repo, wctx, shelvectx):
557 def mergefiles(ui, repo, wctx, shelvectx):
555 """updates to wctx and merges the changes from shelvectx into the
558 """updates to wctx and merges the changes from shelvectx into the
556 dirstate."""
559 dirstate."""
557 oldquiet = ui.quiet
560 oldquiet = ui.quiet
558 try:
561 try:
559 ui.quiet = True
562 ui.quiet = True
560 hg.update(repo, wctx.node())
563 hg.update(repo, wctx.node())
561 files = []
564 files = []
562 files.extend(shelvectx.files())
565 files.extend(shelvectx.files())
563 files.extend(shelvectx.parents()[0].files())
566 files.extend(shelvectx.parents()[0].files())
564
567
565 # revert will overwrite unknown files, so move them out of the way
568 # revert will overwrite unknown files, so move them out of the way
566 for file in repo.status(unknown=True).unknown:
569 for file in repo.status(unknown=True).unknown:
567 if file in files:
570 if file in files:
568 util.rename(file, scmutil.origpath(ui, repo, file))
571 util.rename(file, scmutil.origpath(ui, repo, file))
569 ui.pushbuffer(True)
572 ui.pushbuffer(True)
570 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
573 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
571 *pathtofiles(repo, files),
574 *pathtofiles(repo, files),
572 **{'no_backup': True})
575 **{'no_backup': True})
573 ui.popbuffer()
576 ui.popbuffer()
574 finally:
577 finally:
575 ui.quiet = oldquiet
578 ui.quiet = oldquiet
576
579
577 def restorebranch(ui, repo, branchtorestore):
580 def restorebranch(ui, repo, branchtorestore):
578 if branchtorestore and branchtorestore != repo.dirstate.branch():
581 if branchtorestore and branchtorestore != repo.dirstate.branch():
579 repo.dirstate.setbranch(branchtorestore)
582 repo.dirstate.setbranch(branchtorestore)
580 ui.status(_('marked working directory as branch %s\n')
583 ui.status(_('marked working directory as branch %s\n')
581 % branchtorestore)
584 % branchtorestore)
582
585
583 def unshelvecleanup(ui, repo, name, opts):
586 def unshelvecleanup(ui, repo, name, opts):
584 """remove related files after an unshelve"""
587 """remove related files after an unshelve"""
585 if not opts.get('keep'):
588 if not opts.get('keep'):
586 for filetype in shelvefileextensions:
589 for filetype in shelvefileextensions:
587 shfile = shelvedfile(repo, name, filetype)
590 shfile = shelvedfile(repo, name, filetype)
588 if shfile.exists():
591 if shfile.exists():
589 shfile.movetobackup()
592 shfile.movetobackup()
590 cleanupoldbackups(repo)
593 cleanupoldbackups(repo)
591
594
592 def unshelvecontinue(ui, repo, state, opts):
595 def unshelvecontinue(ui, repo, state, opts):
593 """subcommand to continue an in-progress unshelve"""
596 """subcommand to continue an in-progress unshelve"""
594 # We're finishing off a merge. First parent is our original
597 # We're finishing off a merge. First parent is our original
595 # parent, second is the temporary "fake" commit we're unshelving.
598 # parent, second is the temporary "fake" commit we're unshelving.
596 with repo.lock():
599 with repo.lock():
597 checkparents(repo, state)
600 checkparents(repo, state)
598 ms = merge.mergestate.read(repo)
601 ms = merge.mergestate.read(repo)
599 if [f for f in ms if ms[f] == 'u']:
602 if [f for f in ms if ms[f] == 'u']:
600 raise error.Abort(
603 raise error.Abort(
601 _("unresolved conflicts, can't continue"),
604 _("unresolved conflicts, can't continue"),
602 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
605 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
603
606
604 util.rename(repo.join('unshelverebasestate'),
607 util.rename(repo.join('unshelverebasestate'),
605 repo.join('rebasestate'))
608 repo.join('rebasestate'))
606 try:
609 try:
607 rebase.rebase(ui, repo, **{
610 rebase.rebase(ui, repo, **{
608 'continue' : True
611 'continue' : True
609 })
612 })
610 except Exception:
613 except Exception:
611 util.rename(repo.join('rebasestate'),
614 util.rename(repo.join('rebasestate'),
612 repo.join('unshelverebasestate'))
615 repo.join('unshelverebasestate'))
613 raise
616 raise
614
617
615 shelvectx = repo['tip']
618 shelvectx = repo['tip']
616 if not shelvectx in state.pendingctx.children():
619 if not shelvectx in state.pendingctx.children():
617 # rebase was a no-op, so it produced no child commit
620 # rebase was a no-op, so it produced no child commit
618 shelvectx = state.pendingctx
621 shelvectx = state.pendingctx
619 else:
622 else:
620 # only strip the shelvectx if the rebase produced it
623 # only strip the shelvectx if the rebase produced it
621 state.stripnodes.append(shelvectx.node())
624 state.stripnodes.append(shelvectx.node())
622
625
623 mergefiles(ui, repo, state.wctx, shelvectx)
626 mergefiles(ui, repo, state.wctx, shelvectx)
624 restorebranch(ui, repo, state.branchtorestore)
627 restorebranch(ui, repo, state.branchtorestore)
625
628
626 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
629 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
627 shelvedstate.clear(repo)
630 shelvedstate.clear(repo)
628 unshelvecleanup(ui, repo, state.name, opts)
631 unshelvecleanup(ui, repo, state.name, opts)
629 ui.status(_("unshelve of '%s' complete\n") % state.name)
632 ui.status(_("unshelve of '%s' complete\n") % state.name)
630
633
631 @command('unshelve',
634 @command('unshelve',
632 [('a', 'abort', None,
635 [('a', 'abort', None,
633 _('abort an incomplete unshelve operation')),
636 _('abort an incomplete unshelve operation')),
634 ('c', 'continue', None,
637 ('c', 'continue', None,
635 _('continue an incomplete unshelve operation')),
638 _('continue an incomplete unshelve operation')),
636 ('k', 'keep', None,
639 ('k', 'keep', None,
637 _('keep shelve after unshelving')),
640 _('keep shelve after unshelving')),
638 ('t', 'tool', '', _('specify merge tool')),
641 ('t', 'tool', '', _('specify merge tool')),
639 ('', 'date', '',
642 ('', 'date', '',
640 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
643 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
641 _('hg unshelve [SHELVED]'))
644 _('hg unshelve [SHELVED]'))
642 def unshelve(ui, repo, *shelved, **opts):
645 def unshelve(ui, repo, *shelved, **opts):
643 """restore a shelved change to the working directory
646 """restore a shelved change to the working directory
644
647
645 This command accepts an optional name of a shelved change to
648 This command accepts an optional name of a shelved change to
646 restore. If none is given, the most recent shelved change is used.
649 restore. If none is given, the most recent shelved change is used.
647
650
648 If a shelved change is applied successfully, the bundle that
651 If a shelved change is applied successfully, the bundle that
649 contains the shelved changes is moved to a backup location
652 contains the shelved changes is moved to a backup location
650 (.hg/shelve-backup).
653 (.hg/shelve-backup).
651
654
652 Since you can restore a shelved change on top of an arbitrary
655 Since you can restore a shelved change on top of an arbitrary
653 commit, it is possible that unshelving will result in a conflict
656 commit, it is possible that unshelving will result in a conflict
654 between your changes and the commits you are unshelving onto. If
657 between your changes and the commits you are unshelving onto. If
655 this occurs, you must resolve the conflict, then use
658 this occurs, you must resolve the conflict, then use
656 ``--continue`` to complete the unshelve operation. (The bundle
659 ``--continue`` to complete the unshelve operation. (The bundle
657 will not be moved until you successfully complete the unshelve.)
660 will not be moved until you successfully complete the unshelve.)
658
661
659 (Alternatively, you can use ``--abort`` to abandon an unshelve
662 (Alternatively, you can use ``--abort`` to abandon an unshelve
660 that causes a conflict. This reverts the unshelved changes, and
663 that causes a conflict. This reverts the unshelved changes, and
661 leaves the bundle in place.)
664 leaves the bundle in place.)
662
665
663 If bare shelved change(when no files are specified, without interactive,
666 If bare shelved change(when no files are specified, without interactive,
664 include and exclude option) was done on newly created branch it would
667 include and exclude option) was done on newly created branch it would
665 restore branch information to the working directory.
668 restore branch information to the working directory.
666
669
667 After a successful unshelve, the shelved changes are stored in a
670 After a successful unshelve, the shelved changes are stored in a
668 backup directory. Only the N most recent backups are kept. N
671 backup directory. Only the N most recent backups are kept. N
669 defaults to 10 but can be overridden using the ``shelve.maxbackups``
672 defaults to 10 but can be overridden using the ``shelve.maxbackups``
670 configuration option.
673 configuration option.
671
674
672 .. container:: verbose
675 .. container:: verbose
673
676
674 Timestamp in seconds is used to decide order of backups. More
677 Timestamp in seconds is used to decide order of backups. More
675 than ``maxbackups`` backups are kept, if same timestamp
678 than ``maxbackups`` backups are kept, if same timestamp
676 prevents from deciding exact order of them, for safety.
679 prevents from deciding exact order of them, for safety.
677 """
680 """
678 with repo.wlock():
681 with repo.wlock():
679 return _dounshelve(ui, repo, *shelved, **opts)
682 return _dounshelve(ui, repo, *shelved, **opts)
680
683
681 def _dounshelve(ui, repo, *shelved, **opts):
684 def _dounshelve(ui, repo, *shelved, **opts):
682 abortf = opts.get('abort')
685 abortf = opts.get('abort')
683 continuef = opts.get('continue')
686 continuef = opts.get('continue')
684 if not abortf and not continuef:
687 if not abortf and not continuef:
685 cmdutil.checkunfinished(repo)
688 cmdutil.checkunfinished(repo)
686
689
687 if abortf or continuef:
690 if abortf or continuef:
688 if abortf and continuef:
691 if abortf and continuef:
689 raise error.Abort(_('cannot use both abort and continue'))
692 raise error.Abort(_('cannot use both abort and continue'))
690 if shelved:
693 if shelved:
691 raise error.Abort(_('cannot combine abort/continue with '
694 raise error.Abort(_('cannot combine abort/continue with '
692 'naming a shelved change'))
695 'naming a shelved change'))
693 if abortf and opts.get('tool', False):
696 if abortf and opts.get('tool', False):
694 ui.warn(_('tool option will be ignored\n'))
697 ui.warn(_('tool option will be ignored\n'))
695
698
696 try:
699 try:
697 state = shelvedstate.load(repo)
700 state = shelvedstate.load(repo)
698 except IOError as err:
701 except IOError as err:
699 if err.errno != errno.ENOENT:
702 if err.errno != errno.ENOENT:
700 raise
703 raise
701 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
704 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
702 except error.CorruptedState as err:
705 except error.CorruptedState as err:
703 ui.debug(str(err) + '\n')
706 ui.debug(str(err) + '\n')
704 if continuef:
707 if continuef:
705 msg = _('corrupted shelved state file')
708 msg = _('corrupted shelved state file')
706 hint = _('please run hg unshelve --abort to abort unshelve '
709 hint = _('please run hg unshelve --abort to abort unshelve '
707 'operation')
710 'operation')
708 raise error.Abort(msg, hint=hint)
711 raise error.Abort(msg, hint=hint)
709 elif abortf:
712 elif abortf:
710 msg = _('could not read shelved state file, your working copy '
713 msg = _('could not read shelved state file, your working copy '
711 'may be in an unexpected state\nplease update to some '
714 'may be in an unexpected state\nplease update to some '
712 'commit\n')
715 'commit\n')
713 ui.warn(msg)
716 ui.warn(msg)
714 shelvedstate.clear(repo)
717 shelvedstate.clear(repo)
715 return
718 return
716
719
717 if abortf:
720 if abortf:
718 return unshelveabort(ui, repo, state, opts)
721 return unshelveabort(ui, repo, state, opts)
719 elif continuef:
722 elif continuef:
720 return unshelvecontinue(ui, repo, state, opts)
723 return unshelvecontinue(ui, repo, state, opts)
721 elif len(shelved) > 1:
724 elif len(shelved) > 1:
722 raise error.Abort(_('can only unshelve one change at a time'))
725 raise error.Abort(_('can only unshelve one change at a time'))
723 elif not shelved:
726 elif not shelved:
724 shelved = listshelves(repo)
727 shelved = listshelves(repo)
725 if not shelved:
728 if not shelved:
726 raise error.Abort(_('no shelved changes to apply!'))
729 raise error.Abort(_('no shelved changes to apply!'))
727 basename = util.split(shelved[0][1])[1]
730 basename = util.split(shelved[0][1])[1]
728 ui.status(_("unshelving change '%s'\n") % basename)
731 ui.status(_("unshelving change '%s'\n") % basename)
729 else:
732 else:
730 basename = shelved[0]
733 basename = shelved[0]
731
734
732 if not shelvedfile(repo, basename, 'patch').exists():
735 if not shelvedfile(repo, basename, 'patch').exists():
733 raise error.Abort(_("shelved change '%s' not found") % basename)
736 raise error.Abort(_("shelved change '%s' not found") % basename)
734
737
735 oldquiet = ui.quiet
738 oldquiet = ui.quiet
736 lock = tr = None
739 lock = tr = None
737 forcemerge = ui.backupconfig('ui', 'forcemerge')
740 forcemerge = ui.backupconfig('ui', 'forcemerge')
738 try:
741 try:
739 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
742 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
740 lock = repo.lock()
743 lock = repo.lock()
741
744
742 tr = repo.transaction('unshelve', report=lambda x: None)
745 tr = repo.transaction('unshelve', report=lambda x: None)
743 oldtiprev = len(repo)
746 oldtiprev = len(repo)
744
747
745 pctx = repo['.']
748 pctx = repo['.']
746 tmpwctx = pctx
749 tmpwctx = pctx
747 # The goal is to have a commit structure like so:
750 # The goal is to have a commit structure like so:
748 # ...-> pctx -> tmpwctx -> shelvectx
751 # ...-> pctx -> tmpwctx -> shelvectx
749 # where tmpwctx is an optional commit with the user's pending changes
752 # where tmpwctx is an optional commit with the user's pending changes
750 # and shelvectx is the unshelved changes. Then we merge it all down
753 # and shelvectx is the unshelved changes. Then we merge it all down
751 # to the original pctx.
754 # to the original pctx.
752
755
753 # Store pending changes in a commit and remember added in case a shelve
756 # Store pending changes in a commit and remember added in case a shelve
754 # contains unknown files that are part of the pending change
757 # contains unknown files that are part of the pending change
755 s = repo.status()
758 s = repo.status()
756 addedbefore = frozenset(s.added)
759 addedbefore = frozenset(s.added)
757 if s.modified or s.added or s.removed or s.deleted:
760 if s.modified or s.added or s.removed or s.deleted:
758 ui.status(_("temporarily committing pending changes "
761 ui.status(_("temporarily committing pending changes "
759 "(restore with 'hg unshelve --abort')\n"))
762 "(restore with 'hg unshelve --abort')\n"))
760 commitfunc = getcommitfunc(extra=None, interactive=False,
763 commitfunc = getcommitfunc(extra=None, interactive=False,
761 editor=False)
764 editor=False)
762 tempopts = {}
765 tempopts = {}
763 tempopts['message'] = "pending changes temporary commit"
766 tempopts['message'] = "pending changes temporary commit"
764 tempopts['date'] = opts.get('date')
767 tempopts['date'] = opts.get('date')
765 ui.quiet = True
768 ui.quiet = True
766 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
769 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
767 tmpwctx = repo[node]
770 tmpwctx = repo[node]
768
771
769 ui.quiet = True
772 ui.quiet = True
770 shelvedfile(repo, basename, 'hg').applybundle()
773 shelvedfile(repo, basename, 'hg').applybundle()
771
774
772 ui.quiet = oldquiet
775 ui.quiet = oldquiet
773
776
774 shelvectx = repo['tip']
777 shelvectx = repo['tip']
775
778
776 branchtorestore = ''
779 branchtorestore = ''
777 if shelvectx.branch() != shelvectx.p1().branch():
780 if shelvectx.branch() != shelvectx.p1().branch():
778 branchtorestore = shelvectx.branch()
781 branchtorestore = shelvectx.branch()
779
782
780 # If the shelve is not immediately on top of the commit
783 # If the shelve is not immediately on top of the commit
781 # we'll be merging with, rebase it to be on top.
784 # we'll be merging with, rebase it to be on top.
782 if tmpwctx.node() != shelvectx.parents()[0].node():
785 if tmpwctx.node() != shelvectx.parents()[0].node():
783 ui.status(_('rebasing shelved changes\n'))
786 ui.status(_('rebasing shelved changes\n'))
784 try:
787 try:
785 rebase.rebase(ui, repo, **{
788 rebase.rebase(ui, repo, **{
786 'rev' : [shelvectx.rev()],
789 'rev' : [shelvectx.rev()],
787 'dest' : str(tmpwctx.rev()),
790 'dest' : str(tmpwctx.rev()),
788 'keep' : True,
791 'keep' : True,
789 'tool' : opts.get('tool', ''),
792 'tool' : opts.get('tool', ''),
790 })
793 })
791 except error.InterventionRequired:
794 except error.InterventionRequired:
792 tr.close()
795 tr.close()
793
796
794 stripnodes = [repo.changelog.node(rev)
797 stripnodes = [repo.changelog.node(rev)
795 for rev in xrange(oldtiprev, len(repo))]
798 for rev in xrange(oldtiprev, len(repo))]
796 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes,
799 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes,
797 branchtorestore)
800 branchtorestore)
798
801
799 util.rename(repo.join('rebasestate'),
802 util.rename(repo.join('rebasestate'),
800 repo.join('unshelverebasestate'))
803 repo.join('unshelverebasestate'))
801 raise error.InterventionRequired(
804 raise error.InterventionRequired(
802 _("unresolved conflicts (see 'hg resolve', then "
805 _("unresolved conflicts (see 'hg resolve', then "
803 "'hg unshelve --continue')"))
806 "'hg unshelve --continue')"))
804
807
805 # refresh ctx after rebase completes
808 # refresh ctx after rebase completes
806 shelvectx = repo['tip']
809 shelvectx = repo['tip']
807
810
808 if not shelvectx in tmpwctx.children():
811 if not shelvectx in tmpwctx.children():
809 # rebase was a no-op, so it produced no child commit
812 # rebase was a no-op, so it produced no child commit
810 shelvectx = tmpwctx
813 shelvectx = tmpwctx
811
814
812 mergefiles(ui, repo, pctx, shelvectx)
815 mergefiles(ui, repo, pctx, shelvectx)
813 restorebranch(ui, repo, branchtorestore)
816 restorebranch(ui, repo, branchtorestore)
814
817
815 # Forget any files that were unknown before the shelve, unknown before
818 # Forget any files that were unknown before the shelve, unknown before
816 # unshelve started, but are now added.
819 # unshelve started, but are now added.
817 shelveunknown = shelvectx.extra().get('shelve_unknown')
820 shelveunknown = shelvectx.extra().get('shelve_unknown')
818 if shelveunknown:
821 if shelveunknown:
819 shelveunknown = frozenset(shelveunknown.split('\0'))
822 shelveunknown = frozenset(shelveunknown.split('\0'))
820 addedafter = frozenset(repo.status().added)
823 addedafter = frozenset(repo.status().added)
821 toforget = (addedafter & shelveunknown) - addedbefore
824 toforget = (addedafter & shelveunknown) - addedbefore
822 repo[None].forget(toforget)
825 repo[None].forget(toforget)
823
826
824 shelvedstate.clear(repo)
827 shelvedstate.clear(repo)
825
828
826 # The transaction aborting will strip all the commits for us,
829 # The transaction aborting will strip all the commits for us,
827 # but it doesn't update the inmemory structures, so addchangegroup
830 # but it doesn't update the inmemory structures, so addchangegroup
828 # hooks still fire and try to operate on the missing commits.
831 # hooks still fire and try to operate on the missing commits.
829 # Clean up manually to prevent this.
832 # Clean up manually to prevent this.
830 repo.unfiltered().changelog.strip(oldtiprev, tr)
833 repo.unfiltered().changelog.strip(oldtiprev, tr)
831
834
832 unshelvecleanup(ui, repo, basename, opts)
835 unshelvecleanup(ui, repo, basename, opts)
833
836
834 _aborttransaction(repo)
837 _aborttransaction(repo)
835 finally:
838 finally:
836 ui.quiet = oldquiet
839 ui.quiet = oldquiet
837 if tr:
840 if tr:
838 tr.release()
841 tr.release()
839 lockmod.release(lock)
842 lockmod.release(lock)
840 ui.restoreconfig(forcemerge)
843 ui.restoreconfig(forcemerge)
841
844
842 @command('shelve',
845 @command('shelve',
843 [('A', 'addremove', None,
846 [('A', 'addremove', None,
844 _('mark new/missing files as added/removed before shelving')),
847 _('mark new/missing files as added/removed before shelving')),
845 ('u', 'unknown', None,
848 ('u', 'unknown', None,
846 _('store unknown files in the shelve')),
849 _('store unknown files in the shelve')),
847 ('', 'cleanup', None,
850 ('', 'cleanup', None,
848 _('delete all shelved changes')),
851 _('delete all shelved changes')),
849 ('', 'date', '',
852 ('', 'date', '',
850 _('shelve with the specified commit date'), _('DATE')),
853 _('shelve with the specified commit date'), _('DATE')),
851 ('d', 'delete', None,
854 ('d', 'delete', None,
852 _('delete the named shelved change(s)')),
855 _('delete the named shelved change(s)')),
853 ('e', 'edit', False,
856 ('e', 'edit', False,
854 _('invoke editor on commit messages')),
857 _('invoke editor on commit messages')),
855 ('l', 'list', None,
858 ('l', 'list', None,
856 _('list current shelves')),
859 _('list current shelves')),
857 ('m', 'message', '',
860 ('m', 'message', '',
858 _('use text as shelve message'), _('TEXT')),
861 _('use text as shelve message'), _('TEXT')),
859 ('n', 'name', '',
862 ('n', 'name', '',
860 _('use the given name for the shelved commit'), _('NAME')),
863 _('use the given name for the shelved commit'), _('NAME')),
861 ('p', 'patch', None,
864 ('p', 'patch', None,
862 _('show patch')),
865 _('show patch')),
863 ('i', 'interactive', None,
866 ('i', 'interactive', None,
864 _('interactive mode, only works while creating a shelve')),
867 _('interactive mode, only works while creating a shelve')),
865 ('', 'stat', None,
868 ('', 'stat', None,
866 _('output diffstat-style summary of changes'))] + commands.walkopts,
869 _('output diffstat-style summary of changes'))] + commands.walkopts,
867 _('hg shelve [OPTION]... [FILE]...'))
870 _('hg shelve [OPTION]... [FILE]...'))
868 def shelvecmd(ui, repo, *pats, **opts):
871 def shelvecmd(ui, repo, *pats, **opts):
869 '''save and set aside changes from the working directory
872 '''save and set aside changes from the working directory
870
873
871 Shelving takes files that "hg status" reports as not clean, saves
874 Shelving takes files that "hg status" reports as not clean, saves
872 the modifications to a bundle (a shelved change), and reverts the
875 the modifications to a bundle (a shelved change), and reverts the
873 files so that their state in the working directory becomes clean.
876 files so that their state in the working directory becomes clean.
874
877
875 To restore these changes to the working directory, using "hg
878 To restore these changes to the working directory, using "hg
876 unshelve"; this will work even if you switch to a different
879 unshelve"; this will work even if you switch to a different
877 commit.
880 commit.
878
881
879 When no files are specified, "hg shelve" saves all not-clean
882 When no files are specified, "hg shelve" saves all not-clean
880 files. If specific files or directories are named, only changes to
883 files. If specific files or directories are named, only changes to
881 those files are shelved.
884 those files are shelved.
882
885
883 In bare shelve(when no files are specified, without interactive,
886 In bare shelve(when no files are specified, without interactive,
884 include and exclude option), shelving remembers information if the
887 include and exclude option), shelving remembers information if the
885 working directory was on newly created branch, in other words working
888 working directory was on newly created branch, in other words working
886 directory was on different branch than its first parent. In this
889 directory was on different branch than its first parent. In this
887 situation unshelving restores branch information to the working directory.
890 situation unshelving restores branch information to the working directory.
888
891
889 Each shelved change has a name that makes it easier to find later.
892 Each shelved change has a name that makes it easier to find later.
890 The name of a shelved change defaults to being based on the active
893 The name of a shelved change defaults to being based on the active
891 bookmark, or if there is no active bookmark, the current named
894 bookmark, or if there is no active bookmark, the current named
892 branch. To specify a different name, use ``--name``.
895 branch. To specify a different name, use ``--name``.
893
896
894 To see a list of existing shelved changes, use the ``--list``
897 To see a list of existing shelved changes, use the ``--list``
895 option. For each shelved change, this will print its name, age,
898 option. For each shelved change, this will print its name, age,
896 and description; use ``--patch`` or ``--stat`` for more details.
899 and description; use ``--patch`` or ``--stat`` for more details.
897
900
898 To delete specific shelved changes, use ``--delete``. To delete
901 To delete specific shelved changes, use ``--delete``. To delete
899 all shelved changes, use ``--cleanup``.
902 all shelved changes, use ``--cleanup``.
900 '''
903 '''
901 allowables = [
904 allowables = [
902 ('addremove', set(['create'])), # 'create' is pseudo action
905 ('addremove', set(['create'])), # 'create' is pseudo action
903 ('unknown', set(['create'])),
906 ('unknown', set(['create'])),
904 ('cleanup', set(['cleanup'])),
907 ('cleanup', set(['cleanup'])),
905 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
908 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
906 ('delete', set(['delete'])),
909 ('delete', set(['delete'])),
907 ('edit', set(['create'])),
910 ('edit', set(['create'])),
908 ('list', set(['list'])),
911 ('list', set(['list'])),
909 ('message', set(['create'])),
912 ('message', set(['create'])),
910 ('name', set(['create'])),
913 ('name', set(['create'])),
911 ('patch', set(['patch', 'list'])),
914 ('patch', set(['patch', 'list'])),
912 ('stat', set(['stat', 'list'])),
915 ('stat', set(['stat', 'list'])),
913 ]
916 ]
914 def checkopt(opt):
917 def checkopt(opt):
915 if opts.get(opt):
918 if opts.get(opt):
916 for i, allowable in allowables:
919 for i, allowable in allowables:
917 if opts[i] and opt not in allowable:
920 if opts[i] and opt not in allowable:
918 raise error.Abort(_("options '--%s' and '--%s' may not be "
921 raise error.Abort(_("options '--%s' and '--%s' may not be "
919 "used together") % (opt, i))
922 "used together") % (opt, i))
920 return True
923 return True
921 if checkopt('cleanup'):
924 if checkopt('cleanup'):
922 if pats:
925 if pats:
923 raise error.Abort(_("cannot specify names when using '--cleanup'"))
926 raise error.Abort(_("cannot specify names when using '--cleanup'"))
924 return cleanupcmd(ui, repo)
927 return cleanupcmd(ui, repo)
925 elif checkopt('delete'):
928 elif checkopt('delete'):
926 return deletecmd(ui, repo, pats)
929 return deletecmd(ui, repo, pats)
927 elif checkopt('list'):
930 elif checkopt('list'):
928 return listcmd(ui, repo, pats, opts)
931 return listcmd(ui, repo, pats, opts)
929 elif checkopt('patch'):
932 elif checkopt('patch'):
930 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
933 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
931 elif checkopt('stat'):
934 elif checkopt('stat'):
932 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
935 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
933 else:
936 else:
934 return createcmd(ui, repo, pats, opts)
937 return createcmd(ui, repo, pats, opts)
935
938
936 def extsetup(ui):
939 def extsetup(ui):
937 cmdutil.unfinishedstates.append(
940 cmdutil.unfinishedstates.append(
938 [shelvedstate._filename, False, False,
941 [shelvedstate._filename, False, False,
939 _('unshelve already in progress'),
942 _('unshelve already in progress'),
940 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
943 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
941 cmdutil.afterresolvedstates.append(
944 cmdutil.afterresolvedstates.append(
942 [shelvedstate._filename, _('hg unshelve --continue')])
945 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now