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