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