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