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