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