##// END OF EJS Templates
merge with stable
Augie Fackler -
r30542:64b55bff merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

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