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