##// END OF EJS Templates
upgrade: no longer keep all revlogs in memory at any point...
marmoute -
r50447:19948429 stable
parent child Browse files
Show More
@@ -1,679 +1,683
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, rl)
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, rl)
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, rl)
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, oldrl) in sorted(filelogs.items()):
292 for unencoded, rl_type in sorted(filelogs.items()):
293 oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
294
293 newrl = _perform_clone(
295 newrl = _perform_clone(
294 ui,
296 ui,
295 dstrepo,
297 dstrepo,
296 tr,
298 tr,
297 oldrl,
299 oldrl,
298 rl_type,
300 rl_type,
299 unencoded,
301 unencoded,
300 upgrade_op,
302 upgrade_op,
301 sidedata_helpers,
303 sidedata_helpers,
302 oncopiedrevision,
304 oncopiedrevision,
303 )
305 )
304 info = newrl.storageinfo(storedsize=True)
306 info = newrl.storageinfo(storedsize=True)
305 fdstsize += info[b'storedsize'] or 0
307 fdstsize += info[b'storedsize'] or 0
306 ui.status(
308 ui.status(
307 _(
309 _(
308 b'finished migrating %d filelog revisions across %d '
310 b'finished migrating %d filelog revisions across %d '
309 b'filelogs; change in size: %s\n'
311 b'filelogs; change in size: %s\n'
310 )
312 )
311 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
313 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
312 )
314 )
313
315
314 # Migrating manifests
316 # Migrating manifests
315 ui.status(
317 ui.status(
316 _(
318 _(
317 b'migrating %d manifests containing %d revisions '
319 b'migrating %d manifests containing %d revisions '
318 b'(%s in store; %s tracked data)\n'
320 b'(%s in store; %s tracked data)\n'
319 )
321 )
320 % (
322 % (
321 mcount,
323 mcount,
322 mrevcount,
324 mrevcount,
323 util.bytecount(msrcsize),
325 util.bytecount(msrcsize),
324 util.bytecount(mrawsize),
326 util.bytecount(mrawsize),
325 )
327 )
326 )
328 )
327 if progress:
329 if progress:
328 progress.complete()
330 progress.complete()
329 progress = srcrepo.ui.makeprogress(
331 progress = srcrepo.ui.makeprogress(
330 _(b'manifest revisions'), total=mrevcount
332 _(b'manifest revisions'), total=mrevcount
331 )
333 )
332 for unencoded, (rl_type, oldrl) in sorted(manifests.items()):
334 for unencoded, rl_type in sorted(manifests.items()):
335 oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
333 newrl = _perform_clone(
336 newrl = _perform_clone(
334 ui,
337 ui,
335 dstrepo,
338 dstrepo,
336 tr,
339 tr,
337 oldrl,
340 oldrl,
338 rl_type,
341 rl_type,
339 unencoded,
342 unencoded,
340 upgrade_op,
343 upgrade_op,
341 sidedata_helpers,
344 sidedata_helpers,
342 oncopiedrevision,
345 oncopiedrevision,
343 )
346 )
344 info = newrl.storageinfo(storedsize=True)
347 info = newrl.storageinfo(storedsize=True)
345 mdstsize += info[b'storedsize'] or 0
348 mdstsize += info[b'storedsize'] or 0
346 ui.status(
349 ui.status(
347 _(
350 _(
348 b'finished migrating %d manifest revisions across %d '
351 b'finished migrating %d manifest revisions across %d '
349 b'manifests; change in size: %s\n'
352 b'manifests; change in size: %s\n'
350 )
353 )
351 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
354 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
352 )
355 )
353
356
354 # Migrating changelog
357 # Migrating changelog
355 ui.status(
358 ui.status(
356 _(
359 _(
357 b'migrating changelog containing %d revisions '
360 b'migrating changelog containing %d revisions '
358 b'(%s in store; %s tracked data)\n'
361 b'(%s in store; %s tracked data)\n'
359 )
362 )
360 % (
363 % (
361 crevcount,
364 crevcount,
362 util.bytecount(csrcsize),
365 util.bytecount(csrcsize),
363 util.bytecount(crawsize),
366 util.bytecount(crawsize),
364 )
367 )
365 )
368 )
366 if progress:
369 if progress:
367 progress.complete()
370 progress.complete()
368 progress = srcrepo.ui.makeprogress(
371 progress = srcrepo.ui.makeprogress(
369 _(b'changelog revisions'), total=crevcount
372 _(b'changelog revisions'), total=crevcount
370 )
373 )
371 for unencoded, (rl_type, oldrl) in sorted(changelogs.items()):
374 for unencoded, rl_type in sorted(changelogs.items()):
375 oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
372 newrl = _perform_clone(
376 newrl = _perform_clone(
373 ui,
377 ui,
374 dstrepo,
378 dstrepo,
375 tr,
379 tr,
376 oldrl,
380 oldrl,
377 rl_type,
381 rl_type,
378 unencoded,
382 unencoded,
379 upgrade_op,
383 upgrade_op,
380 sidedata_helpers,
384 sidedata_helpers,
381 oncopiedrevision,
385 oncopiedrevision,
382 )
386 )
383 info = newrl.storageinfo(storedsize=True)
387 info = newrl.storageinfo(storedsize=True)
384 cdstsize += info[b'storedsize'] or 0
388 cdstsize += info[b'storedsize'] or 0
385 progress.complete()
389 progress.complete()
386 ui.status(
390 ui.status(
387 _(
391 _(
388 b'finished migrating %d changelog revisions; change in size: '
392 b'finished migrating %d changelog revisions; change in size: '
389 b'%s\n'
393 b'%s\n'
390 )
394 )
391 % (crevcount, util.bytecount(cdstsize - csrcsize))
395 % (crevcount, util.bytecount(cdstsize - csrcsize))
392 )
396 )
393
397
394 dstsize = fdstsize + mdstsize + cdstsize
398 dstsize = fdstsize + mdstsize + cdstsize
395 ui.status(
399 ui.status(
396 _(
400 _(
397 b'finished migrating %d total revisions; total change in store '
401 b'finished migrating %d total revisions; total change in store '
398 b'size: %s\n'
402 b'size: %s\n'
399 )
403 )
400 % (revcount, util.bytecount(dstsize - srcsize))
404 % (revcount, util.bytecount(dstsize - srcsize))
401 )
405 )
402
406
403
407
404 def _files_to_copy_post_revlog_clone(srcrepo):
408 def _files_to_copy_post_revlog_clone(srcrepo):
405 """yields files which should be copied to destination after revlogs
409 """yields files which should be copied to destination after revlogs
406 are cloned"""
410 are cloned"""
407 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)):
408 # don't copy revlogs as they are already cloned
412 # don't copy revlogs as they are already cloned
409 if store.revlog_type(path) is not None:
413 if store.revlog_type(path) is not None:
410 continue
414 continue
411 # Skip transaction related files.
415 # Skip transaction related files.
412 if path.startswith(b'undo'):
416 if path.startswith(b'undo'):
413 continue
417 continue
414 # Only copy regular files.
418 # Only copy regular files.
415 if kind != stat.S_IFREG:
419 if kind != stat.S_IFREG:
416 continue
420 continue
417 # Skip other skipped files.
421 # Skip other skipped files.
418 if path in (b'lock', b'fncache'):
422 if path in (b'lock', b'fncache'):
419 continue
423 continue
420 # TODO: should we skip cache too?
424 # TODO: should we skip cache too?
421
425
422 yield path
426 yield path
423
427
424
428
425 def _replacestores(currentrepo, upgradedrepo, backupvfs, upgrade_op):
429 def _replacestores(currentrepo, upgradedrepo, backupvfs, upgrade_op):
426 """Replace the stores after current repository is upgraded
430 """Replace the stores after current repository is upgraded
427
431
428 Creates a backup of current repository store at backup path
432 Creates a backup of current repository store at backup path
429 Replaces upgraded store files in current repo from upgraded one
433 Replaces upgraded store files in current repo from upgraded one
430
434
431 Arguments:
435 Arguments:
432 currentrepo: repo object of current repository
436 currentrepo: repo object of current repository
433 upgradedrepo: repo object of the upgraded data
437 upgradedrepo: repo object of the upgraded data
434 backupvfs: vfs object for the backup path
438 backupvfs: vfs object for the backup path
435 upgrade_op: upgrade operation object
439 upgrade_op: upgrade operation object
436 to be used to decide what all is upgraded
440 to be used to decide what all is upgraded
437 """
441 """
438 # TODO: don't blindly rename everything in store
442 # TODO: don't blindly rename everything in store
439 # There can be upgrades where store is not touched at all
443 # There can be upgrades where store is not touched at all
440 if upgrade_op.backup_store:
444 if upgrade_op.backup_store:
441 util.rename(currentrepo.spath, backupvfs.join(b'store'))
445 util.rename(currentrepo.spath, backupvfs.join(b'store'))
442 else:
446 else:
443 currentrepo.vfs.rmtree(b'store', forcibly=True)
447 currentrepo.vfs.rmtree(b'store', forcibly=True)
444 util.rename(upgradedrepo.spath, currentrepo.spath)
448 util.rename(upgradedrepo.spath, currentrepo.spath)
445
449
446
450
447 def finishdatamigration(ui, srcrepo, dstrepo, requirements):
451 def finishdatamigration(ui, srcrepo, dstrepo, requirements):
448 """Hook point for extensions to perform additional actions during upgrade.
452 """Hook point for extensions to perform additional actions during upgrade.
449
453
450 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
451 before the new store is swapped into the original location.
455 before the new store is swapped into the original location.
452 """
456 """
453
457
454
458
455 def upgrade(ui, srcrepo, dstrepo, upgrade_op):
459 def upgrade(ui, srcrepo, dstrepo, upgrade_op):
456 """Do the low-level work of upgrading a repository.
460 """Do the low-level work of upgrading a repository.
457
461
458 The upgrade is effectively performed as a copy between a source
462 The upgrade is effectively performed as a copy between a source
459 repository and a temporary destination repository.
463 repository and a temporary destination repository.
460
464
461 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
462 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
463 readers and without corrupting the source repository.
467 readers and without corrupting the source repository.
464 """
468 """
465 assert srcrepo.currentwlock()
469 assert srcrepo.currentwlock()
466 assert dstrepo.currentwlock()
470 assert dstrepo.currentwlock()
467 backuppath = None
471 backuppath = None
468 backupvfs = None
472 backupvfs = None
469
473
470 ui.status(
474 ui.status(
471 _(
475 _(
472 b'(it is safe to interrupt this process any time before '
476 b'(it is safe to interrupt this process any time before '
473 b'data migration completes)\n'
477 b'data migration completes)\n'
474 )
478 )
475 )
479 )
476
480
477 if upgrade_actions.dirstatev2 in upgrade_op.upgrade_actions:
481 if upgrade_actions.dirstatev2 in upgrade_op.upgrade_actions:
478 ui.status(_(b'upgrading to dirstate-v2 from v1\n'))
482 ui.status(_(b'upgrading to dirstate-v2 from v1\n'))
479 upgrade_dirstate(ui, srcrepo, upgrade_op, b'v1', b'v2')
483 upgrade_dirstate(ui, srcrepo, upgrade_op, b'v1', b'v2')
480 upgrade_op.upgrade_actions.remove(upgrade_actions.dirstatev2)
484 upgrade_op.upgrade_actions.remove(upgrade_actions.dirstatev2)
481
485
482 if upgrade_actions.dirstatev2 in upgrade_op.removed_actions:
486 if upgrade_actions.dirstatev2 in upgrade_op.removed_actions:
483 ui.status(_(b'downgrading from dirstate-v2 to v1\n'))
487 ui.status(_(b'downgrading from dirstate-v2 to v1\n'))
484 upgrade_dirstate(ui, srcrepo, upgrade_op, b'v2', b'v1')
488 upgrade_dirstate(ui, srcrepo, upgrade_op, b'v2', b'v1')
485 upgrade_op.removed_actions.remove(upgrade_actions.dirstatev2)
489 upgrade_op.removed_actions.remove(upgrade_actions.dirstatev2)
486
490
487 if upgrade_actions.dirstatetrackedkey in upgrade_op.upgrade_actions:
491 if upgrade_actions.dirstatetrackedkey in upgrade_op.upgrade_actions:
488 ui.status(_(b'create dirstate-tracked-hint file\n'))
492 ui.status(_(b'create dirstate-tracked-hint file\n'))
489 upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=True)
493 upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=True)
490 upgrade_op.upgrade_actions.remove(upgrade_actions.dirstatetrackedkey)
494 upgrade_op.upgrade_actions.remove(upgrade_actions.dirstatetrackedkey)
491 elif upgrade_actions.dirstatetrackedkey in upgrade_op.removed_actions:
495 elif upgrade_actions.dirstatetrackedkey in upgrade_op.removed_actions:
492 ui.status(_(b'remove dirstate-tracked-hint file\n'))
496 ui.status(_(b'remove dirstate-tracked-hint file\n'))
493 upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=False)
497 upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=False)
494 upgrade_op.removed_actions.remove(upgrade_actions.dirstatetrackedkey)
498 upgrade_op.removed_actions.remove(upgrade_actions.dirstatetrackedkey)
495
499
496 if not (upgrade_op.upgrade_actions or upgrade_op.removed_actions):
500 if not (upgrade_op.upgrade_actions or upgrade_op.removed_actions):
497 return
501 return
498
502
499 if upgrade_op.requirements_only:
503 if upgrade_op.requirements_only:
500 ui.status(_(b'upgrading repository requirements\n'))
504 ui.status(_(b'upgrading repository requirements\n'))
501 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
505 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
502 # 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
503 # directly write the nodemap file and update requirements instead of going
507 # directly write the nodemap file and update requirements instead of going
504 # through the whole cloning process
508 # through the whole cloning process
505 elif (
509 elif (
506 len(upgrade_op.upgrade_actions) == 1
510 len(upgrade_op.upgrade_actions) == 1
507 and b'persistent-nodemap' in upgrade_op.upgrade_actions_names
511 and b'persistent-nodemap' in upgrade_op.upgrade_actions_names
508 and not upgrade_op.removed_actions
512 and not upgrade_op.removed_actions
509 ):
513 ):
510 ui.status(
514 ui.status(
511 _(b'upgrading repository to use persistent nodemap feature\n')
515 _(b'upgrading repository to use persistent nodemap feature\n')
512 )
516 )
513 with srcrepo.transaction(b'upgrade') as tr:
517 with srcrepo.transaction(b'upgrade') as tr:
514 unfi = srcrepo.unfiltered()
518 unfi = srcrepo.unfiltered()
515 cl = unfi.changelog
519 cl = unfi.changelog
516 nodemap.persist_nodemap(tr, cl, force=True)
520 nodemap.persist_nodemap(tr, cl, force=True)
517 # we want to directly operate on the underlying revlog to force
521 # we want to directly operate on the underlying revlog to force
518 # 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
519 # and it heavily relies on repository being revlog based
523 # and it heavily relies on repository being revlog based
520 # hence accessing private attributes can be justified
524 # hence accessing private attributes can be justified
521 nodemap.persist_nodemap(
525 nodemap.persist_nodemap(
522 tr, unfi.manifestlog._rootstore._revlog, force=True
526 tr, unfi.manifestlog._rootstore._revlog, force=True
523 )
527 )
524 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
528 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
525 elif (
529 elif (
526 len(upgrade_op.removed_actions) == 1
530 len(upgrade_op.removed_actions) == 1
527 and [
531 and [
528 x
532 x
529 for x in upgrade_op.removed_actions
533 for x in upgrade_op.removed_actions
530 if x.name == b'persistent-nodemap'
534 if x.name == b'persistent-nodemap'
531 ]
535 ]
532 and not upgrade_op.upgrade_actions
536 and not upgrade_op.upgrade_actions
533 ):
537 ):
534 ui.status(
538 ui.status(
535 _(b'downgrading repository to not use persistent nodemap feature\n')
539 _(b'downgrading repository to not use persistent nodemap feature\n')
536 )
540 )
537 with srcrepo.transaction(b'upgrade') as tr:
541 with srcrepo.transaction(b'upgrade') as tr:
538 unfi = srcrepo.unfiltered()
542 unfi = srcrepo.unfiltered()
539 cl = unfi.changelog
543 cl = unfi.changelog
540 nodemap.delete_nodemap(tr, srcrepo, cl)
544 nodemap.delete_nodemap(tr, srcrepo, cl)
541 # check comment 20 lines above for accessing private attributes
545 # check comment 20 lines above for accessing private attributes
542 nodemap.delete_nodemap(
546 nodemap.delete_nodemap(
543 tr, srcrepo, unfi.manifestlog._rootstore._revlog
547 tr, srcrepo, unfi.manifestlog._rootstore._revlog
544 )
548 )
545 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
549 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
546 else:
550 else:
547 with dstrepo.transaction(b'upgrade') as tr:
551 with dstrepo.transaction(b'upgrade') as tr:
548 _clonerevlogs(
552 _clonerevlogs(
549 ui,
553 ui,
550 srcrepo,
554 srcrepo,
551 dstrepo,
555 dstrepo,
552 tr,
556 tr,
553 upgrade_op,
557 upgrade_op,
554 )
558 )
555
559
556 # Now copy other files in the store directory.
560 # Now copy other files in the store directory.
557 for p in _files_to_copy_post_revlog_clone(srcrepo):
561 for p in _files_to_copy_post_revlog_clone(srcrepo):
558 srcrepo.ui.status(_(b'copying %s\n') % p)
562 srcrepo.ui.status(_(b'copying %s\n') % p)
559 src = srcrepo.store.rawvfs.join(p)
563 src = srcrepo.store.rawvfs.join(p)
560 dst = dstrepo.store.rawvfs.join(p)
564 dst = dstrepo.store.rawvfs.join(p)
561 util.copyfile(src, dst, copystat=True)
565 util.copyfile(src, dst, copystat=True)
562
566
563 finishdatamigration(ui, srcrepo, dstrepo, requirements)
567 finishdatamigration(ui, srcrepo, dstrepo, requirements)
564
568
565 ui.status(_(b'data fully upgraded in a temporary repository\n'))
569 ui.status(_(b'data fully upgraded in a temporary repository\n'))
566
570
567 if upgrade_op.backup_store:
571 if upgrade_op.backup_store:
568 backuppath = pycompat.mkdtemp(
572 backuppath = pycompat.mkdtemp(
569 prefix=b'upgradebackup.', dir=srcrepo.path
573 prefix=b'upgradebackup.', dir=srcrepo.path
570 )
574 )
571 backupvfs = vfsmod.vfs(backuppath)
575 backupvfs = vfsmod.vfs(backuppath)
572
576
573 # 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.
574 util.copyfile(
578 util.copyfile(
575 srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')
579 srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')
576 )
580 )
577
581
578 # We install an arbitrary requirement that clients must not support
582 # We install an arbitrary requirement that clients must not support
579 # 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
580 # 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
581 # an inconsistent state.
585 # an inconsistent state.
582 ui.status(
586 ui.status(
583 _(
587 _(
584 b'marking source repository as being upgraded; clients will be '
588 b'marking source repository as being upgraded; clients will be '
585 b'unable to read from repository\n'
589 b'unable to read from repository\n'
586 )
590 )
587 )
591 )
588 scmutil.writereporequirements(
592 scmutil.writereporequirements(
589 srcrepo, srcrepo.requirements | {b'upgradeinprogress'}
593 srcrepo, srcrepo.requirements | {b'upgradeinprogress'}
590 )
594 )
591
595
592 ui.status(_(b'starting in-place swap of repository data\n'))
596 ui.status(_(b'starting in-place swap of repository data\n'))
593 if upgrade_op.backup_store:
597 if upgrade_op.backup_store:
594 ui.status(
598 ui.status(
595 _(b'replaced files will be backed up at %s\n') % backuppath
599 _(b'replaced files will be backed up at %s\n') % backuppath
596 )
600 )
597
601
598 # 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
599 # the operation nearly instantaneous and atomic (at least in well-behaved
603 # the operation nearly instantaneous and atomic (at least in well-behaved
600 # environments).
604 # environments).
601 ui.status(_(b'replacing store...\n'))
605 ui.status(_(b'replacing store...\n'))
602 tstart = util.timer()
606 tstart = util.timer()
603 _replacestores(srcrepo, dstrepo, backupvfs, upgrade_op)
607 _replacestores(srcrepo, dstrepo, backupvfs, upgrade_op)
604 elapsed = util.timer() - tstart
608 elapsed = util.timer() - tstart
605 ui.status(
609 ui.status(
606 _(
610 _(
607 b'store replacement complete; repository was inconsistent for '
611 b'store replacement complete; repository was inconsistent for '
608 b'%0.1fs\n'
612 b'%0.1fs\n'
609 )
613 )
610 % elapsed
614 % elapsed
611 )
615 )
612
616
613 # We first write the requirements file. Any new requirements will lock
617 # We first write the requirements file. Any new requirements will lock
614 # out legacy clients.
618 # out legacy clients.
615 ui.status(
619 ui.status(
616 _(
620 _(
617 b'finalizing requirements file and making repository readable '
621 b'finalizing requirements file and making repository readable '
618 b'again\n'
622 b'again\n'
619 )
623 )
620 )
624 )
621 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
625 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
622
626
623 if upgrade_op.backup_store:
627 if upgrade_op.backup_store:
624 # 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
625 # 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
626 # 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
627 # location. This is simpler.
631 # location. This is simpler.
628 assert backupvfs is not None # help pytype
632 assert backupvfs is not None # help pytype
629 backupvfs.unlink(b'store/lock')
633 backupvfs.unlink(b'store/lock')
630
634
631 return backuppath
635 return backuppath
632
636
633
637
634 def upgrade_dirstate(ui, srcrepo, upgrade_op, old, new):
638 def upgrade_dirstate(ui, srcrepo, upgrade_op, old, new):
635 if upgrade_op.backup_store:
639 if upgrade_op.backup_store:
636 backuppath = pycompat.mkdtemp(
640 backuppath = pycompat.mkdtemp(
637 prefix=b'upgradebackup.', dir=srcrepo.path
641 prefix=b'upgradebackup.', dir=srcrepo.path
638 )
642 )
639 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)
640 backupvfs = vfsmod.vfs(backuppath)
644 backupvfs = vfsmod.vfs(backuppath)
641 util.copyfile(
645 util.copyfile(
642 srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')
646 srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')
643 )
647 )
644 try:
648 try:
645 util.copyfile(
649 util.copyfile(
646 srcrepo.vfs.join(b'dirstate'), backupvfs.join(b'dirstate')
650 srcrepo.vfs.join(b'dirstate'), backupvfs.join(b'dirstate')
647 )
651 )
648 except FileNotFoundError:
652 except FileNotFoundError:
649 # 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
650 # revision checked out
654 # revision checked out
651 pass
655 pass
652
656
653 assert srcrepo.dirstate._use_dirstate_v2 == (old == b'v2')
657 assert srcrepo.dirstate._use_dirstate_v2 == (old == b'v2')
654 srcrepo.dirstate._map.preload()
658 srcrepo.dirstate._map.preload()
655 srcrepo.dirstate._use_dirstate_v2 = new == b'v2'
659 srcrepo.dirstate._use_dirstate_v2 = new == b'v2'
656 srcrepo.dirstate._map._use_dirstate_v2 = srcrepo.dirstate._use_dirstate_v2
660 srcrepo.dirstate._map._use_dirstate_v2 = srcrepo.dirstate._use_dirstate_v2
657 srcrepo.dirstate._dirty = True
661 srcrepo.dirstate._dirty = True
658 try:
662 try:
659 srcrepo.vfs.unlink(b'dirstate')
663 srcrepo.vfs.unlink(b'dirstate')
660 except FileNotFoundError:
664 except FileNotFoundError:
661 # The dirstate does not exist on an empty repo or a repo with no
665 # The dirstate does not exist on an empty repo or a repo with no
662 # revision checked out
666 # revision checked out
663 pass
667 pass
664
668
665 srcrepo.dirstate.write(None)
669 srcrepo.dirstate.write(None)
666
670
667 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
671 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
668
672
669
673
670 def upgrade_tracked_hint(ui, srcrepo, upgrade_op, add):
674 def upgrade_tracked_hint(ui, srcrepo, upgrade_op, add):
671 if add:
675 if add:
672 srcrepo.dirstate._use_tracked_hint = True
676 srcrepo.dirstate._use_tracked_hint = True
673 srcrepo.dirstate._dirty = True
677 srcrepo.dirstate._dirty = True
674 srcrepo.dirstate._dirty_tracked_set = True
678 srcrepo.dirstate._dirty_tracked_set = True
675 srcrepo.dirstate.write(None)
679 srcrepo.dirstate.write(None)
676 if not add:
680 if not add:
677 srcrepo.dirstate.delete_tracked_hint()
681 srcrepo.dirstate.delete_tracked_hint()
678
682
679 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
683 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
General Comments 0
You need to be logged in to leave comments. Login now