Show More
@@ -32,11 +32,8 b' from mercurial.node import short' | |||
|
32 | 32 | |
|
33 | 33 | from mercurial import ( |
|
34 | 34 | error, |
|
35 | pycompat, | |
|
36 | 35 | registrar, |
|
37 | revlog, | |
|
38 | 36 | scmutil, |
|
39 | util, | |
|
40 | 37 | ) |
|
41 | 38 | |
|
42 | 39 | cmdtable = {} |
@@ -98,90 +95,5 b" def _docensor(ui, repo, path, rev='', to" | |||
|
98 | 95 | raise error.Abort(_('cannot censor working directory'), |
|
99 | 96 | hint=_('clean/delete/update first')) |
|
100 | 97 | |
|
101 | flogv = flog.version & 0xFFFF | |
|
102 | if flogv != revlog.REVLOGV1: | |
|
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() | |
|
98 | with repo.transaction(b'censor') as tr: | |
|
99 | flog.censorrevision(tr, fnode, tombstone=tombstone) |
@@ -111,6 +111,9 b' class filelog(object):' | |||
|
111 | 111 | def strip(self, minlink, transaction): |
|
112 | 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 | 117 | def files(self): |
|
115 | 118 | return self._revlog.files() |
|
116 | 119 |
@@ -691,6 +691,23 b' class ifilemutation(interfaceutil.Interf' | |||
|
691 | 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 | 711 | def getstrippoint(minlink): |
|
695 | 712 | """Find the minimum revision that must be stripped to strip a linkrev. |
|
696 | 713 |
@@ -2492,3 +2492,92 b' class revlog(object):' | |||
|
2492 | 2492 | finally: |
|
2493 | 2493 | destrevlog._lazydeltabase = oldlazydeltabase |
|
2494 | 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