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