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