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