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