##// END OF EJS Templates
shelve: move listshelves() to new ShelfDir class, so caller need not pass vfs...
Martin von Zweigbergk -
r47015:82edad33 default
parent child Browse files
Show More
@@ -1,1192 +1,1188 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 from __future__ import absolute_import
23 from __future__ import absolute_import
24
24
25 import collections
25 import collections
26 import errno
26 import errno
27 import itertools
27 import itertools
28 import stat
28 import stat
29
29
30 from .i18n import _
30 from .i18n import _
31 from .node import (
31 from .node import (
32 bin,
32 bin,
33 hex,
33 hex,
34 nullid,
34 nullid,
35 nullrev,
35 nullrev,
36 )
36 )
37 from . import (
37 from . import (
38 bookmarks,
38 bookmarks,
39 bundle2,
39 bundle2,
40 changegroup,
40 changegroup,
41 cmdutil,
41 cmdutil,
42 discovery,
42 discovery,
43 error,
43 error,
44 exchange,
44 exchange,
45 hg,
45 hg,
46 lock as lockmod,
46 lock as lockmod,
47 mdiff,
47 mdiff,
48 merge,
48 merge,
49 mergestate as mergestatemod,
49 mergestate as mergestatemod,
50 patch,
50 patch,
51 phases,
51 phases,
52 pycompat,
52 pycompat,
53 repair,
53 repair,
54 scmutil,
54 scmutil,
55 templatefilters,
55 templatefilters,
56 util,
56 util,
57 vfs as vfsmod,
57 vfs as vfsmod,
58 )
58 )
59 from .utils import (
59 from .utils import (
60 dateutil,
60 dateutil,
61 stringutil,
61 stringutil,
62 )
62 )
63
63
64 backupdir = b'shelve-backup'
64 backupdir = b'shelve-backup'
65 shelvedir = b'shelved'
65 shelvedir = b'shelved'
66 shelvefileextensions = [b'hg', b'patch', b'shelve']
66 shelvefileextensions = [b'hg', b'patch', b'shelve']
67
67
68 # we never need the user, so we use a
68 # we never need the user, so we use a
69 # generic user for all shelve operations
69 # generic user for all shelve operations
70 shelveuser = b'shelve@localhost'
70 shelveuser = b'shelve@localhost'
71
71
72
72
73 class ShelfDir(object):
73 class ShelfDir(object):
74 def __init__(self, repo, for_backups=False):
74 def __init__(self, repo, for_backups=False):
75 if for_backups:
75 if for_backups:
76 self.vfs = vfsmod.vfs(repo.vfs.join(backupdir))
76 self.vfs = vfsmod.vfs(repo.vfs.join(backupdir))
77 else:
77 else:
78 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
78 self.vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
79
79
80 def get(self, name):
80 def get(self, name):
81 return Shelf(self.vfs, name)
81 return Shelf(self.vfs, name)
82
82
83 def listshelves(self):
84 """return all shelves in repo as list of (time, name)"""
85 try:
86 names = self.vfs.listdir()
87 except OSError as err:
88 if err.errno != errno.ENOENT:
89 raise
90 return []
91 info = []
92 seen = set()
93 for filename in names:
94 name = filename.rsplit(b'.', 1)[0]
95 if name in seen:
96 continue
97 seen.add(name)
98 shelf = self.get(name)
99 if not shelf.exists():
100 continue
101 mtime = shelf.mtime()
102 info.append((mtime, name))
103 return sorted(info, reverse=True)
104
83
105
84 class Shelf(object):
106 class Shelf(object):
85 """Represents a shelf, including possibly multiple files storing it.
107 """Represents a shelf, including possibly multiple files storing it.
86
108
87 Old shelves will have a .patch and a .hg file. Newer shelves will
109 Old shelves will have a .patch and a .hg file. Newer shelves will
88 also have a .shelve file. This class abstracts away some of the
110 also have a .shelve file. This class abstracts away some of the
89 differences and lets you work with the shelf as a whole.
111 differences and lets you work with the shelf as a whole.
90 """
112 """
91
113
92 def __init__(self, vfs, name):
114 def __init__(self, vfs, name):
93 self.vfs = vfs
115 self.vfs = vfs
94 self.name = name
116 self.name = name
95
117
96 def exists(self):
118 def exists(self):
97 return self.vfs.exists(self.name + b'.patch') and self.vfs.exists(
119 return self.vfs.exists(self.name + b'.patch') and self.vfs.exists(
98 self.name + b'.hg'
120 self.name + b'.hg'
99 )
121 )
100
122
101 def mtime(self):
123 def mtime(self):
102 return self.vfs.stat(self.name + b'.patch')[stat.ST_MTIME]
124 return self.vfs.stat(self.name + b'.patch')[stat.ST_MTIME]
103
125
104 def writeinfo(self, info):
126 def writeinfo(self, info):
105 scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info)
127 scmutil.simplekeyvaluefile(self.vfs, self.name + b'.shelve').write(info)
106
128
107 def hasinfo(self):
129 def hasinfo(self):
108 return self.vfs.exists(self.name + b'.shelve')
130 return self.vfs.exists(self.name + b'.shelve')
109
131
110 def readinfo(self):
132 def readinfo(self):
111 return scmutil.simplekeyvaluefile(
133 return scmutil.simplekeyvaluefile(
112 self.vfs, self.name + b'.shelve'
134 self.vfs, self.name + b'.shelve'
113 ).read()
135 ).read()
114
136
115 def writebundle(self, repo, bases, node):
137 def writebundle(self, repo, bases, node):
116 cgversion = changegroup.safeversion(repo)
138 cgversion = changegroup.safeversion(repo)
117 if cgversion == b'01':
139 if cgversion == b'01':
118 btype = b'HG10BZ'
140 btype = b'HG10BZ'
119 compression = None
141 compression = None
120 else:
142 else:
121 btype = b'HG20'
143 btype = b'HG20'
122 compression = b'BZ'
144 compression = b'BZ'
123
145
124 repo = repo.unfiltered()
146 repo = repo.unfiltered()
125
147
126 outgoing = discovery.outgoing(
148 outgoing = discovery.outgoing(
127 repo, missingroots=bases, ancestorsof=[node]
149 repo, missingroots=bases, ancestorsof=[node]
128 )
150 )
129 cg = changegroup.makechangegroup(repo, outgoing, cgversion, b'shelve')
151 cg = changegroup.makechangegroup(repo, outgoing, cgversion, b'shelve')
130
152
131 bundle_filename = self.vfs.join(self.name + b'.hg')
153 bundle_filename = self.vfs.join(self.name + b'.hg')
132 bundle2.writebundle(
154 bundle2.writebundle(
133 repo.ui,
155 repo.ui,
134 cg,
156 cg,
135 bundle_filename,
157 bundle_filename,
136 btype,
158 btype,
137 self.vfs,
159 self.vfs,
138 compression=compression,
160 compression=compression,
139 )
161 )
140
162
141 def applybundle(self, repo, tr):
163 def applybundle(self, repo, tr):
142 filename = self.name + b'.hg'
164 filename = self.name + b'.hg'
143 fp = self.vfs(filename)
165 fp = self.vfs(filename)
144 try:
166 try:
145 targetphase = phases.internal
167 targetphase = phases.internal
146 if not phases.supportinternal(repo):
168 if not phases.supportinternal(repo):
147 targetphase = phases.secret
169 targetphase = phases.secret
148 gen = exchange.readbundle(repo.ui, fp, filename, self.vfs)
170 gen = exchange.readbundle(repo.ui, fp, filename, self.vfs)
149 pretip = repo[b'tip']
171 pretip = repo[b'tip']
150 bundle2.applybundle(
172 bundle2.applybundle(
151 repo,
173 repo,
152 gen,
174 gen,
153 tr,
175 tr,
154 source=b'unshelve',
176 source=b'unshelve',
155 url=b'bundle:' + self.vfs.join(filename),
177 url=b'bundle:' + self.vfs.join(filename),
156 targetphase=targetphase,
178 targetphase=targetphase,
157 )
179 )
158 shelvectx = repo[b'tip']
180 shelvectx = repo[b'tip']
159 if pretip == shelvectx:
181 if pretip == shelvectx:
160 shelverev = tr.changes[b'revduplicates'][-1]
182 shelverev = tr.changes[b'revduplicates'][-1]
161 shelvectx = repo[shelverev]
183 shelvectx = repo[shelverev]
162 return shelvectx
184 return shelvectx
163 finally:
185 finally:
164 fp.close()
186 fp.close()
165
187
166 def open_patch(self, mode=b'rb'):
188 def open_patch(self, mode=b'rb'):
167 return self.vfs(self.name + b'.patch', mode)
189 return self.vfs(self.name + b'.patch', mode)
168
190
169 def _backupfilename(self, backupvfs, filename):
191 def _backupfilename(self, backupvfs, filename):
170 def gennames(base):
192 def gennames(base):
171 yield base
193 yield base
172 base, ext = base.rsplit(b'.', 1)
194 base, ext = base.rsplit(b'.', 1)
173 for i in itertools.count(1):
195 for i in itertools.count(1):
174 yield b'%s-%d.%s' % (base, i, ext)
196 yield b'%s-%d.%s' % (base, i, ext)
175
197
176 for n in gennames(filename):
198 for n in gennames(filename):
177 if not backupvfs.exists(n):
199 if not backupvfs.exists(n):
178 return backupvfs.join(n)
200 return backupvfs.join(n)
179
201
180 def movetobackup(self, backupvfs):
202 def movetobackup(self, backupvfs):
181 if not backupvfs.isdir():
203 if not backupvfs.isdir():
182 backupvfs.makedir()
204 backupvfs.makedir()
183 for suffix in shelvefileextensions:
205 for suffix in shelvefileextensions:
184 filename = self.name + b'.' + suffix
206 filename = self.name + b'.' + suffix
185 if self.vfs.exists(filename):
207 if self.vfs.exists(filename):
186 util.rename(
208 util.rename(
187 self.vfs.join(filename),
209 self.vfs.join(filename),
188 self._backupfilename(backupvfs, filename),
210 self._backupfilename(backupvfs, filename),
189 )
211 )
190
212
191 def delete(self):
213 def delete(self):
192 for ext in shelvefileextensions:
214 for ext in shelvefileextensions:
193 self.vfs.tryunlink(self.name + b'.' + ext)
215 self.vfs.tryunlink(self.name + b'.' + ext)
194
216
195
217
196 class shelvedstate(object):
218 class shelvedstate(object):
197 """Handle persistence during unshelving operations.
219 """Handle persistence during unshelving operations.
198
220
199 Handles saving and restoring a shelved state. Ensures that different
221 Handles saving and restoring a shelved state. Ensures that different
200 versions of a shelved state are possible and handles them appropriately.
222 versions of a shelved state are possible and handles them appropriately.
201 """
223 """
202
224
203 _version = 2
225 _version = 2
204 _filename = b'shelvedstate'
226 _filename = b'shelvedstate'
205 _keep = b'keep'
227 _keep = b'keep'
206 _nokeep = b'nokeep'
228 _nokeep = b'nokeep'
207 # colon is essential to differentiate from a real bookmark name
229 # colon is essential to differentiate from a real bookmark name
208 _noactivebook = b':no-active-bookmark'
230 _noactivebook = b':no-active-bookmark'
209 _interactive = b'interactive'
231 _interactive = b'interactive'
210
232
211 @classmethod
233 @classmethod
212 def _verifyandtransform(cls, d):
234 def _verifyandtransform(cls, d):
213 """Some basic shelvestate syntactic verification and transformation"""
235 """Some basic shelvestate syntactic verification and transformation"""
214 try:
236 try:
215 d[b'originalwctx'] = bin(d[b'originalwctx'])
237 d[b'originalwctx'] = bin(d[b'originalwctx'])
216 d[b'pendingctx'] = bin(d[b'pendingctx'])
238 d[b'pendingctx'] = bin(d[b'pendingctx'])
217 d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ')]
239 d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ')]
218 d[b'nodestoremove'] = [
240 d[b'nodestoremove'] = [
219 bin(h) for h in d[b'nodestoremove'].split(b' ')
241 bin(h) for h in d[b'nodestoremove'].split(b' ')
220 ]
242 ]
221 except (ValueError, TypeError, KeyError) as err:
243 except (ValueError, TypeError, KeyError) as err:
222 raise error.CorruptedState(pycompat.bytestr(err))
244 raise error.CorruptedState(pycompat.bytestr(err))
223
245
224 @classmethod
246 @classmethod
225 def _getversion(cls, repo):
247 def _getversion(cls, repo):
226 """Read version information from shelvestate file"""
248 """Read version information from shelvestate file"""
227 fp = repo.vfs(cls._filename)
249 fp = repo.vfs(cls._filename)
228 try:
250 try:
229 version = int(fp.readline().strip())
251 version = int(fp.readline().strip())
230 except ValueError as err:
252 except ValueError as err:
231 raise error.CorruptedState(pycompat.bytestr(err))
253 raise error.CorruptedState(pycompat.bytestr(err))
232 finally:
254 finally:
233 fp.close()
255 fp.close()
234 return version
256 return version
235
257
236 @classmethod
258 @classmethod
237 def _readold(cls, repo):
259 def _readold(cls, repo):
238 """Read the old position-based version of a shelvestate file"""
260 """Read the old position-based version of a shelvestate file"""
239 # Order is important, because old shelvestate file uses it
261 # Order is important, because old shelvestate file uses it
240 # to detemine values of fields (i.g. name is on the second line,
262 # to detemine values of fields (i.g. name is on the second line,
241 # originalwctx is on the third and so forth). Please do not change.
263 # originalwctx is on the third and so forth). Please do not change.
242 keys = [
264 keys = [
243 b'version',
265 b'version',
244 b'name',
266 b'name',
245 b'originalwctx',
267 b'originalwctx',
246 b'pendingctx',
268 b'pendingctx',
247 b'parents',
269 b'parents',
248 b'nodestoremove',
270 b'nodestoremove',
249 b'branchtorestore',
271 b'branchtorestore',
250 b'keep',
272 b'keep',
251 b'activebook',
273 b'activebook',
252 ]
274 ]
253 # this is executed only seldomly, so it is not a big deal
275 # this is executed only seldomly, so it is not a big deal
254 # that we open this file twice
276 # that we open this file twice
255 fp = repo.vfs(cls._filename)
277 fp = repo.vfs(cls._filename)
256 d = {}
278 d = {}
257 try:
279 try:
258 for key in keys:
280 for key in keys:
259 d[key] = fp.readline().strip()
281 d[key] = fp.readline().strip()
260 finally:
282 finally:
261 fp.close()
283 fp.close()
262 return d
284 return d
263
285
264 @classmethod
286 @classmethod
265 def load(cls, repo):
287 def load(cls, repo):
266 version = cls._getversion(repo)
288 version = cls._getversion(repo)
267 if version < cls._version:
289 if version < cls._version:
268 d = cls._readold(repo)
290 d = cls._readold(repo)
269 elif version == cls._version:
291 elif version == cls._version:
270 d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename).read(
292 d = scmutil.simplekeyvaluefile(repo.vfs, cls._filename).read(
271 firstlinenonkeyval=True
293 firstlinenonkeyval=True
272 )
294 )
273 else:
295 else:
274 raise error.Abort(
296 raise error.Abort(
275 _(
297 _(
276 b'this version of shelve is incompatible '
298 b'this version of shelve is incompatible '
277 b'with the version used in this repo'
299 b'with the version used in this repo'
278 )
300 )
279 )
301 )
280
302
281 cls._verifyandtransform(d)
303 cls._verifyandtransform(d)
282 try:
304 try:
283 obj = cls()
305 obj = cls()
284 obj.name = d[b'name']
306 obj.name = d[b'name']
285 obj.wctx = repo[d[b'originalwctx']]
307 obj.wctx = repo[d[b'originalwctx']]
286 obj.pendingctx = repo[d[b'pendingctx']]
308 obj.pendingctx = repo[d[b'pendingctx']]
287 obj.parents = d[b'parents']
309 obj.parents = d[b'parents']
288 obj.nodestoremove = d[b'nodestoremove']
310 obj.nodestoremove = d[b'nodestoremove']
289 obj.branchtorestore = d.get(b'branchtorestore', b'')
311 obj.branchtorestore = d.get(b'branchtorestore', b'')
290 obj.keep = d.get(b'keep') == cls._keep
312 obj.keep = d.get(b'keep') == cls._keep
291 obj.activebookmark = b''
313 obj.activebookmark = b''
292 if d.get(b'activebook', b'') != cls._noactivebook:
314 if d.get(b'activebook', b'') != cls._noactivebook:
293 obj.activebookmark = d.get(b'activebook', b'')
315 obj.activebookmark = d.get(b'activebook', b'')
294 obj.interactive = d.get(b'interactive') == cls._interactive
316 obj.interactive = d.get(b'interactive') == cls._interactive
295 except (error.RepoLookupError, KeyError) as err:
317 except (error.RepoLookupError, KeyError) as err:
296 raise error.CorruptedState(pycompat.bytestr(err))
318 raise error.CorruptedState(pycompat.bytestr(err))
297
319
298 return obj
320 return obj
299
321
300 @classmethod
322 @classmethod
301 def save(
323 def save(
302 cls,
324 cls,
303 repo,
325 repo,
304 name,
326 name,
305 originalwctx,
327 originalwctx,
306 pendingctx,
328 pendingctx,
307 nodestoremove,
329 nodestoremove,
308 branchtorestore,
330 branchtorestore,
309 keep=False,
331 keep=False,
310 activebook=b'',
332 activebook=b'',
311 interactive=False,
333 interactive=False,
312 ):
334 ):
313 info = {
335 info = {
314 b"name": name,
336 b"name": name,
315 b"originalwctx": hex(originalwctx.node()),
337 b"originalwctx": hex(originalwctx.node()),
316 b"pendingctx": hex(pendingctx.node()),
338 b"pendingctx": hex(pendingctx.node()),
317 b"parents": b' '.join([hex(p) for p in repo.dirstate.parents()]),
339 b"parents": b' '.join([hex(p) for p in repo.dirstate.parents()]),
318 b"nodestoremove": b' '.join([hex(n) for n in nodestoremove]),
340 b"nodestoremove": b' '.join([hex(n) for n in nodestoremove]),
319 b"branchtorestore": branchtorestore,
341 b"branchtorestore": branchtorestore,
320 b"keep": cls._keep if keep else cls._nokeep,
342 b"keep": cls._keep if keep else cls._nokeep,
321 b"activebook": activebook or cls._noactivebook,
343 b"activebook": activebook or cls._noactivebook,
322 }
344 }
323 if interactive:
345 if interactive:
324 info[b'interactive'] = cls._interactive
346 info[b'interactive'] = cls._interactive
325 scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write(
347 scmutil.simplekeyvaluefile(repo.vfs, cls._filename).write(
326 info, firstline=(b"%d" % cls._version)
348 info, firstline=(b"%d" % cls._version)
327 )
349 )
328
350
329 @classmethod
351 @classmethod
330 def clear(cls, repo):
352 def clear(cls, repo):
331 repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
353 repo.vfs.unlinkpath(cls._filename, ignoremissing=True)
332
354
333
355
334 def cleanupoldbackups(repo):
356 def cleanupoldbackups(repo):
335 vfs = vfsmod.vfs(repo.vfs.join(backupdir))
336 maxbackups = repo.ui.configint(b'shelve', b'maxbackups')
357 maxbackups = repo.ui.configint(b'shelve', b'maxbackups')
337 hgfiles = listshelves(vfs)
358 backup_dir = ShelfDir(repo, for_backups=True)
359 hgfiles = backup_dir.listshelves()
338 if maxbackups > 0 and maxbackups < len(hgfiles):
360 if maxbackups > 0 and maxbackups < len(hgfiles):
339 bordermtime = hgfiles[maxbackups - 1][0]
361 bordermtime = hgfiles[maxbackups - 1][0]
340 else:
362 else:
341 bordermtime = None
363 bordermtime = None
342 for mtime, name in hgfiles[maxbackups:]:
364 for mtime, name in hgfiles[maxbackups:]:
343 if mtime == bordermtime:
365 if mtime == bordermtime:
344 # keep it, because timestamp can't decide exact order of backups
366 # keep it, because timestamp can't decide exact order of backups
345 continue
367 continue
346 Shelf.open_backup(repo, name).delete()
368 backup_dir.get(name).delete()
347
369
348
370
349 def _backupactivebookmark(repo):
371 def _backupactivebookmark(repo):
350 activebookmark = repo._activebookmark
372 activebookmark = repo._activebookmark
351 if activebookmark:
373 if activebookmark:
352 bookmarks.deactivate(repo)
374 bookmarks.deactivate(repo)
353 return activebookmark
375 return activebookmark
354
376
355
377
356 def _restoreactivebookmark(repo, mark):
378 def _restoreactivebookmark(repo, mark):
357 if mark:
379 if mark:
358 bookmarks.activate(repo, mark)
380 bookmarks.activate(repo, mark)
359
381
360
382
361 def _aborttransaction(repo, tr):
383 def _aborttransaction(repo, tr):
362 """Abort current transaction for shelve/unshelve, but keep dirstate"""
384 """Abort current transaction for shelve/unshelve, but keep dirstate"""
363 dirstatebackupname = b'dirstate.shelve'
385 dirstatebackupname = b'dirstate.shelve'
364 repo.dirstate.savebackup(tr, dirstatebackupname)
386 repo.dirstate.savebackup(tr, dirstatebackupname)
365 tr.abort()
387 tr.abort()
366 repo.dirstate.restorebackup(None, dirstatebackupname)
388 repo.dirstate.restorebackup(None, dirstatebackupname)
367
389
368
390
369 def getshelvename(repo, parent, opts):
391 def getshelvename(repo, parent, opts):
370 """Decide on the name this shelve is going to have"""
392 """Decide on the name this shelve is going to have"""
371
393
372 def gennames():
394 def gennames():
373 yield label
395 yield label
374 for i in itertools.count(1):
396 for i in itertools.count(1):
375 yield b'%s-%02d' % (label, i)
397 yield b'%s-%02d' % (label, i)
376
398
377 name = opts.get(b'name')
399 name = opts.get(b'name')
378 label = repo._activebookmark or parent.branch() or b'default'
400 label = repo._activebookmark or parent.branch() or b'default'
379 # slashes aren't allowed in filenames, therefore we rename it
401 # slashes aren't allowed in filenames, therefore we rename it
380 label = label.replace(b'/', b'_')
402 label = label.replace(b'/', b'_')
381 label = label.replace(b'\\', b'_')
403 label = label.replace(b'\\', b'_')
382 # filenames must not start with '.' as it should not be hidden
404 # filenames must not start with '.' as it should not be hidden
383 if label.startswith(b'.'):
405 if label.startswith(b'.'):
384 label = label.replace(b'.', b'_', 1)
406 label = label.replace(b'.', b'_', 1)
385
407
386 if name:
408 if name:
387 if ShelfDir(repo).get(name).exists():
409 if ShelfDir(repo).get(name).exists():
388 e = _(b"a shelved change named '%s' already exists") % name
410 e = _(b"a shelved change named '%s' already exists") % name
389 raise error.Abort(e)
411 raise error.Abort(e)
390
412
391 # ensure we are not creating a subdirectory or a hidden file
413 # ensure we are not creating a subdirectory or a hidden file
392 if b'/' in name or b'\\' in name:
414 if b'/' in name or b'\\' in name:
393 raise error.Abort(
415 raise error.Abort(
394 _(b'shelved change names can not contain slashes')
416 _(b'shelved change names can not contain slashes')
395 )
417 )
396 if name.startswith(b'.'):
418 if name.startswith(b'.'):
397 raise error.Abort(_(b"shelved change names can not start with '.'"))
419 raise error.Abort(_(b"shelved change names can not start with '.'"))
398
420
399 else:
421 else:
400 shelf_dir = ShelfDir(repo)
422 shelf_dir = ShelfDir(repo)
401 for n in gennames():
423 for n in gennames():
402 if not shelf_dir.get(n).exists():
424 if not shelf_dir.get(n).exists():
403 name = n
425 name = n
404 break
426 break
405
427
406 return name
428 return name
407
429
408
430
409 def mutableancestors(ctx):
431 def mutableancestors(ctx):
410 """return all mutable ancestors for ctx (included)
432 """return all mutable ancestors for ctx (included)
411
433
412 Much faster than the revset ancestors(ctx) & draft()"""
434 Much faster than the revset ancestors(ctx) & draft()"""
413 seen = {nullrev}
435 seen = {nullrev}
414 visit = collections.deque()
436 visit = collections.deque()
415 visit.append(ctx)
437 visit.append(ctx)
416 while visit:
438 while visit:
417 ctx = visit.popleft()
439 ctx = visit.popleft()
418 yield ctx.node()
440 yield ctx.node()
419 for parent in ctx.parents():
441 for parent in ctx.parents():
420 rev = parent.rev()
442 rev = parent.rev()
421 if rev not in seen:
443 if rev not in seen:
422 seen.add(rev)
444 seen.add(rev)
423 if parent.mutable():
445 if parent.mutable():
424 visit.append(parent)
446 visit.append(parent)
425
447
426
448
427 def getcommitfunc(extra, interactive, editor=False):
449 def getcommitfunc(extra, interactive, editor=False):
428 def commitfunc(ui, repo, message, match, opts):
450 def commitfunc(ui, repo, message, match, opts):
429 hasmq = util.safehasattr(repo, b'mq')
451 hasmq = util.safehasattr(repo, b'mq')
430 if hasmq:
452 if hasmq:
431 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
453 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
432
454
433 targetphase = phases.internal
455 targetphase = phases.internal
434 if not phases.supportinternal(repo):
456 if not phases.supportinternal(repo):
435 targetphase = phases.secret
457 targetphase = phases.secret
436 overrides = {(b'phases', b'new-commit'): targetphase}
458 overrides = {(b'phases', b'new-commit'): targetphase}
437 try:
459 try:
438 editor_ = False
460 editor_ = False
439 if editor:
461 if editor:
440 editor_ = cmdutil.getcommiteditor(
462 editor_ = cmdutil.getcommiteditor(
441 editform=b'shelve.shelve', **pycompat.strkwargs(opts)
463 editform=b'shelve.shelve', **pycompat.strkwargs(opts)
442 )
464 )
443 with repo.ui.configoverride(overrides):
465 with repo.ui.configoverride(overrides):
444 return repo.commit(
466 return repo.commit(
445 message,
467 message,
446 shelveuser,
468 shelveuser,
447 opts.get(b'date'),
469 opts.get(b'date'),
448 match,
470 match,
449 editor=editor_,
471 editor=editor_,
450 extra=extra,
472 extra=extra,
451 )
473 )
452 finally:
474 finally:
453 if hasmq:
475 if hasmq:
454 repo.mq.checkapplied = saved
476 repo.mq.checkapplied = saved
455
477
456 def interactivecommitfunc(ui, repo, *pats, **opts):
478 def interactivecommitfunc(ui, repo, *pats, **opts):
457 opts = pycompat.byteskwargs(opts)
479 opts = pycompat.byteskwargs(opts)
458 match = scmutil.match(repo[b'.'], pats, {})
480 match = scmutil.match(repo[b'.'], pats, {})
459 message = opts[b'message']
481 message = opts[b'message']
460 return commitfunc(ui, repo, message, match, opts)
482 return commitfunc(ui, repo, message, match, opts)
461
483
462 return interactivecommitfunc if interactive else commitfunc
484 return interactivecommitfunc if interactive else commitfunc
463
485
464
486
465 def _nothingtoshelvemessaging(ui, repo, pats, opts):
487 def _nothingtoshelvemessaging(ui, repo, pats, opts):
466 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
488 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
467 if stat.deleted:
489 if stat.deleted:
468 ui.status(
490 ui.status(
469 _(b"nothing changed (%d missing files, see 'hg status')\n")
491 _(b"nothing changed (%d missing files, see 'hg status')\n")
470 % len(stat.deleted)
492 % len(stat.deleted)
471 )
493 )
472 else:
494 else:
473 ui.status(_(b"nothing changed\n"))
495 ui.status(_(b"nothing changed\n"))
474
496
475
497
476 def _shelvecreatedcommit(repo, node, name, match):
498 def _shelvecreatedcommit(repo, node, name, match):
477 info = {b'node': hex(node)}
499 info = {b'node': hex(node)}
478 shelf = ShelfDir(repo).get(name)
500 shelf = ShelfDir(repo).get(name)
479 shelf.writeinfo(info)
501 shelf.writeinfo(info)
480 bases = list(mutableancestors(repo[node]))
502 bases = list(mutableancestors(repo[node]))
481 shelf.writebundle(repo, bases, node)
503 shelf.writebundle(repo, bases, node)
482 with shelf.open_patch(b'wb') as fp:
504 with shelf.open_patch(b'wb') as fp:
483 cmdutil.exportfile(
505 cmdutil.exportfile(
484 repo, [node], fp, opts=mdiff.diffopts(git=True), match=match
506 repo, [node], fp, opts=mdiff.diffopts(git=True), match=match
485 )
507 )
486
508
487
509
488 def _includeunknownfiles(repo, pats, opts, extra):
510 def _includeunknownfiles(repo, pats, opts, extra):
489 s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True)
511 s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True)
490 if s.unknown:
512 if s.unknown:
491 extra[b'shelve_unknown'] = b'\0'.join(s.unknown)
513 extra[b'shelve_unknown'] = b'\0'.join(s.unknown)
492 repo[None].add(s.unknown)
514 repo[None].add(s.unknown)
493
515
494
516
495 def _finishshelve(repo, tr):
517 def _finishshelve(repo, tr):
496 if phases.supportinternal(repo):
518 if phases.supportinternal(repo):
497 tr.close()
519 tr.close()
498 else:
520 else:
499 _aborttransaction(repo, tr)
521 _aborttransaction(repo, tr)
500
522
501
523
502 def createcmd(ui, repo, pats, opts):
524 def createcmd(ui, repo, pats, opts):
503 """subcommand that creates a new shelve"""
525 """subcommand that creates a new shelve"""
504 with repo.wlock():
526 with repo.wlock():
505 cmdutil.checkunfinished(repo)
527 cmdutil.checkunfinished(repo)
506 return _docreatecmd(ui, repo, pats, opts)
528 return _docreatecmd(ui, repo, pats, opts)
507
529
508
530
509 def _docreatecmd(ui, repo, pats, opts):
531 def _docreatecmd(ui, repo, pats, opts):
510 wctx = repo[None]
532 wctx = repo[None]
511 parents = wctx.parents()
533 parents = wctx.parents()
512 parent = parents[0]
534 parent = parents[0]
513 origbranch = wctx.branch()
535 origbranch = wctx.branch()
514
536
515 if parent.node() != nullid:
537 if parent.node() != nullid:
516 desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0]
538 desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0]
517 else:
539 else:
518 desc = b'(changes in empty repository)'
540 desc = b'(changes in empty repository)'
519
541
520 if not opts.get(b'message'):
542 if not opts.get(b'message'):
521 opts[b'message'] = desc
543 opts[b'message'] = desc
522
544
523 lock = tr = activebookmark = None
545 lock = tr = activebookmark = None
524 try:
546 try:
525 lock = repo.lock()
547 lock = repo.lock()
526
548
527 # use an uncommitted transaction to generate the bundle to avoid
549 # use an uncommitted transaction to generate the bundle to avoid
528 # pull races. ensure we don't print the abort message to stderr.
550 # pull races. ensure we don't print the abort message to stderr.
529 tr = repo.transaction(b'shelve', report=lambda x: None)
551 tr = repo.transaction(b'shelve', report=lambda x: None)
530
552
531 interactive = opts.get(b'interactive', False)
553 interactive = opts.get(b'interactive', False)
532 includeunknown = opts.get(b'unknown', False) and not opts.get(
554 includeunknown = opts.get(b'unknown', False) and not opts.get(
533 b'addremove', False
555 b'addremove', False
534 )
556 )
535
557
536 name = getshelvename(repo, parent, opts)
558 name = getshelvename(repo, parent, opts)
537 activebookmark = _backupactivebookmark(repo)
559 activebookmark = _backupactivebookmark(repo)
538 extra = {b'internal': b'shelve'}
560 extra = {b'internal': b'shelve'}
539 if includeunknown:
561 if includeunknown:
540 _includeunknownfiles(repo, pats, opts, extra)
562 _includeunknownfiles(repo, pats, opts, extra)
541
563
542 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
564 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
543 # In non-bare shelve we don't store newly created branch
565 # In non-bare shelve we don't store newly created branch
544 # at bundled commit
566 # at bundled commit
545 repo.dirstate.setbranch(repo[b'.'].branch())
567 repo.dirstate.setbranch(repo[b'.'].branch())
546
568
547 commitfunc = getcommitfunc(extra, interactive, editor=True)
569 commitfunc = getcommitfunc(extra, interactive, editor=True)
548 if not interactive:
570 if not interactive:
549 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
571 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
550 else:
572 else:
551 node = cmdutil.dorecord(
573 node = cmdutil.dorecord(
552 ui,
574 ui,
553 repo,
575 repo,
554 commitfunc,
576 commitfunc,
555 None,
577 None,
556 False,
578 False,
557 cmdutil.recordfilter,
579 cmdutil.recordfilter,
558 *pats,
580 *pats,
559 **pycompat.strkwargs(opts)
581 **pycompat.strkwargs(opts)
560 )
582 )
561 if not node:
583 if not node:
562 _nothingtoshelvemessaging(ui, repo, pats, opts)
584 _nothingtoshelvemessaging(ui, repo, pats, opts)
563 return 1
585 return 1
564
586
565 # Create a matcher so that prefetch doesn't attempt to fetch
587 # Create a matcher so that prefetch doesn't attempt to fetch
566 # the entire repository pointlessly, and as an optimisation
588 # the entire repository pointlessly, and as an optimisation
567 # for movedirstate, if needed.
589 # for movedirstate, if needed.
568 match = scmutil.matchfiles(repo, repo[node].files())
590 match = scmutil.matchfiles(repo, repo[node].files())
569 _shelvecreatedcommit(repo, node, name, match)
591 _shelvecreatedcommit(repo, node, name, match)
570
592
571 ui.status(_(b'shelved as %s\n') % name)
593 ui.status(_(b'shelved as %s\n') % name)
572 if opts[b'keep']:
594 if opts[b'keep']:
573 with repo.dirstate.parentchange():
595 with repo.dirstate.parentchange():
574 scmutil.movedirstate(repo, parent, match)
596 scmutil.movedirstate(repo, parent, match)
575 else:
597 else:
576 hg.update(repo, parent.node())
598 hg.update(repo, parent.node())
577 ms = mergestatemod.mergestate.read(repo)
599 ms = mergestatemod.mergestate.read(repo)
578 if not ms.unresolvedcount():
600 if not ms.unresolvedcount():
579 ms.reset()
601 ms.reset()
580
602
581 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
603 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
582 repo.dirstate.setbranch(origbranch)
604 repo.dirstate.setbranch(origbranch)
583
605
584 _finishshelve(repo, tr)
606 _finishshelve(repo, tr)
585 finally:
607 finally:
586 _restoreactivebookmark(repo, activebookmark)
608 _restoreactivebookmark(repo, activebookmark)
587 lockmod.release(tr, lock)
609 lockmod.release(tr, lock)
588
610
589
611
590 def _isbareshelve(pats, opts):
612 def _isbareshelve(pats, opts):
591 return (
613 return (
592 not pats
614 not pats
593 and not opts.get(b'interactive', False)
615 and not opts.get(b'interactive', False)
594 and not opts.get(b'include', False)
616 and not opts.get(b'include', False)
595 and not opts.get(b'exclude', False)
617 and not opts.get(b'exclude', False)
596 )
618 )
597
619
598
620
599 def _iswctxonnewbranch(repo):
621 def _iswctxonnewbranch(repo):
600 return repo[None].branch() != repo[b'.'].branch()
622 return repo[None].branch() != repo[b'.'].branch()
601
623
602
624
603 def cleanupcmd(ui, repo):
625 def cleanupcmd(ui, repo):
604 """subcommand that deletes all shelves"""
626 """subcommand that deletes all shelves"""
605
627
606 with repo.wlock():
628 with repo.wlock():
607 vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
629 shelf_dir = ShelfDir(repo)
608 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
630 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
609 for _mtime, name in listshelves(vfs):
631 for _mtime, name in shelf_dir.listshelves():
610 Shelf(vfs, name).movetobackup(backupvfs)
632 shelf_dir.get(name).movetobackup(backupvfs)
611 cleanupoldbackups(repo)
633 cleanupoldbackups(repo)
612
634
613
635
614 def deletecmd(ui, repo, pats):
636 def deletecmd(ui, repo, pats):
615 """subcommand that deletes a specific shelve"""
637 """subcommand that deletes a specific shelve"""
616 if not pats:
638 if not pats:
617 raise error.InputError(_(b'no shelved changes specified!'))
639 raise error.InputError(_(b'no shelved changes specified!'))
618 with repo.wlock():
640 with repo.wlock():
619 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
641 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
620 for name in pats:
642 for name in pats:
621 shelf = ShelfDir(repo).get(name)
643 shelf = ShelfDir(repo).get(name)
622 if not shelf.exists():
644 if not shelf.exists():
623 raise error.InputError(
645 raise error.InputError(
624 _(b"shelved change '%s' not found") % name
646 _(b"shelved change '%s' not found") % name
625 )
647 )
626 shelf.movetobackup(backupvfs)
648 shelf.movetobackup(backupvfs)
627 cleanupoldbackups(repo)
649 cleanupoldbackups(repo)
628
650
629
651
630 def listshelves(vfs):
631 """return all shelves in repo as list of (time, name)"""
632 try:
633 names = vfs.listdir()
634 except OSError as err:
635 if err.errno != errno.ENOENT:
636 raise
637 return []
638 info = []
639 seen = set()
640 for filename in names:
641 name = filename.rsplit(b'.', 1)[0]
642 if name in seen:
643 continue
644 seen.add(name)
645 shelf = Shelf(vfs, name)
646 if not shelf.exists():
647 continue
648 mtime = shelf.mtime()
649 info.append((mtime, name))
650 return sorted(info, reverse=True)
651
652
653 def listcmd(ui, repo, pats, opts):
652 def listcmd(ui, repo, pats, opts):
654 """subcommand that displays the list of shelves"""
653 """subcommand that displays the list of shelves"""
655 pats = set(pats)
654 pats = set(pats)
656 width = 80
655 width = 80
657 if not ui.plain():
656 if not ui.plain():
658 width = ui.termwidth()
657 width = ui.termwidth()
659 namelabel = b'shelve.newest'
658 namelabel = b'shelve.newest'
660 ui.pager(b'shelve')
659 ui.pager(b'shelve')
661 vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
662 shelf_dir = ShelfDir(repo)
660 shelf_dir = ShelfDir(repo)
663 for mtime, name in listshelves(vfs):
661 for mtime, name in shelf_dir.listshelves():
664 if pats and name not in pats:
662 if pats and name not in pats:
665 continue
663 continue
666 ui.write(name, label=namelabel)
664 ui.write(name, label=namelabel)
667 namelabel = b'shelve.name'
665 namelabel = b'shelve.name'
668 if ui.quiet:
666 if ui.quiet:
669 ui.write(b'\n')
667 ui.write(b'\n')
670 continue
668 continue
671 ui.write(b' ' * (16 - len(name)))
669 ui.write(b' ' * (16 - len(name)))
672 used = 16
670 used = 16
673 date = dateutil.makedate(mtime)
671 date = dateutil.makedate(mtime)
674 age = b'(%s)' % templatefilters.age(date, abbrev=True)
672 age = b'(%s)' % templatefilters.age(date, abbrev=True)
675 ui.write(age, label=b'shelve.age')
673 ui.write(age, label=b'shelve.age')
676 ui.write(b' ' * (12 - len(age)))
674 ui.write(b' ' * (12 - len(age)))
677 used += 12
675 used += 12
678 with shelf_dir.get(name).open_patch() as fp:
676 with shelf_dir.get(name).open_patch() as fp:
679 while True:
677 while True:
680 line = fp.readline()
678 line = fp.readline()
681 if not line:
679 if not line:
682 break
680 break
683 if not line.startswith(b'#'):
681 if not line.startswith(b'#'):
684 desc = line.rstrip()
682 desc = line.rstrip()
685 if ui.formatted():
683 if ui.formatted():
686 desc = stringutil.ellipsis(desc, width - used)
684 desc = stringutil.ellipsis(desc, width - used)
687 ui.write(desc)
685 ui.write(desc)
688 break
686 break
689 ui.write(b'\n')
687 ui.write(b'\n')
690 if not (opts[b'patch'] or opts[b'stat']):
688 if not (opts[b'patch'] or opts[b'stat']):
691 continue
689 continue
692 difflines = fp.readlines()
690 difflines = fp.readlines()
693 if opts[b'patch']:
691 if opts[b'patch']:
694 for chunk, label in patch.difflabel(iter, difflines):
692 for chunk, label in patch.difflabel(iter, difflines):
695 ui.write(chunk, label=label)
693 ui.write(chunk, label=label)
696 if opts[b'stat']:
694 if opts[b'stat']:
697 for chunk, label in patch.diffstatui(difflines, width=width):
695 for chunk, label in patch.diffstatui(difflines, width=width):
698 ui.write(chunk, label=label)
696 ui.write(chunk, label=label)
699
697
700
698
701 def patchcmds(ui, repo, pats, opts):
699 def patchcmds(ui, repo, pats, opts):
702 """subcommand that displays shelves"""
700 """subcommand that displays shelves"""
701 shelf_dir = ShelfDir(repo)
703 if len(pats) == 0:
702 if len(pats) == 0:
704 vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
703 shelves = shelf_dir.listshelves()
705 shelves = listshelves(vfs)
706 if not shelves:
704 if not shelves:
707 raise error.Abort(_(b"there are no shelves to show"))
705 raise error.Abort(_(b"there are no shelves to show"))
708 mtime, name = shelves[0]
706 mtime, name = shelves[0]
709 pats = [name]
707 pats = [name]
710
708
711 shelf_dir = ShelfDir(repo)
712 for shelfname in pats:
709 for shelfname in pats:
713 if not shelf_dir.get(shelfname).exists():
710 if not shelf_dir.get(shelfname).exists():
714 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
711 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
715
712
716 listcmd(ui, repo, pats, opts)
713 listcmd(ui, repo, pats, opts)
717
714
718
715
719 def checkparents(repo, state):
716 def checkparents(repo, state):
720 """check parent while resuming an unshelve"""
717 """check parent while resuming an unshelve"""
721 if state.parents != repo.dirstate.parents():
718 if state.parents != repo.dirstate.parents():
722 raise error.Abort(
719 raise error.Abort(
723 _(b'working directory parents do not match unshelve state')
720 _(b'working directory parents do not match unshelve state')
724 )
721 )
725
722
726
723
727 def _loadshelvedstate(ui, repo, opts):
724 def _loadshelvedstate(ui, repo, opts):
728 try:
725 try:
729 state = shelvedstate.load(repo)
726 state = shelvedstate.load(repo)
730 if opts.get(b'keep') is None:
727 if opts.get(b'keep') is None:
731 opts[b'keep'] = state.keep
728 opts[b'keep'] = state.keep
732 except IOError as err:
729 except IOError as err:
733 if err.errno != errno.ENOENT:
730 if err.errno != errno.ENOENT:
734 raise
731 raise
735 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
732 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
736 except error.CorruptedState as err:
733 except error.CorruptedState as err:
737 ui.debug(pycompat.bytestr(err) + b'\n')
734 ui.debug(pycompat.bytestr(err) + b'\n')
738 if opts.get(b'continue'):
735 if opts.get(b'continue'):
739 msg = _(b'corrupted shelved state file')
736 msg = _(b'corrupted shelved state file')
740 hint = _(
737 hint = _(
741 b'please run hg unshelve --abort to abort unshelve '
738 b'please run hg unshelve --abort to abort unshelve '
742 b'operation'
739 b'operation'
743 )
740 )
744 raise error.Abort(msg, hint=hint)
741 raise error.Abort(msg, hint=hint)
745 elif opts.get(b'abort'):
742 elif opts.get(b'abort'):
746 shelvedstate.clear(repo)
743 shelvedstate.clear(repo)
747 raise error.Abort(
744 raise error.Abort(
748 _(
745 _(
749 b'could not read shelved state file, your '
746 b'could not read shelved state file, your '
750 b'working copy may be in an unexpected state\n'
747 b'working copy may be in an unexpected state\n'
751 b'please update to some commit\n'
748 b'please update to some commit\n'
752 )
749 )
753 )
750 )
754 return state
751 return state
755
752
756
753
757 def unshelveabort(ui, repo, state):
754 def unshelveabort(ui, repo, state):
758 """subcommand that abort an in-progress unshelve"""
755 """subcommand that abort an in-progress unshelve"""
759 with repo.lock():
756 with repo.lock():
760 try:
757 try:
761 checkparents(repo, state)
758 checkparents(repo, state)
762
759
763 merge.clean_update(state.pendingctx)
760 merge.clean_update(state.pendingctx)
764 if state.activebookmark and state.activebookmark in repo._bookmarks:
761 if state.activebookmark and state.activebookmark in repo._bookmarks:
765 bookmarks.activate(repo, state.activebookmark)
762 bookmarks.activate(repo, state.activebookmark)
766 mergefiles(ui, repo, state.wctx, state.pendingctx)
763 mergefiles(ui, repo, state.wctx, state.pendingctx)
767 if not phases.supportinternal(repo):
764 if not phases.supportinternal(repo):
768 repair.strip(
765 repair.strip(
769 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
766 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
770 )
767 )
771 finally:
768 finally:
772 shelvedstate.clear(repo)
769 shelvedstate.clear(repo)
773 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
770 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
774
771
775
772
776 def hgabortunshelve(ui, repo):
773 def hgabortunshelve(ui, repo):
777 """logic to abort unshelve using 'hg abort"""
774 """logic to abort unshelve using 'hg abort"""
778 with repo.wlock():
775 with repo.wlock():
779 state = _loadshelvedstate(ui, repo, {b'abort': True})
776 state = _loadshelvedstate(ui, repo, {b'abort': True})
780 return unshelveabort(ui, repo, state)
777 return unshelveabort(ui, repo, state)
781
778
782
779
783 def mergefiles(ui, repo, wctx, shelvectx):
780 def mergefiles(ui, repo, wctx, shelvectx):
784 """updates to wctx and merges the changes from shelvectx into the
781 """updates to wctx and merges the changes from shelvectx into the
785 dirstate."""
782 dirstate."""
786 with ui.configoverride({(b'ui', b'quiet'): True}):
783 with ui.configoverride({(b'ui', b'quiet'): True}):
787 hg.update(repo, wctx.node())
784 hg.update(repo, wctx.node())
788 ui.pushbuffer(True)
785 ui.pushbuffer(True)
789 cmdutil.revert(ui, repo, shelvectx)
786 cmdutil.revert(ui, repo, shelvectx)
790 ui.popbuffer()
787 ui.popbuffer()
791
788
792
789
793 def restorebranch(ui, repo, branchtorestore):
790 def restorebranch(ui, repo, branchtorestore):
794 if branchtorestore and branchtorestore != repo.dirstate.branch():
791 if branchtorestore and branchtorestore != repo.dirstate.branch():
795 repo.dirstate.setbranch(branchtorestore)
792 repo.dirstate.setbranch(branchtorestore)
796 ui.status(
793 ui.status(
797 _(b'marked working directory as branch %s\n') % branchtorestore
794 _(b'marked working directory as branch %s\n') % branchtorestore
798 )
795 )
799
796
800
797
801 def unshelvecleanup(ui, repo, name, opts):
798 def unshelvecleanup(ui, repo, name, opts):
802 """remove related files after an unshelve"""
799 """remove related files after an unshelve"""
803 if not opts.get(b'keep'):
800 if not opts.get(b'keep'):
804 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
801 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
805 ShelfDir(repo).get(name).movetobackup(backupvfs)
802 ShelfDir(repo).get(name).movetobackup(backupvfs)
806 cleanupoldbackups(repo)
803 cleanupoldbackups(repo)
807
804
808
805
809 def unshelvecontinue(ui, repo, state, opts):
806 def unshelvecontinue(ui, repo, state, opts):
810 """subcommand to continue an in-progress unshelve"""
807 """subcommand to continue an in-progress unshelve"""
811 # We're finishing off a merge. First parent is our original
808 # We're finishing off a merge. First parent is our original
812 # parent, second is the temporary "fake" commit we're unshelving.
809 # parent, second is the temporary "fake" commit we're unshelving.
813 interactive = state.interactive
810 interactive = state.interactive
814 basename = state.name
811 basename = state.name
815 with repo.lock():
812 with repo.lock():
816 checkparents(repo, state)
813 checkparents(repo, state)
817 ms = mergestatemod.mergestate.read(repo)
814 ms = mergestatemod.mergestate.read(repo)
818 if list(ms.unresolved()):
815 if list(ms.unresolved()):
819 raise error.Abort(
816 raise error.Abort(
820 _(b"unresolved conflicts, can't continue"),
817 _(b"unresolved conflicts, can't continue"),
821 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
818 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
822 )
819 )
823
820
824 shelvectx = repo[state.parents[1]]
821 shelvectx = repo[state.parents[1]]
825 pendingctx = state.pendingctx
822 pendingctx = state.pendingctx
826
823
827 with repo.dirstate.parentchange():
824 with repo.dirstate.parentchange():
828 repo.setparents(state.pendingctx.node(), nullid)
825 repo.setparents(state.pendingctx.node(), nullid)
829 repo.dirstate.write(repo.currenttransaction())
826 repo.dirstate.write(repo.currenttransaction())
830
827
831 targetphase = phases.internal
828 targetphase = phases.internal
832 if not phases.supportinternal(repo):
829 if not phases.supportinternal(repo):
833 targetphase = phases.secret
830 targetphase = phases.secret
834 overrides = {(b'phases', b'new-commit'): targetphase}
831 overrides = {(b'phases', b'new-commit'): targetphase}
835 with repo.ui.configoverride(overrides, b'unshelve'):
832 with repo.ui.configoverride(overrides, b'unshelve'):
836 with repo.dirstate.parentchange():
833 with repo.dirstate.parentchange():
837 repo.setparents(state.parents[0], nullid)
834 repo.setparents(state.parents[0], nullid)
838 newnode, ispartialunshelve = _createunshelvectx(
835 newnode, ispartialunshelve = _createunshelvectx(
839 ui, repo, shelvectx, basename, interactive, opts
836 ui, repo, shelvectx, basename, interactive, opts
840 )
837 )
841
838
842 if newnode is None:
839 if newnode is None:
843 shelvectx = state.pendingctx
840 shelvectx = state.pendingctx
844 msg = _(
841 msg = _(
845 b'note: unshelved changes already existed '
842 b'note: unshelved changes already existed '
846 b'in the working copy\n'
843 b'in the working copy\n'
847 )
844 )
848 ui.status(msg)
845 ui.status(msg)
849 else:
846 else:
850 # only strip the shelvectx if we produced one
847 # only strip the shelvectx if we produced one
851 state.nodestoremove.append(newnode)
848 state.nodestoremove.append(newnode)
852 shelvectx = repo[newnode]
849 shelvectx = repo[newnode]
853
850
854 merge.update(pendingctx)
851 merge.update(pendingctx)
855 mergefiles(ui, repo, state.wctx, shelvectx)
852 mergefiles(ui, repo, state.wctx, shelvectx)
856 restorebranch(ui, repo, state.branchtorestore)
853 restorebranch(ui, repo, state.branchtorestore)
857
854
858 if not phases.supportinternal(repo):
855 if not phases.supportinternal(repo):
859 repair.strip(
856 repair.strip(
860 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
857 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
861 )
858 )
862 shelvedstate.clear(repo)
859 shelvedstate.clear(repo)
863 if not ispartialunshelve:
860 if not ispartialunshelve:
864 unshelvecleanup(ui, repo, state.name, opts)
861 unshelvecleanup(ui, repo, state.name, opts)
865 _restoreactivebookmark(repo, state.activebookmark)
862 _restoreactivebookmark(repo, state.activebookmark)
866 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
863 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
867
864
868
865
869 def hgcontinueunshelve(ui, repo):
866 def hgcontinueunshelve(ui, repo):
870 """logic to resume unshelve using 'hg continue'"""
867 """logic to resume unshelve using 'hg continue'"""
871 with repo.wlock():
868 with repo.wlock():
872 state = _loadshelvedstate(ui, repo, {b'continue': True})
869 state = _loadshelvedstate(ui, repo, {b'continue': True})
873 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
870 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
874
871
875
872
876 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
873 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
877 """Temporarily commit working copy changes before moving unshelve commit"""
874 """Temporarily commit working copy changes before moving unshelve commit"""
878 # Store pending changes in a commit and remember added in case a shelve
875 # Store pending changes in a commit and remember added in case a shelve
879 # contains unknown files that are part of the pending change
876 # contains unknown files that are part of the pending change
880 s = repo.status()
877 s = repo.status()
881 addedbefore = frozenset(s.added)
878 addedbefore = frozenset(s.added)
882 if not (s.modified or s.added or s.removed):
879 if not (s.modified or s.added or s.removed):
883 return tmpwctx, addedbefore
880 return tmpwctx, addedbefore
884 ui.status(
881 ui.status(
885 _(
882 _(
886 b"temporarily committing pending changes "
883 b"temporarily committing pending changes "
887 b"(restore with 'hg unshelve --abort')\n"
884 b"(restore with 'hg unshelve --abort')\n"
888 )
885 )
889 )
886 )
890 extra = {b'internal': b'shelve'}
887 extra = {b'internal': b'shelve'}
891 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
888 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
892 tempopts = {}
889 tempopts = {}
893 tempopts[b'message'] = b"pending changes temporary commit"
890 tempopts[b'message'] = b"pending changes temporary commit"
894 tempopts[b'date'] = opts.get(b'date')
891 tempopts[b'date'] = opts.get(b'date')
895 with ui.configoverride({(b'ui', b'quiet'): True}):
892 with ui.configoverride({(b'ui', b'quiet'): True}):
896 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
893 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
897 tmpwctx = repo[node]
894 tmpwctx = repo[node]
898 return tmpwctx, addedbefore
895 return tmpwctx, addedbefore
899
896
900
897
901 def _unshelverestorecommit(ui, repo, tr, basename):
898 def _unshelverestorecommit(ui, repo, tr, basename):
902 """Recreate commit in the repository during the unshelve"""
899 """Recreate commit in the repository during the unshelve"""
903 repo = repo.unfiltered()
900 repo = repo.unfiltered()
904 node = None
901 node = None
905 shelf = ShelfDir(repo).get(basename)
902 shelf = ShelfDir(repo).get(basename)
906 if shelf.hasinfo():
903 if shelf.hasinfo():
907 node = shelf.readinfo()[b'node']
904 node = shelf.readinfo()[b'node']
908 if node is None or node not in repo:
905 if node is None or node not in repo:
909 with ui.configoverride({(b'ui', b'quiet'): True}):
906 with ui.configoverride({(b'ui', b'quiet'): True}):
910 shelvectx = shelf.applybundle(repo, tr)
907 shelvectx = shelf.applybundle(repo, tr)
911 # We might not strip the unbundled changeset, so we should keep track of
908 # We might not strip the unbundled changeset, so we should keep track of
912 # the unshelve node in case we need to reuse it (eg: unshelve --keep)
909 # the unshelve node in case we need to reuse it (eg: unshelve --keep)
913 if node is None:
910 if node is None:
914 info = {b'node': hex(shelvectx.node())}
911 info = {b'node': hex(shelvectx.node())}
915 shelf.writeinfo(info)
912 shelf.writeinfo(info)
916 else:
913 else:
917 shelvectx = repo[node]
914 shelvectx = repo[node]
918
915
919 return repo, shelvectx
916 return repo, shelvectx
920
917
921
918
922 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
919 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
923 """Handles the creation of unshelve commit and updates the shelve if it
920 """Handles the creation of unshelve commit and updates the shelve if it
924 was partially unshelved.
921 was partially unshelved.
925
922
926 If interactive is:
923 If interactive is:
927
924
928 * False: Commits all the changes in the working directory.
925 * False: Commits all the changes in the working directory.
929 * True: Prompts the user to select changes to unshelve and commit them.
926 * True: Prompts the user to select changes to unshelve and commit them.
930 Update the shelve with remaining changes.
927 Update the shelve with remaining changes.
931
928
932 Returns the node of the new commit formed and a bool indicating whether
929 Returns the node of the new commit formed and a bool indicating whether
933 the shelve was partially unshelved.Creates a commit ctx to unshelve
930 the shelve was partially unshelved.Creates a commit ctx to unshelve
934 interactively or non-interactively.
931 interactively or non-interactively.
935
932
936 The user might want to unshelve certain changes only from the stored
933 The user might want to unshelve certain changes only from the stored
937 shelve in interactive. So, we would create two commits. One with requested
934 shelve in interactive. So, we would create two commits. One with requested
938 changes to unshelve at that time and the latter is shelved for future.
935 changes to unshelve at that time and the latter is shelved for future.
939
936
940 Here, we return both the newnode which is created interactively and a
937 Here, we return both the newnode which is created interactively and a
941 bool to know whether the shelve is partly done or completely done.
938 bool to know whether the shelve is partly done or completely done.
942 """
939 """
943 opts[b'message'] = shelvectx.description()
940 opts[b'message'] = shelvectx.description()
944 opts[b'interactive-unshelve'] = True
941 opts[b'interactive-unshelve'] = True
945 pats = []
942 pats = []
946 if not interactive:
943 if not interactive:
947 newnode = repo.commit(
944 newnode = repo.commit(
948 text=shelvectx.description(),
945 text=shelvectx.description(),
949 extra=shelvectx.extra(),
946 extra=shelvectx.extra(),
950 user=shelvectx.user(),
947 user=shelvectx.user(),
951 date=shelvectx.date(),
948 date=shelvectx.date(),
952 )
949 )
953 return newnode, False
950 return newnode, False
954
951
955 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True)
952 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True)
956 newnode = cmdutil.dorecord(
953 newnode = cmdutil.dorecord(
957 ui,
954 ui,
958 repo,
955 repo,
959 commitfunc,
956 commitfunc,
960 None,
957 None,
961 False,
958 False,
962 cmdutil.recordfilter,
959 cmdutil.recordfilter,
963 *pats,
960 *pats,
964 **pycompat.strkwargs(opts)
961 **pycompat.strkwargs(opts)
965 )
962 )
966 snode = repo.commit(
963 snode = repo.commit(
967 text=shelvectx.description(),
964 text=shelvectx.description(),
968 extra=shelvectx.extra(),
965 extra=shelvectx.extra(),
969 user=shelvectx.user(),
966 user=shelvectx.user(),
970 )
967 )
971 if snode:
968 if snode:
972 m = scmutil.matchfiles(repo, repo[snode].files())
969 m = scmutil.matchfiles(repo, repo[snode].files())
973 _shelvecreatedcommit(repo, snode, basename, m)
970 _shelvecreatedcommit(repo, snode, basename, m)
974
971
975 return newnode, bool(snode)
972 return newnode, bool(snode)
976
973
977
974
978 def _rebaserestoredcommit(
975 def _rebaserestoredcommit(
979 ui,
976 ui,
980 repo,
977 repo,
981 opts,
978 opts,
982 tr,
979 tr,
983 oldtiprev,
980 oldtiprev,
984 basename,
981 basename,
985 pctx,
982 pctx,
986 tmpwctx,
983 tmpwctx,
987 shelvectx,
984 shelvectx,
988 branchtorestore,
985 branchtorestore,
989 activebookmark,
986 activebookmark,
990 ):
987 ):
991 """Rebase restored commit from its original location to a destination"""
988 """Rebase restored commit from its original location to a destination"""
992 # If the shelve is not immediately on top of the commit
989 # If the shelve is not immediately on top of the commit
993 # we'll be merging with, rebase it to be on top.
990 # we'll be merging with, rebase it to be on top.
994 interactive = opts.get(b'interactive')
991 interactive = opts.get(b'interactive')
995 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
992 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
996 # We won't skip on interactive mode because, the user might want to
993 # We won't skip on interactive mode because, the user might want to
997 # unshelve certain changes only.
994 # unshelve certain changes only.
998 return shelvectx, False
995 return shelvectx, False
999
996
1000 overrides = {
997 overrides = {
1001 (b'ui', b'forcemerge'): opts.get(b'tool', b''),
998 (b'ui', b'forcemerge'): opts.get(b'tool', b''),
1002 (b'phases', b'new-commit'): phases.secret,
999 (b'phases', b'new-commit'): phases.secret,
1003 }
1000 }
1004 with repo.ui.configoverride(overrides, b'unshelve'):
1001 with repo.ui.configoverride(overrides, b'unshelve'):
1005 ui.status(_(b'rebasing shelved changes\n'))
1002 ui.status(_(b'rebasing shelved changes\n'))
1006 stats = merge.graft(
1003 stats = merge.graft(
1007 repo,
1004 repo,
1008 shelvectx,
1005 shelvectx,
1009 labels=[b'working-copy', b'shelve'],
1006 labels=[b'working-copy', b'shelve'],
1010 keepconflictparent=True,
1007 keepconflictparent=True,
1011 )
1008 )
1012 if stats.unresolvedcount:
1009 if stats.unresolvedcount:
1013 tr.close()
1010 tr.close()
1014
1011
1015 nodestoremove = [
1012 nodestoremove = [
1016 repo.changelog.node(rev)
1013 repo.changelog.node(rev)
1017 for rev in pycompat.xrange(oldtiprev, len(repo))
1014 for rev in pycompat.xrange(oldtiprev, len(repo))
1018 ]
1015 ]
1019 shelvedstate.save(
1016 shelvedstate.save(
1020 repo,
1017 repo,
1021 basename,
1018 basename,
1022 pctx,
1019 pctx,
1023 tmpwctx,
1020 tmpwctx,
1024 nodestoremove,
1021 nodestoremove,
1025 branchtorestore,
1022 branchtorestore,
1026 opts.get(b'keep'),
1023 opts.get(b'keep'),
1027 activebookmark,
1024 activebookmark,
1028 interactive,
1025 interactive,
1029 )
1026 )
1030 raise error.ConflictResolutionRequired(b'unshelve')
1027 raise error.ConflictResolutionRequired(b'unshelve')
1031
1028
1032 with repo.dirstate.parentchange():
1029 with repo.dirstate.parentchange():
1033 repo.setparents(tmpwctx.node(), nullid)
1030 repo.setparents(tmpwctx.node(), nullid)
1034 newnode, ispartialunshelve = _createunshelvectx(
1031 newnode, ispartialunshelve = _createunshelvectx(
1035 ui, repo, shelvectx, basename, interactive, opts
1032 ui, repo, shelvectx, basename, interactive, opts
1036 )
1033 )
1037
1034
1038 if newnode is None:
1035 if newnode is None:
1039 shelvectx = tmpwctx
1036 shelvectx = tmpwctx
1040 msg = _(
1037 msg = _(
1041 b'note: unshelved changes already existed '
1038 b'note: unshelved changes already existed '
1042 b'in the working copy\n'
1039 b'in the working copy\n'
1043 )
1040 )
1044 ui.status(msg)
1041 ui.status(msg)
1045 else:
1042 else:
1046 shelvectx = repo[newnode]
1043 shelvectx = repo[newnode]
1047 merge.update(tmpwctx)
1044 merge.update(tmpwctx)
1048
1045
1049 return shelvectx, ispartialunshelve
1046 return shelvectx, ispartialunshelve
1050
1047
1051
1048
1052 def _forgetunknownfiles(repo, shelvectx, addedbefore):
1049 def _forgetunknownfiles(repo, shelvectx, addedbefore):
1053 # Forget any files that were unknown before the shelve, unknown before
1050 # Forget any files that were unknown before the shelve, unknown before
1054 # unshelve started, but are now added.
1051 # unshelve started, but are now added.
1055 shelveunknown = shelvectx.extra().get(b'shelve_unknown')
1052 shelveunknown = shelvectx.extra().get(b'shelve_unknown')
1056 if not shelveunknown:
1053 if not shelveunknown:
1057 return
1054 return
1058 shelveunknown = frozenset(shelveunknown.split(b'\0'))
1055 shelveunknown = frozenset(shelveunknown.split(b'\0'))
1059 addedafter = frozenset(repo.status().added)
1056 addedafter = frozenset(repo.status().added)
1060 toforget = (addedafter & shelveunknown) - addedbefore
1057 toforget = (addedafter & shelveunknown) - addedbefore
1061 repo[None].forget(toforget)
1058 repo[None].forget(toforget)
1062
1059
1063
1060
1064 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
1061 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
1065 _restoreactivebookmark(repo, activebookmark)
1062 _restoreactivebookmark(repo, activebookmark)
1066 # The transaction aborting will strip all the commits for us,
1063 # The transaction aborting will strip all the commits for us,
1067 # but it doesn't update the inmemory structures, so addchangegroup
1064 # but it doesn't update the inmemory structures, so addchangegroup
1068 # hooks still fire and try to operate on the missing commits.
1065 # hooks still fire and try to operate on the missing commits.
1069 # Clean up manually to prevent this.
1066 # Clean up manually to prevent this.
1070 repo.unfiltered().changelog.strip(oldtiprev, tr)
1067 repo.unfiltered().changelog.strip(oldtiprev, tr)
1071 _aborttransaction(repo, tr)
1068 _aborttransaction(repo, tr)
1072
1069
1073
1070
1074 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
1071 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
1075 """Check potential problems which may result from working
1072 """Check potential problems which may result from working
1076 copy having untracked changes."""
1073 copy having untracked changes."""
1077 wcdeleted = set(repo.status().deleted)
1074 wcdeleted = set(repo.status().deleted)
1078 shelvetouched = set(shelvectx.files())
1075 shelvetouched = set(shelvectx.files())
1079 intersection = wcdeleted.intersection(shelvetouched)
1076 intersection = wcdeleted.intersection(shelvetouched)
1080 if intersection:
1077 if intersection:
1081 m = _(b"shelved change touches missing files")
1078 m = _(b"shelved change touches missing files")
1082 hint = _(b"run hg status to see which files are missing")
1079 hint = _(b"run hg status to see which files are missing")
1083 raise error.Abort(m, hint=hint)
1080 raise error.Abort(m, hint=hint)
1084
1081
1085
1082
1086 def unshelvecmd(ui, repo, *shelved, **opts):
1083 def unshelvecmd(ui, repo, *shelved, **opts):
1087 opts = pycompat.byteskwargs(opts)
1084 opts = pycompat.byteskwargs(opts)
1088 abortf = opts.get(b'abort')
1085 abortf = opts.get(b'abort')
1089 continuef = opts.get(b'continue')
1086 continuef = opts.get(b'continue')
1090 interactive = opts.get(b'interactive')
1087 interactive = opts.get(b'interactive')
1091 if not abortf and not continuef:
1088 if not abortf and not continuef:
1092 cmdutil.checkunfinished(repo)
1089 cmdutil.checkunfinished(repo)
1093 shelved = list(shelved)
1090 shelved = list(shelved)
1094 if opts.get(b"name"):
1091 if opts.get(b"name"):
1095 shelved.append(opts[b"name"])
1092 shelved.append(opts[b"name"])
1096
1093
1097 if interactive and opts.get(b'keep'):
1094 if interactive and opts.get(b'keep'):
1098 raise error.InputError(
1095 raise error.InputError(
1099 _(b'--keep on --interactive is not yet supported')
1096 _(b'--keep on --interactive is not yet supported')
1100 )
1097 )
1101 if abortf or continuef:
1098 if abortf or continuef:
1102 if abortf and continuef:
1099 if abortf and continuef:
1103 raise error.InputError(_(b'cannot use both abort and continue'))
1100 raise error.InputError(_(b'cannot use both abort and continue'))
1104 if shelved:
1101 if shelved:
1105 raise error.InputError(
1102 raise error.InputError(
1106 _(
1103 _(
1107 b'cannot combine abort/continue with '
1104 b'cannot combine abort/continue with '
1108 b'naming a shelved change'
1105 b'naming a shelved change'
1109 )
1106 )
1110 )
1107 )
1111 if abortf and opts.get(b'tool', False):
1108 if abortf and opts.get(b'tool', False):
1112 ui.warn(_(b'tool option will be ignored\n'))
1109 ui.warn(_(b'tool option will be ignored\n'))
1113
1110
1114 state = _loadshelvedstate(ui, repo, opts)
1111 state = _loadshelvedstate(ui, repo, opts)
1115 if abortf:
1112 if abortf:
1116 return unshelveabort(ui, repo, state)
1113 return unshelveabort(ui, repo, state)
1117 elif continuef and interactive:
1114 elif continuef and interactive:
1118 raise error.InputError(
1115 raise error.InputError(
1119 _(b'cannot use both continue and interactive')
1116 _(b'cannot use both continue and interactive')
1120 )
1117 )
1121 elif continuef:
1118 elif continuef:
1122 return unshelvecontinue(ui, repo, state, opts)
1119 return unshelvecontinue(ui, repo, state, opts)
1123 elif len(shelved) > 1:
1120 elif len(shelved) > 1:
1124 raise error.InputError(_(b'can only unshelve one change at a time'))
1121 raise error.InputError(_(b'can only unshelve one change at a time'))
1125 elif not shelved:
1122 elif not shelved:
1126 vfs = vfsmod.vfs(repo.vfs.join(shelvedir))
1123 shelved = ShelfDir(repo).listshelves()
1127 shelved = listshelves(vfs)
1128 if not shelved:
1124 if not shelved:
1129 raise error.StateError(_(b'no shelved changes to apply!'))
1125 raise error.StateError(_(b'no shelved changes to apply!'))
1130 basename = shelved[0][1]
1126 basename = shelved[0][1]
1131 ui.status(_(b"unshelving change '%s'\n") % basename)
1127 ui.status(_(b"unshelving change '%s'\n") % basename)
1132 else:
1128 else:
1133 basename = shelved[0]
1129 basename = shelved[0]
1134
1130
1135 if not ShelfDir(repo).get(basename).exists():
1131 if not ShelfDir(repo).get(basename).exists():
1136 raise error.InputError(_(b"shelved change '%s' not found") % basename)
1132 raise error.InputError(_(b"shelved change '%s' not found") % basename)
1137
1133
1138 return _dounshelve(ui, repo, basename, opts)
1134 return _dounshelve(ui, repo, basename, opts)
1139
1135
1140
1136
1141 def _dounshelve(ui, repo, basename, opts):
1137 def _dounshelve(ui, repo, basename, opts):
1142 repo = repo.unfiltered()
1138 repo = repo.unfiltered()
1143 lock = tr = None
1139 lock = tr = None
1144 try:
1140 try:
1145 lock = repo.lock()
1141 lock = repo.lock()
1146 tr = repo.transaction(b'unshelve', report=lambda x: None)
1142 tr = repo.transaction(b'unshelve', report=lambda x: None)
1147 oldtiprev = len(repo)
1143 oldtiprev = len(repo)
1148
1144
1149 pctx = repo[b'.']
1145 pctx = repo[b'.']
1150 tmpwctx = pctx
1146 tmpwctx = pctx
1151 # The goal is to have a commit structure like so:
1147 # The goal is to have a commit structure like so:
1152 # ...-> pctx -> tmpwctx -> shelvectx
1148 # ...-> pctx -> tmpwctx -> shelvectx
1153 # where tmpwctx is an optional commit with the user's pending changes
1149 # where tmpwctx is an optional commit with the user's pending changes
1154 # and shelvectx is the unshelved changes. Then we merge it all down
1150 # and shelvectx is the unshelved changes. Then we merge it all down
1155 # to the original pctx.
1151 # to the original pctx.
1156
1152
1157 activebookmark = _backupactivebookmark(repo)
1153 activebookmark = _backupactivebookmark(repo)
1158 tmpwctx, addedbefore = _commitworkingcopychanges(
1154 tmpwctx, addedbefore = _commitworkingcopychanges(
1159 ui, repo, opts, tmpwctx
1155 ui, repo, opts, tmpwctx
1160 )
1156 )
1161 repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
1157 repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
1162 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1158 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1163 branchtorestore = b''
1159 branchtorestore = b''
1164 if shelvectx.branch() != shelvectx.p1().branch():
1160 if shelvectx.branch() != shelvectx.p1().branch():
1165 branchtorestore = shelvectx.branch()
1161 branchtorestore = shelvectx.branch()
1166
1162
1167 shelvectx, ispartialunshelve = _rebaserestoredcommit(
1163 shelvectx, ispartialunshelve = _rebaserestoredcommit(
1168 ui,
1164 ui,
1169 repo,
1165 repo,
1170 opts,
1166 opts,
1171 tr,
1167 tr,
1172 oldtiprev,
1168 oldtiprev,
1173 basename,
1169 basename,
1174 pctx,
1170 pctx,
1175 tmpwctx,
1171 tmpwctx,
1176 shelvectx,
1172 shelvectx,
1177 branchtorestore,
1173 branchtorestore,
1178 activebookmark,
1174 activebookmark,
1179 )
1175 )
1180 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
1176 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
1181 with ui.configoverride(overrides, b'unshelve'):
1177 with ui.configoverride(overrides, b'unshelve'):
1182 mergefiles(ui, repo, pctx, shelvectx)
1178 mergefiles(ui, repo, pctx, shelvectx)
1183 restorebranch(ui, repo, branchtorestore)
1179 restorebranch(ui, repo, branchtorestore)
1184 shelvedstate.clear(repo)
1180 shelvedstate.clear(repo)
1185 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1181 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1186 _forgetunknownfiles(repo, shelvectx, addedbefore)
1182 _forgetunknownfiles(repo, shelvectx, addedbefore)
1187 if not ispartialunshelve:
1183 if not ispartialunshelve:
1188 unshelvecleanup(ui, repo, basename, opts)
1184 unshelvecleanup(ui, repo, basename, opts)
1189 finally:
1185 finally:
1190 if tr:
1186 if tr:
1191 tr.release()
1187 tr.release()
1192 lockmod.release(lock)
1188 lockmod.release(lock)
General Comments 0
You need to be logged in to leave comments. Login now