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