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