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