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