##// END OF EJS Templates
shelve: mention walk options in help
Mads Kiilerich -
r20409:0b7a9940 default
parent child Browse files
Show More
@@ -1,691 +1,691 b''
1 # shelve.py - save/restore working directory state
1 # shelve.py - save/restore working directory state
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """save and restore changes to the working directory
8 """save and restore changes to the working directory
9
9
10 The "hg shelve" command saves changes made to the working directory
10 The "hg shelve" command saves changes made to the working directory
11 and reverts those changes, resetting the working directory to a clean
11 and reverts those changes, resetting the working directory to a clean
12 state.
12 state.
13
13
14 Later on, the "hg unshelve" command restores the changes saved by "hg
14 Later on, the "hg unshelve" command restores the changes saved by "hg
15 shelve". Changes can be restored even after updating to a different
15 shelve". Changes can be restored even after updating to a different
16 parent, in which case Mercurial's merge machinery will resolve any
16 parent, in which case Mercurial's merge machinery will resolve any
17 conflicts if necessary.
17 conflicts if necessary.
18
18
19 You can have more than one shelved change outstanding at a time; each
19 You can have more than one shelved change outstanding at a time; each
20 shelved change has a distinct name. For details, see the help for "hg
20 shelved change has a distinct name. For details, see the help for "hg
21 shelve".
21 shelve".
22 """
22 """
23
23
24 from mercurial.i18n import _
24 from mercurial.i18n import _
25 from mercurial.node import nullid, nullrev, bin, hex
25 from mercurial.node import nullid, nullrev, bin, hex
26 from mercurial import changegroup, cmdutil, scmutil, phases
26 from mercurial import changegroup, cmdutil, scmutil, phases, commands
27 from mercurial import error, hg, mdiff, merge, patch, repair, util
27 from mercurial import error, hg, mdiff, merge, patch, repair, util
28 from mercurial import templatefilters
28 from mercurial import templatefilters
29 from mercurial import lock as lockmod
29 from mercurial import lock as lockmod
30 from hgext import rebase
30 from hgext import rebase
31 import errno
31 import errno
32
32
33 cmdtable = {}
33 cmdtable = {}
34 command = cmdutil.command(cmdtable)
34 command = cmdutil.command(cmdtable)
35 testedwith = 'internal'
35 testedwith = 'internal'
36
36
37 class shelvedfile(object):
37 class shelvedfile(object):
38 """Helper for the file storing a single shelve
38 """Helper for the file storing a single shelve
39
39
40 Handles common functions on shelve files (.hg/.files/.patch) using
40 Handles common functions on shelve files (.hg/.files/.patch) using
41 the vfs layer"""
41 the vfs layer"""
42 def __init__(self, repo, name, filetype=None):
42 def __init__(self, repo, name, filetype=None):
43 self.repo = repo
43 self.repo = repo
44 self.name = name
44 self.name = name
45 self.vfs = scmutil.vfs(repo.join('shelved'))
45 self.vfs = scmutil.vfs(repo.join('shelved'))
46 if filetype:
46 if filetype:
47 self.fname = name + '.' + filetype
47 self.fname = name + '.' + filetype
48 else:
48 else:
49 self.fname = name
49 self.fname = name
50
50
51 def exists(self):
51 def exists(self):
52 return self.vfs.exists(self.fname)
52 return self.vfs.exists(self.fname)
53
53
54 def filename(self):
54 def filename(self):
55 return self.vfs.join(self.fname)
55 return self.vfs.join(self.fname)
56
56
57 def unlink(self):
57 def unlink(self):
58 util.unlink(self.filename())
58 util.unlink(self.filename())
59
59
60 def stat(self):
60 def stat(self):
61 return self.vfs.stat(self.fname)
61 return self.vfs.stat(self.fname)
62
62
63 def opener(self, mode='rb'):
63 def opener(self, mode='rb'):
64 try:
64 try:
65 return self.vfs(self.fname, mode)
65 return self.vfs(self.fname, mode)
66 except IOError, err:
66 except IOError, err:
67 if err.errno != errno.ENOENT:
67 if err.errno != errno.ENOENT:
68 raise
68 raise
69 raise util.Abort(_("shelved change '%s' not found") % self.name)
69 raise util.Abort(_("shelved change '%s' not found") % self.name)
70
70
71 class shelvedstate(object):
71 class shelvedstate(object):
72 """Handle persistence during unshelving operations.
72 """Handle persistence during unshelving operations.
73
73
74 Handles saving and restoring a shelved state. Ensures that different
74 Handles saving and restoring a shelved state. Ensures that different
75 versions of a shelved state are possible and handles them appropriately.
75 versions of a shelved state are possible and handles them appropriately.
76 """
76 """
77 _version = 1
77 _version = 1
78 _filename = 'shelvedstate'
78 _filename = 'shelvedstate'
79
79
80 @classmethod
80 @classmethod
81 def load(cls, repo):
81 def load(cls, repo):
82 fp = repo.opener(cls._filename)
82 fp = repo.opener(cls._filename)
83 try:
83 try:
84 version = int(fp.readline().strip())
84 version = int(fp.readline().strip())
85
85
86 if version != cls._version:
86 if version != cls._version:
87 raise util.Abort(_('this version of shelve is incompatible '
87 raise util.Abort(_('this version of shelve is incompatible '
88 'with the version used in this repo'))
88 'with the version used in this repo'))
89 name = fp.readline().strip()
89 name = fp.readline().strip()
90 wctx = fp.readline().strip()
90 wctx = fp.readline().strip()
91 pendingctx = fp.readline().strip()
91 pendingctx = fp.readline().strip()
92 parents = [bin(h) for h in fp.readline().split()]
92 parents = [bin(h) for h in fp.readline().split()]
93 stripnodes = [bin(h) for h in fp.readline().split()]
93 stripnodes = [bin(h) for h in fp.readline().split()]
94 finally:
94 finally:
95 fp.close()
95 fp.close()
96
96
97 obj = cls()
97 obj = cls()
98 obj.name = name
98 obj.name = name
99 obj.wctx = repo[bin(wctx)]
99 obj.wctx = repo[bin(wctx)]
100 obj.pendingctx = repo[bin(pendingctx)]
100 obj.pendingctx = repo[bin(pendingctx)]
101 obj.parents = parents
101 obj.parents = parents
102 obj.stripnodes = stripnodes
102 obj.stripnodes = stripnodes
103
103
104 return obj
104 return obj
105
105
106 @classmethod
106 @classmethod
107 def save(cls, repo, name, originalwctx, pendingctx, stripnodes):
107 def save(cls, repo, name, originalwctx, pendingctx, stripnodes):
108 fp = repo.opener(cls._filename, 'wb')
108 fp = repo.opener(cls._filename, 'wb')
109 fp.write('%i\n' % cls._version)
109 fp.write('%i\n' % cls._version)
110 fp.write('%s\n' % name)
110 fp.write('%s\n' % name)
111 fp.write('%s\n' % hex(originalwctx.node()))
111 fp.write('%s\n' % hex(originalwctx.node()))
112 fp.write('%s\n' % hex(pendingctx.node()))
112 fp.write('%s\n' % hex(pendingctx.node()))
113 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()]))
113 fp.write('%s\n' % ' '.join([hex(p) for p in repo.dirstate.parents()]))
114 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes]))
114 fp.write('%s\n' % ' '.join([hex(n) for n in stripnodes]))
115 fp.close()
115 fp.close()
116
116
117 @classmethod
117 @classmethod
118 def clear(cls, repo):
118 def clear(cls, repo):
119 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
119 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
120
120
121 def createcmd(ui, repo, pats, opts):
121 def createcmd(ui, repo, pats, opts):
122 """subcommand that creates a new shelve"""
122 """subcommand that creates a new shelve"""
123
123
124 def publicancestors(ctx):
124 def publicancestors(ctx):
125 """Compute the public ancestors of a commit.
125 """Compute the public ancestors of a commit.
126
126
127 Much faster than the revset ancestors(ctx) & draft()"""
127 Much faster than the revset ancestors(ctx) & draft()"""
128 seen = set([nullrev])
128 seen = set([nullrev])
129 visit = util.deque()
129 visit = util.deque()
130 visit.append(ctx)
130 visit.append(ctx)
131 while visit:
131 while visit:
132 ctx = visit.popleft()
132 ctx = visit.popleft()
133 yield ctx.node()
133 yield ctx.node()
134 for parent in ctx.parents():
134 for parent in ctx.parents():
135 rev = parent.rev()
135 rev = parent.rev()
136 if rev not in seen:
136 if rev not in seen:
137 seen.add(rev)
137 seen.add(rev)
138 if parent.mutable():
138 if parent.mutable():
139 visit.append(parent)
139 visit.append(parent)
140
140
141 wctx = repo[None]
141 wctx = repo[None]
142 parents = wctx.parents()
142 parents = wctx.parents()
143 if len(parents) > 1:
143 if len(parents) > 1:
144 raise util.Abort(_('cannot shelve while merging'))
144 raise util.Abort(_('cannot shelve while merging'))
145 parent = parents[0]
145 parent = parents[0]
146
146
147 # we never need the user, so we use a generic user for all shelve operations
147 # we never need the user, so we use a generic user for all shelve operations
148 user = 'shelve@localhost'
148 user = 'shelve@localhost'
149 label = repo._bookmarkcurrent or parent.branch() or 'default'
149 label = repo._bookmarkcurrent or parent.branch() or 'default'
150
150
151 # slashes aren't allowed in filenames, therefore we rename it
151 # slashes aren't allowed in filenames, therefore we rename it
152 origlabel, label = label, label.replace('/', '_')
152 origlabel, label = label, label.replace('/', '_')
153
153
154 def gennames():
154 def gennames():
155 yield label
155 yield label
156 for i in xrange(1, 100):
156 for i in xrange(1, 100):
157 yield '%s-%02d' % (label, i)
157 yield '%s-%02d' % (label, i)
158
158
159 shelvedfiles = []
159 shelvedfiles = []
160
160
161 def commitfunc(ui, repo, message, match, opts):
161 def commitfunc(ui, repo, message, match, opts):
162 # check modified, added, removed, deleted only
162 # check modified, added, removed, deleted only
163 for flist in repo.status(match=match)[:4]:
163 for flist in repo.status(match=match)[:4]:
164 shelvedfiles.extend(flist)
164 shelvedfiles.extend(flist)
165 hasmq = util.safehasattr(repo, 'mq')
165 hasmq = util.safehasattr(repo, 'mq')
166 if hasmq:
166 if hasmq:
167 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
167 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
168 try:
168 try:
169 return repo.commit(message, user, opts.get('date'), match)
169 return repo.commit(message, user, opts.get('date'), match)
170 finally:
170 finally:
171 if hasmq:
171 if hasmq:
172 repo.mq.checkapplied = saved
172 repo.mq.checkapplied = saved
173
173
174 if parent.node() != nullid:
174 if parent.node() != nullid:
175 desc = parent.description().split('\n', 1)[0]
175 desc = parent.description().split('\n', 1)[0]
176 else:
176 else:
177 desc = '(empty repository)'
177 desc = '(empty repository)'
178
178
179 if not opts['message']:
179 if not opts['message']:
180 opts['message'] = desc
180 opts['message'] = desc
181
181
182 name = opts['name']
182 name = opts['name']
183
183
184 wlock = lock = tr = bms = None
184 wlock = lock = tr = bms = None
185 try:
185 try:
186 wlock = repo.wlock()
186 wlock = repo.wlock()
187 lock = repo.lock()
187 lock = repo.lock()
188
188
189 bms = repo._bookmarks.copy()
189 bms = repo._bookmarks.copy()
190 # use an uncommitted transaction to generate the bundle to avoid
190 # use an uncommitted transaction to generate the bundle to avoid
191 # pull races. ensure we don't print the abort message to stderr.
191 # pull races. ensure we don't print the abort message to stderr.
192 tr = repo.transaction('commit', report=lambda x: None)
192 tr = repo.transaction('commit', report=lambda x: None)
193
193
194 if name:
194 if name:
195 if shelvedfile(repo, name, 'hg').exists():
195 if shelvedfile(repo, name, 'hg').exists():
196 raise util.Abort(_("a shelved change named '%s' already exists")
196 raise util.Abort(_("a shelved change named '%s' already exists")
197 % name)
197 % name)
198 else:
198 else:
199 for n in gennames():
199 for n in gennames():
200 if not shelvedfile(repo, n, 'hg').exists():
200 if not shelvedfile(repo, n, 'hg').exists():
201 name = n
201 name = n
202 break
202 break
203 else:
203 else:
204 raise util.Abort(_("too many shelved changes named '%s'") %
204 raise util.Abort(_("too many shelved changes named '%s'") %
205 label)
205 label)
206
206
207 # ensure we are not creating a subdirectory or a hidden file
207 # ensure we are not creating a subdirectory or a hidden file
208 if '/' in name or '\\' in name:
208 if '/' in name or '\\' in name:
209 raise util.Abort(_('shelved change names may not contain slashes'))
209 raise util.Abort(_('shelved change names may not contain slashes'))
210 if name.startswith('.'):
210 if name.startswith('.'):
211 raise util.Abort(_("shelved change names may not start with '.'"))
211 raise util.Abort(_("shelved change names may not start with '.'"))
212
212
213 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
213 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
214
214
215 if not node:
215 if not node:
216 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
216 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
217 if stat[3]:
217 if stat[3]:
218 ui.status(_("nothing changed (%d missing files, see "
218 ui.status(_("nothing changed (%d missing files, see "
219 "'hg status')\n") % len(stat[3]))
219 "'hg status')\n") % len(stat[3]))
220 else:
220 else:
221 ui.status(_("nothing changed\n"))
221 ui.status(_("nothing changed\n"))
222 return 1
222 return 1
223
223
224 phases.retractboundary(repo, phases.secret, [node])
224 phases.retractboundary(repo, phases.secret, [node])
225
225
226 fp = shelvedfile(repo, name, 'files').opener('wb')
226 fp = shelvedfile(repo, name, 'files').opener('wb')
227 fp.write('\0'.join(shelvedfiles))
227 fp.write('\0'.join(shelvedfiles))
228
228
229 bases = list(publicancestors(repo[node]))
229 bases = list(publicancestors(repo[node]))
230 cg = repo.changegroupsubset(bases, [node], 'shelve')
230 cg = repo.changegroupsubset(bases, [node], 'shelve')
231 changegroup.writebundle(cg, shelvedfile(repo, name, 'hg').filename(),
231 changegroup.writebundle(cg, shelvedfile(repo, name, 'hg').filename(),
232 'HG10UN')
232 'HG10UN')
233 cmdutil.export(repo, [node],
233 cmdutil.export(repo, [node],
234 fp=shelvedfile(repo, name, 'patch').opener('wb'),
234 fp=shelvedfile(repo, name, 'patch').opener('wb'),
235 opts=mdiff.diffopts(git=True))
235 opts=mdiff.diffopts(git=True))
236
236
237
237
238 if ui.formatted():
238 if ui.formatted():
239 desc = util.ellipsis(desc, ui.termwidth())
239 desc = util.ellipsis(desc, ui.termwidth())
240 ui.status(_('shelved as %s\n') % name)
240 ui.status(_('shelved as %s\n') % name)
241 hg.update(repo, parent.node())
241 hg.update(repo, parent.node())
242 finally:
242 finally:
243 if bms:
243 if bms:
244 # restore old bookmarks
244 # restore old bookmarks
245 repo._bookmarks.update(bms)
245 repo._bookmarks.update(bms)
246 repo._bookmarks.write()
246 repo._bookmarks.write()
247 if tr:
247 if tr:
248 tr.abort()
248 tr.abort()
249 lockmod.release(lock, wlock)
249 lockmod.release(lock, wlock)
250
250
251 def cleanupcmd(ui, repo):
251 def cleanupcmd(ui, repo):
252 """subcommand that deletes all shelves"""
252 """subcommand that deletes all shelves"""
253
253
254 wlock = None
254 wlock = None
255 try:
255 try:
256 wlock = repo.wlock()
256 wlock = repo.wlock()
257 for (name, _) in repo.vfs.readdir('shelved'):
257 for (name, _) in repo.vfs.readdir('shelved'):
258 suffix = name.rsplit('.', 1)[-1]
258 suffix = name.rsplit('.', 1)[-1]
259 if suffix in ('hg', 'files', 'patch'):
259 if suffix in ('hg', 'files', 'patch'):
260 shelvedfile(repo, name).unlink()
260 shelvedfile(repo, name).unlink()
261 finally:
261 finally:
262 lockmod.release(wlock)
262 lockmod.release(wlock)
263
263
264 def deletecmd(ui, repo, pats):
264 def deletecmd(ui, repo, pats):
265 """subcommand that deletes a specific shelve"""
265 """subcommand that deletes a specific shelve"""
266 if not pats:
266 if not pats:
267 raise util.Abort(_('no shelved changes specified!'))
267 raise util.Abort(_('no shelved changes specified!'))
268 wlock = None
268 wlock = None
269 try:
269 try:
270 wlock = repo.wlock()
270 wlock = repo.wlock()
271 try:
271 try:
272 for name in pats:
272 for name in pats:
273 for suffix in 'hg files patch'.split():
273 for suffix in 'hg files patch'.split():
274 shelvedfile(repo, name, suffix).unlink()
274 shelvedfile(repo, name, suffix).unlink()
275 except OSError, err:
275 except OSError, err:
276 if err.errno != errno.ENOENT:
276 if err.errno != errno.ENOENT:
277 raise
277 raise
278 raise util.Abort(_("shelved change '%s' not found") % name)
278 raise util.Abort(_("shelved change '%s' not found") % name)
279 finally:
279 finally:
280 lockmod.release(wlock)
280 lockmod.release(wlock)
281
281
282 def listshelves(repo):
282 def listshelves(repo):
283 """return all shelves in repo as list of (time, filename)"""
283 """return all shelves in repo as list of (time, filename)"""
284 try:
284 try:
285 names = repo.vfs.readdir('shelved')
285 names = repo.vfs.readdir('shelved')
286 except OSError, err:
286 except OSError, err:
287 if err.errno != errno.ENOENT:
287 if err.errno != errno.ENOENT:
288 raise
288 raise
289 return []
289 return []
290 info = []
290 info = []
291 for (name, _) in names:
291 for (name, _) in names:
292 pfx, sfx = name.rsplit('.', 1)
292 pfx, sfx = name.rsplit('.', 1)
293 if not pfx or sfx != 'patch':
293 if not pfx or sfx != 'patch':
294 continue
294 continue
295 st = shelvedfile(repo, name).stat()
295 st = shelvedfile(repo, name).stat()
296 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
296 info.append((st.st_mtime, shelvedfile(repo, pfx).filename()))
297 return sorted(info, reverse=True)
297 return sorted(info, reverse=True)
298
298
299 def listcmd(ui, repo, pats, opts):
299 def listcmd(ui, repo, pats, opts):
300 """subcommand that displays the list of shelves"""
300 """subcommand that displays the list of shelves"""
301 pats = set(pats)
301 pats = set(pats)
302 width = 80
302 width = 80
303 if not ui.plain():
303 if not ui.plain():
304 width = ui.termwidth()
304 width = ui.termwidth()
305 namelabel = 'shelve.newest'
305 namelabel = 'shelve.newest'
306 for mtime, name in listshelves(repo):
306 for mtime, name in listshelves(repo):
307 sname = util.split(name)[1]
307 sname = util.split(name)[1]
308 if pats and sname not in pats:
308 if pats and sname not in pats:
309 continue
309 continue
310 ui.write(sname, label=namelabel)
310 ui.write(sname, label=namelabel)
311 namelabel = 'shelve.name'
311 namelabel = 'shelve.name'
312 if ui.quiet:
312 if ui.quiet:
313 ui.write('\n')
313 ui.write('\n')
314 continue
314 continue
315 ui.write(' ' * (16 - len(sname)))
315 ui.write(' ' * (16 - len(sname)))
316 used = 16
316 used = 16
317 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
317 age = '(%s)' % templatefilters.age(util.makedate(mtime), abbrev=True)
318 ui.write(age, label='shelve.age')
318 ui.write(age, label='shelve.age')
319 ui.write(' ' * (12 - len(age)))
319 ui.write(' ' * (12 - len(age)))
320 used += 12
320 used += 12
321 fp = open(name + '.patch', 'rb')
321 fp = open(name + '.patch', 'rb')
322 try:
322 try:
323 while True:
323 while True:
324 line = fp.readline()
324 line = fp.readline()
325 if not line:
325 if not line:
326 break
326 break
327 if not line.startswith('#'):
327 if not line.startswith('#'):
328 desc = line.rstrip()
328 desc = line.rstrip()
329 if ui.formatted():
329 if ui.formatted():
330 desc = util.ellipsis(desc, width - used)
330 desc = util.ellipsis(desc, width - used)
331 ui.write(desc)
331 ui.write(desc)
332 break
332 break
333 ui.write('\n')
333 ui.write('\n')
334 if not (opts['patch'] or opts['stat']):
334 if not (opts['patch'] or opts['stat']):
335 continue
335 continue
336 difflines = fp.readlines()
336 difflines = fp.readlines()
337 if opts['patch']:
337 if opts['patch']:
338 for chunk, label in patch.difflabel(iter, difflines):
338 for chunk, label in patch.difflabel(iter, difflines):
339 ui.write(chunk, label=label)
339 ui.write(chunk, label=label)
340 if opts['stat']:
340 if opts['stat']:
341 for chunk, label in patch.diffstatui(difflines, width=width,
341 for chunk, label in patch.diffstatui(difflines, width=width,
342 git=True):
342 git=True):
343 ui.write(chunk, label=label)
343 ui.write(chunk, label=label)
344 finally:
344 finally:
345 fp.close()
345 fp.close()
346
346
347 def checkparents(repo, state):
347 def checkparents(repo, state):
348 """check parent while resuming an unshelve"""
348 """check parent while resuming an unshelve"""
349 if state.parents != repo.dirstate.parents():
349 if state.parents != repo.dirstate.parents():
350 raise util.Abort(_('working directory parents do not match unshelve '
350 raise util.Abort(_('working directory parents do not match unshelve '
351 'state'))
351 'state'))
352
352
353 def pathtofiles(repo, files):
353 def pathtofiles(repo, files):
354 cwd = repo.getcwd()
354 cwd = repo.getcwd()
355 return [repo.pathto(f, cwd) for f in files]
355 return [repo.pathto(f, cwd) for f in files]
356
356
357 def unshelveabort(ui, repo, state, opts):
357 def unshelveabort(ui, repo, state, opts):
358 """subcommand that abort an in-progress unshelve"""
358 """subcommand that abort an in-progress unshelve"""
359 wlock = repo.wlock()
359 wlock = repo.wlock()
360 lock = None
360 lock = None
361 try:
361 try:
362 checkparents(repo, state)
362 checkparents(repo, state)
363
363
364 util.rename(repo.join('unshelverebasestate'),
364 util.rename(repo.join('unshelverebasestate'),
365 repo.join('rebasestate'))
365 repo.join('rebasestate'))
366 try:
366 try:
367 rebase.rebase(ui, repo, **{
367 rebase.rebase(ui, repo, **{
368 'abort' : True
368 'abort' : True
369 })
369 })
370 except Exception:
370 except Exception:
371 util.rename(repo.join('rebasestate'),
371 util.rename(repo.join('rebasestate'),
372 repo.join('unshelverebasestate'))
372 repo.join('unshelverebasestate'))
373 raise
373 raise
374
374
375 lock = repo.lock()
375 lock = repo.lock()
376
376
377 mergefiles(ui, repo, state.wctx, state.pendingctx)
377 mergefiles(ui, repo, state.wctx, state.pendingctx)
378
378
379 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
379 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
380 shelvedstate.clear(repo)
380 shelvedstate.clear(repo)
381 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
381 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
382 finally:
382 finally:
383 lockmod.release(lock, wlock)
383 lockmod.release(lock, wlock)
384
384
385 def mergefiles(ui, repo, wctx, shelvectx):
385 def mergefiles(ui, repo, wctx, shelvectx):
386 """updates to wctx and merges the changes from shelvectx into the
386 """updates to wctx and merges the changes from shelvectx into the
387 dirstate."""
387 dirstate."""
388 oldquiet = ui.quiet
388 oldquiet = ui.quiet
389 try:
389 try:
390 ui.quiet = True
390 ui.quiet = True
391 hg.update(repo, wctx.node())
391 hg.update(repo, wctx.node())
392 files = []
392 files = []
393 files.extend(shelvectx.files())
393 files.extend(shelvectx.files())
394 files.extend(shelvectx.parents()[0].files())
394 files.extend(shelvectx.parents()[0].files())
395
395
396 # revert will overwrite unknown files, so move them out of the way
396 # revert will overwrite unknown files, so move them out of the way
397 m, a, r, d, u = repo.status(unknown=True)[:5]
397 m, a, r, d, u = repo.status(unknown=True)[:5]
398 for file in u:
398 for file in u:
399 if file in files:
399 if file in files:
400 util.rename(file, file + ".orig")
400 util.rename(file, file + ".orig")
401 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
401 cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
402 *pathtofiles(repo, files),
402 *pathtofiles(repo, files),
403 **{'no_backup': True})
403 **{'no_backup': True})
404 finally:
404 finally:
405 ui.quiet = oldquiet
405 ui.quiet = oldquiet
406
406
407 def unshelvecleanup(ui, repo, name, opts):
407 def unshelvecleanup(ui, repo, name, opts):
408 """remove related files after an unshelve"""
408 """remove related files after an unshelve"""
409 if not opts['keep']:
409 if not opts['keep']:
410 for filetype in 'hg files patch'.split():
410 for filetype in 'hg files patch'.split():
411 shelvedfile(repo, name, filetype).unlink()
411 shelvedfile(repo, name, filetype).unlink()
412
412
413 def unshelvecontinue(ui, repo, state, opts):
413 def unshelvecontinue(ui, repo, state, opts):
414 """subcommand to continue an in-progress unshelve"""
414 """subcommand to continue an in-progress unshelve"""
415 # We're finishing off a merge. First parent is our original
415 # We're finishing off a merge. First parent is our original
416 # parent, second is the temporary "fake" commit we're unshelving.
416 # parent, second is the temporary "fake" commit we're unshelving.
417 wlock = repo.wlock()
417 wlock = repo.wlock()
418 lock = None
418 lock = None
419 try:
419 try:
420 checkparents(repo, state)
420 checkparents(repo, state)
421 ms = merge.mergestate(repo)
421 ms = merge.mergestate(repo)
422 if [f for f in ms if ms[f] == 'u']:
422 if [f for f in ms if ms[f] == 'u']:
423 raise util.Abort(
423 raise util.Abort(
424 _("unresolved conflicts, can't continue"),
424 _("unresolved conflicts, can't continue"),
425 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
425 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
426
426
427 lock = repo.lock()
427 lock = repo.lock()
428
428
429 util.rename(repo.join('unshelverebasestate'),
429 util.rename(repo.join('unshelverebasestate'),
430 repo.join('rebasestate'))
430 repo.join('rebasestate'))
431 try:
431 try:
432 rebase.rebase(ui, repo, **{
432 rebase.rebase(ui, repo, **{
433 'continue' : True
433 'continue' : True
434 })
434 })
435 except Exception:
435 except Exception:
436 util.rename(repo.join('rebasestate'),
436 util.rename(repo.join('rebasestate'),
437 repo.join('unshelverebasestate'))
437 repo.join('unshelverebasestate'))
438 raise
438 raise
439
439
440 shelvectx = repo['tip']
440 shelvectx = repo['tip']
441 if not shelvectx in state.pendingctx.children():
441 if not shelvectx in state.pendingctx.children():
442 # rebase was a no-op, so it produced no child commit
442 # rebase was a no-op, so it produced no child commit
443 shelvectx = state.pendingctx
443 shelvectx = state.pendingctx
444
444
445 mergefiles(ui, repo, state.wctx, shelvectx)
445 mergefiles(ui, repo, state.wctx, shelvectx)
446
446
447 state.stripnodes.append(shelvectx.node())
447 state.stripnodes.append(shelvectx.node())
448 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
448 repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
449 shelvedstate.clear(repo)
449 shelvedstate.clear(repo)
450 unshelvecleanup(ui, repo, state.name, opts)
450 unshelvecleanup(ui, repo, state.name, opts)
451 ui.status(_("unshelve of '%s' complete\n") % state.name)
451 ui.status(_("unshelve of '%s' complete\n") % state.name)
452 finally:
452 finally:
453 lockmod.release(lock, wlock)
453 lockmod.release(lock, wlock)
454
454
455 @command('unshelve',
455 @command('unshelve',
456 [('a', 'abort', None,
456 [('a', 'abort', None,
457 _('abort an incomplete unshelve operation')),
457 _('abort an incomplete unshelve operation')),
458 ('c', 'continue', None,
458 ('c', 'continue', None,
459 _('continue an incomplete unshelve operation')),
459 _('continue an incomplete unshelve operation')),
460 ('', 'keep', None,
460 ('', 'keep', None,
461 _('keep shelve after unshelving'))],
461 _('keep shelve after unshelving'))],
462 _('hg unshelve [SHELVED]'))
462 _('hg unshelve [SHELVED]'))
463 def unshelve(ui, repo, *shelved, **opts):
463 def unshelve(ui, repo, *shelved, **opts):
464 """restore a shelved change to the working directory
464 """restore a shelved change to the working directory
465
465
466 This command accepts an optional name of a shelved change to
466 This command accepts an optional name of a shelved change to
467 restore. If none is given, the most recent shelved change is used.
467 restore. If none is given, the most recent shelved change is used.
468
468
469 If a shelved change is applied successfully, the bundle that
469 If a shelved change is applied successfully, the bundle that
470 contains the shelved changes is deleted afterwards.
470 contains the shelved changes is deleted afterwards.
471
471
472 Since you can restore a shelved change on top of an arbitrary
472 Since you can restore a shelved change on top of an arbitrary
473 commit, it is possible that unshelving will result in a conflict
473 commit, it is possible that unshelving will result in a conflict
474 between your changes and the commits you are unshelving onto. If
474 between your changes and the commits you are unshelving onto. If
475 this occurs, you must resolve the conflict, then use
475 this occurs, you must resolve the conflict, then use
476 ``--continue`` to complete the unshelve operation. (The bundle
476 ``--continue`` to complete the unshelve operation. (The bundle
477 will not be deleted until you successfully complete the unshelve.)
477 will not be deleted until you successfully complete the unshelve.)
478
478
479 (Alternatively, you can use ``--abort`` to abandon an unshelve
479 (Alternatively, you can use ``--abort`` to abandon an unshelve
480 that causes a conflict. This reverts the unshelved changes, and
480 that causes a conflict. This reverts the unshelved changes, and
481 does not delete the bundle.)
481 does not delete the bundle.)
482 """
482 """
483 abortf = opts['abort']
483 abortf = opts['abort']
484 continuef = opts['continue']
484 continuef = opts['continue']
485 if not abortf and not continuef:
485 if not abortf and not continuef:
486 cmdutil.checkunfinished(repo)
486 cmdutil.checkunfinished(repo)
487
487
488 if abortf or continuef:
488 if abortf or continuef:
489 if abortf and continuef:
489 if abortf and continuef:
490 raise util.Abort(_('cannot use both abort and continue'))
490 raise util.Abort(_('cannot use both abort and continue'))
491 if shelved:
491 if shelved:
492 raise util.Abort(_('cannot combine abort/continue with '
492 raise util.Abort(_('cannot combine abort/continue with '
493 'naming a shelved change'))
493 'naming a shelved change'))
494
494
495 try:
495 try:
496 state = shelvedstate.load(repo)
496 state = shelvedstate.load(repo)
497 except IOError, err:
497 except IOError, err:
498 if err.errno != errno.ENOENT:
498 if err.errno != errno.ENOENT:
499 raise
499 raise
500 raise util.Abort(_('no unshelve operation underway'))
500 raise util.Abort(_('no unshelve operation underway'))
501
501
502 if abortf:
502 if abortf:
503 return unshelveabort(ui, repo, state, opts)
503 return unshelveabort(ui, repo, state, opts)
504 elif continuef:
504 elif continuef:
505 return unshelvecontinue(ui, repo, state, opts)
505 return unshelvecontinue(ui, repo, state, opts)
506 elif len(shelved) > 1:
506 elif len(shelved) > 1:
507 raise util.Abort(_('can only unshelve one change at a time'))
507 raise util.Abort(_('can only unshelve one change at a time'))
508 elif not shelved:
508 elif not shelved:
509 shelved = listshelves(repo)
509 shelved = listshelves(repo)
510 if not shelved:
510 if not shelved:
511 raise util.Abort(_('no shelved changes to apply!'))
511 raise util.Abort(_('no shelved changes to apply!'))
512 basename = util.split(shelved[0][1])[1]
512 basename = util.split(shelved[0][1])[1]
513 ui.status(_("unshelving change '%s'\n") % basename)
513 ui.status(_("unshelving change '%s'\n") % basename)
514 else:
514 else:
515 basename = shelved[0]
515 basename = shelved[0]
516
516
517 if not shelvedfile(repo, basename, 'files').exists():
517 if not shelvedfile(repo, basename, 'files').exists():
518 raise util.Abort(_("shelved change '%s' not found") % basename)
518 raise util.Abort(_("shelved change '%s' not found") % basename)
519
519
520 wlock = lock = tr = None
520 wlock = lock = tr = None
521 try:
521 try:
522 lock = repo.lock()
522 lock = repo.lock()
523 wlock = repo.wlock()
523 wlock = repo.wlock()
524
524
525 tr = repo.transaction('unshelve', report=lambda x: None)
525 tr = repo.transaction('unshelve', report=lambda x: None)
526 oldtiprev = len(repo)
526 oldtiprev = len(repo)
527
527
528 wctx = repo['.']
528 wctx = repo['.']
529 tmpwctx = wctx
529 tmpwctx = wctx
530 # The goal is to have a commit structure like so:
530 # The goal is to have a commit structure like so:
531 # ...-> wctx -> tmpwctx -> shelvectx
531 # ...-> wctx -> tmpwctx -> shelvectx
532 # where tmpwctx is an optional commit with the user's pending changes
532 # where tmpwctx is an optional commit with the user's pending changes
533 # and shelvectx is the unshelved changes. Then we merge it all down
533 # and shelvectx is the unshelved changes. Then we merge it all down
534 # to the original wctx.
534 # to the original wctx.
535
535
536 # Store pending changes in a commit
536 # Store pending changes in a commit
537 m, a, r, d = repo.status()[:4]
537 m, a, r, d = repo.status()[:4]
538 if m or a or r or d:
538 if m or a or r or d:
539 def commitfunc(ui, repo, message, match, opts):
539 def commitfunc(ui, repo, message, match, opts):
540 hasmq = util.safehasattr(repo, 'mq')
540 hasmq = util.safehasattr(repo, 'mq')
541 if hasmq:
541 if hasmq:
542 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
542 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
543
543
544 try:
544 try:
545 return repo.commit(message, 'shelve@localhost',
545 return repo.commit(message, 'shelve@localhost',
546 opts.get('date'), match)
546 opts.get('date'), match)
547 finally:
547 finally:
548 if hasmq:
548 if hasmq:
549 repo.mq.checkapplied = saved
549 repo.mq.checkapplied = saved
550
550
551 tempopts = {}
551 tempopts = {}
552 tempopts['message'] = "pending changes temporary commit"
552 tempopts['message'] = "pending changes temporary commit"
553 oldquiet = ui.quiet
553 oldquiet = ui.quiet
554 try:
554 try:
555 ui.quiet = True
555 ui.quiet = True
556 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
556 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
557 finally:
557 finally:
558 ui.quiet = oldquiet
558 ui.quiet = oldquiet
559 tmpwctx = repo[node]
559 tmpwctx = repo[node]
560
560
561 try:
561 try:
562 fp = shelvedfile(repo, basename, 'hg').opener()
562 fp = shelvedfile(repo, basename, 'hg').opener()
563 gen = changegroup.readbundle(fp, fp.name)
563 gen = changegroup.readbundle(fp, fp.name)
564 repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name)
564 repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name)
565 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
565 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
566 phases.retractboundary(repo, phases.secret, nodes)
566 phases.retractboundary(repo, phases.secret, nodes)
567 finally:
567 finally:
568 fp.close()
568 fp.close()
569
569
570 shelvectx = repo['tip']
570 shelvectx = repo['tip']
571
571
572 # If the shelve is not immediately on top of the commit
572 # If the shelve is not immediately on top of the commit
573 # we'll be merging with, rebase it to be on top.
573 # we'll be merging with, rebase it to be on top.
574 if tmpwctx.node() != shelvectx.parents()[0].node():
574 if tmpwctx.node() != shelvectx.parents()[0].node():
575 try:
575 try:
576 rebase.rebase(ui, repo, **{
576 rebase.rebase(ui, repo, **{
577 'rev' : [shelvectx.rev()],
577 'rev' : [shelvectx.rev()],
578 'dest' : str(tmpwctx.rev()),
578 'dest' : str(tmpwctx.rev()),
579 'keep' : True,
579 'keep' : True,
580 })
580 })
581 except error.InterventionRequired:
581 except error.InterventionRequired:
582 tr.close()
582 tr.close()
583
583
584 stripnodes = [repo.changelog.node(rev)
584 stripnodes = [repo.changelog.node(rev)
585 for rev in xrange(oldtiprev, len(repo))]
585 for rev in xrange(oldtiprev, len(repo))]
586 shelvedstate.save(repo, basename, wctx, tmpwctx, stripnodes)
586 shelvedstate.save(repo, basename, wctx, tmpwctx, stripnodes)
587
587
588 util.rename(repo.join('rebasestate'),
588 util.rename(repo.join('rebasestate'),
589 repo.join('unshelverebasestate'))
589 repo.join('unshelverebasestate'))
590 raise error.InterventionRequired(
590 raise error.InterventionRequired(
591 _("unresolved conflicts (see 'hg resolve', then "
591 _("unresolved conflicts (see 'hg resolve', then "
592 "'hg unshelve --continue')"))
592 "'hg unshelve --continue')"))
593
593
594 # refresh ctx after rebase completes
594 # refresh ctx after rebase completes
595 shelvectx = repo['tip']
595 shelvectx = repo['tip']
596
596
597 if not shelvectx in tmpwctx.children():
597 if not shelvectx in tmpwctx.children():
598 # rebase was a no-op, so it produced no child commit
598 # rebase was a no-op, so it produced no child commit
599 shelvectx = tmpwctx
599 shelvectx = tmpwctx
600
600
601 mergefiles(ui, repo, wctx, shelvectx)
601 mergefiles(ui, repo, wctx, shelvectx)
602 shelvedstate.clear(repo)
602 shelvedstate.clear(repo)
603
603
604 # The transaction aborting will strip all the commits for us,
604 # The transaction aborting will strip all the commits for us,
605 # but it doesn't update the inmemory structures, so addchangegroup
605 # but it doesn't update the inmemory structures, so addchangegroup
606 # hooks still fire and try to operate on the missing commits.
606 # hooks still fire and try to operate on the missing commits.
607 # Clean up manually to prevent this.
607 # Clean up manually to prevent this.
608 repo.unfiltered().changelog.strip(oldtiprev, tr)
608 repo.unfiltered().changelog.strip(oldtiprev, tr)
609
609
610 unshelvecleanup(ui, repo, basename, opts)
610 unshelvecleanup(ui, repo, basename, opts)
611 finally:
611 finally:
612 if tr:
612 if tr:
613 tr.release()
613 tr.release()
614 lockmod.release(lock, wlock)
614 lockmod.release(lock, wlock)
615
615
616 @command('shelve',
616 @command('shelve',
617 [('A', 'addremove', None,
617 [('A', 'addremove', None,
618 _('mark new/missing files as added/removed before shelving')),
618 _('mark new/missing files as added/removed before shelving')),
619 ('', 'cleanup', None,
619 ('', 'cleanup', None,
620 _('delete all shelved changes')),
620 _('delete all shelved changes')),
621 ('', 'date', '',
621 ('', 'date', '',
622 _('shelve with the specified commit date'), _('DATE')),
622 _('shelve with the specified commit date'), _('DATE')),
623 ('d', 'delete', None,
623 ('d', 'delete', None,
624 _('delete the named shelved change(s)')),
624 _('delete the named shelved change(s)')),
625 ('l', 'list', None,
625 ('l', 'list', None,
626 _('list current shelves')),
626 _('list current shelves')),
627 ('m', 'message', '',
627 ('m', 'message', '',
628 _('use text as shelve message'), _('TEXT')),
628 _('use text as shelve message'), _('TEXT')),
629 ('n', 'name', '',
629 ('n', 'name', '',
630 _('use the given name for the shelved commit'), _('NAME')),
630 _('use the given name for the shelved commit'), _('NAME')),
631 ('p', 'patch', None,
631 ('p', 'patch', None,
632 _('show patch')),
632 _('show patch')),
633 ('', 'stat', None,
633 ('', 'stat', None,
634 _('output diffstat-style summary of changes'))],
634 _('output diffstat-style summary of changes'))] + commands.walkopts,
635 _('hg shelve'))
635 _('hg shelve [OPTION]...'))
636 def shelvecmd(ui, repo, *pats, **opts):
636 def shelvecmd(ui, repo, *pats, **opts):
637 '''save and set aside changes from the working directory
637 '''save and set aside changes from the working directory
638
638
639 Shelving takes files that "hg status" reports as not clean, saves
639 Shelving takes files that "hg status" reports as not clean, saves
640 the modifications to a bundle (a shelved change), and reverts the
640 the modifications to a bundle (a shelved change), and reverts the
641 files so that their state in the working directory becomes clean.
641 files so that their state in the working directory becomes clean.
642
642
643 To restore these changes to the working directory, using "hg
643 To restore these changes to the working directory, using "hg
644 unshelve"; this will work even if you switch to a different
644 unshelve"; this will work even if you switch to a different
645 commit.
645 commit.
646
646
647 When no files are specified, "hg shelve" saves all not-clean
647 When no files are specified, "hg shelve" saves all not-clean
648 files. If specific files or directories are named, only changes to
648 files. If specific files or directories are named, only changes to
649 those files are shelved.
649 those files are shelved.
650
650
651 Each shelved change has a name that makes it easier to find later.
651 Each shelved change has a name that makes it easier to find later.
652 The name of a shelved change defaults to being based on the active
652 The name of a shelved change defaults to being based on the active
653 bookmark, or if there is no active bookmark, the current named
653 bookmark, or if there is no active bookmark, the current named
654 branch. To specify a different name, use ``--name``.
654 branch. To specify a different name, use ``--name``.
655
655
656 To see a list of existing shelved changes, use the ``--list``
656 To see a list of existing shelved changes, use the ``--list``
657 option. For each shelved change, this will print its name, age,
657 option. For each shelved change, this will print its name, age,
658 and description; use ``--patch`` or ``--stat`` for more details.
658 and description; use ``--patch`` or ``--stat`` for more details.
659
659
660 To delete specific shelved changes, use ``--delete``. To delete
660 To delete specific shelved changes, use ``--delete``. To delete
661 all shelved changes, use ``--cleanup``.
661 all shelved changes, use ``--cleanup``.
662 '''
662 '''
663 cmdutil.checkunfinished(repo)
663 cmdutil.checkunfinished(repo)
664
664
665 def checkopt(opt, incompatible):
665 def checkopt(opt, incompatible):
666 if opts[opt]:
666 if opts[opt]:
667 for i in incompatible.split():
667 for i in incompatible.split():
668 if opts[i]:
668 if opts[i]:
669 raise util.Abort(_("options '--%s' and '--%s' may not be "
669 raise util.Abort(_("options '--%s' and '--%s' may not be "
670 "used together") % (opt, i))
670 "used together") % (opt, i))
671 return True
671 return True
672 if checkopt('cleanup', 'addremove delete list message name patch stat'):
672 if checkopt('cleanup', 'addremove delete list message name patch stat'):
673 if pats:
673 if pats:
674 raise util.Abort(_("cannot specify names when using '--cleanup'"))
674 raise util.Abort(_("cannot specify names when using '--cleanup'"))
675 return cleanupcmd(ui, repo)
675 return cleanupcmd(ui, repo)
676 elif checkopt('delete', 'addremove cleanup list message name patch stat'):
676 elif checkopt('delete', 'addremove cleanup list message name patch stat'):
677 return deletecmd(ui, repo, pats)
677 return deletecmd(ui, repo, pats)
678 elif checkopt('list', 'addremove cleanup delete message name'):
678 elif checkopt('list', 'addremove cleanup delete message name'):
679 return listcmd(ui, repo, pats, opts)
679 return listcmd(ui, repo, pats, opts)
680 else:
680 else:
681 for i in ('patch', 'stat'):
681 for i in ('patch', 'stat'):
682 if opts[i]:
682 if opts[i]:
683 raise util.Abort(_("option '--%s' may not be "
683 raise util.Abort(_("option '--%s' may not be "
684 "used when shelving a change") % (i,))
684 "used when shelving a change") % (i,))
685 return createcmd(ui, repo, pats, opts)
685 return createcmd(ui, repo, pats, opts)
686
686
687 def extsetup(ui):
687 def extsetup(ui):
688 cmdutil.unfinishedstates.append(
688 cmdutil.unfinishedstates.append(
689 [shelvedstate._filename, False, False,
689 [shelvedstate._filename, False, False,
690 _('unshelve already in progress'),
690 _('unshelve already in progress'),
691 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
691 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
General Comments 0
You need to be logged in to leave comments. Login now