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