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