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