##// END OF EJS Templates
shelve: repo['.'] is not a wctx but a pctx...
Mads Kiilerich -
r20958:df33c901 default
parent child Browse files
Show More
@@ -1,696 +1,696
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, commands
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, changegroup
28 from mercurial import templatefilters, changegroup
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 label = label.replace('/', '_')
152 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 = "changes to '%s'" % parent.description().split('\n', 1)[0]
175 desc = "changes to '%s'" % parent.description().split('\n', 1)[0]
176 else:
176 else:
177 desc = '(changes in empty repository)'
177 desc = '(changes in 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 = changegroup.changegroupsubset(repo, bases, [node], 'shelve')
230 cg = changegroup.changegroupsubset(repo, 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 oldquiet = ui.quiet
520 oldquiet = ui.quiet
521 wlock = lock = tr = None
521 wlock = lock = tr = None
522 try:
522 try:
523 lock = repo.lock()
523 lock = repo.lock()
524 wlock = repo.wlock()
524 wlock = repo.wlock()
525
525
526 tr = repo.transaction('unshelve', report=lambda x: None)
526 tr = repo.transaction('unshelve', report=lambda x: None)
527 oldtiprev = len(repo)
527 oldtiprev = len(repo)
528
528
529 wctx = repo['.']
529 pctx = repo['.']
530 tmpwctx = wctx
530 tmpwctx = pctx
531 # The goal is to have a commit structure like so:
531 # The goal is to have a commit structure like so:
532 # ...-> wctx -> tmpwctx -> shelvectx
532 # ...-> pctx -> tmpwctx -> shelvectx
533 # where tmpwctx is an optional commit with the user's pending changes
533 # where tmpwctx is an optional commit with the user's pending changes
534 # and shelvectx is the unshelved changes. Then we merge it all down
534 # and shelvectx is the unshelved changes. Then we merge it all down
535 # to the original wctx.
535 # to the original pctx.
536
536
537 # Store pending changes in a commit
537 # Store pending changes in a commit
538 m, a, r, d = repo.status()[:4]
538 m, a, r, d = repo.status()[:4]
539 if m or a or r or d:
539 if m or a or r or d:
540 ui.status(_("temporarily committing pending changes "
540 ui.status(_("temporarily committing pending changes "
541 "(restore with 'hg unshelve --abort')\n"))
541 "(restore with 'hg unshelve --abort')\n"))
542 def commitfunc(ui, repo, message, match, opts):
542 def commitfunc(ui, repo, message, match, opts):
543 hasmq = util.safehasattr(repo, 'mq')
543 hasmq = util.safehasattr(repo, 'mq')
544 if hasmq:
544 if hasmq:
545 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
545 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
546
546
547 try:
547 try:
548 return repo.commit(message, 'shelve@localhost',
548 return repo.commit(message, 'shelve@localhost',
549 opts.get('date'), match)
549 opts.get('date'), match)
550 finally:
550 finally:
551 if hasmq:
551 if hasmq:
552 repo.mq.checkapplied = saved
552 repo.mq.checkapplied = saved
553
553
554 tempopts = {}
554 tempopts = {}
555 tempopts['message'] = "pending changes temporary commit"
555 tempopts['message'] = "pending changes temporary commit"
556 ui.quiet = True
556 ui.quiet = True
557 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
557 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
558 tmpwctx = repo[node]
558 tmpwctx = repo[node]
559
559
560 try:
560 try:
561 ui.quiet = True
561 ui.quiet = True
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 changegroup.addchangegroup(repo, gen, 'unshelve',
564 changegroup.addchangegroup(repo, gen, 'unshelve',
565 'bundle:' + fp.name)
565 'bundle:' + fp.name)
566 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
566 nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
567 phases.retractboundary(repo, phases.secret, nodes)
567 phases.retractboundary(repo, phases.secret, nodes)
568 finally:
568 finally:
569 fp.close()
569 fp.close()
570
570
571 ui.quiet = oldquiet
571 ui.quiet = oldquiet
572
572
573 shelvectx = repo['tip']
573 shelvectx = repo['tip']
574
574
575 # If the shelve is not immediately on top of the commit
575 # If the shelve is not immediately on top of the commit
576 # we'll be merging with, rebase it to be on top.
576 # we'll be merging with, rebase it to be on top.
577 if tmpwctx.node() != shelvectx.parents()[0].node():
577 if tmpwctx.node() != shelvectx.parents()[0].node():
578 ui.status(_('rebasing shelved changes\n'))
578 ui.status(_('rebasing shelved changes\n'))
579 try:
579 try:
580 rebase.rebase(ui, repo, **{
580 rebase.rebase(ui, repo, **{
581 'rev' : [shelvectx.rev()],
581 'rev' : [shelvectx.rev()],
582 'dest' : str(tmpwctx.rev()),
582 'dest' : str(tmpwctx.rev()),
583 'keep' : True,
583 'keep' : True,
584 })
584 })
585 except error.InterventionRequired:
585 except error.InterventionRequired:
586 tr.close()
586 tr.close()
587
587
588 stripnodes = [repo.changelog.node(rev)
588 stripnodes = [repo.changelog.node(rev)
589 for rev in xrange(oldtiprev, len(repo))]
589 for rev in xrange(oldtiprev, len(repo))]
590 shelvedstate.save(repo, basename, wctx, tmpwctx, stripnodes)
590 shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes)
591
591
592 util.rename(repo.join('rebasestate'),
592 util.rename(repo.join('rebasestate'),
593 repo.join('unshelverebasestate'))
593 repo.join('unshelverebasestate'))
594 raise error.InterventionRequired(
594 raise error.InterventionRequired(
595 _("unresolved conflicts (see 'hg resolve', then "
595 _("unresolved conflicts (see 'hg resolve', then "
596 "'hg unshelve --continue')"))
596 "'hg unshelve --continue')"))
597
597
598 # refresh ctx after rebase completes
598 # refresh ctx after rebase completes
599 shelvectx = repo['tip']
599 shelvectx = repo['tip']
600
600
601 if not shelvectx in tmpwctx.children():
601 if not shelvectx in tmpwctx.children():
602 # rebase was a no-op, so it produced no child commit
602 # rebase was a no-op, so it produced no child commit
603 shelvectx = tmpwctx
603 shelvectx = tmpwctx
604
604
605 mergefiles(ui, repo, wctx, shelvectx)
605 mergefiles(ui, repo, pctx, shelvectx)
606 shelvedstate.clear(repo)
606 shelvedstate.clear(repo)
607
607
608 # The transaction aborting will strip all the commits for us,
608 # The transaction aborting will strip all the commits for us,
609 # but it doesn't update the inmemory structures, so addchangegroup
609 # but it doesn't update the inmemory structures, so addchangegroup
610 # hooks still fire and try to operate on the missing commits.
610 # hooks still fire and try to operate on the missing commits.
611 # Clean up manually to prevent this.
611 # Clean up manually to prevent this.
612 repo.unfiltered().changelog.strip(oldtiprev, tr)
612 repo.unfiltered().changelog.strip(oldtiprev, tr)
613
613
614 unshelvecleanup(ui, repo, basename, opts)
614 unshelvecleanup(ui, repo, basename, opts)
615 finally:
615 finally:
616 ui.quiet = oldquiet
616 ui.quiet = oldquiet
617 if tr:
617 if tr:
618 tr.release()
618 tr.release()
619 lockmod.release(lock, wlock)
619 lockmod.release(lock, wlock)
620
620
621 @command('shelve',
621 @command('shelve',
622 [('A', 'addremove', None,
622 [('A', 'addremove', None,
623 _('mark new/missing files as added/removed before shelving')),
623 _('mark new/missing files as added/removed before shelving')),
624 ('', 'cleanup', None,
624 ('', 'cleanup', None,
625 _('delete all shelved changes')),
625 _('delete all shelved changes')),
626 ('', 'date', '',
626 ('', 'date', '',
627 _('shelve with the specified commit date'), _('DATE')),
627 _('shelve with the specified commit date'), _('DATE')),
628 ('d', 'delete', None,
628 ('d', 'delete', None,
629 _('delete the named shelved change(s)')),
629 _('delete the named shelved change(s)')),
630 ('l', 'list', None,
630 ('l', 'list', None,
631 _('list current shelves')),
631 _('list current shelves')),
632 ('m', 'message', '',
632 ('m', 'message', '',
633 _('use text as shelve message'), _('TEXT')),
633 _('use text as shelve message'), _('TEXT')),
634 ('n', 'name', '',
634 ('n', 'name', '',
635 _('use the given name for the shelved commit'), _('NAME')),
635 _('use the given name for the shelved commit'), _('NAME')),
636 ('p', 'patch', None,
636 ('p', 'patch', None,
637 _('show patch')),
637 _('show patch')),
638 ('', 'stat', None,
638 ('', 'stat', None,
639 _('output diffstat-style summary of changes'))] + commands.walkopts,
639 _('output diffstat-style summary of changes'))] + commands.walkopts,
640 _('hg shelve [OPTION]... [FILE]...'))
640 _('hg shelve [OPTION]... [FILE]...'))
641 def shelvecmd(ui, repo, *pats, **opts):
641 def shelvecmd(ui, repo, *pats, **opts):
642 '''save and set aside changes from the working directory
642 '''save and set aside changes from the working directory
643
643
644 Shelving takes files that "hg status" reports as not clean, saves
644 Shelving takes files that "hg status" reports as not clean, saves
645 the modifications to a bundle (a shelved change), and reverts the
645 the modifications to a bundle (a shelved change), and reverts the
646 files so that their state in the working directory becomes clean.
646 files so that their state in the working directory becomes clean.
647
647
648 To restore these changes to the working directory, using "hg
648 To restore these changes to the working directory, using "hg
649 unshelve"; this will work even if you switch to a different
649 unshelve"; this will work even if you switch to a different
650 commit.
650 commit.
651
651
652 When no files are specified, "hg shelve" saves all not-clean
652 When no files are specified, "hg shelve" saves all not-clean
653 files. If specific files or directories are named, only changes to
653 files. If specific files or directories are named, only changes to
654 those files are shelved.
654 those files are shelved.
655
655
656 Each shelved change has a name that makes it easier to find later.
656 Each shelved change has a name that makes it easier to find later.
657 The name of a shelved change defaults to being based on the active
657 The name of a shelved change defaults to being based on the active
658 bookmark, or if there is no active bookmark, the current named
658 bookmark, or if there is no active bookmark, the current named
659 branch. To specify a different name, use ``--name``.
659 branch. To specify a different name, use ``--name``.
660
660
661 To see a list of existing shelved changes, use the ``--list``
661 To see a list of existing shelved changes, use the ``--list``
662 option. For each shelved change, this will print its name, age,
662 option. For each shelved change, this will print its name, age,
663 and description; use ``--patch`` or ``--stat`` for more details.
663 and description; use ``--patch`` or ``--stat`` for more details.
664
664
665 To delete specific shelved changes, use ``--delete``. To delete
665 To delete specific shelved changes, use ``--delete``. To delete
666 all shelved changes, use ``--cleanup``.
666 all shelved changes, use ``--cleanup``.
667 '''
667 '''
668 cmdutil.checkunfinished(repo)
668 cmdutil.checkunfinished(repo)
669
669
670 def checkopt(opt, incompatible):
670 def checkopt(opt, incompatible):
671 if opts[opt]:
671 if opts[opt]:
672 for i in incompatible.split():
672 for i in incompatible.split():
673 if opts[i]:
673 if opts[i]:
674 raise util.Abort(_("options '--%s' and '--%s' may not be "
674 raise util.Abort(_("options '--%s' and '--%s' may not be "
675 "used together") % (opt, i))
675 "used together") % (opt, i))
676 return True
676 return True
677 if checkopt('cleanup', 'addremove delete list message name patch stat'):
677 if checkopt('cleanup', 'addremove delete list message name patch stat'):
678 if pats:
678 if pats:
679 raise util.Abort(_("cannot specify names when using '--cleanup'"))
679 raise util.Abort(_("cannot specify names when using '--cleanup'"))
680 return cleanupcmd(ui, repo)
680 return cleanupcmd(ui, repo)
681 elif checkopt('delete', 'addremove cleanup list message name patch stat'):
681 elif checkopt('delete', 'addremove cleanup list message name patch stat'):
682 return deletecmd(ui, repo, pats)
682 return deletecmd(ui, repo, pats)
683 elif checkopt('list', 'addremove cleanup delete message name'):
683 elif checkopt('list', 'addremove cleanup delete message name'):
684 return listcmd(ui, repo, pats, opts)
684 return listcmd(ui, repo, pats, opts)
685 else:
685 else:
686 for i in ('patch', 'stat'):
686 for i in ('patch', 'stat'):
687 if opts[i]:
687 if opts[i]:
688 raise util.Abort(_("option '--%s' may not be "
688 raise util.Abort(_("option '--%s' may not be "
689 "used when shelving a change") % (i,))
689 "used when shelving a change") % (i,))
690 return createcmd(ui, repo, pats, opts)
690 return createcmd(ui, repo, pats, opts)
691
691
692 def extsetup(ui):
692 def extsetup(ui):
693 cmdutil.unfinishedstates.append(
693 cmdutil.unfinishedstates.append(
694 [shelvedstate._filename, False, False,
694 [shelvedstate._filename, False, False,
695 _('unshelve already in progress'),
695 _('unshelve already in progress'),
696 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
696 _("use 'hg unshelve --continue' or 'hg unshelve --abort'")])
General Comments 0
You need to be logged in to leave comments. Login now