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