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