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