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