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