##// END OF EJS Templates
branch: pass current transaction when writing branch in shelve
marmoute -
r51159:921f4834 default
parent child Browse files
Show More
@@ -1,1247 +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)
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, b'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(repo[b'.'].branch())
634 repo.dirstate.setbranch(
635 repo[b'.'].branch(), repo.currenttransaction()
636 )
635
637
636 commitfunc = getcommitfunc(extra, interactive, editor=True)
638 commitfunc = getcommitfunc(extra, interactive, editor=True)
637 if not interactive:
639 if not interactive:
638 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
640 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
639 else:
641 else:
640 node = cmdutil.dorecord(
642 node = cmdutil.dorecord(
641 ui,
643 ui,
642 repo,
644 repo,
643 commitfunc,
645 commitfunc,
644 None,
646 None,
645 False,
647 False,
646 cmdutil.recordfilter,
648 cmdutil.recordfilter,
647 *pats,
649 *pats,
648 **pycompat.strkwargs(opts)
650 **pycompat.strkwargs(opts)
649 )
651 )
650 if not node:
652 if not node:
651 _nothingtoshelvemessaging(ui, repo, pats, opts)
653 _nothingtoshelvemessaging(ui, repo, pats, opts)
652 return 1
654 return 1
653
655
654 match = _optimized_match(repo, node)
656 match = _optimized_match(repo, node)
655 _shelvecreatedcommit(repo, node, name, match)
657 _shelvecreatedcommit(repo, node, name, match)
656
658
657 ui.status(_(b'shelved as %s\n') % name)
659 ui.status(_(b'shelved as %s\n') % name)
658 if opts[b'keep']:
660 if opts[b'keep']:
659 with repo.dirstate.changing_parents(repo):
661 with repo.dirstate.changing_parents(repo):
660 scmutil.movedirstate(repo, parent, match)
662 scmutil.movedirstate(repo, parent, match)
661 else:
663 else:
662 hg.update(repo, parent.node())
664 hg.update(repo, parent.node())
663 ms = mergestatemod.mergestate.read(repo)
665 ms = mergestatemod.mergestate.read(repo)
664 if not ms.unresolvedcount():
666 if not ms.unresolvedcount():
665 ms.reset()
667 ms.reset()
666
668
667 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
669 if origbranch != repo[b'.'].branch() and not _isbareshelve(pats, opts):
668 repo.dirstate.setbranch(origbranch)
670 repo.dirstate.setbranch(origbranch, repo.currenttransaction())
669
671
670 _finishshelve(repo, tr)
672 _finishshelve(repo, tr)
671 finally:
673 finally:
672 _restoreactivebookmark(repo, activebookmark)
674 _restoreactivebookmark(repo, activebookmark)
673 lockmod.release(tr, lock)
675 lockmod.release(tr, lock)
674
676
675
677
676 def _isbareshelve(pats, opts):
678 def _isbareshelve(pats, opts):
677 return (
679 return (
678 not pats
680 not pats
679 and not opts.get(b'interactive', False)
681 and not opts.get(b'interactive', False)
680 and not opts.get(b'include', False)
682 and not opts.get(b'include', False)
681 and not opts.get(b'exclude', False)
683 and not opts.get(b'exclude', False)
682 )
684 )
683
685
684
686
685 def _iswctxonnewbranch(repo):
687 def _iswctxonnewbranch(repo):
686 return repo[None].branch() != repo[b'.'].branch()
688 return repo[None].branch() != repo[b'.'].branch()
687
689
688
690
689 def cleanupcmd(ui, repo):
691 def cleanupcmd(ui, repo):
690 """subcommand that deletes all shelves"""
692 """subcommand that deletes all shelves"""
691
693
692 with repo.wlock():
694 with repo.wlock():
693 shelf_dir = ShelfDir(repo)
695 shelf_dir = ShelfDir(repo)
694 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
696 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
695 for _mtime, name in shelf_dir.listshelves():
697 for _mtime, name in shelf_dir.listshelves():
696 shelf_dir.get(name).movetobackup(backupvfs)
698 shelf_dir.get(name).movetobackup(backupvfs)
697 cleanupoldbackups(repo)
699 cleanupoldbackups(repo)
698
700
699
701
700 def deletecmd(ui, repo, pats):
702 def deletecmd(ui, repo, pats):
701 """subcommand that deletes a specific shelve"""
703 """subcommand that deletes a specific shelve"""
702 if not pats:
704 if not pats:
703 raise error.InputError(_(b'no shelved changes specified!'))
705 raise error.InputError(_(b'no shelved changes specified!'))
704 with repo.wlock():
706 with repo.wlock():
705 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
707 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
706 for name in pats:
708 for name in pats:
707 shelf = ShelfDir(repo).get(name)
709 shelf = ShelfDir(repo).get(name)
708 if not shelf.exists():
710 if not shelf.exists():
709 raise error.InputError(
711 raise error.InputError(
710 _(b"shelved change '%s' not found") % name
712 _(b"shelved change '%s' not found") % name
711 )
713 )
712 shelf.movetobackup(backupvfs)
714 shelf.movetobackup(backupvfs)
713 cleanupoldbackups(repo)
715 cleanupoldbackups(repo)
714
716
715
717
716 def listcmd(ui, repo, pats, opts):
718 def listcmd(ui, repo, pats, opts):
717 """subcommand that displays the list of shelves"""
719 """subcommand that displays the list of shelves"""
718 pats = set(pats)
720 pats = set(pats)
719 width = 80
721 width = 80
720 if not ui.plain():
722 if not ui.plain():
721 width = ui.termwidth()
723 width = ui.termwidth()
722 namelabel = b'shelve.newest'
724 namelabel = b'shelve.newest'
723 ui.pager(b'shelve')
725 ui.pager(b'shelve')
724 shelf_dir = ShelfDir(repo)
726 shelf_dir = ShelfDir(repo)
725 for mtime, name in shelf_dir.listshelves():
727 for mtime, name in shelf_dir.listshelves():
726 if pats and name not in pats:
728 if pats and name not in pats:
727 continue
729 continue
728 ui.write(name, label=namelabel)
730 ui.write(name, label=namelabel)
729 namelabel = b'shelve.name'
731 namelabel = b'shelve.name'
730 if ui.quiet:
732 if ui.quiet:
731 ui.write(b'\n')
733 ui.write(b'\n')
732 continue
734 continue
733 ui.write(b' ' * (16 - len(name)))
735 ui.write(b' ' * (16 - len(name)))
734 used = 16
736 used = 16
735 date = dateutil.makedate(mtime)
737 date = dateutil.makedate(mtime)
736 age = b'(%s)' % templatefilters.age(date, abbrev=True)
738 age = b'(%s)' % templatefilters.age(date, abbrev=True)
737 ui.write(age, label=b'shelve.age')
739 ui.write(age, label=b'shelve.age')
738 ui.write(b' ' * (12 - len(age)))
740 ui.write(b' ' * (12 - len(age)))
739 used += 12
741 used += 12
740 with shelf_dir.get(name).load_patch(repo) as fp:
742 with shelf_dir.get(name).load_patch(repo) as fp:
741 while True:
743 while True:
742 line = fp.readline()
744 line = fp.readline()
743 if not line:
745 if not line:
744 break
746 break
745 if not line.startswith(b'#'):
747 if not line.startswith(b'#'):
746 desc = line.rstrip()
748 desc = line.rstrip()
747 if ui.formatted():
749 if ui.formatted():
748 desc = stringutil.ellipsis(desc, width - used)
750 desc = stringutil.ellipsis(desc, width - used)
749 ui.write(desc)
751 ui.write(desc)
750 break
752 break
751 ui.write(b'\n')
753 ui.write(b'\n')
752 if not (opts[b'patch'] or opts[b'stat']):
754 if not (opts[b'patch'] or opts[b'stat']):
753 continue
755 continue
754 difflines = fp.readlines()
756 difflines = fp.readlines()
755 if opts[b'patch']:
757 if opts[b'patch']:
756 for chunk, label in patch.difflabel(iter, difflines):
758 for chunk, label in patch.difflabel(iter, difflines):
757 ui.write(chunk, label=label)
759 ui.write(chunk, label=label)
758 if opts[b'stat']:
760 if opts[b'stat']:
759 for chunk, label in patch.diffstatui(difflines, width=width):
761 for chunk, label in patch.diffstatui(difflines, width=width):
760 ui.write(chunk, label=label)
762 ui.write(chunk, label=label)
761
763
762
764
763 def patchcmds(ui, repo, pats, opts):
765 def patchcmds(ui, repo, pats, opts):
764 """subcommand that displays shelves"""
766 """subcommand that displays shelves"""
765 shelf_dir = ShelfDir(repo)
767 shelf_dir = ShelfDir(repo)
766 if len(pats) == 0:
768 if len(pats) == 0:
767 shelves = shelf_dir.listshelves()
769 shelves = shelf_dir.listshelves()
768 if not shelves:
770 if not shelves:
769 raise error.Abort(_(b"there are no shelves to show"))
771 raise error.Abort(_(b"there are no shelves to show"))
770 mtime, name = shelves[0]
772 mtime, name = shelves[0]
771 pats = [name]
773 pats = [name]
772
774
773 for shelfname in pats:
775 for shelfname in pats:
774 if not shelf_dir.get(shelfname).exists():
776 if not shelf_dir.get(shelfname).exists():
775 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
777 raise error.Abort(_(b"cannot find shelf %s") % shelfname)
776
778
777 listcmd(ui, repo, pats, opts)
779 listcmd(ui, repo, pats, opts)
778
780
779
781
780 def checkparents(repo, state):
782 def checkparents(repo, state):
781 """check parent while resuming an unshelve"""
783 """check parent while resuming an unshelve"""
782 if state.parents != repo.dirstate.parents():
784 if state.parents != repo.dirstate.parents():
783 raise error.Abort(
785 raise error.Abort(
784 _(b'working directory parents do not match unshelve state')
786 _(b'working directory parents do not match unshelve state')
785 )
787 )
786
788
787
789
788 def _loadshelvedstate(ui, repo, opts):
790 def _loadshelvedstate(ui, repo, opts):
789 try:
791 try:
790 state = shelvedstate.load(repo)
792 state = shelvedstate.load(repo)
791 if opts.get(b'keep') is None:
793 if opts.get(b'keep') is None:
792 opts[b'keep'] = state.keep
794 opts[b'keep'] = state.keep
793 except FileNotFoundError:
795 except FileNotFoundError:
794 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
796 cmdutil.wrongtooltocontinue(repo, _(b'unshelve'))
795 except error.CorruptedState as err:
797 except error.CorruptedState as err:
796 ui.debug(pycompat.bytestr(err) + b'\n')
798 ui.debug(pycompat.bytestr(err) + b'\n')
797 if opts.get(b'continue'):
799 if opts.get(b'continue'):
798 msg = _(b'corrupted shelved state file')
800 msg = _(b'corrupted shelved state file')
799 hint = _(
801 hint = _(
800 b'please run hg unshelve --abort to abort unshelve '
802 b'please run hg unshelve --abort to abort unshelve '
801 b'operation'
803 b'operation'
802 )
804 )
803 raise error.Abort(msg, hint=hint)
805 raise error.Abort(msg, hint=hint)
804 elif opts.get(b'abort'):
806 elif opts.get(b'abort'):
805 shelvedstate.clear(repo)
807 shelvedstate.clear(repo)
806 raise error.Abort(
808 raise error.Abort(
807 _(
809 _(
808 b'could not read shelved state file, your '
810 b'could not read shelved state file, your '
809 b'working copy may be in an unexpected state\n'
811 b'working copy may be in an unexpected state\n'
810 b'please update to some commit\n'
812 b'please update to some commit\n'
811 )
813 )
812 )
814 )
813 return state
815 return state
814
816
815
817
816 def unshelveabort(ui, repo, state):
818 def unshelveabort(ui, repo, state):
817 """subcommand that abort an in-progress unshelve"""
819 """subcommand that abort an in-progress unshelve"""
818 with repo.lock():
820 with repo.lock():
819 try:
821 try:
820 checkparents(repo, state)
822 checkparents(repo, state)
821
823
822 merge.clean_update(state.pendingctx)
824 merge.clean_update(state.pendingctx)
823 if state.activebookmark and state.activebookmark in repo._bookmarks:
825 if state.activebookmark and state.activebookmark in repo._bookmarks:
824 bookmarks.activate(repo, state.activebookmark)
826 bookmarks.activate(repo, state.activebookmark)
825 mergefiles(ui, repo, state.wctx, state.pendingctx)
827 mergefiles(ui, repo, state.wctx, state.pendingctx)
826 if not _use_internal_phase(repo):
828 if not _use_internal_phase(repo):
827 repair.strip(
829 repair.strip(
828 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
830 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
829 )
831 )
830 finally:
832 finally:
831 shelvedstate.clear(repo)
833 shelvedstate.clear(repo)
832 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
834 ui.warn(_(b"unshelve of '%s' aborted\n") % state.name)
833
835
834
836
835 def hgabortunshelve(ui, repo):
837 def hgabortunshelve(ui, repo):
836 """logic to abort unshelve using 'hg abort"""
838 """logic to abort unshelve using 'hg abort"""
837 with repo.wlock():
839 with repo.wlock():
838 state = _loadshelvedstate(ui, repo, {b'abort': True})
840 state = _loadshelvedstate(ui, repo, {b'abort': True})
839 return unshelveabort(ui, repo, state)
841 return unshelveabort(ui, repo, state)
840
842
841
843
842 def mergefiles(ui, repo, wctx, shelvectx):
844 def mergefiles(ui, repo, wctx, shelvectx):
843 """updates to wctx and merges the changes from shelvectx into the
845 """updates to wctx and merges the changes from shelvectx into the
844 dirstate."""
846 dirstate."""
845 with ui.configoverride({(b'ui', b'quiet'): True}):
847 with ui.configoverride({(b'ui', b'quiet'): True}):
846 hg.update(repo, wctx.node())
848 hg.update(repo, wctx.node())
847 cmdutil.revert(ui, repo, shelvectx)
849 cmdutil.revert(ui, repo, shelvectx)
848
850
849
851
850 def restorebranch(ui, repo, branchtorestore):
852 def restorebranch(ui, repo, branchtorestore):
851 if branchtorestore and branchtorestore != repo.dirstate.branch():
853 if branchtorestore and branchtorestore != repo.dirstate.branch():
852 repo.dirstate.setbranch(branchtorestore)
854 repo.dirstate.setbranch(branchtorestore, repo.currenttransaction())
853 ui.status(
855 ui.status(
854 _(b'marked working directory as branch %s\n') % branchtorestore
856 _(b'marked working directory as branch %s\n') % branchtorestore
855 )
857 )
856
858
857
859
858 def unshelvecleanup(ui, repo, name, opts):
860 def unshelvecleanup(ui, repo, name, opts):
859 """remove related files after an unshelve"""
861 """remove related files after an unshelve"""
860 if not opts.get(b'keep'):
862 if not opts.get(b'keep'):
861 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
863 backupvfs = vfsmod.vfs(repo.vfs.join(backupdir))
862 ShelfDir(repo).get(name).movetobackup(backupvfs)
864 ShelfDir(repo).get(name).movetobackup(backupvfs)
863 cleanupoldbackups(repo)
865 cleanupoldbackups(repo)
864
866
865
867
866 def unshelvecontinue(ui, repo, state, opts):
868 def unshelvecontinue(ui, repo, state, opts):
867 """subcommand to continue an in-progress unshelve"""
869 """subcommand to continue an in-progress unshelve"""
868 # We're finishing off a merge. First parent is our original
870 # We're finishing off a merge. First parent is our original
869 # parent, second is the temporary "fake" commit we're unshelving.
871 # parent, second is the temporary "fake" commit we're unshelving.
870 interactive = state.interactive
872 interactive = state.interactive
871 basename = state.name
873 basename = state.name
872 with repo.lock():
874 with repo.lock():
873 checkparents(repo, state)
875 checkparents(repo, state)
874 ms = mergestatemod.mergestate.read(repo)
876 ms = mergestatemod.mergestate.read(repo)
875 if ms.unresolvedcount():
877 if ms.unresolvedcount():
876 raise error.Abort(
878 raise error.Abort(
877 _(b"unresolved conflicts, can't continue"),
879 _(b"unresolved conflicts, can't continue"),
878 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
880 hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"),
879 )
881 )
880
882
881 shelvectx = repo[state.parents[1]]
883 shelvectx = repo[state.parents[1]]
882 pendingctx = state.pendingctx
884 pendingctx = state.pendingctx
883
885
884 with repo.dirstate.changing_parents(repo):
886 with repo.dirstate.changing_parents(repo):
885 repo.setparents(state.pendingctx.node(), repo.nullid)
887 repo.setparents(state.pendingctx.node(), repo.nullid)
886 repo.dirstate.write(repo.currenttransaction())
888 repo.dirstate.write(repo.currenttransaction())
887
889
888 targetphase = _target_phase(repo)
890 targetphase = _target_phase(repo)
889 overrides = {(b'phases', b'new-commit'): targetphase}
891 overrides = {(b'phases', b'new-commit'): targetphase}
890 with repo.ui.configoverride(overrides, b'unshelve'):
892 with repo.ui.configoverride(overrides, b'unshelve'):
891 with repo.dirstate.changing_parents(repo):
893 with repo.dirstate.changing_parents(repo):
892 repo.setparents(state.parents[0], repo.nullid)
894 repo.setparents(state.parents[0], repo.nullid)
893 newnode, ispartialunshelve = _createunshelvectx(
895 newnode, ispartialunshelve = _createunshelvectx(
894 ui, repo, shelvectx, basename, interactive, opts
896 ui, repo, shelvectx, basename, interactive, opts
895 )
897 )
896
898
897 if newnode is None:
899 if newnode is None:
898 shelvectx = state.pendingctx
900 shelvectx = state.pendingctx
899 msg = _(
901 msg = _(
900 b'note: unshelved changes already existed '
902 b'note: unshelved changes already existed '
901 b'in the working copy\n'
903 b'in the working copy\n'
902 )
904 )
903 ui.status(msg)
905 ui.status(msg)
904 else:
906 else:
905 # only strip the shelvectx if we produced one
907 # only strip the shelvectx if we produced one
906 state.nodestoremove.append(newnode)
908 state.nodestoremove.append(newnode)
907 shelvectx = repo[newnode]
909 shelvectx = repo[newnode]
908
910
909 merge.update(pendingctx)
911 merge.update(pendingctx)
910 mergefiles(ui, repo, state.wctx, shelvectx)
912 mergefiles(ui, repo, state.wctx, shelvectx)
911 restorebranch(ui, repo, state.branchtorestore)
913 restorebranch(ui, repo, state.branchtorestore)
912
914
913 if not _use_internal_phase(repo):
915 if not _use_internal_phase(repo):
914 repair.strip(
916 repair.strip(
915 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
917 ui, repo, state.nodestoremove, backup=False, topic=b'shelve'
916 )
918 )
917 shelvedstate.clear(repo)
919 shelvedstate.clear(repo)
918 if not ispartialunshelve:
920 if not ispartialunshelve:
919 unshelvecleanup(ui, repo, state.name, opts)
921 unshelvecleanup(ui, repo, state.name, opts)
920 _restoreactivebookmark(repo, state.activebookmark)
922 _restoreactivebookmark(repo, state.activebookmark)
921 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
923 ui.status(_(b"unshelve of '%s' complete\n") % state.name)
922
924
923
925
924 def hgcontinueunshelve(ui, repo):
926 def hgcontinueunshelve(ui, repo):
925 """logic to resume unshelve using 'hg continue'"""
927 """logic to resume unshelve using 'hg continue'"""
926 with repo.wlock():
928 with repo.wlock():
927 state = _loadshelvedstate(ui, repo, {b'continue': True})
929 state = _loadshelvedstate(ui, repo, {b'continue': True})
928 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
930 return unshelvecontinue(ui, repo, state, {b'keep': state.keep})
929
931
930
932
931 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
933 def _commitworkingcopychanges(ui, repo, opts, tmpwctx):
932 """Temporarily commit working copy changes before moving unshelve commit"""
934 """Temporarily commit working copy changes before moving unshelve commit"""
933 # 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
934 # contains unknown files that are part of the pending change
936 # contains unknown files that are part of the pending change
935 s = repo.status()
937 s = repo.status()
936 addedbefore = frozenset(s.added)
938 addedbefore = frozenset(s.added)
937 if not (s.modified or s.added or s.removed):
939 if not (s.modified or s.added or s.removed):
938 return tmpwctx, addedbefore
940 return tmpwctx, addedbefore
939 ui.status(
941 ui.status(
940 _(
942 _(
941 b"temporarily committing pending changes "
943 b"temporarily committing pending changes "
942 b"(restore with 'hg unshelve --abort')\n"
944 b"(restore with 'hg unshelve --abort')\n"
943 )
945 )
944 )
946 )
945 extra = {b'internal': b'shelve'}
947 extra = {b'internal': b'shelve'}
946 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
948 commitfunc = getcommitfunc(extra=extra, interactive=False, editor=False)
947 tempopts = {}
949 tempopts = {}
948 tempopts[b'message'] = b"pending changes temporary commit"
950 tempopts[b'message'] = b"pending changes temporary commit"
949 tempopts[b'date'] = opts.get(b'date')
951 tempopts[b'date'] = opts.get(b'date')
950 with ui.configoverride({(b'ui', b'quiet'): True}):
952 with ui.configoverride({(b'ui', b'quiet'): True}):
951 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
953 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
952 tmpwctx = repo[node]
954 tmpwctx = repo[node]
953 return tmpwctx, addedbefore
955 return tmpwctx, addedbefore
954
956
955
957
956 def _unshelverestorecommit(ui, repo, tr, basename):
958 def _unshelverestorecommit(ui, repo, tr, basename):
957 """Recreate commit in the repository during the unshelve"""
959 """Recreate commit in the repository during the unshelve"""
958 repo = repo.unfiltered()
960 repo = repo.unfiltered()
959 node = None
961 node = None
960 shelf = ShelfDir(repo).get(basename)
962 shelf = ShelfDir(repo).get(basename)
961 if shelf.hasinfo():
963 if shelf.hasinfo():
962 node = shelf.readinfo()[b'node']
964 node = shelf.readinfo()[b'node']
963 if node is None or node not in repo:
965 if node is None or node not in repo:
964 with ui.configoverride({(b'ui', b'quiet'): True}):
966 with ui.configoverride({(b'ui', b'quiet'): True}):
965 shelvectx = shelf.applybundle(repo, tr)
967 shelvectx = shelf.applybundle(repo, tr)
966 # 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
967 # 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)
968 if node is None:
970 if node is None:
969 info = {b'node': hex(shelvectx.node())}
971 info = {b'node': hex(shelvectx.node())}
970 shelf.writeinfo(info)
972 shelf.writeinfo(info)
971 else:
973 else:
972 shelvectx = repo[node]
974 shelvectx = repo[node]
973
975
974 return repo, shelvectx
976 return repo, shelvectx
975
977
976
978
977 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
979 def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
978 """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
979 was partially unshelved.
981 was partially unshelved.
980
982
981 If interactive is:
983 If interactive is:
982
984
983 * False: Commits all the changes in the working directory.
985 * False: Commits all the changes in the working directory.
984 * 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.
985 Update the shelve with remaining changes.
987 Update the shelve with remaining changes.
986
988
987 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
988 the shelve was partially unshelved.Creates a commit ctx to unshelve
990 the shelve was partially unshelved.Creates a commit ctx to unshelve
989 interactively or non-interactively.
991 interactively or non-interactively.
990
992
991 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
992 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
993 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.
994
996
995 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
996 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.
997 """
999 """
998 opts[b'message'] = shelvectx.description()
1000 opts[b'message'] = shelvectx.description()
999 opts[b'interactive-unshelve'] = True
1001 opts[b'interactive-unshelve'] = True
1000 pats = []
1002 pats = []
1001 if not interactive:
1003 if not interactive:
1002 newnode = repo.commit(
1004 newnode = repo.commit(
1003 text=shelvectx.description(),
1005 text=shelvectx.description(),
1004 extra=shelvectx.extra(),
1006 extra=shelvectx.extra(),
1005 user=shelvectx.user(),
1007 user=shelvectx.user(),
1006 date=shelvectx.date(),
1008 date=shelvectx.date(),
1007 )
1009 )
1008 return newnode, False
1010 return newnode, False
1009
1011
1010 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True)
1012 commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, editor=True)
1011 newnode = cmdutil.dorecord(
1013 newnode = cmdutil.dorecord(
1012 ui,
1014 ui,
1013 repo,
1015 repo,
1014 commitfunc,
1016 commitfunc,
1015 None,
1017 None,
1016 False,
1018 False,
1017 cmdutil.recordfilter,
1019 cmdutil.recordfilter,
1018 *pats,
1020 *pats,
1019 **pycompat.strkwargs(opts)
1021 **pycompat.strkwargs(opts)
1020 )
1022 )
1021 snode = repo.commit(
1023 snode = repo.commit(
1022 text=shelvectx.description(),
1024 text=shelvectx.description(),
1023 extra=shelvectx.extra(),
1025 extra=shelvectx.extra(),
1024 user=shelvectx.user(),
1026 user=shelvectx.user(),
1025 )
1027 )
1026 if snode:
1028 if snode:
1027 m = _optimized_match(repo, snode)
1029 m = _optimized_match(repo, snode)
1028 _shelvecreatedcommit(repo, snode, basename, m)
1030 _shelvecreatedcommit(repo, snode, basename, m)
1029
1031
1030 return newnode, bool(snode)
1032 return newnode, bool(snode)
1031
1033
1032
1034
1033 def _rebaserestoredcommit(
1035 def _rebaserestoredcommit(
1034 ui,
1036 ui,
1035 repo,
1037 repo,
1036 opts,
1038 opts,
1037 tr,
1039 tr,
1038 oldtiprev,
1040 oldtiprev,
1039 basename,
1041 basename,
1040 pctx,
1042 pctx,
1041 tmpwctx,
1043 tmpwctx,
1042 shelvectx,
1044 shelvectx,
1043 branchtorestore,
1045 branchtorestore,
1044 activebookmark,
1046 activebookmark,
1045 ):
1047 ):
1046 """Rebase restored commit from its original location to a destination"""
1048 """Rebase restored commit from its original location to a destination"""
1047 # If the shelve is not immediately on top of the commit
1049 # If the shelve is not immediately on top of the commit
1048 # we'll be merging with, rebase it to be on top.
1050 # we'll be merging with, rebase it to be on top.
1049 interactive = opts.get(b'interactive')
1051 interactive = opts.get(b'interactive')
1050 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
1052 if tmpwctx.node() == shelvectx.p1().node() and not interactive:
1051 # 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
1052 # unshelve certain changes only.
1054 # unshelve certain changes only.
1053 return shelvectx, False
1055 return shelvectx, False
1054
1056
1055 overrides = {
1057 overrides = {
1056 (b'ui', b'forcemerge'): opts.get(b'tool', b''),
1058 (b'ui', b'forcemerge'): opts.get(b'tool', b''),
1057 (b'phases', b'new-commit'): phases.secret,
1059 (b'phases', b'new-commit'): phases.secret,
1058 }
1060 }
1059 with repo.ui.configoverride(overrides, b'unshelve'):
1061 with repo.ui.configoverride(overrides, b'unshelve'):
1060 ui.status(_(b'rebasing shelved changes\n'))
1062 ui.status(_(b'rebasing shelved changes\n'))
1061 stats = merge.graft(
1063 stats = merge.graft(
1062 repo,
1064 repo,
1063 shelvectx,
1065 shelvectx,
1064 labels=[
1066 labels=[
1065 b'working-copy',
1067 b'working-copy',
1066 b'shelved change',
1068 b'shelved change',
1067 b'parent of shelved change',
1069 b'parent of shelved change',
1068 ],
1070 ],
1069 keepconflictparent=True,
1071 keepconflictparent=True,
1070 )
1072 )
1071 if stats.unresolvedcount:
1073 if stats.unresolvedcount:
1072 tr.close()
1074 tr.close()
1073
1075
1074 nodestoremove = [
1076 nodestoremove = [
1075 repo.changelog.node(rev) for rev in range(oldtiprev, len(repo))
1077 repo.changelog.node(rev) for rev in range(oldtiprev, len(repo))
1076 ]
1078 ]
1077 shelvedstate.save(
1079 shelvedstate.save(
1078 repo,
1080 repo,
1079 basename,
1081 basename,
1080 pctx,
1082 pctx,
1081 tmpwctx,
1083 tmpwctx,
1082 nodestoremove,
1084 nodestoremove,
1083 branchtorestore,
1085 branchtorestore,
1084 opts.get(b'keep'),
1086 opts.get(b'keep'),
1085 activebookmark,
1087 activebookmark,
1086 interactive,
1088 interactive,
1087 )
1089 )
1088 raise error.ConflictResolutionRequired(b'unshelve')
1090 raise error.ConflictResolutionRequired(b'unshelve')
1089
1091
1090 with repo.dirstate.changing_parents(repo):
1092 with repo.dirstate.changing_parents(repo):
1091 repo.setparents(tmpwctx.node(), repo.nullid)
1093 repo.setparents(tmpwctx.node(), repo.nullid)
1092 newnode, ispartialunshelve = _createunshelvectx(
1094 newnode, ispartialunshelve = _createunshelvectx(
1093 ui, repo, shelvectx, basename, interactive, opts
1095 ui, repo, shelvectx, basename, interactive, opts
1094 )
1096 )
1095
1097
1096 if newnode is None:
1098 if newnode is None:
1097 shelvectx = tmpwctx
1099 shelvectx = tmpwctx
1098 msg = _(
1100 msg = _(
1099 b'note: unshelved changes already existed '
1101 b'note: unshelved changes already existed '
1100 b'in the working copy\n'
1102 b'in the working copy\n'
1101 )
1103 )
1102 ui.status(msg)
1104 ui.status(msg)
1103 else:
1105 else:
1104 shelvectx = repo[newnode]
1106 shelvectx = repo[newnode]
1105 merge.update(tmpwctx)
1107 merge.update(tmpwctx)
1106
1108
1107 return shelvectx, ispartialunshelve
1109 return shelvectx, ispartialunshelve
1108
1110
1109
1111
1110 def _forgetunknownfiles(repo, shelvectx, addedbefore):
1112 def _forgetunknownfiles(repo, shelvectx, addedbefore):
1111 # Forget any files that were unknown before the shelve, unknown before
1113 # Forget any files that were unknown before the shelve, unknown before
1112 # unshelve started, but are now added.
1114 # unshelve started, but are now added.
1113 shelveunknown = shelvectx.extra().get(b'shelve_unknown')
1115 shelveunknown = shelvectx.extra().get(b'shelve_unknown')
1114 if not shelveunknown:
1116 if not shelveunknown:
1115 return
1117 return
1116 shelveunknown = frozenset(shelveunknown.split(b'\0'))
1118 shelveunknown = frozenset(shelveunknown.split(b'\0'))
1117 addedafter = frozenset(repo.status().added)
1119 addedafter = frozenset(repo.status().added)
1118 toforget = (addedafter & shelveunknown) - addedbefore
1120 toforget = (addedafter & shelveunknown) - addedbefore
1119 repo[None].forget(toforget)
1121 repo[None].forget(toforget)
1120
1122
1121
1123
1122 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
1124 def _finishunshelve(repo, oldtiprev, tr, activebookmark):
1123 _restoreactivebookmark(repo, activebookmark)
1125 _restoreactivebookmark(repo, activebookmark)
1124 # The transaction aborting will strip all the commits for us,
1126 # The transaction aborting will strip all the commits for us,
1125 # but it doesn't update the inmemory structures, so addchangegroup
1127 # but it doesn't update the inmemory structures, so addchangegroup
1126 # hooks still fire and try to operate on the missing commits.
1128 # hooks still fire and try to operate on the missing commits.
1127 # Clean up manually to prevent this.
1129 # Clean up manually to prevent this.
1128 repo.unfiltered().changelog.strip(oldtiprev, tr)
1130 repo.unfiltered().changelog.strip(oldtiprev, tr)
1129 _aborttransaction(repo, tr)
1131 _aborttransaction(repo, tr)
1130
1132
1131
1133
1132 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
1134 def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
1133 """Check potential problems which may result from working
1135 """Check potential problems which may result from working
1134 copy having untracked changes."""
1136 copy having untracked changes."""
1135 wcdeleted = set(repo.status().deleted)
1137 wcdeleted = set(repo.status().deleted)
1136 shelvetouched = set(shelvectx.files())
1138 shelvetouched = set(shelvectx.files())
1137 intersection = wcdeleted.intersection(shelvetouched)
1139 intersection = wcdeleted.intersection(shelvetouched)
1138 if intersection:
1140 if intersection:
1139 m = _(b"shelved change touches missing files")
1141 m = _(b"shelved change touches missing files")
1140 hint = _(b"run hg status to see which files are missing")
1142 hint = _(b"run hg status to see which files are missing")
1141 raise error.Abort(m, hint=hint)
1143 raise error.Abort(m, hint=hint)
1142
1144
1143
1145
1144 def unshelvecmd(ui, repo, *shelved, **opts):
1146 def unshelvecmd(ui, repo, *shelved, **opts):
1145 opts = pycompat.byteskwargs(opts)
1147 opts = pycompat.byteskwargs(opts)
1146 abortf = opts.get(b'abort')
1148 abortf = opts.get(b'abort')
1147 continuef = opts.get(b'continue')
1149 continuef = opts.get(b'continue')
1148 interactive = opts.get(b'interactive')
1150 interactive = opts.get(b'interactive')
1149 if not abortf and not continuef:
1151 if not abortf and not continuef:
1150 cmdutil.checkunfinished(repo)
1152 cmdutil.checkunfinished(repo)
1151 shelved = list(shelved)
1153 shelved = list(shelved)
1152 if opts.get(b"name"):
1154 if opts.get(b"name"):
1153 shelved.append(opts[b"name"])
1155 shelved.append(opts[b"name"])
1154
1156
1155 if interactive and opts.get(b'keep'):
1157 if interactive and opts.get(b'keep'):
1156 raise error.InputError(
1158 raise error.InputError(
1157 _(b'--keep on --interactive is not yet supported')
1159 _(b'--keep on --interactive is not yet supported')
1158 )
1160 )
1159 if abortf or continuef:
1161 if abortf or continuef:
1160 if abortf and continuef:
1162 if abortf and continuef:
1161 raise error.InputError(_(b'cannot use both abort and continue'))
1163 raise error.InputError(_(b'cannot use both abort and continue'))
1162 if shelved:
1164 if shelved:
1163 raise error.InputError(
1165 raise error.InputError(
1164 _(
1166 _(
1165 b'cannot combine abort/continue with '
1167 b'cannot combine abort/continue with '
1166 b'naming a shelved change'
1168 b'naming a shelved change'
1167 )
1169 )
1168 )
1170 )
1169 if abortf and opts.get(b'tool', False):
1171 if abortf and opts.get(b'tool', False):
1170 ui.warn(_(b'tool option will be ignored\n'))
1172 ui.warn(_(b'tool option will be ignored\n'))
1171
1173
1172 state = _loadshelvedstate(ui, repo, opts)
1174 state = _loadshelvedstate(ui, repo, opts)
1173 if abortf:
1175 if abortf:
1174 return unshelveabort(ui, repo, state)
1176 return unshelveabort(ui, repo, state)
1175 elif continuef and interactive:
1177 elif continuef and interactive:
1176 raise error.InputError(
1178 raise error.InputError(
1177 _(b'cannot use both continue and interactive')
1179 _(b'cannot use both continue and interactive')
1178 )
1180 )
1179 elif continuef:
1181 elif continuef:
1180 return unshelvecontinue(ui, repo, state, opts)
1182 return unshelvecontinue(ui, repo, state, opts)
1181 elif len(shelved) > 1:
1183 elif len(shelved) > 1:
1182 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'))
1183 elif not shelved:
1185 elif not shelved:
1184 shelved = ShelfDir(repo).listshelves()
1186 shelved = ShelfDir(repo).listshelves()
1185 if not shelved:
1187 if not shelved:
1186 raise error.StateError(_(b'no shelved changes to apply!'))
1188 raise error.StateError(_(b'no shelved changes to apply!'))
1187 basename = shelved[0][1]
1189 basename = shelved[0][1]
1188 ui.status(_(b"unshelving change '%s'\n") % basename)
1190 ui.status(_(b"unshelving change '%s'\n") % basename)
1189 else:
1191 else:
1190 basename = shelved[0]
1192 basename = shelved[0]
1191
1193
1192 if not ShelfDir(repo).get(basename).exists():
1194 if not ShelfDir(repo).get(basename).exists():
1193 raise error.InputError(_(b"shelved change '%s' not found") % basename)
1195 raise error.InputError(_(b"shelved change '%s' not found") % basename)
1194
1196
1195 return _dounshelve(ui, repo, basename, opts)
1197 return _dounshelve(ui, repo, basename, opts)
1196
1198
1197
1199
1198 def _dounshelve(ui, repo, basename, opts):
1200 def _dounshelve(ui, repo, basename, opts):
1199 repo = repo.unfiltered()
1201 repo = repo.unfiltered()
1200 lock = tr = None
1202 lock = tr = None
1201 try:
1203 try:
1202 lock = repo.lock()
1204 lock = repo.lock()
1203 tr = repo.transaction(b'unshelve', report=lambda x: None)
1205 tr = repo.transaction(b'unshelve', report=lambda x: None)
1204 oldtiprev = len(repo)
1206 oldtiprev = len(repo)
1205
1207
1206 pctx = repo[b'.']
1208 pctx = repo[b'.']
1207 # The goal is to have a commit structure like so:
1209 # The goal is to have a commit structure like so:
1208 # ...-> pctx -> tmpwctx -> shelvectx
1210 # ...-> pctx -> tmpwctx -> shelvectx
1209 # 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
1210 # 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
1211 # to the original pctx.
1213 # to the original pctx.
1212
1214
1213 activebookmark = _backupactivebookmark(repo)
1215 activebookmark = _backupactivebookmark(repo)
1214 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts, pctx)
1216 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts, pctx)
1215 repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
1217 repo, shelvectx = _unshelverestorecommit(ui, repo, tr, basename)
1216 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1218 _checkunshelveuntrackedproblems(ui, repo, shelvectx)
1217 branchtorestore = b''
1219 branchtorestore = b''
1218 if shelvectx.branch() != shelvectx.p1().branch():
1220 if shelvectx.branch() != shelvectx.p1().branch():
1219 branchtorestore = shelvectx.branch()
1221 branchtorestore = shelvectx.branch()
1220
1222
1221 shelvectx, ispartialunshelve = _rebaserestoredcommit(
1223 shelvectx, ispartialunshelve = _rebaserestoredcommit(
1222 ui,
1224 ui,
1223 repo,
1225 repo,
1224 opts,
1226 opts,
1225 tr,
1227 tr,
1226 oldtiprev,
1228 oldtiprev,
1227 basename,
1229 basename,
1228 pctx,
1230 pctx,
1229 tmpwctx,
1231 tmpwctx,
1230 shelvectx,
1232 shelvectx,
1231 branchtorestore,
1233 branchtorestore,
1232 activebookmark,
1234 activebookmark,
1233 )
1235 )
1234 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
1236 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
1235 with ui.configoverride(overrides, b'unshelve'):
1237 with ui.configoverride(overrides, b'unshelve'):
1236 mergefiles(ui, repo, pctx, shelvectx)
1238 mergefiles(ui, repo, pctx, shelvectx)
1237 restorebranch(ui, repo, branchtorestore)
1239 restorebranch(ui, repo, branchtorestore)
1238 shelvedstate.clear(repo)
1240 shelvedstate.clear(repo)
1239 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1241 _finishunshelve(repo, oldtiprev, tr, activebookmark)
1240 with repo.dirstate.changing_files(repo):
1242 with repo.dirstate.changing_files(repo):
1241 _forgetunknownfiles(repo, shelvectx, addedbefore)
1243 _forgetunknownfiles(repo, shelvectx, addedbefore)
1242 if not ispartialunshelve:
1244 if not ispartialunshelve:
1243 unshelvecleanup(ui, repo, basename, opts)
1245 unshelvecleanup(ui, repo, basename, opts)
1244 finally:
1246 finally:
1245 if tr:
1247 if tr:
1246 tr.release()
1248 tr.release()
1247 lockmod.release(lock)
1249 lockmod.release(lock)
General Comments 0
You need to be logged in to leave comments. Login now