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