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