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