##// END OF EJS Templates
safehasattr: pass attribute name as string instead of bytes...
marmoute -
r51489:4a60280b default
parent child Browse files
Show More
@@ -1,1249 +1,1249 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 # disable the transaction invalidation of the dirstate, to preserve the
435 # disable the transaction invalidation of the dirstate, to preserve the
436 # current change in memory.
436 # current change in memory.
437 ds = repo.dirstate
437 ds = repo.dirstate
438 # The assert below check that nobody else did such wrapping.
438 # The assert below check that nobody else did such wrapping.
439 #
439 #
440 # These is not such other wrapping currently, but if someone try to
440 # These is not such other wrapping currently, but if someone try to
441 # implement one in the future, this will explicitly break here instead of
441 # implement one in the future, this will explicitly break here instead of
442 # misbehaving in subtle ways.
442 # misbehaving in subtle ways.
443 current_branch = ds.branch()
443 current_branch = ds.branch()
444 assert 'invalidate' not in vars(ds)
444 assert 'invalidate' not in vars(ds)
445 try:
445 try:
446 # note : we could simply disable the transaction abort callback, but
446 # note : we could simply disable the transaction abort callback, but
447 # other code also tries to rollback and invalidate this.
447 # other code also tries to rollback and invalidate this.
448 ds.invalidate = lambda: None
448 ds.invalidate = lambda: None
449 tr.abort()
449 tr.abort()
450 finally:
450 finally:
451 del ds.invalidate
451 del ds.invalidate
452 # manually write the change in memory since we can no longer rely on the
452 # manually write the change in memory since we can no longer rely on the
453 # transaction to do so.
453 # transaction to do so.
454 assert repo.currenttransaction() is None
454 assert repo.currenttransaction() is None
455 repo.dirstate.write(None)
455 repo.dirstate.write(None)
456 ds.setbranch(current_branch, None)
456 ds.setbranch(current_branch, None)
457
457
458
458
459 def getshelvename(repo, parent, opts):
459 def getshelvename(repo, parent, opts):
460 """Decide on the name this shelve is going to have"""
460 """Decide on the name this shelve is going to have"""
461
461
462 def gennames():
462 def gennames():
463 yield label
463 yield label
464 for i in itertools.count(1):
464 for i in itertools.count(1):
465 yield b'%s-%02d' % (label, i)
465 yield b'%s-%02d' % (label, i)
466
466
467 name = opts.get(b'name')
467 name = opts.get(b'name')
468 label = repo._activebookmark or parent.branch() or b'default'
468 label = repo._activebookmark or parent.branch() or b'default'
469 # slashes aren't allowed in filenames, therefore we rename it
469 # slashes aren't allowed in filenames, therefore we rename it
470 label = label.replace(b'/', b'_')
470 label = label.replace(b'/', b'_')
471 label = label.replace(b'\\', b'_')
471 label = label.replace(b'\\', b'_')
472 # filenames must not start with '.' as it should not be hidden
472 # filenames must not start with '.' as it should not be hidden
473 if label.startswith(b'.'):
473 if label.startswith(b'.'):
474 label = label.replace(b'.', b'_', 1)
474 label = label.replace(b'.', b'_', 1)
475
475
476 if name:
476 if name:
477 if ShelfDir(repo).get(name).exists():
477 if ShelfDir(repo).get(name).exists():
478 e = _(b"a shelved change named '%s' already exists") % name
478 e = _(b"a shelved change named '%s' already exists") % name
479 raise error.Abort(e)
479 raise error.Abort(e)
480
480
481 # ensure we are not creating a subdirectory or a hidden file
481 # ensure we are not creating a subdirectory or a hidden file
482 if b'/' in name or b'\\' in name:
482 if b'/' in name or b'\\' in name:
483 raise error.Abort(
483 raise error.Abort(
484 _(b'shelved change names can not contain slashes')
484 _(b'shelved change names can not contain slashes')
485 )
485 )
486 if name.startswith(b'.'):
486 if name.startswith(b'.'):
487 raise error.Abort(_(b"shelved change names can not start with '.'"))
487 raise error.Abort(_(b"shelved change names can not start with '.'"))
488
488
489 else:
489 else:
490 shelf_dir = ShelfDir(repo)
490 shelf_dir = ShelfDir(repo)
491 for n in gennames():
491 for n in gennames():
492 if not shelf_dir.get(n).exists():
492 if not shelf_dir.get(n).exists():
493 name = n
493 name = n
494 break
494 break
495
495
496 return name
496 return name
497
497
498
498
499 def mutableancestors(ctx):
499 def mutableancestors(ctx):
500 """return all mutable ancestors for ctx (included)
500 """return all mutable ancestors for ctx (included)
501
501
502 Much faster than the revset ancestors(ctx) & draft()"""
502 Much faster than the revset ancestors(ctx) & draft()"""
503 seen = {nullrev}
503 seen = {nullrev}
504 visit = collections.deque()
504 visit = collections.deque()
505 visit.append(ctx)
505 visit.append(ctx)
506 while visit:
506 while visit:
507 ctx = visit.popleft()
507 ctx = visit.popleft()
508 yield ctx.node()
508 yield ctx.node()
509 for parent in ctx.parents():
509 for parent in ctx.parents():
510 rev = parent.rev()
510 rev = parent.rev()
511 if rev not in seen:
511 if rev not in seen:
512 seen.add(rev)
512 seen.add(rev)
513 if parent.mutable():
513 if parent.mutable():
514 visit.append(parent)
514 visit.append(parent)
515
515
516
516
517 def getcommitfunc(extra, interactive, editor=False):
517 def getcommitfunc(extra, interactive, editor=False):
518 def commitfunc(ui, repo, message, match, opts):
518 def commitfunc(ui, repo, message, match, opts):
519 hasmq = util.safehasattr(repo, b'mq')
519 hasmq = util.safehasattr(repo, 'mq')
520 if hasmq:
520 if hasmq:
521 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
521 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
522
522
523 targetphase = _target_phase(repo)
523 targetphase = _target_phase(repo)
524 overrides = {(b'phases', b'new-commit'): targetphase}
524 overrides = {(b'phases', b'new-commit'): targetphase}
525 try:
525 try:
526 editor_ = False
526 editor_ = False
527 if editor:
527 if editor:
528 editor_ = cmdutil.getcommiteditor(
528 editor_ = cmdutil.getcommiteditor(
529 editform=b'shelve.shelve', **pycompat.strkwargs(opts)
529 editform=b'shelve.shelve', **pycompat.strkwargs(opts)
530 )
530 )
531 with repo.ui.configoverride(overrides):
531 with repo.ui.configoverride(overrides):
532 return repo.commit(
532 return repo.commit(
533 message,
533 message,
534 shelveuser,
534 shelveuser,
535 opts.get(b'date'),
535 opts.get(b'date'),
536 match,
536 match,
537 editor=editor_,
537 editor=editor_,
538 extra=extra,
538 extra=extra,
539 )
539 )
540 finally:
540 finally:
541 if hasmq:
541 if hasmq:
542 repo.mq.checkapplied = saved
542 repo.mq.checkapplied = saved
543
543
544 def interactivecommitfunc(ui, repo, *pats, **opts):
544 def interactivecommitfunc(ui, repo, *pats, **opts):
545 opts = pycompat.byteskwargs(opts)
545 opts = pycompat.byteskwargs(opts)
546 match = scmutil.match(repo[b'.'], pats, {})
546 match = scmutil.match(repo[b'.'], pats, {})
547 message = opts[b'message']
547 message = opts[b'message']
548 return commitfunc(ui, repo, message, match, opts)
548 return commitfunc(ui, repo, message, match, opts)
549
549
550 return interactivecommitfunc if interactive else commitfunc
550 return interactivecommitfunc if interactive else commitfunc
551
551
552
552
553 def _nothingtoshelvemessaging(ui, repo, pats, opts):
553 def _nothingtoshelvemessaging(ui, repo, pats, opts):
554 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
554 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
555 if stat.deleted:
555 if stat.deleted:
556 ui.status(
556 ui.status(
557 _(b"nothing changed (%d missing files, see 'hg status')\n")
557 _(b"nothing changed (%d missing files, see 'hg status')\n")
558 % len(stat.deleted)
558 % len(stat.deleted)
559 )
559 )
560 else:
560 else:
561 ui.status(_(b"nothing changed\n"))
561 ui.status(_(b"nothing changed\n"))
562
562
563
563
564 def _shelvecreatedcommit(repo, node, name, match):
564 def _shelvecreatedcommit(repo, node, name, match):
565 info = {b'node': hex(node)}
565 info = {b'node': hex(node)}
566 shelf = ShelfDir(repo).get(name)
566 shelf = ShelfDir(repo).get(name)
567 shelf.writeinfo(info)
567 shelf.writeinfo(info)
568 bases = list(mutableancestors(repo[node]))
568 bases = list(mutableancestors(repo[node]))
569 shelf.writebundle(repo, bases, node)
569 shelf.writebundle(repo, bases, node)
570 with shelf.open_patch(b'wb') as fp:
570 with shelf.open_patch(b'wb') as fp:
571 cmdutil.exportfile(
571 cmdutil.exportfile(
572 repo, [node], fp, opts=mdiff.diffopts(git=True), match=match
572 repo, [node], fp, opts=mdiff.diffopts(git=True), match=match
573 )
573 )
574
574
575
575
576 def _includeunknownfiles(repo, pats, opts, extra):
576 def _includeunknownfiles(repo, pats, opts, extra):
577 s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True)
577 s = repo.status(match=scmutil.match(repo[None], pats, opts), unknown=True)
578 if s.unknown:
578 if s.unknown:
579 extra[b'shelve_unknown'] = b'\0'.join(s.unknown)
579 extra[b'shelve_unknown'] = b'\0'.join(s.unknown)
580 repo[None].add(s.unknown)
580 repo[None].add(s.unknown)
581
581
582
582
583 def _finishshelve(repo, tr):
583 def _finishshelve(repo, tr):
584 if _use_internal_phase(repo):
584 if _use_internal_phase(repo):
585 tr.close()
585 tr.close()
586 else:
586 else:
587 _aborttransaction(repo, tr)
587 _aborttransaction(repo, tr)
588
588
589
589
590 def createcmd(ui, repo, pats, opts):
590 def createcmd(ui, repo, pats, opts):
591 """subcommand that creates a new shelve"""
591 """subcommand that creates a new shelve"""
592 with repo.wlock():
592 with repo.wlock():
593 cmdutil.checkunfinished(repo)
593 cmdutil.checkunfinished(repo)
594 return _docreatecmd(ui, repo, pats, opts)
594 return _docreatecmd(ui, repo, pats, opts)
595
595
596
596
597 def _docreatecmd(ui, repo, pats, opts):
597 def _docreatecmd(ui, repo, pats, opts):
598 wctx = repo[None]
598 wctx = repo[None]
599 parents = wctx.parents()
599 parents = wctx.parents()
600 parent = parents[0]
600 parent = parents[0]
601 origbranch = wctx.branch()
601 origbranch = wctx.branch()
602
602
603 if parent.rev() != nullrev:
603 if parent.rev() != nullrev:
604 desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0]
604 desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0]
605 else:
605 else:
606 desc = b'(changes in empty repository)'
606 desc = b'(changes in empty repository)'
607
607
608 if not opts.get(b'message'):
608 if not opts.get(b'message'):
609 opts[b'message'] = desc
609 opts[b'message'] = desc
610
610
611 lock = tr = activebookmark = None
611 lock = tr = activebookmark = None
612 try:
612 try:
613 lock = repo.lock()
613 lock = repo.lock()
614
614
615 # use an uncommitted transaction to generate the bundle to avoid
615 # use an uncommitted transaction to generate the bundle to avoid
616 # pull races. ensure we don't print the abort message to stderr.
616 # pull races. ensure we don't print the abort message to stderr.
617 tr = repo.transaction(b'shelve', report=lambda x: None)
617 tr = repo.transaction(b'shelve', report=lambda x: None)
618
618
619 interactive = opts.get(b'interactive', False)
619 interactive = opts.get(b'interactive', False)
620 includeunknown = opts.get(b'unknown', False) and not opts.get(
620 includeunknown = opts.get(b'unknown', False) and not opts.get(
621 b'addremove', False
621 b'addremove', False
622 )
622 )
623
623
624 name = getshelvename(repo, parent, opts)
624 name = getshelvename(repo, parent, opts)
625 activebookmark = _backupactivebookmark(repo)
625 activebookmark = _backupactivebookmark(repo)
626 extra = {b'internal': b'shelve'}
626 extra = {b'internal': b'shelve'}
627 if includeunknown:
627 if includeunknown:
628 with repo.dirstate.changing_files(repo):
628 with repo.dirstate.changing_files(repo):
629 _includeunknownfiles(repo, pats, opts, extra)
629 _includeunknownfiles(repo, pats, opts, extra)
630
630
631 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
631 if _iswctxonnewbranch(repo) and not _isbareshelve(pats, opts):
632 # In non-bare shelve we don't store newly created branch
632 # In non-bare shelve we don't store newly created branch
633 # at bundled commit
633 # at bundled commit
634 repo.dirstate.setbranch(
634 repo.dirstate.setbranch(
635 repo[b'.'].branch(), repo.currenttransaction()
635 repo[b'.'].branch(), repo.currenttransaction()
636 )
636 )
637
637
638 commitfunc = getcommitfunc(extra, interactive, editor=True)
638 commitfunc = getcommitfunc(extra, interactive, editor=True)
639 if not interactive:
639 if not interactive:
640 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
640 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
641 else:
641 else:
642 node = cmdutil.dorecord(
642 node = cmdutil.dorecord(
643 ui,
643 ui,
644 repo,
644 repo,
645 commitfunc,
645 commitfunc,
646 None,
646 None,
647 False,
647 False,
648 cmdutil.recordfilter,
648 cmdutil.recordfilter,
649 *pats,
649 *pats,
650 **pycompat.strkwargs(opts)
650 **pycompat.strkwargs(opts)
651 )
651 )
652 if not node:
652 if not node:
653 _nothingtoshelvemessaging(ui, repo, pats, opts)
653 _nothingtoshelvemessaging(ui, repo, pats, opts)
654 return 1
654 return 1
655
655
656 match = _optimized_match(repo, node)
656 match = _optimized_match(repo, node)
657 _shelvecreatedcommit(repo, node, name, match)
657 _shelvecreatedcommit(repo, node, name, match)
658
658
659 ui.status(_(b'shelved as %s\n') % name)
659 ui.status(_(b'shelved as %s\n') % name)
660 if opts[b'keep']:
660 if opts[b'keep']:
661 with repo.dirstate.changing_parents(repo):
661 with repo.dirstate.changing_parents(repo):
662 scmutil.movedirstate(repo, parent, match)
662 scmutil.movedirstate(repo, parent, match)
663 else:
663 else:
664 hg.update(repo, parent.node())
664 hg.update(repo, parent.node())
665 ms = mergestatemod.mergestate.read(repo)
665 ms = mergestatemod.mergestate.read(repo)
666 if not ms.unresolvedcount():
666 if not ms.unresolvedcount():
667 ms.reset()
667 ms.reset()
668
668
669 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
669 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
670 repo.dirstate.setbranch(origbranch, repo.currenttransaction())
670 repo.dirstate.setbranch(origbranch, repo.currenttransaction())
671
671
672 _finishshelve(repo, tr)
672 _finishshelve(repo, tr)
673 finally:
673 finally:
674 _restoreactivebookmark(repo, activebookmark)
674 _restoreactivebookmark(repo, activebookmark)
675 lockmod.release(tr, lock)
675 lockmod.release(tr, lock)
676
676
677
677
678 def _isbareshelve(pats, opts):
678 def _isbareshelve(pats, opts):
679 return (
679 return (
680 not pats
680 not pats
681 and not opts.get(b'interactive', False)
681 and not opts.get(b'interactive', False)
682 and not opts.get(b'include', False)
682 and not opts.get(b'include', False)
683 and not opts.get(b'exclude', False)
683 and not opts.get(b'exclude', False)
684 )
684 )
685
685
686
686
687 def _iswctxonnewbranch(repo):
687 def _iswctxonnewbranch(repo):
688 return repo[None].branch() != repo[b'.'].branch()
688 return repo[None].branch() != repo[b'.'].branch()
689
689
690
690
691 def cleanupcmd(ui, repo):
691 def cleanupcmd(ui, repo):
692 """subcommand that deletes all shelves"""
692 """subcommand that deletes all shelves"""
693
693
694 with repo.wlock():
694 with repo.wlock():
695 shelf_dir = ShelfDir(repo)
695 shelf_dir = ShelfDir(repo)
696 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
696 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
697 for _mtime, name in shelf_dir.listshelves():
697 for _mtime, name in shelf_dir.listshelves():
698 shelf_dir.get(name).movetobackup(backupvfs)
698 shelf_dir.get(name).movetobackup(backupvfs)
699 cleanupoldbackups(repo)
699 cleanupoldbackups(repo)
700
700
701
701
702 def deletecmd(ui, repo, pats):
702 def deletecmd(ui, repo, pats):
703 """subcommand that deletes a specific shelve"""
703 """subcommand that deletes a specific shelve"""
704 if not pats:
704 if not pats:
705 raise error.InputError(_(b'no shelved changes specified!'))
705 raise error.InputError(_(b'no shelved changes specified!'))
706 with repo.wlock():
706 with repo.wlock():
707 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
707 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
708 for name in pats:
708 for name in pats:
709 shelf = ShelfDir(repo).get(name)
709 shelf = ShelfDir(repo).get(name)
710 if not shelf.exists():
710 if not shelf.exists():
711 raise error.InputError(
711 raise error.InputError(
712 _(b"shelved change '%s' not found") % name
712 _(b"shelved change '%s' not found") % name
713 )
713 )
714 shelf.movetobackup(backupvfs)
714 shelf.movetobackup(backupvfs)
715 cleanupoldbackups(repo)
715 cleanupoldbackups(repo)
716
716
717
717
718 def listcmd(ui, repo, pats, opts):
718 def listcmd(ui, repo, pats, opts):
719 """subcommand that displays the list of shelves"""
719 """subcommand that displays the list of shelves"""
720 pats = set(pats)
720 pats = set(pats)
721 width = 80
721 width = 80
722 if not ui.plain():
722 if not ui.plain():
723 width = ui.termwidth()
723 width = ui.termwidth()
724 namelabel = b'shelve.newest'
724 namelabel = b'shelve.newest'
725 ui.pager(b'shelve')
725 ui.pager(b'shelve')
726 shelf_dir = ShelfDir(repo)
726 shelf_dir = ShelfDir(repo)
727 for mtime, name in shelf_dir.listshelves():
727 for mtime, name in shelf_dir.listshelves():
728 if pats and name not in pats:
728 if pats and name not in pats:
729 continue
729 continue
730 ui.write(name, label=namelabel)
730 ui.write(name, label=namelabel)
731 namelabel = b'shelve.name'
731 namelabel = b'shelve.name'
732 if ui.quiet:
732 if ui.quiet:
733 ui.write(b'\n')
733 ui.write(b'\n')
734 continue
734 continue
735 ui.write(b' ' * (16 - len(name)))
735 ui.write(b' ' * (16 - len(name)))
736 used = 16
736 used = 16
737 date = dateutil.makedate(mtime)
737 date = dateutil.makedate(mtime)
738 age = b'(%s)' % templatefilters.age(date, abbrev=True)
738 age = b'(%s)' % templatefilters.age(date, abbrev=True)
739 ui.write(age, label=b'shelve.age')
739 ui.write(age, label=b'shelve.age')
740 ui.write(b' ' * (12 - len(age)))
740 ui.write(b' ' * (12 - len(age)))
741 used += 12
741 used += 12
742 with shelf_dir.get(name).load_patch(repo) as fp:
742 with shelf_dir.get(name).load_patch(repo) as fp:
743 while True:
743 while True:
744 line = fp.readline()
744 line = fp.readline()
745 if not line:
745 if not line:
746 break
746 break
747 if not line.startswith(b'#'):
747 if not line.startswith(b'#'):
748 desc = line.rstrip()
748 desc = line.rstrip()
749 if ui.formatted():
749 if ui.formatted():
750 desc = stringutil.ellipsis(desc, width - used)
750 desc = stringutil.ellipsis(desc, width - used)
751 ui.write(desc)
751 ui.write(desc)
752 break
752 break
753 ui.write(b'\n')
753 ui.write(b'\n')
754 if not (opts[b'patch'] or opts[b'stat']):
754 if not (opts[b'patch'] or opts[b'stat']):
755 continue
755 continue
756 difflines = fp.readlines()
756 difflines = fp.readlines()
757 if opts[b'patch']:
757 if opts[b'patch']:
758 for chunk, label in patch.difflabel(iter, difflines):
758 for chunk, label in patch.difflabel(iter, difflines):
759 ui.write(chunk, label=label)
759 ui.write(chunk, label=label)
760 if opts[b'stat']:
760 if opts[b'stat']:
761 for chunk, label in patch.diffstatui(difflines, width=width):
761 for chunk, label in patch.diffstatui(difflines, width=width):
762 ui.write(chunk, label=label)
762 ui.write(chunk, label=label)
763
763
764
764
765 def patchcmds(ui, repo, pats, opts):
765 def patchcmds(ui, repo, pats, opts):
766 """subcommand that displays shelves"""
766 """subcommand that displays shelves"""
767 shelf_dir = ShelfDir(repo)
767 shelf_dir = ShelfDir(repo)
768 if len(pats) == 0:
768 if len(pats) == 0:
769 shelves = shelf_dir.listshelves()
769 shelves = shelf_dir.listshelves()
770 if not shelves:
770 if not shelves:
771 raise error.Abort(_(b"there are no shelves to show"))
771 raise error.Abort(_(b"there are no shelves to show"))
772 mtime, name = shelves[0]
772 mtime, name = shelves[0]
773 pats = [name]
773 pats = [name]
774
774
775 for shelfname in pats:
775 for shelfname in pats:
776 if not shelf_dir.get(shelfname).exists():
776 if not shelf_dir.get(shelfname).exists():
777 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
777 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
778
778
779 listcmd(ui, repo, pats, opts)
779 listcmd(ui, repo, pats, opts)
780
780
781
781
782 def checkparents(repo, state):
782 def checkparents(repo, state):
783 """check parent while resuming an unshelve"""
783 """check parent while resuming an unshelve"""
784 if state.parents != repo.dirstate.parents():
784 if state.parents != repo.dirstate.parents():
785 raise error.Abort(
785 raise error.Abort(
786 _(b'working directory parents do not match unshelve state')
786 _(b'working directory parents do not match unshelve state')
787 )
787 )
788
788
789
789
790 def _loadshelvedstate(ui, repo, opts):
790 def _loadshelvedstate(ui, repo, opts):
791 try:
791 try:
792 state = shelvedstate.load(repo)
792 state = shelvedstate.load(repo)
793 if opts.get(b'keep') is None:
793 if opts.get(b'keep') is None:
794 opts[b'keep'] = state.keep
794 opts[b'keep'] = state.keep
795 except FileNotFoundError:
795 except FileNotFoundError:
796 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
796 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
797 except error.CorruptedState as err:
797 except error.CorruptedState as err:
798 ui.debug(pycompat.bytestr(err) + b'\n')
798 ui.debug(pycompat.bytestr(err) + b'\n')
799 if opts.get(b'continue'):
799 if opts.get(b'continue'):
800 msg = _(b'corrupted shelved state file')
800 msg = _(b'corrupted shelved state file')
801 hint = _(
801 hint = _(
802 b'please run hg unshelve --abort to abort unshelve '
802 b'please run hg unshelve --abort to abort unshelve '
803 b'operation'
803 b'operation'
804 )
804 )
805 raise error.Abort(msg, hint=hint)
805 raise error.Abort(msg, hint=hint)
806 elif opts.get(b'abort'):
806 elif opts.get(b'abort'):
807 shelvedstate.clear(repo)
807 shelvedstate.clear(repo)
808 raise error.Abort(
808 raise error.Abort(
809 _(
809 _(
810 b'could not read shelved state file, your '
810 b'could not read shelved state file, your '
811 b'working copy may be in an unexpected state\n'
811 b'working copy may be in an unexpected state\n'
812 b'please update to some commit\n'
812 b'please update to some commit\n'
813 )
813 )
814 )
814 )
815 return state
815 return state
816
816
817
817
818 def unshelveabort(ui, repo, state):
818 def unshelveabort(ui, repo, state):
819 """subcommand that abort an in-progress unshelve"""
819 """subcommand that abort an in-progress unshelve"""
820 with repo.lock():
820 with repo.lock():
821 try:
821 try:
822 checkparents(repo, state)
822 checkparents(repo, state)
823
823
824 merge.clean_update(state.pendingctx)
824 merge.clean_update(state.pendingctx)
825 if state.activebookmark and state.activebookmark in repo._bookmarks:
825 if state.activebookmark and state.activebookmark in repo._bookmarks:
826 bookmarks.activate(repo, state.activebookmark)
826 bookmarks.activate(repo, state.activebookmark)
827 mergefiles(ui, repo, state.wctx, state.pendingctx)
827 mergefiles(ui, repo, state.wctx, state.pendingctx)
828 if not _use_internal_phase(repo):
828 if not _use_internal_phase(repo):
829 repair.strip(
829 repair.strip(
830 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
830 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
831 )
831 )
832 finally:
832 finally:
833 shelvedstate.clear(repo)
833 shelvedstate.clear(repo)
834 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
834 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
835
835
836
836
837 def hgabortunshelve(ui, repo):
837 def hgabortunshelve(ui, repo):
838 """logic to abort unshelve using 'hg abort"""
838 """logic to abort unshelve using 'hg abort"""
839 with repo.wlock():
839 with repo.wlock():
840 state = _loadshelvedstate(ui, repo, {b'abort': True})
840 state = _loadshelvedstate(ui, repo, {b'abort': True})
841 return unshelveabort(ui, repo, state)
841 return unshelveabort(ui, repo, state)
842
842
843
843
844 def mergefiles(ui, repo, wctx, shelvectx):
844 def mergefiles(ui, repo, wctx, shelvectx):
845 """updates to wctx and merges the changes from shelvectx into the
845 """updates to wctx and merges the changes from shelvectx into the
846 dirstate."""
846 dirstate."""
847 with ui.configoverride({(b'ui', b'quiet'): True}):
847 with ui.configoverride({(b'ui', b'quiet'): True}):
848 hg.update(repo, wctx.node())
848 hg.update(repo, wctx.node())
849 cmdutil.revert(ui, repo, shelvectx)
849 cmdutil.revert(ui, repo, shelvectx)
850
850
851
851
852 def restorebranch(ui, repo, branchtorestore):
852 def restorebranch(ui, repo, branchtorestore):
853 if branchtorestore and branchtorestore != repo.dirstate.branch():
853 if branchtorestore and branchtorestore != repo.dirstate.branch():
854 repo.dirstate.setbranch(branchtorestore, repo.currenttransaction())
854 repo.dirstate.setbranch(branchtorestore, repo.currenttransaction())
855 ui.status(
855 ui.status(
856 _(b'marked working directory as branch %s\n') % branchtorestore
856 _(b'marked working directory as branch %s\n') % branchtorestore
857 )
857 )
858
858
859
859
860 def unshelvecleanup(ui, repo, name, opts):
860 def unshelvecleanup(ui, repo, name, opts):
861 """remove related files after an unshelve"""
861 """remove related files after an unshelve"""
862 if not opts.get(b'keep'):
862 if not opts.get(b'keep'):
863 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
863 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
864 ShelfDir(repo).get(name).movetobackup(backupvfs)
864 ShelfDir(repo).get(name).movetobackup(backupvfs)
865 cleanupoldbackups(repo)
865 cleanupoldbackups(repo)
866
866
867
867
868 def unshelvecontinue(ui, repo, state, opts):
868 def unshelvecontinue(ui, repo, state, opts):
869 """subcommand to continue an in-progress unshelve"""
869 """subcommand to continue an in-progress unshelve"""
870 # We're finishing off a merge. First parent is our original
870 # We're finishing off a merge. First parent is our original
871 # parent, second is the temporary "fake" commit we're unshelving.
871 # parent, second is the temporary "fake" commit we're unshelving.
872 interactive = state.interactive
872 interactive = state.interactive
873 basename = state.name
873 basename = state.name
874 with repo.lock():
874 with repo.lock():
875 checkparents(repo, state)
875 checkparents(repo, state)
876 ms = mergestatemod.mergestate.read(repo)
876 ms = mergestatemod.mergestate.read(repo)
877 if ms.unresolvedcount():
877 if ms.unresolvedcount():
878 raise error.Abort(
878 raise error.Abort(
879 _(b"unresolved conflicts, can't continue"),
879 _(b"unresolved conflicts, can't continue"),
880 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
880 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
881 )
881 )
882
882
883 shelvectx = repo[state.parents[1]]
883 shelvectx = repo[state.parents[1]]
884 pendingctx = state.pendingctx
884 pendingctx = state.pendingctx
885
885
886 with repo.dirstate.changing_parents(repo):
886 with repo.dirstate.changing_parents(repo):
887 repo.setparents(state.pendingctx.node(), repo.nullid)
887 repo.setparents(state.pendingctx.node(), repo.nullid)
888 repo.dirstate.write(repo.currenttransaction())
888 repo.dirstate.write(repo.currenttransaction())
889
889
890 targetphase = _target_phase(repo)
890 targetphase = _target_phase(repo)
891 overrides = {(b'phases', b'new-commit'): targetphase}
891 overrides = {(b'phases', b'new-commit'): targetphase}
892 with repo.ui.configoverride(overrides, b'unshelve'):
892 with repo.ui.configoverride(overrides, b'unshelve'):
893 with repo.dirstate.changing_parents(repo):
893 with repo.dirstate.changing_parents(repo):
894 repo.setparents(state.parents[0], repo.nullid)
894 repo.setparents(state.parents[0], repo.nullid)
895 newnode, ispartialunshelve = _createunshelvectx(
895 newnode, ispartialunshelve = _createunshelvectx(
896 ui, repo, shelvectx, basename, interactive, opts
896 ui, repo, shelvectx, basename, interactive, opts
897 )
897 )
898
898
899 if newnode is None:
899 if newnode is None:
900 shelvectx = state.pendingctx
900 shelvectx = state.pendingctx
901 msg = _(
901 msg = _(
902 b'note: unshelved changes already existed '
902 b'note: unshelved changes already existed '
903 b'in the working copy\n'
903 b'in the working copy\n'
904 )
904 )
905 ui.status(msg)
905 ui.status(msg)
906 else:
906 else:
907 # only strip the shelvectx if we produced one
907 # only strip the shelvectx if we produced one
908 state.nodestoremove.append(newnode)
908 state.nodestoremove.append(newnode)
909 shelvectx = repo[newnode]
909 shelvectx = repo[newnode]
910
910
911 merge.update(pendingctx)
911 merge.update(pendingctx)
912 mergefiles(ui, repo, state.wctx, shelvectx)
912 mergefiles(ui, repo, state.wctx, shelvectx)
913 restorebranch(ui, repo, state.branchtorestore)
913 restorebranch(ui, repo, state.branchtorestore)
914
914
915 if not _use_internal_phase(repo):
915 if not _use_internal_phase(repo):
916 repair.strip(
916 repair.strip(
917 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
917 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
918 )
918 )
919 shelvedstate.clear(repo)
919 shelvedstate.clear(repo)
920 if not ispartialunshelve:
920 if not ispartialunshelve:
921 unshelvecleanup(ui, repo, state.name, opts)
921 unshelvecleanup(ui, repo, state.name, opts)
922 _restoreactivebookmark(repo, state.activebookmark)
922 _restoreactivebookmark(repo, state.activebookmark)
923 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
923 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
924
924
925
925
926 def hgcontinueunshelve(ui, repo):
926 def hgcontinueunshelve(ui, repo):
927 """logic to resume unshelve using 'hg continue'"""
927 """logic to resume unshelve using 'hg continue'"""
928 with repo.wlock():
928 with repo.wlock():
929 state = _loadshelvedstate(ui, repo, {b'continue': True})
929 state = _loadshelvedstate(ui, repo, {b'continue': True})
930 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
930 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
931
931
932
932
933 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
933 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
934 """Temporarily commit working copy changes before moving unshelve commit"""
934 """Temporarily commit working copy changes before moving unshelve commit"""
935 # Store pending changes in a commit and remember added in case a shelve
935 # Store pending changes in a commit and remember added in case a shelve
936 # contains unknown files that are part of the pending change
936 # contains unknown files that are part of the pending change
937 s = repo.status()
937 s = repo.status()
938 addedbefore = frozenset(s.added)
938 addedbefore = frozenset(s.added)
939 if not (s.modified or s.added or s.removed):
939 if not (s.modified or s.added or s.removed):
940 return tmpwctx, addedbefore
940 return tmpwctx, addedbefore
941 ui.status(
941 ui.status(
942 _(
942 _(
943 b"temporarily committing pending changes "
943 b"temporarily committing pending changes "
944 b"(restore with 'hg unshelve --abort')\n"
944 b"(restore with 'hg unshelve --abort')\n"
945 )
945 )
946 )
946 )
947 extra = {b'internal': b'shelve'}
947 extra = {b'internal': b'shelve'}
948 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
948 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
949 tempopts = {}
949 tempopts = {}
950 tempopts[b'message'] = b"pending changes temporary commit"
950 tempopts[b'message'] = b"pending changes temporary commit"
951 tempopts[b'date'] = opts.get(b'date')
951 tempopts[b'date'] = opts.get(b'date')
952 with ui.configoverride({(b'ui', b'quiet'): True}):
952 with ui.configoverride({(b'ui', b'quiet'): True}):
953 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
953 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
954 tmpwctx = repo[node]
954 tmpwctx = repo[node]
955 return tmpwctx, addedbefore
955 return tmpwctx, addedbefore
956
956
957
957
958 def _unshelverestorecommit(ui, repo, tr, basename):
958 def _unshelverestorecommit(ui, repo, tr, basename):
959 """Recreate commit in the repository during the unshelve"""
959 """Recreate commit in the repository during the unshelve"""
960 repo = repo.unfiltered()
960 repo = repo.unfiltered()
961 node = None
961 node = None
962 shelf = ShelfDir(repo).get(basename)
962 shelf = ShelfDir(repo).get(basename)
963 if shelf.hasinfo():
963 if shelf.hasinfo():
964 node = shelf.readinfo()[b'node']
964 node = shelf.readinfo()[b'node']
965 if node is None or node not in repo:
965 if node is None or node not in repo:
966 with ui.configoverride({(b'ui', b'quiet'): True}):
966 with ui.configoverride({(b'ui', b'quiet'): True}):
967 shelvectx = shelf.applybundle(repo, tr)
967 shelvectx = shelf.applybundle(repo, tr)
968 # We might not strip the unbundled changeset, so we should keep track of
968 # We might not strip the unbundled changeset, so we should keep track of
969 # the unshelve node in case we need to reuse it (eg: unshelve --keep)
969 # the unshelve node in case we need to reuse it (eg: unshelve --keep)
970 if node is None:
970 if node is None:
971 info = {b'node': hex(shelvectx.node())}
971 info = {b'node': hex(shelvectx.node())}
972 shelf.writeinfo(info)
972 shelf.writeinfo(info)
973 else:
973 else:
974 shelvectx = repo[node]
974 shelvectx = repo[node]
975
975
976 return repo, shelvectx
976 return repo, shelvectx
977
977
978
978
979 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
979 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
980 """Handles the creation of unshelve commit and updates the shelve if it
980 """Handles the creation of unshelve commit and updates the shelve if it
981 was partially unshelved.
981 was partially unshelved.
982
982
983 If interactive is:
983 If interactive is:
984
984
985 * False: Commits all the changes in the working directory.
985 * False: Commits all the changes in the working directory.
986 * True: Prompts the user to select changes to unshelve and commit them.
986 * True: Prompts the user to select changes to unshelve and commit them.
987 Update the shelve with remaining changes.
987 Update the shelve with remaining changes.
988
988
989 Returns the node of the new commit formed and a bool indicating whether
989 Returns the node of the new commit formed and a bool indicating whether
990 the shelve was partially unshelved.Creates a commit ctx to unshelve
990 the shelve was partially unshelved.Creates a commit ctx to unshelve
991 interactively or non-interactively.
991 interactively or non-interactively.
992
992
993 The user might want to unshelve certain changes only from the stored
993 The user might want to unshelve certain changes only from the stored
994 shelve in interactive. So, we would create two commits. One with requested
994 shelve in interactive. So, we would create two commits. One with requested
995 changes to unshelve at that time and the latter is shelved for future.
995 changes to unshelve at that time and the latter is shelved for future.
996
996
997 Here, we return both the newnode which is created interactively and a
997 Here, we return both the newnode which is created interactively and a
998 bool to know whether the shelve is partly done or completely done.
998 bool to know whether the shelve is partly done or completely done.
999 """
999 """
1000 opts[b'message'] = shelvectx.description()
1000 opts[b'message'] = shelvectx.description()
1001 opts[b'interactive-unshelve'] = True
1001 opts[b'interactive-unshelve'] = True
1002 pats = []
1002 pats = []
1003 if not interactive:
1003 if not interactive:
1004 newnode = repo.commit(
1004 newnode = repo.commit(
1005 text=shelvectx.description(),
1005 text=shelvectx.description(),
1006 extra=shelvectx.extra(),
1006 extra=shelvectx.extra(),
1007 user=shelvectx.user(),
1007 user=shelvectx.user(),
1008 date=shelvectx.date(),
1008 date=shelvectx.date(),
1009 )
1009 )
1010 return newnode, False
1010 return newnode, False
1011
1011
1012 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True)
1012 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True)
1013 newnode = cmdutil.dorecord(
1013 newnode = cmdutil.dorecord(
1014 ui,
1014 ui,
1015 repo,
1015 repo,
1016 commitfunc,
1016 commitfunc,
1017 None,
1017 None,
1018 False,
1018 False,
1019 cmdutil.recordfilter,
1019 cmdutil.recordfilter,
1020 *pats,
1020 *pats,
1021 **pycompat.strkwargs(opts)
1021 **pycompat.strkwargs(opts)
1022 )
1022 )
1023 snode = repo.commit(
1023 snode = repo.commit(
1024 text=shelvectx.description(),
1024 text=shelvectx.description(),
1025 extra=shelvectx.extra(),
1025 extra=shelvectx.extra(),
1026 user=shelvectx.user(),
1026 user=shelvectx.user(),
1027 )
1027 )
1028 if snode:
1028 if snode:
1029 m = _optimized_match(repo, snode)
1029 m = _optimized_match(repo, snode)
1030 _shelvecreatedcommit(repo, snode, basename, m)
1030 _shelvecreatedcommit(repo, snode, basename, m)
1031
1031
1032 return newnode, bool(snode)
1032 return newnode, bool(snode)
1033
1033
1034
1034
1035 def _rebaserestoredcommit(
1035 def _rebaserestoredcommit(
1036 ui,
1036 ui,
1037 repo,
1037 repo,
1038 opts,
1038 opts,
1039 tr,
1039 tr,
1040 oldtiprev,
1040 oldtiprev,
1041 basename,
1041 basename,
1042 pctx,
1042 pctx,
1043 tmpwctx,
1043 tmpwctx,
1044 shelvectx,
1044 shelvectx,
1045 branchtorestore,
1045 branchtorestore,
1046 activebookmark,
1046 activebookmark,
1047 ):
1047 ):
1048 """Rebase restored commit from its original location to a destination"""
1048 """Rebase restored commit from its original location to a destination"""
1049 # If the shelve is not immediately on top of the commit
1049 # If the shelve is not immediately on top of the commit
1050 # we'll be merging with, rebase it to be on top.
1050 # we'll be merging with, rebase it to be on top.
1051 interactive = opts.get(b'interactive')
1051 interactive = opts.get(b'interactive')
1052 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
1052 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
1053 # We won't skip on interactive mode because, the user might want to
1053 # We won't skip on interactive mode because, the user might want to
1054 # unshelve certain changes only.
1054 # unshelve certain changes only.
1055 return shelvectx, False
1055 return shelvectx, False
1056
1056
1057 overrides = {
1057 overrides = {
1058 (b'ui', b'forcemerge'): opts.get(b'tool', b''),
1058 (b'ui', b'forcemerge'): opts.get(b'tool', b''),
1059 (b'phases', b'new-commit'): phases.secret,
1059 (b'phases', b'new-commit'): phases.secret,
1060 }
1060 }
1061 with repo.ui.configoverride(overrides, b'unshelve'):
1061 with repo.ui.configoverride(overrides, b'unshelve'):
1062 ui.status(_(b'rebasing shelved changes\n'))
1062 ui.status(_(b'rebasing shelved changes\n'))
1063 stats = merge.graft(
1063 stats = merge.graft(
1064 repo,
1064 repo,
1065 shelvectx,
1065 shelvectx,
1066 labels=[
1066 labels=[
1067 b'working-copy',
1067 b'working-copy',
1068 b'shelved change',
1068 b'shelved change',
1069 b'parent of shelved change',
1069 b'parent of shelved change',
1070 ],
1070 ],
1071 keepconflictparent=True,
1071 keepconflictparent=True,
1072 )
1072 )
1073 if stats.unresolvedcount:
1073 if stats.unresolvedcount:
1074 tr.close()
1074 tr.close()
1075
1075
1076 nodestoremove = [
1076 nodestoremove = [
1077 repo.changelog.node(rev) for rev in range(oldtiprev, len(repo))
1077 repo.changelog.node(rev) for rev in range(oldtiprev, len(repo))
1078 ]
1078 ]
1079 shelvedstate.save(
1079 shelvedstate.save(
1080 repo,
1080 repo,
1081 basename,
1081 basename,
1082 pctx,
1082 pctx,
1083 tmpwctx,
1083 tmpwctx,
1084 nodestoremove,
1084 nodestoremove,
1085 branchtorestore,
1085 branchtorestore,
1086 opts.get(b'keep'),
1086 opts.get(b'keep'),
1087 activebookmark,
1087 activebookmark,
1088 interactive,
1088 interactive,
1089 )
1089 )
1090 raise error.ConflictResolutionRequired(b'unshelve')
1090 raise error.ConflictResolutionRequired(b'unshelve')
1091
1091
1092 with repo.dirstate.changing_parents(repo):
1092 with repo.dirstate.changing_parents(repo):
1093 repo.setparents(tmpwctx.node(), repo.nullid)
1093 repo.setparents(tmpwctx.node(), repo.nullid)
1094 newnode, ispartialunshelve = _createunshelvectx(
1094 newnode, ispartialunshelve = _createunshelvectx(
1095 ui, repo, shelvectx, basename, interactive, opts
1095 ui, repo, shelvectx, basename, interactive, opts
1096 )
1096 )
1097
1097
1098 if newnode is None:
1098 if newnode is None:
1099 shelvectx = tmpwctx
1099 shelvectx = tmpwctx
1100 msg = _(
1100 msg = _(
1101 b'note: unshelved changes already existed '
1101 b'note: unshelved changes already existed '
1102 b'in the working copy\n'
1102 b'in the working copy\n'
1103 )
1103 )
1104 ui.status(msg)
1104 ui.status(msg)
1105 else:
1105 else:
1106 shelvectx = repo[newnode]
1106 shelvectx = repo[newnode]
1107 merge.update(tmpwctx)
1107 merge.update(tmpwctx)
1108
1108
1109 return shelvectx, ispartialunshelve
1109 return shelvectx, ispartialunshelve
1110
1110
1111
1111
1112 def _forgetunknownfiles(repo, shelvectx, addedbefore):
1112 def _forgetunknownfiles(repo, shelvectx, addedbefore):
1113 # Forget any files that were unknown before the shelve, unknown before
1113 # Forget any files that were unknown before the shelve, unknown before
1114 # unshelve started, but are now added.
1114 # unshelve started, but are now added.
1115 shelveunknown = shelvectx.extra().get(b'shelve_unknown')
1115 shelveunknown = shelvectx.extra().get(b'shelve_unknown')
1116 if not shelveunknown:
1116 if not shelveunknown:
1117 return
1117 return
1118 shelveunknown = frozenset(shelveunknown.split(b'\0'))
1118 shelveunknown = frozenset(shelveunknown.split(b'\0'))
1119 addedafter = frozenset(repo.status().added)
1119 addedafter = frozenset(repo.status().added)
1120 toforget = (addedafter & shelveunknown) - addedbefore
1120 toforget = (addedafter & shelveunknown) - addedbefore
1121 repo[None].forget(toforget)
1121 repo[None].forget(toforget)
1122
1122
1123
1123
1124 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
1124 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
1125 _restoreactivebookmark(repo, activebookmark)
1125 _restoreactivebookmark(repo, activebookmark)
1126 # The transaction aborting will strip all the commits for us,
1126 # The transaction aborting will strip all the commits for us,
1127 # but it doesn't update the inmemory structures, so addchangegroup
1127 # but it doesn't update the inmemory structures, so addchangegroup
1128 # hooks still fire and try to operate on the missing commits.
1128 # hooks still fire and try to operate on the missing commits.
1129 # Clean up manually to prevent this.
1129 # Clean up manually to prevent this.
1130 repo.unfiltered().changelog.strip(oldtiprev, tr)
1130 repo.unfiltered().changelog.strip(oldtiprev, tr)
1131 _aborttransaction(repo, tr)
1131 _aborttransaction(repo, tr)
1132
1132
1133
1133
1134 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
1134 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
1135 """Check potential problems which may result from working
1135 """Check potential problems which may result from working
1136 copy having untracked changes."""
1136 copy having untracked changes."""
1137 wcdeleted = set(repo.status().deleted)
1137 wcdeleted = set(repo.status().deleted)
1138 shelvetouched = set(shelvectx.files())
1138 shelvetouched = set(shelvectx.files())
1139 intersection = wcdeleted.intersection(shelvetouched)
1139 intersection = wcdeleted.intersection(shelvetouched)
1140 if intersection:
1140 if intersection:
1141 m = _(b"shelved change touches missing files")
1141 m = _(b"shelved change touches missing files")
1142 hint = _(b"run hg status to see which files are missing")
1142 hint = _(b"run hg status to see which files are missing")
1143 raise error.Abort(m, hint=hint)
1143 raise error.Abort(m, hint=hint)
1144
1144
1145
1145
1146 def unshelvecmd(ui, repo, *shelved, **opts):
1146 def unshelvecmd(ui, repo, *shelved, **opts):
1147 opts = pycompat.byteskwargs(opts)
1147 opts = pycompat.byteskwargs(opts)
1148 abortf = opts.get(b'abort')
1148 abortf = opts.get(b'abort')
1149 continuef = opts.get(b'continue')
1149 continuef = opts.get(b'continue')
1150 interactive = opts.get(b'interactive')
1150 interactive = opts.get(b'interactive')
1151 if not abortf and not continuef:
1151 if not abortf and not continuef:
1152 cmdutil.checkunfinished(repo)
1152 cmdutil.checkunfinished(repo)
1153 shelved = list(shelved)
1153 shelved = list(shelved)
1154 if opts.get(b"name"):
1154 if opts.get(b"name"):
1155 shelved.append(opts[b"name"])
1155 shelved.append(opts[b"name"])
1156
1156
1157 if interactive and opts.get(b'keep'):
1157 if interactive and opts.get(b'keep'):
1158 raise error.InputError(
1158 raise error.InputError(
1159 _(b'--keep on --interactive is not yet supported')
1159 _(b'--keep on --interactive is not yet supported')
1160 )
1160 )
1161 if abortf or continuef:
1161 if abortf or continuef:
1162 if abortf and continuef:
1162 if abortf and continuef:
1163 raise error.InputError(_(b'cannot use both abort and continue'))
1163 raise error.InputError(_(b'cannot use both abort and continue'))
1164 if shelved:
1164 if shelved:
1165 raise error.InputError(
1165 raise error.InputError(
1166 _(
1166 _(
1167 b'cannot combine abort/continue with '
1167 b'cannot combine abort/continue with '
1168 b'naming a shelved change'
1168 b'naming a shelved change'
1169 )
1169 )
1170 )
1170 )
1171 if abortf and opts.get(b'tool', False):
1171 if abortf and opts.get(b'tool', False):
1172 ui.warn(_(b'tool option will be ignored\n'))
1172 ui.warn(_(b'tool option will be ignored\n'))
1173
1173
1174 state = _loadshelvedstate(ui, repo, opts)
1174 state = _loadshelvedstate(ui, repo, opts)
1175 if abortf:
1175 if abortf:
1176 return unshelveabort(ui, repo, state)
1176 return unshelveabort(ui, repo, state)
1177 elif continuef and interactive:
1177 elif continuef and interactive:
1178 raise error.InputError(
1178 raise error.InputError(
1179 _(b'cannot use both continue and interactive')
1179 _(b'cannot use both continue and interactive')
1180 )
1180 )
1181 elif continuef:
1181 elif continuef:
1182 return unshelvecontinue(ui, repo, state, opts)
1182 return unshelvecontinue(ui, repo, state, opts)
1183 elif len(shelved) > 1:
1183 elif len(shelved) > 1:
1184 raise error.InputError(_(b'can only unshelve one change at a time'))
1184 raise error.InputError(_(b'can only unshelve one change at a time'))
1185 elif not shelved:
1185 elif not shelved:
1186 shelved = ShelfDir(repo).listshelves()
1186 shelved = ShelfDir(repo).listshelves()
1187 if not shelved:
1187 if not shelved:
1188 raise error.StateError(_(b'no shelved changes to apply!'))
1188 raise error.StateError(_(b'no shelved changes to apply!'))
1189 basename = shelved[0][1]
1189 basename = shelved[0][1]
1190 ui.status(_(b"unshelving change '%s'\n") % basename)
1190 ui.status(_(b"unshelving change '%s'\n") % basename)
1191 else:
1191 else:
1192 basename = shelved[0]
1192 basename = shelved[0]
1193
1193
1194 if not ShelfDir(repo).get(basename).exists():
1194 if not ShelfDir(repo).get(basename).exists():
1195 raise error.InputError(_(b"shelved change '%s' not found") % basename)
1195 raise error.InputError(_(b"shelved change '%s' not found") % basename)
1196
1196
1197 return _dounshelve(ui, repo, basename, opts)
1197 return _dounshelve(ui, repo, basename, opts)
1198
1198
1199
1199
1200 def _dounshelve(ui, repo, basename, opts):
1200 def _dounshelve(ui, repo, basename, opts):
1201 repo = repo.unfiltered()
1201 repo = repo.unfiltered()
1202 lock = tr = None
1202 lock = tr = None
1203 try:
1203 try:
1204 lock = repo.lock()
1204 lock = repo.lock()
1205 tr = repo.transaction(b'unshelve', report=lambda x: None)
1205 tr = repo.transaction(b'unshelve', report=lambda x: None)
1206 oldtiprev = len(repo)
1206 oldtiprev = len(repo)
1207
1207
1208 pctx = repo[b'.']
1208 pctx = repo[b'.']
1209 # The goal is to have a commit structure like so:
1209 # The goal is to have a commit structure like so:
1210 # ...-> pctx -> tmpwctx -> shelvectx
1210 # ...-> pctx -> tmpwctx -> shelvectx
1211 # where tmpwctx is an optional commit with the user's pending changes
1211 # where tmpwctx is an optional commit with the user's pending changes
1212 # and shelvectx is the unshelved changes. Then we merge it all down
1212 # and shelvectx is the unshelved changes. Then we merge it all down
1213 # to the original pctx.
1213 # to the original pctx.
1214
1214
1215 activebookmark = _backupactivebookmark(repo)
1215 activebookmark = _backupactivebookmark(repo)
1216 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts, pctx)
1216 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts, pctx)
1217 repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
1217 repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
1218 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1218 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1219 branchtorestore = b''
1219 branchtorestore = b''
1220 if shelvectx.branch() != shelvectx.p1().branch():
1220 if shelvectx.branch() != shelvectx.p1().branch():
1221 branchtorestore = shelvectx.branch()
1221 branchtorestore = shelvectx.branch()
1222
1222
1223 shelvectx, ispartialunshelve = _rebaserestoredcommit(
1223 shelvectx, ispartialunshelve = _rebaserestoredcommit(
1224 ui,
1224 ui,
1225 repo,
1225 repo,
1226 opts,
1226 opts,
1227 tr,
1227 tr,
1228 oldtiprev,
1228 oldtiprev,
1229 basename,
1229 basename,
1230 pctx,
1230 pctx,
1231 tmpwctx,
1231 tmpwctx,
1232 shelvectx,
1232 shelvectx,
1233 branchtorestore,
1233 branchtorestore,
1234 activebookmark,
1234 activebookmark,
1235 )
1235 )
1236 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
1236 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
1237 with ui.configoverride(overrides, b'unshelve'):
1237 with ui.configoverride(overrides, b'unshelve'):
1238 mergefiles(ui, repo, pctx, shelvectx)
1238 mergefiles(ui, repo, pctx, shelvectx)
1239 restorebranch(ui, repo, branchtorestore)
1239 restorebranch(ui, repo, branchtorestore)
1240 shelvedstate.clear(repo)
1240 shelvedstate.clear(repo)
1241 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1241 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1242 with repo.dirstate.changing_files(repo):
1242 with repo.dirstate.changing_files(repo):
1243 _forgetunknownfiles(repo, shelvectx, addedbefore)
1243 _forgetunknownfiles(repo, shelvectx, addedbefore)
1244 if not ispartialunshelve:
1244 if not ispartialunshelve:
1245 unshelvecleanup(ui, repo, basename, opts)
1245 unshelvecleanup(ui, repo, basename, opts)
1246 finally:
1246 finally:
1247 if tr:
1247 if tr:
1248 tr.release()
1248 tr.release()
1249 lockmod.release(lock)
1249 lockmod.release(lock)
General Comments 0
You need to be logged in to leave comments. Login now