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