##// END OF EJS Templates
repo-upgrade: write new requirement before upgrading the dirstate...
Raphaël Gomès -
r51345:b4b1791f stable
parent child Browse files
Show More
@@ -1,683 +1,689 b''
1 # upgrade.py - functions for in place upgrade of Mercurial repository
1 # upgrade.py - functions for in place upgrade of Mercurial repository
2 #
2 #
3 # Copyright (c) 2016-present, Gregory Szorc
3 # Copyright (c) 2016-present, Gregory Szorc
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
8
9 import stat
9 import stat
10
10
11 from ..i18n import _
11 from ..i18n import _
12 from ..pycompat import getattr
12 from ..pycompat import getattr
13 from .. import (
13 from .. import (
14 changelog,
14 changelog,
15 error,
15 error,
16 filelog,
16 filelog,
17 manifest,
17 manifest,
18 metadata,
18 metadata,
19 pycompat,
19 pycompat,
20 requirements,
20 requirements,
21 scmutil,
21 scmutil,
22 store,
22 store,
23 util,
23 util,
24 vfs as vfsmod,
24 vfs as vfsmod,
25 )
25 )
26 from ..revlogutils import (
26 from ..revlogutils import (
27 constants as revlogconst,
27 constants as revlogconst,
28 flagutil,
28 flagutil,
29 nodemap,
29 nodemap,
30 sidedata as sidedatamod,
30 sidedata as sidedatamod,
31 )
31 )
32 from . import actions as upgrade_actions
32 from . import actions as upgrade_actions
33
33
34
34
35 def get_sidedata_helpers(srcrepo, dstrepo):
35 def get_sidedata_helpers(srcrepo, dstrepo):
36 use_w = srcrepo.ui.configbool(b'experimental', b'worker.repository-upgrade')
36 use_w = srcrepo.ui.configbool(b'experimental', b'worker.repository-upgrade')
37 sequential = pycompat.iswindows or not use_w
37 sequential = pycompat.iswindows or not use_w
38 if not sequential:
38 if not sequential:
39 srcrepo.register_sidedata_computer(
39 srcrepo.register_sidedata_computer(
40 revlogconst.KIND_CHANGELOG,
40 revlogconst.KIND_CHANGELOG,
41 sidedatamod.SD_FILES,
41 sidedatamod.SD_FILES,
42 (sidedatamod.SD_FILES,),
42 (sidedatamod.SD_FILES,),
43 metadata._get_worker_sidedata_adder(srcrepo, dstrepo),
43 metadata._get_worker_sidedata_adder(srcrepo, dstrepo),
44 flagutil.REVIDX_HASCOPIESINFO,
44 flagutil.REVIDX_HASCOPIESINFO,
45 replace=True,
45 replace=True,
46 )
46 )
47 return sidedatamod.get_sidedata_helpers(srcrepo, dstrepo._wanted_sidedata)
47 return sidedatamod.get_sidedata_helpers(srcrepo, dstrepo._wanted_sidedata)
48
48
49
49
50 def _revlogfrompath(repo, rl_type, path):
50 def _revlogfrompath(repo, rl_type, path):
51 """Obtain a revlog from a repo path.
51 """Obtain a revlog from a repo path.
52
52
53 An instance of the appropriate class is returned.
53 An instance of the appropriate class is returned.
54 """
54 """
55 if rl_type & store.FILEFLAGS_CHANGELOG:
55 if rl_type & store.FILEFLAGS_CHANGELOG:
56 return changelog.changelog(repo.svfs)
56 return changelog.changelog(repo.svfs)
57 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
57 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
58 mandir = b''
58 mandir = b''
59 if b'/' in path:
59 if b'/' in path:
60 mandir = path.rsplit(b'/', 1)[0]
60 mandir = path.rsplit(b'/', 1)[0]
61 return manifest.manifestrevlog(
61 return manifest.manifestrevlog(
62 repo.nodeconstants, repo.svfs, tree=mandir
62 repo.nodeconstants, repo.svfs, tree=mandir
63 )
63 )
64 else:
64 else:
65 # drop the extension and the `data/` prefix
65 # drop the extension and the `data/` prefix
66 path_part = path.rsplit(b'.', 1)[0].split(b'/', 1)
66 path_part = path.rsplit(b'.', 1)[0].split(b'/', 1)
67 if len(path_part) < 2:
67 if len(path_part) < 2:
68 msg = _(b'cannot recognize revlog from filename: %s')
68 msg = _(b'cannot recognize revlog from filename: %s')
69 msg %= path
69 msg %= path
70 raise error.Abort(msg)
70 raise error.Abort(msg)
71 path = path_part[1]
71 path = path_part[1]
72 return filelog.filelog(repo.svfs, path)
72 return filelog.filelog(repo.svfs, path)
73
73
74
74
75 def _copyrevlog(tr, destrepo, oldrl, rl_type, unencodedname):
75 def _copyrevlog(tr, destrepo, oldrl, rl_type, unencodedname):
76 """copy all relevant files for `oldrl` into `destrepo` store
76 """copy all relevant files for `oldrl` into `destrepo` store
77
77
78 Files are copied "as is" without any transformation. The copy is performed
78 Files are copied "as is" without any transformation. The copy is performed
79 without extra checks. Callers are responsible for making sure the copied
79 without extra checks. Callers are responsible for making sure the copied
80 content is compatible with format of the destination repository.
80 content is compatible with format of the destination repository.
81 """
81 """
82 oldrl = getattr(oldrl, '_revlog', oldrl)
82 oldrl = getattr(oldrl, '_revlog', oldrl)
83 newrl = _revlogfrompath(destrepo, rl_type, unencodedname)
83 newrl = _revlogfrompath(destrepo, rl_type, unencodedname)
84 newrl = getattr(newrl, '_revlog', newrl)
84 newrl = getattr(newrl, '_revlog', newrl)
85
85
86 oldvfs = oldrl.opener
86 oldvfs = oldrl.opener
87 newvfs = newrl.opener
87 newvfs = newrl.opener
88 oldindex = oldvfs.join(oldrl._indexfile)
88 oldindex = oldvfs.join(oldrl._indexfile)
89 newindex = newvfs.join(newrl._indexfile)
89 newindex = newvfs.join(newrl._indexfile)
90 olddata = oldvfs.join(oldrl._datafile)
90 olddata = oldvfs.join(oldrl._datafile)
91 newdata = newvfs.join(newrl._datafile)
91 newdata = newvfs.join(newrl._datafile)
92
92
93 with newvfs(newrl._indexfile, b'w'):
93 with newvfs(newrl._indexfile, b'w'):
94 pass # create all the directories
94 pass # create all the directories
95
95
96 util.copyfile(oldindex, newindex)
96 util.copyfile(oldindex, newindex)
97 copydata = oldrl.opener.exists(oldrl._datafile)
97 copydata = oldrl.opener.exists(oldrl._datafile)
98 if copydata:
98 if copydata:
99 util.copyfile(olddata, newdata)
99 util.copyfile(olddata, newdata)
100
100
101 if rl_type & store.FILEFLAGS_FILELOG:
101 if rl_type & store.FILEFLAGS_FILELOG:
102 destrepo.svfs.fncache.add(unencodedname)
102 destrepo.svfs.fncache.add(unencodedname)
103 if copydata:
103 if copydata:
104 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
104 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
105
105
106
106
107 UPGRADE_CHANGELOG = b"changelog"
107 UPGRADE_CHANGELOG = b"changelog"
108 UPGRADE_MANIFEST = b"manifest"
108 UPGRADE_MANIFEST = b"manifest"
109 UPGRADE_FILELOGS = b"all-filelogs"
109 UPGRADE_FILELOGS = b"all-filelogs"
110
110
111 UPGRADE_ALL_REVLOGS = frozenset(
111 UPGRADE_ALL_REVLOGS = frozenset(
112 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOGS]
112 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOGS]
113 )
113 )
114
114
115
115
116 def matchrevlog(revlogfilter, rl_type):
116 def matchrevlog(revlogfilter, rl_type):
117 """check if a revlog is selected for cloning.
117 """check if a revlog is selected for cloning.
118
118
119 In other words, are there any updates which need to be done on revlog
119 In other words, are there any updates which need to be done on revlog
120 or it can be blindly copied.
120 or it can be blindly copied.
121
121
122 The store entry is checked against the passed filter"""
122 The store entry is checked against the passed filter"""
123 if rl_type & store.FILEFLAGS_CHANGELOG:
123 if rl_type & store.FILEFLAGS_CHANGELOG:
124 return UPGRADE_CHANGELOG in revlogfilter
124 return UPGRADE_CHANGELOG in revlogfilter
125 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
125 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
126 return UPGRADE_MANIFEST in revlogfilter
126 return UPGRADE_MANIFEST in revlogfilter
127 assert rl_type & store.FILEFLAGS_FILELOG
127 assert rl_type & store.FILEFLAGS_FILELOG
128 return UPGRADE_FILELOGS in revlogfilter
128 return UPGRADE_FILELOGS in revlogfilter
129
129
130
130
131 def _perform_clone(
131 def _perform_clone(
132 ui,
132 ui,
133 dstrepo,
133 dstrepo,
134 tr,
134 tr,
135 old_revlog,
135 old_revlog,
136 rl_type,
136 rl_type,
137 unencoded,
137 unencoded,
138 upgrade_op,
138 upgrade_op,
139 sidedata_helpers,
139 sidedata_helpers,
140 oncopiedrevision,
140 oncopiedrevision,
141 ):
141 ):
142 """returns the new revlog object created"""
142 """returns the new revlog object created"""
143 newrl = None
143 newrl = None
144 if matchrevlog(upgrade_op.revlogs_to_process, rl_type):
144 if matchrevlog(upgrade_op.revlogs_to_process, rl_type):
145 ui.note(
145 ui.note(
146 _(b'cloning %d revisions from %s\n') % (len(old_revlog), unencoded)
146 _(b'cloning %d revisions from %s\n') % (len(old_revlog), unencoded)
147 )
147 )
148 newrl = _revlogfrompath(dstrepo, rl_type, unencoded)
148 newrl = _revlogfrompath(dstrepo, rl_type, unencoded)
149 old_revlog.clone(
149 old_revlog.clone(
150 tr,
150 tr,
151 newrl,
151 newrl,
152 addrevisioncb=oncopiedrevision,
152 addrevisioncb=oncopiedrevision,
153 deltareuse=upgrade_op.delta_reuse_mode,
153 deltareuse=upgrade_op.delta_reuse_mode,
154 forcedeltabothparents=upgrade_op.force_re_delta_both_parents,
154 forcedeltabothparents=upgrade_op.force_re_delta_both_parents,
155 sidedata_helpers=sidedata_helpers,
155 sidedata_helpers=sidedata_helpers,
156 )
156 )
157 else:
157 else:
158 msg = _(b'blindly copying %s containing %i revisions\n')
158 msg = _(b'blindly copying %s containing %i revisions\n')
159 ui.note(msg % (unencoded, len(old_revlog)))
159 ui.note(msg % (unencoded, len(old_revlog)))
160 _copyrevlog(tr, dstrepo, old_revlog, rl_type, unencoded)
160 _copyrevlog(tr, dstrepo, old_revlog, rl_type, unencoded)
161
161
162 newrl = _revlogfrompath(dstrepo, rl_type, unencoded)
162 newrl = _revlogfrompath(dstrepo, rl_type, unencoded)
163 return newrl
163 return newrl
164
164
165
165
166 def _clonerevlogs(
166 def _clonerevlogs(
167 ui,
167 ui,
168 srcrepo,
168 srcrepo,
169 dstrepo,
169 dstrepo,
170 tr,
170 tr,
171 upgrade_op,
171 upgrade_op,
172 ):
172 ):
173 """Copy revlogs between 2 repos."""
173 """Copy revlogs between 2 repos."""
174 revcount = 0
174 revcount = 0
175 srcsize = 0
175 srcsize = 0
176 srcrawsize = 0
176 srcrawsize = 0
177 dstsize = 0
177 dstsize = 0
178 fcount = 0
178 fcount = 0
179 frevcount = 0
179 frevcount = 0
180 fsrcsize = 0
180 fsrcsize = 0
181 frawsize = 0
181 frawsize = 0
182 fdstsize = 0
182 fdstsize = 0
183 mcount = 0
183 mcount = 0
184 mrevcount = 0
184 mrevcount = 0
185 msrcsize = 0
185 msrcsize = 0
186 mrawsize = 0
186 mrawsize = 0
187 mdstsize = 0
187 mdstsize = 0
188 crevcount = 0
188 crevcount = 0
189 csrcsize = 0
189 csrcsize = 0
190 crawsize = 0
190 crawsize = 0
191 cdstsize = 0
191 cdstsize = 0
192
192
193 alldatafiles = list(srcrepo.store.walk())
193 alldatafiles = list(srcrepo.store.walk())
194 # mapping of data files which needs to be cloned
194 # mapping of data files which needs to be cloned
195 # key is unencoded filename
195 # key is unencoded filename
196 # value is revlog_object_from_srcrepo
196 # value is revlog_object_from_srcrepo
197 manifests = {}
197 manifests = {}
198 changelogs = {}
198 changelogs = {}
199 filelogs = {}
199 filelogs = {}
200
200
201 # Perform a pass to collect metadata. This validates we can open all
201 # Perform a pass to collect metadata. This validates we can open all
202 # source files and allows a unified progress bar to be displayed.
202 # source files and allows a unified progress bar to be displayed.
203 for rl_type, unencoded, size in alldatafiles:
203 for rl_type, unencoded, size in alldatafiles:
204 if not rl_type & store.FILEFLAGS_REVLOG_MAIN:
204 if not rl_type & store.FILEFLAGS_REVLOG_MAIN:
205 continue
205 continue
206
206
207 # the store.walk function will wrongly pickup transaction backup and
207 # the store.walk function will wrongly pickup transaction backup and
208 # get confused. As a quick fix for 5.9 release, we ignore those.
208 # get confused. As a quick fix for 5.9 release, we ignore those.
209 # (this is not a module constants because it seems better to keep the
209 # (this is not a module constants because it seems better to keep the
210 # hack together)
210 # hack together)
211 skip_undo = (
211 skip_undo = (
212 b'undo.backup.00changelog.i',
212 b'undo.backup.00changelog.i',
213 b'undo.backup.00manifest.i',
213 b'undo.backup.00manifest.i',
214 )
214 )
215 if unencoded in skip_undo:
215 if unencoded in skip_undo:
216 continue
216 continue
217
217
218 rl = _revlogfrompath(srcrepo, rl_type, unencoded)
218 rl = _revlogfrompath(srcrepo, rl_type, unencoded)
219
219
220 info = rl.storageinfo(
220 info = rl.storageinfo(
221 exclusivefiles=True,
221 exclusivefiles=True,
222 revisionscount=True,
222 revisionscount=True,
223 trackedsize=True,
223 trackedsize=True,
224 storedsize=True,
224 storedsize=True,
225 )
225 )
226
226
227 revcount += info[b'revisionscount'] or 0
227 revcount += info[b'revisionscount'] or 0
228 datasize = info[b'storedsize'] or 0
228 datasize = info[b'storedsize'] or 0
229 rawsize = info[b'trackedsize'] or 0
229 rawsize = info[b'trackedsize'] or 0
230
230
231 srcsize += datasize
231 srcsize += datasize
232 srcrawsize += rawsize
232 srcrawsize += rawsize
233
233
234 # This is for the separate progress bars.
234 # This is for the separate progress bars.
235 if rl_type & store.FILEFLAGS_CHANGELOG:
235 if rl_type & store.FILEFLAGS_CHANGELOG:
236 changelogs[unencoded] = rl_type
236 changelogs[unencoded] = rl_type
237 crevcount += len(rl)
237 crevcount += len(rl)
238 csrcsize += datasize
238 csrcsize += datasize
239 crawsize += rawsize
239 crawsize += rawsize
240 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
240 elif rl_type & store.FILEFLAGS_MANIFESTLOG:
241 manifests[unencoded] = rl_type
241 manifests[unencoded] = rl_type
242 mcount += 1
242 mcount += 1
243 mrevcount += len(rl)
243 mrevcount += len(rl)
244 msrcsize += datasize
244 msrcsize += datasize
245 mrawsize += rawsize
245 mrawsize += rawsize
246 elif rl_type & store.FILEFLAGS_FILELOG:
246 elif rl_type & store.FILEFLAGS_FILELOG:
247 filelogs[unencoded] = rl_type
247 filelogs[unencoded] = rl_type
248 fcount += 1
248 fcount += 1
249 frevcount += len(rl)
249 frevcount += len(rl)
250 fsrcsize += datasize
250 fsrcsize += datasize
251 frawsize += rawsize
251 frawsize += rawsize
252 else:
252 else:
253 error.ProgrammingError(b'unknown revlog type')
253 error.ProgrammingError(b'unknown revlog type')
254
254
255 if not revcount:
255 if not revcount:
256 return
256 return
257
257
258 ui.status(
258 ui.status(
259 _(
259 _(
260 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
260 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
261 b'%d in changelog)\n'
261 b'%d in changelog)\n'
262 )
262 )
263 % (revcount, frevcount, mrevcount, crevcount)
263 % (revcount, frevcount, mrevcount, crevcount)
264 )
264 )
265 ui.status(
265 ui.status(
266 _(b'migrating %s in store; %s tracked data\n')
266 _(b'migrating %s in store; %s tracked data\n')
267 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
267 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
268 )
268 )
269
269
270 # Used to keep track of progress.
270 # Used to keep track of progress.
271 progress = None
271 progress = None
272
272
273 def oncopiedrevision(rl, rev, node):
273 def oncopiedrevision(rl, rev, node):
274 progress.increment()
274 progress.increment()
275
275
276 sidedata_helpers = get_sidedata_helpers(srcrepo, dstrepo)
276 sidedata_helpers = get_sidedata_helpers(srcrepo, dstrepo)
277
277
278 # Migrating filelogs
278 # Migrating filelogs
279 ui.status(
279 ui.status(
280 _(
280 _(
281 b'migrating %d filelogs containing %d revisions '
281 b'migrating %d filelogs containing %d revisions '
282 b'(%s in store; %s tracked data)\n'
282 b'(%s in store; %s tracked data)\n'
283 )
283 )
284 % (
284 % (
285 fcount,
285 fcount,
286 frevcount,
286 frevcount,
287 util.bytecount(fsrcsize),
287 util.bytecount(fsrcsize),
288 util.bytecount(frawsize),
288 util.bytecount(frawsize),
289 )
289 )
290 )
290 )
291 progress = srcrepo.ui.makeprogress(_(b'file revisions'), total=frevcount)
291 progress = srcrepo.ui.makeprogress(_(b'file revisions'), total=frevcount)
292 for unencoded, rl_type in sorted(filelogs.items()):
292 for unencoded, rl_type in sorted(filelogs.items()):
293 oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
293 oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
294
294
295 newrl = _perform_clone(
295 newrl = _perform_clone(
296 ui,
296 ui,
297 dstrepo,
297 dstrepo,
298 tr,
298 tr,
299 oldrl,
299 oldrl,
300 rl_type,
300 rl_type,
301 unencoded,
301 unencoded,
302 upgrade_op,
302 upgrade_op,
303 sidedata_helpers,
303 sidedata_helpers,
304 oncopiedrevision,
304 oncopiedrevision,
305 )
305 )
306 info = newrl.storageinfo(storedsize=True)
306 info = newrl.storageinfo(storedsize=True)
307 fdstsize += info[b'storedsize'] or 0
307 fdstsize += info[b'storedsize'] or 0
308 ui.status(
308 ui.status(
309 _(
309 _(
310 b'finished migrating %d filelog revisions across %d '
310 b'finished migrating %d filelog revisions across %d '
311 b'filelogs; change in size: %s\n'
311 b'filelogs; change in size: %s\n'
312 )
312 )
313 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
313 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
314 )
314 )
315
315
316 # Migrating manifests
316 # Migrating manifests
317 ui.status(
317 ui.status(
318 _(
318 _(
319 b'migrating %d manifests containing %d revisions '
319 b'migrating %d manifests containing %d revisions '
320 b'(%s in store; %s tracked data)\n'
320 b'(%s in store; %s tracked data)\n'
321 )
321 )
322 % (
322 % (
323 mcount,
323 mcount,
324 mrevcount,
324 mrevcount,
325 util.bytecount(msrcsize),
325 util.bytecount(msrcsize),
326 util.bytecount(mrawsize),
326 util.bytecount(mrawsize),
327 )
327 )
328 )
328 )
329 if progress:
329 if progress:
330 progress.complete()
330 progress.complete()
331 progress = srcrepo.ui.makeprogress(
331 progress = srcrepo.ui.makeprogress(
332 _(b'manifest revisions'), total=mrevcount
332 _(b'manifest revisions'), total=mrevcount
333 )
333 )
334 for unencoded, rl_type in sorted(manifests.items()):
334 for unencoded, rl_type in sorted(manifests.items()):
335 oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
335 oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
336 newrl = _perform_clone(
336 newrl = _perform_clone(
337 ui,
337 ui,
338 dstrepo,
338 dstrepo,
339 tr,
339 tr,
340 oldrl,
340 oldrl,
341 rl_type,
341 rl_type,
342 unencoded,
342 unencoded,
343 upgrade_op,
343 upgrade_op,
344 sidedata_helpers,
344 sidedata_helpers,
345 oncopiedrevision,
345 oncopiedrevision,
346 )
346 )
347 info = newrl.storageinfo(storedsize=True)
347 info = newrl.storageinfo(storedsize=True)
348 mdstsize += info[b'storedsize'] or 0
348 mdstsize += info[b'storedsize'] or 0
349 ui.status(
349 ui.status(
350 _(
350 _(
351 b'finished migrating %d manifest revisions across %d '
351 b'finished migrating %d manifest revisions across %d '
352 b'manifests; change in size: %s\n'
352 b'manifests; change in size: %s\n'
353 )
353 )
354 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
354 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
355 )
355 )
356
356
357 # Migrating changelog
357 # Migrating changelog
358 ui.status(
358 ui.status(
359 _(
359 _(
360 b'migrating changelog containing %d revisions '
360 b'migrating changelog containing %d revisions '
361 b'(%s in store; %s tracked data)\n'
361 b'(%s in store; %s tracked data)\n'
362 )
362 )
363 % (
363 % (
364 crevcount,
364 crevcount,
365 util.bytecount(csrcsize),
365 util.bytecount(csrcsize),
366 util.bytecount(crawsize),
366 util.bytecount(crawsize),
367 )
367 )
368 )
368 )
369 if progress:
369 if progress:
370 progress.complete()
370 progress.complete()
371 progress = srcrepo.ui.makeprogress(
371 progress = srcrepo.ui.makeprogress(
372 _(b'changelog revisions'), total=crevcount
372 _(b'changelog revisions'), total=crevcount
373 )
373 )
374 for unencoded, rl_type in sorted(changelogs.items()):
374 for unencoded, rl_type in sorted(changelogs.items()):
375 oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
375 oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
376 newrl = _perform_clone(
376 newrl = _perform_clone(
377 ui,
377 ui,
378 dstrepo,
378 dstrepo,
379 tr,
379 tr,
380 oldrl,
380 oldrl,
381 rl_type,
381 rl_type,
382 unencoded,
382 unencoded,
383 upgrade_op,
383 upgrade_op,
384 sidedata_helpers,
384 sidedata_helpers,
385 oncopiedrevision,
385 oncopiedrevision,
386 )
386 )
387 info = newrl.storageinfo(storedsize=True)
387 info = newrl.storageinfo(storedsize=True)
388 cdstsize += info[b'storedsize'] or 0
388 cdstsize += info[b'storedsize'] or 0
389 progress.complete()
389 progress.complete()
390 ui.status(
390 ui.status(
391 _(
391 _(
392 b'finished migrating %d changelog revisions; change in size: '
392 b'finished migrating %d changelog revisions; change in size: '
393 b'%s\n'
393 b'%s\n'
394 )
394 )
395 % (crevcount, util.bytecount(cdstsize - csrcsize))
395 % (crevcount, util.bytecount(cdstsize - csrcsize))
396 )
396 )
397
397
398 dstsize = fdstsize + mdstsize + cdstsize
398 dstsize = fdstsize + mdstsize + cdstsize
399 ui.status(
399 ui.status(
400 _(
400 _(
401 b'finished migrating %d total revisions; total change in store '
401 b'finished migrating %d total revisions; total change in store '
402 b'size: %s\n'
402 b'size: %s\n'
403 )
403 )
404 % (revcount, util.bytecount(dstsize - srcsize))
404 % (revcount, util.bytecount(dstsize - srcsize))
405 )
405 )
406
406
407
407
408 def _files_to_copy_post_revlog_clone(srcrepo):
408 def _files_to_copy_post_revlog_clone(srcrepo):
409 """yields files which should be copied to destination after revlogs
409 """yields files which should be copied to destination after revlogs
410 are cloned"""
410 are cloned"""
411 for path, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
411 for path, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
412 # don't copy revlogs as they are already cloned
412 # don't copy revlogs as they are already cloned
413 if store.revlog_type(path) is not None:
413 if store.revlog_type(path) is not None:
414 continue
414 continue
415 # Skip transaction related files.
415 # Skip transaction related files.
416 if path.startswith(b'undo'):
416 if path.startswith(b'undo'):
417 continue
417 continue
418 # Only copy regular files.
418 # Only copy regular files.
419 if kind != stat.S_IFREG:
419 if kind != stat.S_IFREG:
420 continue
420 continue
421 # Skip other skipped files.
421 # Skip other skipped files.
422 if path in (b'lock', b'fncache'):
422 if path in (b'lock', b'fncache'):
423 continue
423 continue
424 # TODO: should we skip cache too?
424 # TODO: should we skip cache too?
425
425
426 yield path
426 yield path
427
427
428
428
429 def _replacestores(currentrepo, upgradedrepo, backupvfs, upgrade_op):
429 def _replacestores(currentrepo, upgradedrepo, backupvfs, upgrade_op):
430 """Replace the stores after current repository is upgraded
430 """Replace the stores after current repository is upgraded
431
431
432 Creates a backup of current repository store at backup path
432 Creates a backup of current repository store at backup path
433 Replaces upgraded store files in current repo from upgraded one
433 Replaces upgraded store files in current repo from upgraded one
434
434
435 Arguments:
435 Arguments:
436 currentrepo: repo object of current repository
436 currentrepo: repo object of current repository
437 upgradedrepo: repo object of the upgraded data
437 upgradedrepo: repo object of the upgraded data
438 backupvfs: vfs object for the backup path
438 backupvfs: vfs object for the backup path
439 upgrade_op: upgrade operation object
439 upgrade_op: upgrade operation object
440 to be used to decide what all is upgraded
440 to be used to decide what all is upgraded
441 """
441 """
442 # TODO: don't blindly rename everything in store
442 # TODO: don't blindly rename everything in store
443 # There can be upgrades where store is not touched at all
443 # There can be upgrades where store is not touched at all
444 if upgrade_op.backup_store:
444 if upgrade_op.backup_store:
445 util.rename(currentrepo.spath, backupvfs.join(b'store'))
445 util.rename(currentrepo.spath, backupvfs.join(b'store'))
446 else:
446 else:
447 currentrepo.vfs.rmtree(b'store', forcibly=True)
447 currentrepo.vfs.rmtree(b'store', forcibly=True)
448 util.rename(upgradedrepo.spath, currentrepo.spath)
448 util.rename(upgradedrepo.spath, currentrepo.spath)
449
449
450
450
451 def finishdatamigration(ui, srcrepo, dstrepo, requirements):
451 def finishdatamigration(ui, srcrepo, dstrepo, requirements):
452 """Hook point for extensions to perform additional actions during upgrade.
452 """Hook point for extensions to perform additional actions during upgrade.
453
453
454 This function is called after revlogs and store files have been copied but
454 This function is called after revlogs and store files have been copied but
455 before the new store is swapped into the original location.
455 before the new store is swapped into the original location.
456 """
456 """
457
457
458
458
459 def upgrade(ui, srcrepo, dstrepo, upgrade_op):
459 def upgrade(ui, srcrepo, dstrepo, upgrade_op):
460 """Do the low-level work of upgrading a repository.
460 """Do the low-level work of upgrading a repository.
461
461
462 The upgrade is effectively performed as a copy between a source
462 The upgrade is effectively performed as a copy between a source
463 repository and a temporary destination repository.
463 repository and a temporary destination repository.
464
464
465 The source repository is unmodified for as long as possible so the
465 The source repository is unmodified for as long as possible so the
466 upgrade can abort at any time without causing loss of service for
466 upgrade can abort at any time without causing loss of service for
467 readers and without corrupting the source repository.
467 readers and without corrupting the source repository.
468 """
468 """
469 assert srcrepo.currentwlock()
469 assert srcrepo.currentwlock()
470 assert dstrepo.currentwlock()
470 assert dstrepo.currentwlock()
471 backuppath = None
471 backuppath = None
472 backupvfs = None
472 backupvfs = None
473
473
474 ui.status(
474 ui.status(
475 _(
475 _(
476 b'(it is safe to interrupt this process any time before '
476 b'(it is safe to interrupt this process any time before '
477 b'data migration completes)\n'
477 b'data migration completes)\n'
478 )
478 )
479 )
479 )
480
480
481 if upgrade_actions.dirstatev2 in upgrade_op.upgrade_actions:
481 if upgrade_actions.dirstatev2 in upgrade_op.upgrade_actions:
482 ui.status(_(b'upgrading to dirstate-v2 from v1\n'))
482 ui.status(_(b'upgrading to dirstate-v2 from v1\n'))
483 upgrade_dirstate(ui, srcrepo, upgrade_op, b'v1', b'v2')
483 upgrade_dirstate(ui, srcrepo, upgrade_op, b'v1', b'v2')
484 upgrade_op.upgrade_actions.remove(upgrade_actions.dirstatev2)
484 upgrade_op.upgrade_actions.remove(upgrade_actions.dirstatev2)
485
485
486 if upgrade_actions.dirstatev2 in upgrade_op.removed_actions:
486 if upgrade_actions.dirstatev2 in upgrade_op.removed_actions:
487 ui.status(_(b'downgrading from dirstate-v2 to v1\n'))
487 ui.status(_(b'downgrading from dirstate-v2 to v1\n'))
488 upgrade_dirstate(ui, srcrepo, upgrade_op, b'v2', b'v1')
488 upgrade_dirstate(ui, srcrepo, upgrade_op, b'v2', b'v1')
489 upgrade_op.removed_actions.remove(upgrade_actions.dirstatev2)
489 upgrade_op.removed_actions.remove(upgrade_actions.dirstatev2)
490
490
491 if upgrade_actions.dirstatetrackedkey in upgrade_op.upgrade_actions:
491 if upgrade_actions.dirstatetrackedkey in upgrade_op.upgrade_actions:
492 ui.status(_(b'create dirstate-tracked-hint file\n'))
492 ui.status(_(b'create dirstate-tracked-hint file\n'))
493 upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=True)
493 upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=True)
494 upgrade_op.upgrade_actions.remove(upgrade_actions.dirstatetrackedkey)
494 upgrade_op.upgrade_actions.remove(upgrade_actions.dirstatetrackedkey)
495 elif upgrade_actions.dirstatetrackedkey in upgrade_op.removed_actions:
495 elif upgrade_actions.dirstatetrackedkey in upgrade_op.removed_actions:
496 ui.status(_(b'remove dirstate-tracked-hint file\n'))
496 ui.status(_(b'remove dirstate-tracked-hint file\n'))
497 upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=False)
497 upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=False)
498 upgrade_op.removed_actions.remove(upgrade_actions.dirstatetrackedkey)
498 upgrade_op.removed_actions.remove(upgrade_actions.dirstatetrackedkey)
499
499
500 if not (upgrade_op.upgrade_actions or upgrade_op.removed_actions):
500 if not (upgrade_op.upgrade_actions or upgrade_op.removed_actions):
501 return
501 return
502
502
503 if upgrade_op.requirements_only:
503 if upgrade_op.requirements_only:
504 ui.status(_(b'upgrading repository requirements\n'))
504 ui.status(_(b'upgrading repository requirements\n'))
505 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
505 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
506 # if there is only one action and that is persistent nodemap upgrade
506 # if there is only one action and that is persistent nodemap upgrade
507 # directly write the nodemap file and update requirements instead of going
507 # directly write the nodemap file and update requirements instead of going
508 # through the whole cloning process
508 # through the whole cloning process
509 elif (
509 elif (
510 len(upgrade_op.upgrade_actions) == 1
510 len(upgrade_op.upgrade_actions) == 1
511 and b'persistent-nodemap' in upgrade_op.upgrade_actions_names
511 and b'persistent-nodemap' in upgrade_op.upgrade_actions_names
512 and not upgrade_op.removed_actions
512 and not upgrade_op.removed_actions
513 ):
513 ):
514 ui.status(
514 ui.status(
515 _(b'upgrading repository to use persistent nodemap feature\n')
515 _(b'upgrading repository to use persistent nodemap feature\n')
516 )
516 )
517 with srcrepo.transaction(b'upgrade') as tr:
517 with srcrepo.transaction(b'upgrade') as tr:
518 unfi = srcrepo.unfiltered()
518 unfi = srcrepo.unfiltered()
519 cl = unfi.changelog
519 cl = unfi.changelog
520 nodemap.persist_nodemap(tr, cl, force=True)
520 nodemap.persist_nodemap(tr, cl, force=True)
521 # we want to directly operate on the underlying revlog to force
521 # we want to directly operate on the underlying revlog to force
522 # create a nodemap file. This is fine since this is upgrade code
522 # create a nodemap file. This is fine since this is upgrade code
523 # and it heavily relies on repository being revlog based
523 # and it heavily relies on repository being revlog based
524 # hence accessing private attributes can be justified
524 # hence accessing private attributes can be justified
525 nodemap.persist_nodemap(
525 nodemap.persist_nodemap(
526 tr, unfi.manifestlog._rootstore._revlog, force=True
526 tr, unfi.manifestlog._rootstore._revlog, force=True
527 )
527 )
528 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
528 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
529 elif (
529 elif (
530 len(upgrade_op.removed_actions) == 1
530 len(upgrade_op.removed_actions) == 1
531 and [
531 and [
532 x
532 x
533 for x in upgrade_op.removed_actions
533 for x in upgrade_op.removed_actions
534 if x.name == b'persistent-nodemap'
534 if x.name == b'persistent-nodemap'
535 ]
535 ]
536 and not upgrade_op.upgrade_actions
536 and not upgrade_op.upgrade_actions
537 ):
537 ):
538 ui.status(
538 ui.status(
539 _(b'downgrading repository to not use persistent nodemap feature\n')
539 _(b'downgrading repository to not use persistent nodemap feature\n')
540 )
540 )
541 with srcrepo.transaction(b'upgrade') as tr:
541 with srcrepo.transaction(b'upgrade') as tr:
542 unfi = srcrepo.unfiltered()
542 unfi = srcrepo.unfiltered()
543 cl = unfi.changelog
543 cl = unfi.changelog
544 nodemap.delete_nodemap(tr, srcrepo, cl)
544 nodemap.delete_nodemap(tr, srcrepo, cl)
545 # check comment 20 lines above for accessing private attributes
545 # check comment 20 lines above for accessing private attributes
546 nodemap.delete_nodemap(
546 nodemap.delete_nodemap(
547 tr, srcrepo, unfi.manifestlog._rootstore._revlog
547 tr, srcrepo, unfi.manifestlog._rootstore._revlog
548 )
548 )
549 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
549 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
550 else:
550 else:
551 with dstrepo.transaction(b'upgrade') as tr:
551 with dstrepo.transaction(b'upgrade') as tr:
552 _clonerevlogs(
552 _clonerevlogs(
553 ui,
553 ui,
554 srcrepo,
554 srcrepo,
555 dstrepo,
555 dstrepo,
556 tr,
556 tr,
557 upgrade_op,
557 upgrade_op,
558 )
558 )
559
559
560 # Now copy other files in the store directory.
560 # Now copy other files in the store directory.
561 for p in _files_to_copy_post_revlog_clone(srcrepo):
561 for p in _files_to_copy_post_revlog_clone(srcrepo):
562 srcrepo.ui.status(_(b'copying %s\n') % p)
562 srcrepo.ui.status(_(b'copying %s\n') % p)
563 src = srcrepo.store.rawvfs.join(p)
563 src = srcrepo.store.rawvfs.join(p)
564 dst = dstrepo.store.rawvfs.join(p)
564 dst = dstrepo.store.rawvfs.join(p)
565 util.copyfile(src, dst, copystat=True)
565 util.copyfile(src, dst, copystat=True)
566
566
567 finishdatamigration(ui, srcrepo, dstrepo, requirements)
567 finishdatamigration(ui, srcrepo, dstrepo, requirements)
568
568
569 ui.status(_(b'data fully upgraded in a temporary repository\n'))
569 ui.status(_(b'data fully upgraded in a temporary repository\n'))
570
570
571 if upgrade_op.backup_store:
571 if upgrade_op.backup_store:
572 backuppath = pycompat.mkdtemp(
572 backuppath = pycompat.mkdtemp(
573 prefix=b'upgradebackup.', dir=srcrepo.path
573 prefix=b'upgradebackup.', dir=srcrepo.path
574 )
574 )
575 backupvfs = vfsmod.vfs(backuppath)
575 backupvfs = vfsmod.vfs(backuppath)
576
576
577 # Make a backup of requires file first, as it is the first to be modified.
577 # Make a backup of requires file first, as it is the first to be modified.
578 util.copyfile(
578 util.copyfile(
579 srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')
579 srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')
580 )
580 )
581
581
582 # We install an arbitrary requirement that clients must not support
582 # We install an arbitrary requirement that clients must not support
583 # as a mechanism to lock out new clients during the data swap. This is
583 # as a mechanism to lock out new clients during the data swap. This is
584 # better than allowing a client to continue while the repository is in
584 # better than allowing a client to continue while the repository is in
585 # an inconsistent state.
585 # an inconsistent state.
586 ui.status(
586 ui.status(
587 _(
587 _(
588 b'marking source repository as being upgraded; clients will be '
588 b'marking source repository as being upgraded; clients will be '
589 b'unable to read from repository\n'
589 b'unable to read from repository\n'
590 )
590 )
591 )
591 )
592 scmutil.writereporequirements(
592 scmutil.writereporequirements(
593 srcrepo, srcrepo.requirements | {b'upgradeinprogress'}
593 srcrepo, srcrepo.requirements | {b'upgradeinprogress'}
594 )
594 )
595
595
596 ui.status(_(b'starting in-place swap of repository data\n'))
596 ui.status(_(b'starting in-place swap of repository data\n'))
597 if upgrade_op.backup_store:
597 if upgrade_op.backup_store:
598 ui.status(
598 ui.status(
599 _(b'replaced files will be backed up at %s\n') % backuppath
599 _(b'replaced files will be backed up at %s\n') % backuppath
600 )
600 )
601
601
602 # Now swap in the new store directory. Doing it as a rename should make
602 # Now swap in the new store directory. Doing it as a rename should make
603 # the operation nearly instantaneous and atomic (at least in well-behaved
603 # the operation nearly instantaneous and atomic (at least in well-behaved
604 # environments).
604 # environments).
605 ui.status(_(b'replacing store...\n'))
605 ui.status(_(b'replacing store...\n'))
606 tstart = util.timer()
606 tstart = util.timer()
607 _replacestores(srcrepo, dstrepo, backupvfs, upgrade_op)
607 _replacestores(srcrepo, dstrepo, backupvfs, upgrade_op)
608 elapsed = util.timer() - tstart
608 elapsed = util.timer() - tstart
609 ui.status(
609 ui.status(
610 _(
610 _(
611 b'store replacement complete; repository was inconsistent for '
611 b'store replacement complete; repository was inconsistent for '
612 b'%0.1fs\n'
612 b'%0.1fs\n'
613 )
613 )
614 % elapsed
614 % elapsed
615 )
615 )
616
616
617 # We first write the requirements file. Any new requirements will lock
617 # We first write the requirements file. Any new requirements will lock
618 # out legacy clients.
618 # out legacy clients.
619 ui.status(
619 ui.status(
620 _(
620 _(
621 b'finalizing requirements file and making repository readable '
621 b'finalizing requirements file and making repository readable '
622 b'again\n'
622 b'again\n'
623 )
623 )
624 )
624 )
625 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
625 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
626
626
627 if upgrade_op.backup_store:
627 if upgrade_op.backup_store:
628 # The lock file from the old store won't be removed because nothing has a
628 # The lock file from the old store won't be removed because nothing has a
629 # reference to its new location. So clean it up manually. Alternatively, we
629 # reference to its new location. So clean it up manually. Alternatively, we
630 # could update srcrepo.svfs and other variables to point to the new
630 # could update srcrepo.svfs and other variables to point to the new
631 # location. This is simpler.
631 # location. This is simpler.
632 assert backupvfs is not None # help pytype
632 assert backupvfs is not None # help pytype
633 backupvfs.unlink(b'store/lock')
633 backupvfs.unlink(b'store/lock')
634
634
635 return backuppath
635 return backuppath
636
636
637
637
638 def upgrade_dirstate(ui, srcrepo, upgrade_op, old, new):
638 def upgrade_dirstate(ui, srcrepo, upgrade_op, old, new):
639 if upgrade_op.backup_store:
639 if upgrade_op.backup_store:
640 backuppath = pycompat.mkdtemp(
640 backuppath = pycompat.mkdtemp(
641 prefix=b'upgradebackup.', dir=srcrepo.path
641 prefix=b'upgradebackup.', dir=srcrepo.path
642 )
642 )
643 ui.status(_(b'replaced files will be backed up at %s\n') % backuppath)
643 ui.status(_(b'replaced files will be backed up at %s\n') % backuppath)
644 backupvfs = vfsmod.vfs(backuppath)
644 backupvfs = vfsmod.vfs(backuppath)
645 util.copyfile(
645 util.copyfile(
646 srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')
646 srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')
647 )
647 )
648 try:
648 try:
649 util.copyfile(
649 util.copyfile(
650 srcrepo.vfs.join(b'dirstate'), backupvfs.join(b'dirstate')
650 srcrepo.vfs.join(b'dirstate'), backupvfs.join(b'dirstate')
651 )
651 )
652 except FileNotFoundError:
652 except FileNotFoundError:
653 # The dirstate does not exist on an empty repo or a repo with no
653 # The dirstate does not exist on an empty repo or a repo with no
654 # revision checked out
654 # revision checked out
655 pass
655 pass
656
656
657 assert srcrepo.dirstate._use_dirstate_v2 == (old == b'v2')
657 assert srcrepo.dirstate._use_dirstate_v2 == (old == b'v2')
658 use_v2 = new == b'v2'
659 if use_v2:
660 # Write the requirements *before* upgrading
661 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
662
658 srcrepo.dirstate._map.preload()
663 srcrepo.dirstate._map.preload()
659 srcrepo.dirstate._use_dirstate_v2 = new == b'v2'
664 srcrepo.dirstate._use_dirstate_v2 = use_v2
660 srcrepo.dirstate._map._use_dirstate_v2 = srcrepo.dirstate._use_dirstate_v2
665 srcrepo.dirstate._map._use_dirstate_v2 = use_v2
661 srcrepo.dirstate._dirty = True
666 srcrepo.dirstate._dirty = True
662 try:
667 try:
663 srcrepo.vfs.unlink(b'dirstate')
668 srcrepo.vfs.unlink(b'dirstate')
664 except FileNotFoundError:
669 except FileNotFoundError:
665 # The dirstate does not exist on an empty repo or a repo with no
670 # The dirstate does not exist on an empty repo or a repo with no
666 # revision checked out
671 # revision checked out
667 pass
672 pass
668
673
669 srcrepo.dirstate.write(None)
674 srcrepo.dirstate.write(None)
670
675 if not use_v2:
671 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
676 # Remove the v2 requirement *after* downgrading
677 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
672
678
673
679
674 def upgrade_tracked_hint(ui, srcrepo, upgrade_op, add):
680 def upgrade_tracked_hint(ui, srcrepo, upgrade_op, add):
675 if add:
681 if add:
676 srcrepo.dirstate._use_tracked_hint = True
682 srcrepo.dirstate._use_tracked_hint = True
677 srcrepo.dirstate._dirty = True
683 srcrepo.dirstate._dirty = True
678 srcrepo.dirstate._dirty_tracked_set = True
684 srcrepo.dirstate._dirty_tracked_set = True
679 srcrepo.dirstate.write(None)
685 srcrepo.dirstate.write(None)
680 if not add:
686 if not add:
681 srcrepo.dirstate.delete_tracked_hint()
687 srcrepo.dirstate.delete_tracked_hint()
682
688
683 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
689 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
General Comments 0
You need to be logged in to leave comments. Login now