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