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