##// END OF EJS Templates
shelve: widen wlock scope of unshelve for consistency while processing...
FUJIWARA Katsunori -
r27287:c9ceea3f default
parent child Browse files
Show More
@@ -1,850 +1,856
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 fp = open(name + '.patch', 'rb')
428 try:
428 try:
429 while True:
429 while True:
430 line = fp.readline()
430 line = fp.readline()
431 if not line:
431 if not line:
432 break
432 break
433 if not line.startswith('#'):
433 if not line.startswith('#'):
434 desc = line.rstrip()
434 desc = line.rstrip()
435 if ui.formatted():
435 if ui.formatted():
436 desc = util.ellipsis(desc, width - used)
436 desc = util.ellipsis(desc, width - used)
437 ui.write(desc)
437 ui.write(desc)
438 break
438 break
439 ui.write('\n')
439 ui.write('\n')
440 if not (opts['patch'] or opts['stat']):
440 if not (opts['patch'] or opts['stat']):
441 continue
441 continue
442 difflines = fp.readlines()
442 difflines = fp.readlines()
443 if opts['patch']:
443 if opts['patch']:
444 for chunk, label in patch.difflabel(iter, difflines):
444 for chunk, label in patch.difflabel(iter, difflines):
445 ui.write(chunk, label=label)
445 ui.write(chunk, label=label)
446 if opts['stat']:
446 if opts['stat']:
447 for chunk, label in patch.diffstatui(difflines, width=width,
447 for chunk, label in patch.diffstatui(difflines, width=width,
448 git=True):
448 git=True):
449 ui.write(chunk, label=label)
449 ui.write(chunk, label=label)
450 finally:
450 finally:
451 fp.close()
451 fp.close()
452
452
453 def singlepatchcmds(ui, repo, pats, opts, subcommand):
453 def singlepatchcmds(ui, repo, pats, opts, subcommand):
454 """subcommand that displays a single shelf"""
454 """subcommand that displays a single shelf"""
455 if len(pats) != 1:
455 if len(pats) != 1:
456 raise error.Abort(_("--%s expects a single shelf") % subcommand)
456 raise error.Abort(_("--%s expects a single shelf") % subcommand)
457 shelfname = pats[0]
457 shelfname = pats[0]
458
458
459 if not shelvedfile(repo, shelfname, 'patch').exists():
459 if not shelvedfile(repo, shelfname, 'patch').exists():
460 raise error.Abort(_("cannot find shelf %s") % shelfname)
460 raise error.Abort(_("cannot find shelf %s") % shelfname)
461
461
462 listcmd(ui, repo, pats, opts)
462 listcmd(ui, repo, pats, opts)
463
463
464 def checkparents(repo, state):
464 def checkparents(repo, state):
465 """check parent while resuming an unshelve"""
465 """check parent while resuming an unshelve"""
466 if state.parents != repo.dirstate.parents():
466 if state.parents != repo.dirstate.parents():
467 raise error.Abort(_('working directory parents do not match unshelve '
467 raise error.Abort(_('working directory parents do not match unshelve '
468 'state'))
468 'state'))
469
469
470 def pathtofiles(repo, files):
470 def pathtofiles(repo, files):
471 cwd = repo.getcwd()
471 cwd = repo.getcwd()
472 return [repo.pathto(f, cwd) for f in files]
472 return [repo.pathto(f, cwd) for f in files]
473
473
474 def unshelveabort(ui, repo, state, opts):
474 def unshelveabort(ui, repo, state, opts):
475 """subcommand that abort an in-progress unshelve"""
475 """subcommand that abort an in-progress unshelve"""
476 wlock = repo.wlock()
476 wlock = repo.wlock()
477 lock = None
477 lock = None
478 try:
478 try:
479 checkparents(repo, state)
479 checkparents(repo, state)
480
480
481 util.rename(repo.join('unshelverebasestate'),
481 util.rename(repo.join('unshelverebasestate'),
482 repo.join('rebasestate'))
482 repo.join('rebasestate'))
483 try:
483 try:
484 rebase.rebase(ui, repo, **{
484 rebase.rebase(ui, repo, **{
485 'abort' : True
485 'abort' : True
486 })
486 })
487 except Exception:
487 except Exception:
488 util.rename(repo.join('rebasestate'),
488 util.rename(repo.join('rebasestate'),
489 repo.join('unshelverebasestate'))
489 repo.join('unshelverebasestate'))
490 raise
490 raise
491
491
492 lock = repo.lock()
492 lock = repo.lock()
493
493
494 mergefiles(ui, repo, state.wctx, state.pendingctx)
494 mergefiles(ui, repo, state.wctx, state.pendingctx)
495
495
496 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
496 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
497 finally:
497 finally:
498 shelvedstate.clear(repo)
498 shelvedstate.clear(repo)
499 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
499 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
500 lockmod.release(lock, wlock)
500 lockmod.release(lock, wlock)
501
501
502 def mergefiles(ui, repo, wctx, shelvectx):
502 def mergefiles(ui, repo, wctx, shelvectx):
503 """updates to wctx and merges the changes from shelvectx into the
503 """updates to wctx and merges the changes from shelvectx into the
504 dirstate."""
504 dirstate."""
505 oldquiet = ui.quiet
505 oldquiet = ui.quiet
506 try:
506 try:
507 ui.quiet = True
507 ui.quiet = True
508 hg.update(repo, wctx.node())
508 hg.update(repo, wctx.node())
509 files = []
509 files = []
510 files.extend(shelvectx.files())
510 files.extend(shelvectx.files())
511 files.extend(shelvectx.parents()[0].files())
511 files.extend(shelvectx.parents()[0].files())
512
512
513 # revert will overwrite unknown files, so move them out of the way
513 # revert will overwrite unknown files, so move them out of the way
514 for file in repo.status(unknown=True).unknown:
514 for file in repo.status(unknown=True).unknown:
515 if file in files:
515 if file in files:
516 util.rename(file, cmdutil.origpath(ui, repo, file))
516 util.rename(file, cmdutil.origpath(ui, repo, file))
517 ui.pushbuffer(True)
517 ui.pushbuffer(True)
518 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
518 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
519 *pathtofiles(repo, files),
519 *pathtofiles(repo, files),
520 **{'no_backup': True})
520 **{'no_backup': True})
521 ui.popbuffer()
521 ui.popbuffer()
522 finally:
522 finally:
523 ui.quiet = oldquiet
523 ui.quiet = oldquiet
524
524
525 def unshelvecleanup(ui, repo, name, opts):
525 def unshelvecleanup(ui, repo, name, opts):
526 """remove related files after an unshelve"""
526 """remove related files after an unshelve"""
527 if not opts['keep']:
527 if not opts['keep']:
528 for filetype in 'hg patch'.split():
528 for filetype in 'hg patch'.split():
529 shelvedfile(repo, name, filetype).movetobackup()
529 shelvedfile(repo, name, filetype).movetobackup()
530 cleanupoldbackups(repo)
530 cleanupoldbackups(repo)
531
531
532 def unshelvecontinue(ui, repo, state, opts):
532 def unshelvecontinue(ui, repo, state, opts):
533 """subcommand to continue an in-progress unshelve"""
533 """subcommand to continue an in-progress unshelve"""
534 # We're finishing off a merge. First parent is our original
534 # We're finishing off a merge. First parent is our original
535 # parent, second is the temporary "fake" commit we're unshelving.
535 # parent, second is the temporary "fake" commit we're unshelving.
536 wlock = repo.wlock()
536 wlock = repo.wlock()
537 lock = None
537 lock = None
538 try:
538 try:
539 checkparents(repo, state)
539 checkparents(repo, state)
540 ms = merge.mergestate.read(repo)
540 ms = merge.mergestate.read(repo)
541 if [f for f in ms if ms[f] == 'u']:
541 if [f for f in ms if ms[f] == 'u']:
542 raise error.Abort(
542 raise error.Abort(
543 _("unresolved conflicts, can't continue"),
543 _("unresolved conflicts, can't continue"),
544 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
544 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
545
545
546 lock = repo.lock()
546 lock = repo.lock()
547
547
548 util.rename(repo.join('unshelverebasestate'),
548 util.rename(repo.join('unshelverebasestate'),
549 repo.join('rebasestate'))
549 repo.join('rebasestate'))
550 try:
550 try:
551 rebase.rebase(ui, repo, **{
551 rebase.rebase(ui, repo, **{
552 'continue' : True
552 'continue' : True
553 })
553 })
554 except Exception:
554 except Exception:
555 util.rename(repo.join('rebasestate'),
555 util.rename(repo.join('rebasestate'),
556 repo.join('unshelverebasestate'))
556 repo.join('unshelverebasestate'))
557 raise
557 raise
558
558
559 shelvectx = repo['tip']
559 shelvectx = repo['tip']
560 if not shelvectx in state.pendingctx.children():
560 if not shelvectx in state.pendingctx.children():
561 # rebase was a no-op, so it produced no child commit
561 # rebase was a no-op, so it produced no child commit
562 shelvectx = state.pendingctx
562 shelvectx = state.pendingctx
563 else:
563 else:
564 # only strip the shelvectx if the rebase produced it
564 # only strip the shelvectx if the rebase produced it
565 state.stripnodes.append(shelvectx.node())
565 state.stripnodes.append(shelvectx.node())
566
566
567 mergefiles(ui, repo, state.wctx, shelvectx)
567 mergefiles(ui, repo, state.wctx, shelvectx)
568
568
569 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
569 repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
570 shelvedstate.clear(repo)
570 shelvedstate.clear(repo)
571 unshelvecleanup(ui, repo, state.name, opts)
571 unshelvecleanup(ui, repo, state.name, opts)
572 ui.status(_("unshelve of '%s' complete\n") % state.name)
572 ui.status(_("unshelve of '%s' complete\n") % state.name)
573 finally:
573 finally:
574 lockmod.release(lock, wlock)
574 lockmod.release(lock, wlock)
575
575
576 @command('unshelve',
576 @command('unshelve',
577 [('a', 'abort', None,
577 [('a', 'abort', None,
578 _('abort an incomplete unshelve operation')),
578 _('abort an incomplete unshelve operation')),
579 ('c', 'continue', None,
579 ('c', 'continue', None,
580 _('continue an incomplete unshelve operation')),
580 _('continue an incomplete unshelve operation')),
581 ('k', 'keep', None,
581 ('k', 'keep', None,
582 _('keep shelve after unshelving')),
582 _('keep shelve after unshelving')),
583 ('t', 'tool', '', _('specify merge tool')),
583 ('t', 'tool', '', _('specify merge tool')),
584 ('', 'date', '',
584 ('', 'date', '',
585 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
585 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
586 _('hg unshelve [SHELVED]'))
586 _('hg unshelve [SHELVED]'))
587 def unshelve(ui, repo, *shelved, **opts):
587 def unshelve(ui, repo, *shelved, **opts):
588 """restore a shelved change to the working directory
588 """restore a shelved change to the working directory
589
589
590 This command accepts an optional name of a shelved change to
590 This command accepts an optional name of a shelved change to
591 restore. If none is given, the most recent shelved change is used.
591 restore. If none is given, the most recent shelved change is used.
592
592
593 If a shelved change is applied successfully, the bundle that
593 If a shelved change is applied successfully, the bundle that
594 contains the shelved changes is moved to a backup location
594 contains the shelved changes is moved to a backup location
595 (.hg/shelve-backup).
595 (.hg/shelve-backup).
596
596
597 Since you can restore a shelved change on top of an arbitrary
597 Since you can restore a shelved change on top of an arbitrary
598 commit, it is possible that unshelving will result in a conflict
598 commit, it is possible that unshelving will result in a conflict
599 between your changes and the commits you are unshelving onto. If
599 between your changes and the commits you are unshelving onto. If
600 this occurs, you must resolve the conflict, then use
600 this occurs, you must resolve the conflict, then use
601 ``--continue`` to complete the unshelve operation. (The bundle
601 ``--continue`` to complete the unshelve operation. (The bundle
602 will not be moved until you successfully complete the unshelve.)
602 will not be moved until you successfully complete the unshelve.)
603
603
604 (Alternatively, you can use ``--abort`` to abandon an unshelve
604 (Alternatively, you can use ``--abort`` to abandon an unshelve
605 that causes a conflict. This reverts the unshelved changes, and
605 that causes a conflict. This reverts the unshelved changes, and
606 leaves the bundle in place.)
606 leaves the bundle in place.)
607
607
608 After a successful unshelve, the shelved changes are stored in a
608 After a successful unshelve, the shelved changes are stored in a
609 backup directory. Only the N most recent backups are kept. N
609 backup directory. Only the N most recent backups are kept. N
610 defaults to 10 but can be overridden using the ``shelve.maxbackups``
610 defaults to 10 but can be overridden using the ``shelve.maxbackups``
611 configuration option.
611 configuration option.
612
612
613 .. container:: verbose
613 .. container:: verbose
614
614
615 Timestamp in seconds is used to decide order of backups. More
615 Timestamp in seconds is used to decide order of backups. More
616 than ``maxbackups`` backups are kept, if same timestamp
616 than ``maxbackups`` backups are kept, if same timestamp
617 prevents from deciding exact order of them, for safety.
617 prevents from deciding exact order of them, for safety.
618 """
618 """
619 wlock = repo.wlock()
620 try:
621 return _dounshelve(ui, repo, *shelved, **opts)
622 finally:
623 lockmod.release(wlock)
624
625 def _dounshelve(ui, repo, *shelved, **opts):
619 abortf = opts['abort']
626 abortf = opts['abort']
620 continuef = opts['continue']
627 continuef = opts['continue']
621 if not abortf and not continuef:
628 if not abortf and not continuef:
622 cmdutil.checkunfinished(repo)
629 cmdutil.checkunfinished(repo)
623
630
624 if abortf or continuef:
631 if abortf or continuef:
625 if abortf and continuef:
632 if abortf and continuef:
626 raise error.Abort(_('cannot use both abort and continue'))
633 raise error.Abort(_('cannot use both abort and continue'))
627 if shelved:
634 if shelved:
628 raise error.Abort(_('cannot combine abort/continue with '
635 raise error.Abort(_('cannot combine abort/continue with '
629 'naming a shelved change'))
636 'naming a shelved change'))
630 if abortf and opts.get('tool', False):
637 if abortf and opts.get('tool', False):
631 ui.warn(_('tool option will be ignored\n'))
638 ui.warn(_('tool option will be ignored\n'))
632
639
633 try:
640 try:
634 state = shelvedstate.load(repo)
641 state = shelvedstate.load(repo)
635 except IOError as err:
642 except IOError as err:
636 if err.errno != errno.ENOENT:
643 if err.errno != errno.ENOENT:
637 raise
644 raise
638 raise error.Abort(_('no unshelve operation underway'))
645 raise error.Abort(_('no unshelve operation underway'))
639
646
640 if abortf:
647 if abortf:
641 return unshelveabort(ui, repo, state, opts)
648 return unshelveabort(ui, repo, state, opts)
642 elif continuef:
649 elif continuef:
643 return unshelvecontinue(ui, repo, state, opts)
650 return unshelvecontinue(ui, repo, state, opts)
644 elif len(shelved) > 1:
651 elif len(shelved) > 1:
645 raise error.Abort(_('can only unshelve one change at a time'))
652 raise error.Abort(_('can only unshelve one change at a time'))
646 elif not shelved:
653 elif not shelved:
647 shelved = listshelves(repo)
654 shelved = listshelves(repo)
648 if not shelved:
655 if not shelved:
649 raise error.Abort(_('no shelved changes to apply!'))
656 raise error.Abort(_('no shelved changes to apply!'))
650 basename = util.split(shelved[0][1])[1]
657 basename = util.split(shelved[0][1])[1]
651 ui.status(_("unshelving change '%s'\n") % basename)
658 ui.status(_("unshelving change '%s'\n") % basename)
652 else:
659 else:
653 basename = shelved[0]
660 basename = shelved[0]
654
661
655 if not shelvedfile(repo, basename, 'patch').exists():
662 if not shelvedfile(repo, basename, 'patch').exists():
656 raise error.Abort(_("shelved change '%s' not found") % basename)
663 raise error.Abort(_("shelved change '%s' not found") % basename)
657
664
658 oldquiet = ui.quiet
665 oldquiet = ui.quiet
659 wlock = lock = tr = None
666 lock = tr = None
660 forcemerge = ui.backupconfig('ui', 'forcemerge')
667 forcemerge = ui.backupconfig('ui', 'forcemerge')
661 try:
668 try:
662 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
669 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
663 wlock = repo.wlock()
664 lock = repo.lock()
670 lock = repo.lock()
665
671
666 tr = repo.transaction('unshelve', report=lambda x: None)
672 tr = repo.transaction('unshelve', report=lambda x: None)
667 oldtiprev = len(repo)
673 oldtiprev = len(repo)
668
674
669 pctx = repo['.']
675 pctx = repo['.']
670 tmpwctx = pctx
676 tmpwctx = pctx
671 # The goal is to have a commit structure like so:
677 # The goal is to have a commit structure like so:
672 # ...-> pctx -> tmpwctx -> shelvectx
678 # ...-> pctx -> tmpwctx -> shelvectx
673 # where tmpwctx is an optional commit with the user's pending changes
679 # where tmpwctx is an optional commit with the user's pending changes
674 # and shelvectx is the unshelved changes. Then we merge it all down
680 # and shelvectx is the unshelved changes. Then we merge it all down
675 # to the original pctx.
681 # to the original pctx.
676
682
677 # Store pending changes in a commit
683 # Store pending changes in a commit
678 s = repo.status()
684 s = repo.status()
679 if s.modified or s.added or s.removed or s.deleted:
685 if s.modified or s.added or s.removed or s.deleted:
680 ui.status(_("temporarily committing pending changes "
686 ui.status(_("temporarily committing pending changes "
681 "(restore with 'hg unshelve --abort')\n"))
687 "(restore with 'hg unshelve --abort')\n"))
682 def commitfunc(ui, repo, message, match, opts):
688 def commitfunc(ui, repo, message, match, opts):
683 hasmq = util.safehasattr(repo, 'mq')
689 hasmq = util.safehasattr(repo, 'mq')
684 if hasmq:
690 if hasmq:
685 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
691 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
686
692
687 backup = repo.ui.backupconfig('phases', 'new-commit')
693 backup = repo.ui.backupconfig('phases', 'new-commit')
688 try:
694 try:
689 repo.ui.setconfig('phases', 'new-commit', phases.secret)
695 repo.ui.setconfig('phases', 'new-commit', phases.secret)
690 return repo.commit(message, 'shelve@localhost',
696 return repo.commit(message, 'shelve@localhost',
691 opts.get('date'), match)
697 opts.get('date'), match)
692 finally:
698 finally:
693 repo.ui.restoreconfig(backup)
699 repo.ui.restoreconfig(backup)
694 if hasmq:
700 if hasmq:
695 repo.mq.checkapplied = saved
701 repo.mq.checkapplied = saved
696
702
697 tempopts = {}
703 tempopts = {}
698 tempopts['message'] = "pending changes temporary commit"
704 tempopts['message'] = "pending changes temporary commit"
699 tempopts['date'] = opts.get('date')
705 tempopts['date'] = opts.get('date')
700 ui.quiet = True
706 ui.quiet = True
701 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
707 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
702 tmpwctx = repo[node]
708 tmpwctx = repo[node]
703
709
704 ui.quiet = True
710 ui.quiet = True
705 shelvedfile(repo, basename, 'hg').applybundle()
711 shelvedfile(repo, basename, 'hg').applybundle()
706
712
707 ui.quiet = oldquiet
713 ui.quiet = oldquiet
708
714
709 shelvectx = repo['tip']
715 shelvectx = repo['tip']
710
716
711 # If the shelve is not immediately on top of the commit
717 # If the shelve is not immediately on top of the commit
712 # we'll be merging with, rebase it to be on top.
718 # we'll be merging with, rebase it to be on top.
713 if tmpwctx.node() != shelvectx.parents()[0].node():
719 if tmpwctx.node() != shelvectx.parents()[0].node():
714 ui.status(_('rebasing shelved changes\n'))
720 ui.status(_('rebasing shelved changes\n'))
715 try:
721 try:
716 rebase.rebase(ui, repo, **{
722 rebase.rebase(ui, repo, **{
717 'rev' : [shelvectx.rev()],
723 'rev' : [shelvectx.rev()],
718 'dest' : str(tmpwctx.rev()),
724 'dest' : str(tmpwctx.rev()),
719 'keep' : True,
725 'keep' : True,
720 'tool' : opts.get('tool', ''),
726 'tool' : opts.get('tool', ''),
721 })
727 })
722 except error.InterventionRequired:
728 except error.InterventionRequired:
723 tr.close()
729 tr.close()
724
730
725 stripnodes = [repo.changelog.node(rev)
731 stripnodes = [repo.changelog.node(rev)
726 for rev in xrange(oldtiprev, len(repo))]
732 for rev in xrange(oldtiprev, len(repo))]
727 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes)
733 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes)
728
734
729 util.rename(repo.join('rebasestate'),
735 util.rename(repo.join('rebasestate'),
730 repo.join('unshelverebasestate'))
736 repo.join('unshelverebasestate'))
731 raise error.InterventionRequired(
737 raise error.InterventionRequired(
732 _("unresolved conflicts (see 'hg resolve', then "
738 _("unresolved conflicts (see 'hg resolve', then "
733 "'hg unshelve --continue')"))
739 "'hg unshelve --continue')"))
734
740
735 # refresh ctx after rebase completes
741 # refresh ctx after rebase completes
736 shelvectx = repo['tip']
742 shelvectx = repo['tip']
737
743
738 if not shelvectx in tmpwctx.children():
744 if not shelvectx in tmpwctx.children():
739 # rebase was a no-op, so it produced no child commit
745 # rebase was a no-op, so it produced no child commit
740 shelvectx = tmpwctx
746 shelvectx = tmpwctx
741
747
742 mergefiles(ui, repo, pctx, shelvectx)
748 mergefiles(ui, repo, pctx, shelvectx)
743 shelvedstate.clear(repo)
749 shelvedstate.clear(repo)
744
750
745 # The transaction aborting will strip all the commits for us,
751 # The transaction aborting will strip all the commits for us,
746 # but it doesn't update the inmemory structures, so addchangegroup
752 # but it doesn't update the inmemory structures, so addchangegroup
747 # hooks still fire and try to operate on the missing commits.
753 # hooks still fire and try to operate on the missing commits.
748 # Clean up manually to prevent this.
754 # Clean up manually to prevent this.
749 repo.unfiltered().changelog.strip(oldtiprev, tr)
755 repo.unfiltered().changelog.strip(oldtiprev, tr)
750
756
751 unshelvecleanup(ui, repo, basename, opts)
757 unshelvecleanup(ui, repo, basename, opts)
752
758
753 _aborttransaction(repo)
759 _aborttransaction(repo)
754 finally:
760 finally:
755 ui.quiet = oldquiet
761 ui.quiet = oldquiet
756 if tr:
762 if tr:
757 tr.release()
763 tr.release()
758 lockmod.release(lock, wlock)
764 lockmod.release(lock)
759 ui.restoreconfig(forcemerge)
765 ui.restoreconfig(forcemerge)
760
766
761 @command('shelve',
767 @command('shelve',
762 [('A', 'addremove', None,
768 [('A', 'addremove', None,
763 _('mark new/missing files as added/removed before shelving')),
769 _('mark new/missing files as added/removed before shelving')),
764 ('', 'cleanup', None,
770 ('', 'cleanup', None,
765 _('delete all shelved changes')),
771 _('delete all shelved changes')),
766 ('', 'date', '',
772 ('', 'date', '',
767 _('shelve with the specified commit date'), _('DATE')),
773 _('shelve with the specified commit date'), _('DATE')),
768 ('d', 'delete', None,
774 ('d', 'delete', None,
769 _('delete the named shelved change(s)')),
775 _('delete the named shelved change(s)')),
770 ('e', 'edit', False,
776 ('e', 'edit', False,
771 _('invoke editor on commit messages')),
777 _('invoke editor on commit messages')),
772 ('l', 'list', None,
778 ('l', 'list', None,
773 _('list current shelves')),
779 _('list current shelves')),
774 ('m', 'message', '',
780 ('m', 'message', '',
775 _('use text as shelve message'), _('TEXT')),
781 _('use text as shelve message'), _('TEXT')),
776 ('n', 'name', '',
782 ('n', 'name', '',
777 _('use the given name for the shelved commit'), _('NAME')),
783 _('use the given name for the shelved commit'), _('NAME')),
778 ('p', 'patch', None,
784 ('p', 'patch', None,
779 _('show patch')),
785 _('show patch')),
780 ('i', 'interactive', None,
786 ('i', 'interactive', None,
781 _('interactive mode, only works while creating a shelve')),
787 _('interactive mode, only works while creating a shelve')),
782 ('', 'stat', None,
788 ('', 'stat', None,
783 _('output diffstat-style summary of changes'))] + commands.walkopts,
789 _('output diffstat-style summary of changes'))] + commands.walkopts,
784 _('hg shelve [OPTION]... [FILE]...'))
790 _('hg shelve [OPTION]... [FILE]...'))
785 def shelvecmd(ui, repo, *pats, **opts):
791 def shelvecmd(ui, repo, *pats, **opts):
786 '''save and set aside changes from the working directory
792 '''save and set aside changes from the working directory
787
793
788 Shelving takes files that "hg status" reports as not clean, saves
794 Shelving takes files that "hg status" reports as not clean, saves
789 the modifications to a bundle (a shelved change), and reverts the
795 the modifications to a bundle (a shelved change), and reverts the
790 files so that their state in the working directory becomes clean.
796 files so that their state in the working directory becomes clean.
791
797
792 To restore these changes to the working directory, using "hg
798 To restore these changes to the working directory, using "hg
793 unshelve"; this will work even if you switch to a different
799 unshelve"; this will work even if you switch to a different
794 commit.
800 commit.
795
801
796 When no files are specified, "hg shelve" saves all not-clean
802 When no files are specified, "hg shelve" saves all not-clean
797 files. If specific files or directories are named, only changes to
803 files. If specific files or directories are named, only changes to
798 those files are shelved.
804 those files are shelved.
799
805
800 Each shelved change has a name that makes it easier to find later.
806 Each shelved change has a name that makes it easier to find later.
801 The name of a shelved change defaults to being based on the active
807 The name of a shelved change defaults to being based on the active
802 bookmark, or if there is no active bookmark, the current named
808 bookmark, or if there is no active bookmark, the current named
803 branch. To specify a different name, use ``--name``.
809 branch. To specify a different name, use ``--name``.
804
810
805 To see a list of existing shelved changes, use the ``--list``
811 To see a list of existing shelved changes, use the ``--list``
806 option. For each shelved change, this will print its name, age,
812 option. For each shelved change, this will print its name, age,
807 and description; use ``--patch`` or ``--stat`` for more details.
813 and description; use ``--patch`` or ``--stat`` for more details.
808
814
809 To delete specific shelved changes, use ``--delete``. To delete
815 To delete specific shelved changes, use ``--delete``. To delete
810 all shelved changes, use ``--cleanup``.
816 all shelved changes, use ``--cleanup``.
811 '''
817 '''
812 allowables = [
818 allowables = [
813 ('addremove', set(['create'])), # 'create' is pseudo action
819 ('addremove', set(['create'])), # 'create' is pseudo action
814 ('cleanup', set(['cleanup'])),
820 ('cleanup', set(['cleanup'])),
815 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
821 # ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
816 ('delete', set(['delete'])),
822 ('delete', set(['delete'])),
817 ('edit', set(['create'])),
823 ('edit', set(['create'])),
818 ('list', set(['list'])),
824 ('list', set(['list'])),
819 ('message', set(['create'])),
825 ('message', set(['create'])),
820 ('name', set(['create'])),
826 ('name', set(['create'])),
821 ('patch', set(['patch', 'list'])),
827 ('patch', set(['patch', 'list'])),
822 ('stat', set(['stat', 'list'])),
828 ('stat', set(['stat', 'list'])),
823 ]
829 ]
824 def checkopt(opt):
830 def checkopt(opt):
825 if opts[opt]:
831 if opts[opt]:
826 for i, allowable in allowables:
832 for i, allowable in allowables:
827 if opts[i] and opt not in allowable:
833 if opts[i] and opt not in allowable:
828 raise error.Abort(_("options '--%s' and '--%s' may not be "
834 raise error.Abort(_("options '--%s' and '--%s' may not be "
829 "used together") % (opt, i))
835 "used together") % (opt, i))
830 return True
836 return True
831 if checkopt('cleanup'):
837 if checkopt('cleanup'):
832 if pats:
838 if pats:
833 raise error.Abort(_("cannot specify names when using '--cleanup'"))
839 raise error.Abort(_("cannot specify names when using '--cleanup'"))
834 return cleanupcmd(ui, repo)
840 return cleanupcmd(ui, repo)
835 elif checkopt('delete'):
841 elif checkopt('delete'):
836 return deletecmd(ui, repo, pats)
842 return deletecmd(ui, repo, pats)
837 elif checkopt('list'):
843 elif checkopt('list'):
838 return listcmd(ui, repo, pats, opts)
844 return listcmd(ui, repo, pats, opts)
839 elif checkopt('patch'):
845 elif checkopt('patch'):
840 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
846 return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
841 elif checkopt('stat'):
847 elif checkopt('stat'):
842 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
848 return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
843 else:
849 else:
844 return createcmd(ui, repo, pats, opts)
850 return createcmd(ui, repo, pats, opts)
845
851
846 def extsetup(ui):
852 def extsetup(ui):
847 cmdutil.unfinishedstates.append(
853 cmdutil.unfinishedstates.append(
848 [shelvedstate._filename, False, False,
854 [shelvedstate._filename, False, False,
849 _('unshelve already in progress'),
855 _('unshelve already in progress'),
850 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
856 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
General Comments 0
You need to be logged in to leave comments. Login now