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