##// END OF EJS Templates
revlog: move censor logic out of censor extension...
Gregory Szorc -
r39814:a6b3c4c1 default
parent child Browse files
Show More
@@ -32,11 +32,8 b' from mercurial.node import short'
32
32
33 from mercurial import (
33 from mercurial import (
34 error,
34 error,
35 pycompat,
36 registrar,
35 registrar,
37 revlog,
38 scmutil,
36 scmutil,
39 util,
40 )
37 )
41
38
42 cmdtable = {}
39 cmdtable = {}
@@ -98,90 +95,5 b" def _docensor(ui, repo, path, rev='', to"
98 raise error.Abort(_('cannot censor working directory'),
95 raise error.Abort(_('cannot censor working directory'),
99 hint=_('clean/delete/update first'))
96 hint=_('clean/delete/update first'))
100
97
101 flogv = flog.version & 0xFFFF
98 with repo.transaction(b'censor') as tr:
102 if flogv != revlog.REVLOGV1:
99 flog.censorrevision(tr, fnode, tombstone=tombstone)
103 raise error.Abort(
104 _('censor does not support revlog version %d') % (flogv,))
105
106 tombstone = revlog.packmeta({"censored": tombstone}, "")
107
108 crev = fctx.filerev()
109
110 if len(tombstone) > flog.rawsize(crev):
111 raise error.Abort(_(
112 'censor tombstone must be no longer than censored data'))
113
114 # Using two files instead of one makes it easy to rewrite entry-by-entry
115 idxread = repo.svfs(flog.indexfile, 'r')
116 idxwrite = repo.svfs(flog.indexfile, 'wb', atomictemp=True)
117 if flog.version & revlog.FLAG_INLINE_DATA:
118 dataread, datawrite = idxread, idxwrite
119 else:
120 dataread = repo.svfs(flog.datafile, 'r')
121 datawrite = repo.svfs(flog.datafile, 'wb', atomictemp=True)
122
123 # Copy all revlog data up to the entry to be censored.
124 rio = revlog.revlogio()
125 offset = flog.start(crev)
126
127 for chunk in util.filechunkiter(idxread, limit=crev * rio.size):
128 idxwrite.write(chunk)
129 for chunk in util.filechunkiter(dataread, limit=offset):
130 datawrite.write(chunk)
131
132 def rewriteindex(r, newoffs, newdata=None):
133 """Rewrite the index entry with a new data offset and optional new data.
134
135 The newdata argument, if given, is a tuple of three positive integers:
136 (new compressed, new uncompressed, added flag bits).
137 """
138 offlags, comp, uncomp, base, link, p1, p2, nodeid = flog.index[r]
139 flags = revlog.gettype(offlags)
140 if newdata:
141 comp, uncomp, nflags = newdata
142 flags |= nflags
143 offlags = revlog.offset_type(newoffs, flags)
144 e = (offlags, comp, uncomp, r, link, p1, p2, nodeid)
145 idxwrite.write(rio.packentry(e, None, flog.version, r))
146 idxread.seek(rio.size, 1)
147
148 def rewrite(r, offs, data, nflags=revlog.REVIDX_DEFAULT_FLAGS):
149 """Write the given full text to the filelog with the given data offset.
150
151 Returns:
152 The integer number of data bytes written, for tracking data offsets.
153 """
154 flag, compdata = flog.compress(data)
155 newcomp = len(flag) + len(compdata)
156 rewriteindex(r, offs, (newcomp, len(data), nflags))
157 datawrite.write(flag)
158 datawrite.write(compdata)
159 dataread.seek(flog.length(r), 1)
160 return newcomp
161
162 # Rewrite censored revlog entry with (padded) tombstone data.
163 pad = ' ' * (flog.rawsize(crev) - len(tombstone))
164 offset += rewrite(crev, offset, tombstone + pad, revlog.REVIDX_ISCENSORED)
165
166 # Rewrite all following filelog revisions fixing up offsets and deltas.
167 for srev in pycompat.xrange(crev + 1, len(flog)):
168 if crev in flog.parentrevs(srev):
169 # Immediate children of censored node must be re-added as fulltext.
170 try:
171 revdata = flog.revision(srev)
172 except error.CensoredNodeError as e:
173 revdata = e.tombstone
174 dlen = rewrite(srev, offset, revdata)
175 else:
176 # Copy any other revision data verbatim after fixing up the offset.
177 rewriteindex(srev, offset)
178 dlen = flog.length(srev)
179 for chunk in util.filechunkiter(dataread, limit=dlen):
180 datawrite.write(chunk)
181 offset += dlen
182
183 idxread.close()
184 idxwrite.close()
185 if dataread is not idxread:
186 dataread.close()
187 datawrite.close()
@@ -111,6 +111,9 b' class filelog(object):'
111 def strip(self, minlink, transaction):
111 def strip(self, minlink, transaction):
112 return self._revlog.strip(minlink, transaction)
112 return self._revlog.strip(minlink, transaction)
113
113
114 def censorrevision(self, tr, node, tombstone=b''):
115 return self._revlog.censorrevision(node, tombstone=tombstone)
116
114 def files(self):
117 def files(self):
115 return self._revlog.files()
118 return self._revlog.files()
116
119
@@ -691,6 +691,23 b' class ifilemutation(interfaceutil.Interf'
691 even if it existed in the store previously.
691 even if it existed in the store previously.
692 """
692 """
693
693
694 def censorrevision(tr, node, tombstone=b''):
695 """Remove the content of a single revision.
696
697 The specified ``node`` will have its content purged from storage.
698 Future attempts to access the revision data for this node will
699 result in failure.
700
701 A ``tombstone`` message can optionally be stored. This message may be
702 displayed to users when they attempt to access the missing revision
703 data.
704
705 Storage backends may have stored deltas against the previous content
706 in this revision. As part of censoring a revision, these storage
707 backends are expected to rewrite any internally stored deltas such
708 that they no longer reference the deleted content.
709 """
710
694 def getstrippoint(minlink):
711 def getstrippoint(minlink):
695 """Find the minimum revision that must be stripped to strip a linkrev.
712 """Find the minimum revision that must be stripped to strip a linkrev.
696
713
@@ -2492,3 +2492,92 b' class revlog(object):'
2492 finally:
2492 finally:
2493 destrevlog._lazydeltabase = oldlazydeltabase
2493 destrevlog._lazydeltabase = oldlazydeltabase
2494 destrevlog._deltabothparents = oldamd
2494 destrevlog._deltabothparents = oldamd
2495
2496 def censorrevision(self, node, tombstone=b''):
2497 if (self.version & 0xFFFF) == REVLOGV0:
2498 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2499 self.version)
2500
2501 rev = self.rev(node)
2502 tombstone = packmeta({b'censored': tombstone}, b'')
2503
2504 if len(tombstone) > self.rawsize(rev):
2505 raise error.Abort(_('censor tombstone must be no longer than '
2506 'censored data'))
2507
2508 # Using two files instead of one makes it easy to rewrite entry-by-entry
2509 idxread = self.opener(self.indexfile, 'r')
2510 idxwrite = self.opener(self.indexfile, 'wb', atomictemp=True)
2511 if self.version & FLAG_INLINE_DATA:
2512 dataread, datawrite = idxread, idxwrite
2513 else:
2514 dataread = self.opener(self.datafile, 'r')
2515 datawrite = self.opener(self.datafile, 'wb', atomictemp=True)
2516
2517 # Copy all revlog data up to the entry to be censored.
2518 offset = self.start(rev)
2519
2520 for chunk in util.filechunkiter(idxread, limit=rev * self._io.size):
2521 idxwrite.write(chunk)
2522 for chunk in util.filechunkiter(dataread, limit=offset):
2523 datawrite.write(chunk)
2524
2525 def rewriteindex(r, newoffs, newdata=None):
2526 """Rewrite the index entry with a new data offset and new data.
2527
2528 The newdata argument, if given, is a tuple of three positive
2529 integers: (new compressed, new uncompressed, added flag bits).
2530 """
2531 offlags, comp, uncomp, base, link, p1, p2, nodeid = self.index[r]
2532 flags = gettype(offlags)
2533 if newdata:
2534 comp, uncomp, nflags = newdata
2535 flags |= nflags
2536 offlags = offset_type(newoffs, flags)
2537 e = (offlags, comp, uncomp, r, link, p1, p2, nodeid)
2538 idxwrite.write(self._io.packentry(e, None, self.version, r))
2539 idxread.seek(self._io.size, 1)
2540
2541 def rewrite(r, offs, data, nflags=REVIDX_DEFAULT_FLAGS):
2542 """Write the given fulltext with the given data offset.
2543
2544 Returns:
2545 The integer number of data bytes written, for tracking data
2546 offsets.
2547 """
2548 flag, compdata = self.compress(data)
2549 newcomp = len(flag) + len(compdata)
2550 rewriteindex(r, offs, (newcomp, len(data), nflags))
2551 datawrite.write(flag)
2552 datawrite.write(compdata)
2553 dataread.seek(self.length(r), 1)
2554 return newcomp
2555
2556 # Rewrite censored entry with (padded) tombstone data.
2557 pad = ' ' * (self.rawsize(rev) - len(tombstone))
2558 offset += rewrite(rev, offset, tombstone + pad, REVIDX_ISCENSORED)
2559
2560 # Rewrite all following filelog revisions fixing up offsets and deltas.
2561 for srev in pycompat.xrange(rev + 1, len(self)):
2562 if rev in self.parentrevs(srev):
2563 # Immediate children of censored node must be re-added as
2564 # fulltext.
2565 try:
2566 revdata = self.revision(srev)
2567 except error.CensoredNodeError as e:
2568 revdata = e.tombstone
2569 dlen = rewrite(srev, offset, revdata)
2570 else:
2571 # Copy any other revision data verbatim after fixing up the
2572 # offset.
2573 rewriteindex(srev, offset)
2574 dlen = self.length(srev)
2575 for chunk in util.filechunkiter(dataread, limit=dlen):
2576 datawrite.write(chunk)
2577 offset += dlen
2578
2579 idxread.close()
2580 idxwrite.close()
2581 if dataread is not idxread:
2582 dataread.close()
2583 datawrite.close()
General Comments 0
You need to be logged in to leave comments. Login now