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