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