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