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