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