##// END OF EJS Templates
shelve: move temporary commit creation to a separate function...
Kostia Balytskyi -
r30453:2e736f01 default
parent child Browse files
Show More
@@ -1,944 +1,951
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):
634 """Temporarily commit working copy changes before moving unshelve commit"""
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
637 s = repo.status()
638 addedbefore = frozenset(s.added)
639 if not (s.modified or s.added or s.removed or s.deleted):
640 return tmpwctx, addedbefore
641 ui.status(_("temporarily committing pending changes "
642 "(restore with 'hg unshelve --abort')\n"))
643 commitfunc = getcommitfunc(extra=None, interactive=False,
644 editor=False)
645 tempopts = {}
646 tempopts['message'] = "pending changes temporary commit"
647 tempopts['date'] = opts.get('date')
648 ui.quiet = True
649 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
650 tmpwctx = repo[node]
651 return tmpwctx, addedbefore
652
633 @command('unshelve',
653 @command('unshelve',
634 [('a', 'abort', None,
654 [('a', 'abort', None,
635 _('abort an incomplete unshelve operation')),
655 _('abort an incomplete unshelve operation')),
636 ('c', 'continue', None,
656 ('c', 'continue', None,
637 _('continue an incomplete unshelve operation')),
657 _('continue an incomplete unshelve operation')),
638 ('k', 'keep', None,
658 ('k', 'keep', None,
639 _('keep shelve after unshelving')),
659 _('keep shelve after unshelving')),
640 ('t', 'tool', '', _('specify merge tool')),
660 ('t', 'tool', '', _('specify merge tool')),
641 ('', 'date', '',
661 ('', 'date', '',
642 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
662 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
643 _('hg unshelve [SHELVED]'))
663 _('hg unshelve [SHELVED]'))
644 def unshelve(ui, repo, *shelved, **opts):
664 def unshelve(ui, repo, *shelved, **opts):
645 """restore a shelved change to the working directory
665 """restore a shelved change to the working directory
646
666
647 This command accepts an optional name of a shelved change to
667 This command accepts an optional name of a shelved change to
648 restore. If none is given, the most recent shelved change is used.
668 restore. If none is given, the most recent shelved change is used.
649
669
650 If a shelved change is applied successfully, the bundle that
670 If a shelved change is applied successfully, the bundle that
651 contains the shelved changes is moved to a backup location
671 contains the shelved changes is moved to a backup location
652 (.hg/shelve-backup).
672 (.hg/shelve-backup).
653
673
654 Since you can restore a shelved change on top of an arbitrary
674 Since you can restore a shelved change on top of an arbitrary
655 commit, it is possible that unshelving will result in a conflict
675 commit, it is possible that unshelving will result in a conflict
656 between your changes and the commits you are unshelving onto. If
676 between your changes and the commits you are unshelving onto. If
657 this occurs, you must resolve the conflict, then use
677 this occurs, you must resolve the conflict, then use
658 ``--continue`` to complete the unshelve operation. (The bundle
678 ``--continue`` to complete the unshelve operation. (The bundle
659 will not be moved until you successfully complete the unshelve.)
679 will not be moved until you successfully complete the unshelve.)
660
680
661 (Alternatively, you can use ``--abort`` to abandon an unshelve
681 (Alternatively, you can use ``--abort`` to abandon an unshelve
662 that causes a conflict. This reverts the unshelved changes, and
682 that causes a conflict. This reverts the unshelved changes, and
663 leaves the bundle in place.)
683 leaves the bundle in place.)
664
684
665 If bare shelved change(when no files are specified, without interactive,
685 If bare shelved change(when no files are specified, without interactive,
666 include and exclude option) was done on newly created branch it would
686 include and exclude option) was done on newly created branch it would
667 restore branch information to the working directory.
687 restore branch information to the working directory.
668
688
669 After a successful unshelve, the shelved changes are stored in a
689 After a successful unshelve, the shelved changes are stored in a
670 backup directory. Only the N most recent backups are kept. N
690 backup directory. Only the N most recent backups are kept. N
671 defaults to 10 but can be overridden using the ``shelve.maxbackups``
691 defaults to 10 but can be overridden using the ``shelve.maxbackups``
672 configuration option.
692 configuration option.
673
693
674 .. container:: verbose
694 .. container:: verbose
675
695
676 Timestamp in seconds is used to decide order of backups. More
696 Timestamp in seconds is used to decide order of backups. More
677 than ``maxbackups`` backups are kept, if same timestamp
697 than ``maxbackups`` backups are kept, if same timestamp
678 prevents from deciding exact order of them, for safety.
698 prevents from deciding exact order of them, for safety.
679 """
699 """
680 with repo.wlock():
700 with repo.wlock():
681 return _dounshelve(ui, repo, *shelved, **opts)
701 return _dounshelve(ui, repo, *shelved, **opts)
682
702
683 def _dounshelve(ui, repo, *shelved, **opts):
703 def _dounshelve(ui, repo, *shelved, **opts):
684 abortf = opts.get('abort')
704 abortf = opts.get('abort')
685 continuef = opts.get('continue')
705 continuef = opts.get('continue')
686 if not abortf and not continuef:
706 if not abortf and not continuef:
687 cmdutil.checkunfinished(repo)
707 cmdutil.checkunfinished(repo)
688
708
689 if abortf or continuef:
709 if abortf or continuef:
690 if abortf and continuef:
710 if abortf and continuef:
691 raise error.Abort(_('cannot use both abort and continue'))
711 raise error.Abort(_('cannot use both abort and continue'))
692 if shelved:
712 if shelved:
693 raise error.Abort(_('cannot combine abort/continue with '
713 raise error.Abort(_('cannot combine abort/continue with '
694 'naming a shelved change'))
714 'naming a shelved change'))
695 if abortf and opts.get('tool', False):
715 if abortf and opts.get('tool', False):
696 ui.warn(_('tool option will be ignored\n'))
716 ui.warn(_('tool option will be ignored\n'))
697
717
698 try:
718 try:
699 state = shelvedstate.load(repo)
719 state = shelvedstate.load(repo)
700 except IOError as err:
720 except IOError as err:
701 if err.errno != errno.ENOENT:
721 if err.errno != errno.ENOENT:
702 raise
722 raise
703 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
723 cmdutil.wrongtooltocontinue(repo, _('unshelve'))
704 except error.CorruptedState as err:
724 except error.CorruptedState as err:
705 ui.debug(str(err) + '\n')
725 ui.debug(str(err) + '\n')
706 if continuef:
726 if continuef:
707 msg = _('corrupted shelved state file')
727 msg = _('corrupted shelved state file')
708 hint = _('please run hg unshelve --abort to abort unshelve '
728 hint = _('please run hg unshelve --abort to abort unshelve '
709 'operation')
729 'operation')
710 raise error.Abort(msg, hint=hint)
730 raise error.Abort(msg, hint=hint)
711 elif abortf:
731 elif abortf:
712 msg = _('could not read shelved state file, your working copy '
732 msg = _('could not read shelved state file, your working copy '
713 'may be in an unexpected state\nplease update to some '
733 'may be in an unexpected state\nplease update to some '
714 'commit\n')
734 'commit\n')
715 ui.warn(msg)
735 ui.warn(msg)
716 shelvedstate.clear(repo)
736 shelvedstate.clear(repo)
717 return
737 return
718
738
719 if abortf:
739 if abortf:
720 return unshelveabort(ui, repo, state, opts)
740 return unshelveabort(ui, repo, state, opts)
721 elif continuef:
741 elif continuef:
722 return unshelvecontinue(ui, repo, state, opts)
742 return unshelvecontinue(ui, repo, state, opts)
723 elif len(shelved) > 1:
743 elif len(shelved) > 1:
724 raise error.Abort(_('can only unshelve one change at a time'))
744 raise error.Abort(_('can only unshelve one change at a time'))
725 elif not shelved:
745 elif not shelved:
726 shelved = listshelves(repo)
746 shelved = listshelves(repo)
727 if not shelved:
747 if not shelved:
728 raise error.Abort(_('no shelved changes to apply!'))
748 raise error.Abort(_('no shelved changes to apply!'))
729 basename = util.split(shelved[0][1])[1]
749 basename = util.split(shelved[0][1])[1]
730 ui.status(_("unshelving change '%s'\n") % basename)
750 ui.status(_("unshelving change '%s'\n") % basename)
731 else:
751 else:
732 basename = shelved[0]
752 basename = shelved[0]
733
753
734 if not shelvedfile(repo, basename, 'patch').exists():
754 if not shelvedfile(repo, basename, 'patch').exists():
735 raise error.Abort(_("shelved change '%s' not found") % basename)
755 raise error.Abort(_("shelved change '%s' not found") % basename)
736
756
737 oldquiet = ui.quiet
757 oldquiet = ui.quiet
738 lock = tr = None
758 lock = tr = None
739 forcemerge = ui.backupconfig('ui', 'forcemerge')
759 forcemerge = ui.backupconfig('ui', 'forcemerge')
740 try:
760 try:
741 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
761 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
742 lock = repo.lock()
762 lock = repo.lock()
743
763
744 tr = repo.transaction('unshelve', report=lambda x: None)
764 tr = repo.transaction('unshelve', report=lambda x: None)
745 oldtiprev = len(repo)
765 oldtiprev = len(repo)
746
766
747 pctx = repo['.']
767 pctx = repo['.']
748 tmpwctx = pctx
768 tmpwctx = pctx
749 # The goal is to have a commit structure like so:
769 # The goal is to have a commit structure like so:
750 # ...-> pctx -> tmpwctx -> shelvectx
770 # ...-> pctx -> tmpwctx -> shelvectx
751 # where tmpwctx is an optional commit with the user's pending changes
771 # where tmpwctx is an optional commit with the user's pending changes
752 # and shelvectx is the unshelved changes. Then we merge it all down
772 # and shelvectx is the unshelved changes. Then we merge it all down
753 # to the original pctx.
773 # to the original pctx.
754
774
755 # Store pending changes in a commit and remember added in case a shelve
775 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
756 # contains unknown files that are part of the pending change
776 tmpwctx)
757 s = repo.status()
758 addedbefore = frozenset(s.added)
759 if s.modified or s.added or s.removed or s.deleted:
760 ui.status(_("temporarily committing pending changes "
761 "(restore with 'hg unshelve --abort')\n"))
762 commitfunc = getcommitfunc(extra=None, interactive=False,
763 editor=False)
764 tempopts = {}
765 tempopts['message'] = "pending changes temporary commit"
766 tempopts['date'] = opts.get('date')
767 ui.quiet = True
768 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
769 tmpwctx = repo[node]
770
777
771 ui.quiet = True
778 ui.quiet = True
772 shelvedfile(repo, basename, 'hg').applybundle()
779 shelvedfile(repo, basename, 'hg').applybundle()
773
780
774 ui.quiet = oldquiet
781 ui.quiet = oldquiet
775
782
776 shelvectx = repo['tip']
783 shelvectx = repo['tip']
777
784
778 branchtorestore = ''
785 branchtorestore = ''
779 if shelvectx.branch() != shelvectx.p1().branch():
786 if shelvectx.branch() != shelvectx.p1().branch():
780 branchtorestore = shelvectx.branch()
787 branchtorestore = shelvectx.branch()
781
788
782 # If the shelve is not immediately on top of the commit
789 # If the shelve is not immediately on top of the commit
783 # we'll be merging with, rebase it to be on top.
790 # we'll be merging with, rebase it to be on top.
784 if tmpwctx.node() != shelvectx.parents()[0].node():
791 if tmpwctx.node() != shelvectx.parents()[0].node():
785 ui.status(_('rebasing shelved changes\n'))
792 ui.status(_('rebasing shelved changes\n'))
786 try:
793 try:
787 rebase.rebase(ui, repo, **{
794 rebase.rebase(ui, repo, **{
788 'rev' : [shelvectx.rev()],
795 'rev' : [shelvectx.rev()],
789 'dest' : str(tmpwctx.rev()),
796 'dest' : str(tmpwctx.rev()),
790 'keep' : True,
797 'keep' : True,
791 'tool' : opts.get('tool', ''),
798 'tool' : opts.get('tool', ''),
792 })
799 })
793 except error.InterventionRequired:
800 except error.InterventionRequired:
794 tr.close()
801 tr.close()
795
802
796 stripnodes = [repo.changelog.node(rev)
803 stripnodes = [repo.changelog.node(rev)
797 for rev in xrange(oldtiprev, len(repo))]
804 for rev in xrange(oldtiprev, len(repo))]
798 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes,
805 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes,
799 branchtorestore)
806 branchtorestore)
800
807
801 util.rename(repo.join('rebasestate'),
808 util.rename(repo.join('rebasestate'),
802 repo.join('unshelverebasestate'))
809 repo.join('unshelverebasestate'))
803 raise error.InterventionRequired(
810 raise error.InterventionRequired(
804 _("unresolved conflicts (see 'hg resolve', then "
811 _("unresolved conflicts (see 'hg resolve', then "
805 "'hg unshelve --continue')"))
812 "'hg unshelve --continue')"))
806
813
807 # refresh ctx after rebase completes
814 # refresh ctx after rebase completes
808 shelvectx = repo['tip']
815 shelvectx = repo['tip']
809
816
810 if not shelvectx in tmpwctx.children():
817 if not shelvectx in tmpwctx.children():
811 # rebase was a no-op, so it produced no child commit
818 # rebase was a no-op, so it produced no child commit
812 shelvectx = tmpwctx
819 shelvectx = tmpwctx
813
820
814 mergefiles(ui, repo, pctx, shelvectx)
821 mergefiles(ui, repo, pctx, shelvectx)
815 restorebranch(ui, repo, branchtorestore)
822 restorebranch(ui, repo, branchtorestore)
816
823
817 # Forget any files that were unknown before the shelve, unknown before
824 # Forget any files that were unknown before the shelve, unknown before
818 # unshelve started, but are now added.
825 # unshelve started, but are now added.
819 shelveunknown = shelvectx.extra().get('shelve_unknown')
826 shelveunknown = shelvectx.extra().get('shelve_unknown')
820 if shelveunknown:
827 if shelveunknown:
821 shelveunknown = frozenset(shelveunknown.split('\0'))
828 shelveunknown = frozenset(shelveunknown.split('\0'))
822 addedafter = frozenset(repo.status().added)
829 addedafter = frozenset(repo.status().added)
823 toforget = (addedafter & shelveunknown) - addedbefore
830 toforget = (addedafter & shelveunknown) - addedbefore
824 repo[None].forget(toforget)
831 repo[None].forget(toforget)
825
832
826 shelvedstate.clear(repo)
833 shelvedstate.clear(repo)
827
834
828 # The transaction aborting will strip all the commits for us,
835 # The transaction aborting will strip all the commits for us,
829 # but it doesn't update the inmemory structures, so addchangegroup
836 # but it doesn't update the inmemory structures, so addchangegroup
830 # hooks still fire and try to operate on the missing commits.
837 # hooks still fire and try to operate on the missing commits.
831 # Clean up manually to prevent this.
838 # Clean up manually to prevent this.
832 repo.unfiltered().changelog.strip(oldtiprev, tr)
839 repo.unfiltered().changelog.strip(oldtiprev, tr)
833
840
834 unshelvecleanup(ui, repo, basename, opts)
841 unshelvecleanup(ui, repo, basename, opts)
835
842
836 _aborttransaction(repo)
843 _aborttransaction(repo)
837 finally:
844 finally:
838 ui.quiet = oldquiet
845 ui.quiet = oldquiet
839 if tr:
846 if tr:
840 tr.release()
847 tr.release()
841 lockmod.release(lock)
848 lockmod.release(lock)
842 ui.restoreconfig(forcemerge)
849 ui.restoreconfig(forcemerge)
843
850
844 @command('shelve',
851 @command('shelve',
845 [('A', 'addremove', None,
852 [('A', 'addremove', None,
846 _('mark new/missing files as added/removed before shelving')),
853 _('mark new/missing files as added/removed before shelving')),
847 ('u', 'unknown', None,
854 ('u', 'unknown', None,
848 _('store unknown files in the shelve')),
855 _('store unknown files in the shelve')),
849 ('', 'cleanup', None,
856 ('', 'cleanup', None,
850 _('delete all shelved changes')),
857 _('delete all shelved changes')),
851 ('', 'date', '',
858 ('', 'date', '',
852 _('shelve with the specified commit date'), _('DATE')),
859 _('shelve with the specified commit date'), _('DATE')),
853 ('d', 'delete', None,
860 ('d', 'delete', None,
854 _('delete the named shelved change(s)')),
861 _('delete the named shelved change(s)')),
855 ('e', 'edit', False,
862 ('e', 'edit', False,
856 _('invoke editor on commit messages')),
863 _('invoke editor on commit messages')),
857 ('l', 'list', None,
864 ('l', 'list', None,
858 _('list current shelves')),
865 _('list current shelves')),
859 ('m', 'message', '',
866 ('m', 'message', '',
860 _('use text as shelve message'), _('TEXT')),
867 _('use text as shelve message'), _('TEXT')),
861 ('n', 'name', '',
868 ('n', 'name', '',
862 _('use the given name for the shelved commit'), _('NAME')),
869 _('use the given name for the shelved commit'), _('NAME')),
863 ('p', 'patch', None,
870 ('p', 'patch', None,
864 _('show patch')),
871 _('show patch')),
865 ('i', 'interactive', None,
872 ('i', 'interactive', None,
866 _('interactive mode, only works while creating a shelve')),
873 _('interactive mode, only works while creating a shelve')),
867 ('', 'stat', None,
874 ('', 'stat', None,
868 _('output diffstat-style summary of changes'))] + commands.walkopts,
875 _('output diffstat-style summary of changes'))] + commands.walkopts,
869 _('hg shelve [OPTION]... [FILE]...'))
876 _('hg shelve [OPTION]... [FILE]...'))
870 def shelvecmd(ui, repo, *pats, **opts):
877 def shelvecmd(ui, repo, *pats, **opts):
871 '''save and set aside changes from the working directory
878 '''save and set aside changes from the working directory
872
879
873 Shelving takes files that "hg status" reports as not clean, saves
880 Shelving takes files that "hg status" reports as not clean, saves
874 the modifications to a bundle (a shelved change), and reverts the
881 the modifications to a bundle (a shelved change), and reverts the
875 files so that their state in the working directory becomes clean.
882 files so that their state in the working directory becomes clean.
876
883
877 To restore these changes to the working directory, using "hg
884 To restore these changes to the working directory, using "hg
878 unshelve"; this will work even if you switch to a different
885 unshelve"; this will work even if you switch to a different
879 commit.
886 commit.
880
887
881 When no files are specified, "hg shelve" saves all not-clean
888 When no files are specified, "hg shelve" saves all not-clean
882 files. If specific files or directories are named, only changes to
889 files. If specific files or directories are named, only changes to
883 those files are shelved.
890 those files are shelved.
884
891
885 In bare shelve(when no files are specified, without interactive,
892 In bare shelve(when no files are specified, without interactive,
886 include and exclude option), shelving remembers information if the
893 include and exclude option), shelving remembers information if the
887 working directory was on newly created branch, in other words working
894 working directory was on newly created branch, in other words working
888 directory was on different branch than its first parent. In this
895 directory was on different branch than its first parent. In this
889 situation unshelving restores branch information to the working directory.
896 situation unshelving restores branch information to the working directory.
890
897
891 Each shelved change has a name that makes it easier to find later.
898 Each shelved change has a name that makes it easier to find later.
892 The name of a shelved change defaults to being based on the active
899 The name of a shelved change defaults to being based on the active
893 bookmark, or if there is no active bookmark, the current named
900 bookmark, or if there is no active bookmark, the current named
894 branch. To specify a different name, use ``--name``.
901 branch. To specify a different name, use ``--name``.
895
902
896 To see a list of existing shelved changes, use the ``--list``
903 To see a list of existing shelved changes, use the ``--list``
897 option. For each shelved change, this will print its name, age,
904 option. For each shelved change, this will print its name, age,
898 and description; use ``--patch`` or ``--stat`` for more details.
905 and description; use ``--patch`` or ``--stat`` for more details.
899
906
900 To delete specific shelved changes, use ``--delete``. To delete
907 To delete specific shelved changes, use ``--delete``. To delete
901 all shelved changes, use ``--cleanup``.
908 all shelved changes, use ``--cleanup``.
902 '''
909 '''
903 allowables = [
910 allowables = [
904 ('addremove', set(['create'])), # 'create' is pseudo action
911 ('addremove', set(['create'])), # 'create' is pseudo action
905 ('unknown', set(['create'])),
912 ('unknown', set(['create'])),
906 ('cleanup', set(['cleanup'])),
913 ('cleanup', set(['cleanup'])),
907 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
914 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
908 ('delete', set(['delete'])),
915 ('delete', set(['delete'])),
909 ('edit', set(['create'])),
916 ('edit', set(['create'])),
910 ('list', set(['list'])),
917 ('list', set(['list'])),
911 ('message', set(['create'])),
918 ('message', set(['create'])),
912 ('name', set(['create'])),
919 ('name', set(['create'])),
913 ('patch', set(['patch', 'list'])),
920 ('patch', set(['patch', 'list'])),
914 ('stat', set(['stat', 'list'])),
921 ('stat', set(['stat', 'list'])),
915 ]
922 ]
916 def checkopt(opt):
923 def checkopt(opt):
917 if opts.get(opt):
924 if opts.get(opt):
918 for i, allowable in allowables:
925 for i, allowable in allowables:
919 if opts[i] and opt not in allowable:
926 if opts[i] and opt not in allowable:
920 raise error.Abort(_("options '--%s' and '--%s' may not be "
927 raise error.Abort(_("options '--%s' and '--%s' may not be "
921 "used together") % (opt, i))
928 "used together") % (opt, i))
922 return True
929 return True
923 if checkopt('cleanup'):
930 if checkopt('cleanup'):
924 if pats:
931 if pats:
925 raise error.Abort(_("cannot specify names when using '--cleanup'"))
932 raise error.Abort(_("cannot specify names when using '--cleanup'"))
926 return cleanupcmd(ui, repo)
933 return cleanupcmd(ui, repo)
927 elif checkopt('delete'):
934 elif checkopt('delete'):
928 return deletecmd(ui, repo, pats)
935 return deletecmd(ui, repo, pats)
929 elif checkopt('list'):
936 elif checkopt('list'):
930 return listcmd(ui, repo, pats, opts)
937 return listcmd(ui, repo, pats, opts)
931 elif checkopt('patch'):
938 elif checkopt('patch'):
932 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
939 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
933 elif checkopt('stat'):
940 elif checkopt('stat'):
934 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
941 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
935 else:
942 else:
936 return createcmd(ui, repo, pats, opts)
943 return createcmd(ui, repo, pats, opts)
937
944
938 def extsetup(ui):
945 def extsetup(ui):
939 cmdutil.unfinishedstates.append(
946 cmdutil.unfinishedstates.append(
940 [shelvedstate._filename, False, False,
947 [shelvedstate._filename, False, False,
941 _('unshelve already in progress'),
948 _('unshelve already in progress'),
942 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
949 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
943 cmdutil.afterresolvedstates.append(
950 cmdutil.afterresolvedstates.append(
944 [shelvedstate._filename, _('hg unshelve --continue')])
951 [shelvedstate._filename, _('hg unshelve --continue')])
General Comments 0
You need to be logged in to leave comments. Login now