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