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