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