Show More
@@ -36,7 +36,6 b' from mercurial.node import short' | |||||
36 |
|
36 | |||
37 | from mercurial import ( |
|
37 | from mercurial import ( | |
38 | error, |
|
38 | error, | |
39 | logcmdutil, |
|
|||
40 | registrar, |
|
39 | registrar, | |
41 | scmutil, |
|
40 | scmutil, | |
42 | ) |
|
41 | ) | |
@@ -56,7 +55,7 b" testedwith = b'ships-with-hg-core'" | |||||
56 | ( |
|
55 | ( | |
57 | b'r', |
|
56 | b'r', | |
58 | b'rev', |
|
57 | b'rev', | |
59 |
|
|
58 | [], | |
60 | _(b'censor file from specified revision'), |
|
59 | _(b'censor file from specified revision'), | |
61 | _(b'REV'), |
|
60 | _(b'REV'), | |
62 | ), |
|
61 | ), | |
@@ -71,7 +70,7 b" testedwith = b'ships-with-hg-core'" | |||||
71 | _(b'-r REV [-t TEXT] [FILE]'), |
|
70 | _(b'-r REV [-t TEXT] [FILE]'), | |
72 | helpcategory=command.CATEGORY_MAINTENANCE, |
|
71 | helpcategory=command.CATEGORY_MAINTENANCE, | |
73 | ) |
|
72 | ) | |
74 |
def censor(ui, repo, path, rev= |
|
73 | def censor(ui, repo, path, rev=(), tombstone=b'', check_heads=True, **opts): | |
75 | with repo.wlock(), repo.lock(): |
|
74 | with repo.wlock(), repo.lock(): | |
76 | return _docensor( |
|
75 | return _docensor( | |
77 | ui, |
|
76 | ui, | |
@@ -84,11 +83,11 b" def censor(ui, repo, path, rev=b'', tomb" | |||||
84 | ) |
|
83 | ) | |
85 |
|
84 | |||
86 |
|
85 | |||
87 |
def _docensor(ui, repo, path, rev= |
|
86 | def _docensor(ui, repo, path, revs=(), tombstone=b'', check_heads=True, **opts): | |
88 | if not path: |
|
87 | if not path: | |
89 | raise error.Abort(_(b'must specify file path to censor')) |
|
88 | raise error.Abort(_(b'must specify file path to censor')) | |
90 | if not rev: |
|
89 | if not revs: | |
91 | raise error.Abort(_(b'must specify revision to censor')) |
|
90 | raise error.Abort(_(b'must specify revisions to censor')) | |
92 |
|
91 | |||
93 | wctx = repo[None] |
|
92 | wctx = repo[None] | |
94 |
|
93 | |||
@@ -100,18 +99,17 b" def _docensor(ui, repo, path, rev=b'', t" | |||||
100 | if not len(flog): |
|
99 | if not len(flog): | |
101 | raise error.Abort(_(b'cannot censor file with no history')) |
|
100 | raise error.Abort(_(b'cannot censor file with no history')) | |
102 |
|
101 | |||
103 |
rev = |
|
102 | revs = scmutil.revrange(repo, revs) | |
104 | try: |
|
103 | if not revs: | |
105 | ctx = repo[rev] |
|
104 | raise error.Abort(_(b'no matching revisions')) | |
106 | except KeyError: |
|
105 | file_nodes = set() | |
107 | raise error.Abort(_(b'invalid revision identifier %s') % rev) |
|
106 | for r in revs: | |
|
107 | try: | |||
|
108 | ctx = repo[r] | |||
|
109 | file_nodes.add(ctx.filectx(path).filenode()) | |||
|
110 | except error.LookupError: | |||
|
111 | raise error.Abort(_(b'file does not exist at revision %s') % ctx) | |||
108 |
|
112 | |||
109 | try: |
|
|||
110 | fctx = ctx.filectx(path) |
|
|||
111 | except error.LookupError: |
|
|||
112 | raise error.Abort(_(b'file does not exist at revision %s') % rev) |
|
|||
113 |
|
||||
114 | fnode = fctx.filenode() |
|
|||
115 | if check_heads: |
|
113 | if check_heads: | |
116 | heads = [] |
|
114 | heads = [] | |
117 | repo_heads = repo.heads() |
|
115 | repo_heads = repo.heads() | |
@@ -120,7 +118,7 b" def _docensor(ui, repo, path, rev=b'', t" | |||||
120 | ui.status(msg) |
|
118 | ui.status(msg) | |
121 | for headnode in repo_heads: |
|
119 | for headnode in repo_heads: | |
122 | hc = repo[headnode] |
|
120 | hc = repo[headnode] | |
123 |
if path in hc and hc.filenode(path) |
|
121 | if path in hc and hc.filenode(path) in file_nodes: | |
124 | heads.append(hc) |
|
122 | heads.append(hc) | |
125 | if heads: |
|
123 | if heads: | |
126 | headlist = b', '.join([short(c.node()) for c in heads]) |
|
124 | headlist = b', '.join([short(c.node()) for c in heads]) | |
@@ -138,7 +136,8 b" def _docensor(ui, repo, path, rev=b'', t" | |||||
138 | hint=_(b'clean/delete/update first'), |
|
136 | hint=_(b'clean/delete/update first'), | |
139 | ) |
|
137 | ) | |
140 |
|
138 | |||
141 |
msg = b'censoring |
|
139 | msg = b'censoring %d file revisions\n' | |
|
140 | msg %= len(file_nodes) | |||
142 | ui.status(msg) |
|
141 | ui.status(msg) | |
143 | with repo.transaction(b'censor') as tr: |
|
142 | with repo.transaction(b'censor') as tr: | |
144 | flog.censorrevision(tr, fnode, tombstone=tombstone) |
|
143 | flog.censorrevision(tr, file_nodes, tombstone=tombstone) |
@@ -810,7 +810,11 b' class sqlitefilestore:' | |||||
810 |
|
810 | |||
811 | return not empty |
|
811 | return not empty | |
812 |
|
812 | |||
813 | def censorrevision(self, tr, censornode, tombstone=b''): |
|
813 | def censorrevision(self, tr, censor_nodes, tombstone=b''): | |
|
814 | for node in censor_nodes: | |||
|
815 | self._censor_one_revision(tr, node, tombstone=tombstone) | |||
|
816 | ||||
|
817 | def _censor_one_revision(self, tr, censornode, tombstone): | |||
814 | tombstone = storageutil.packmeta({b'censored': tombstone}, b'') |
|
818 | tombstone = storageutil.packmeta({b'censored': tombstone}, b'') | |
815 |
|
819 | |||
816 | # This restriction is cargo culted from revlogs and makes no sense for |
|
820 | # This restriction is cargo culted from revlogs and makes no sense for |
@@ -3835,16 +3835,16 b' class revlog:' | |||||
3835 | if addrevisioncb: |
|
3835 | if addrevisioncb: | |
3836 | addrevisioncb(self, rev, node) |
|
3836 | addrevisioncb(self, rev, node) | |
3837 |
|
3837 | |||
3838 | def censorrevision(self, tr, censornode, tombstone=b''): |
|
3838 | def censorrevision(self, tr, censor_nodes, tombstone=b''): | |
3839 | if self._format_version == REVLOGV0: |
|
3839 | if self._format_version == REVLOGV0: | |
3840 | raise error.RevlogError( |
|
3840 | raise error.RevlogError( | |
3841 | _(b'cannot censor with version %d revlogs') |
|
3841 | _(b'cannot censor with version %d revlogs') | |
3842 | % self._format_version |
|
3842 | % self._format_version | |
3843 | ) |
|
3843 | ) | |
3844 | elif self._format_version == REVLOGV1: |
|
3844 | elif self._format_version == REVLOGV1: | |
3845 | rewrite.v1_censor(self, tr, censornode, tombstone) |
|
3845 | rewrite.v1_censor(self, tr, censor_nodes, tombstone) | |
3846 | else: |
|
3846 | else: | |
3847 | rewrite.v2_censor(self, tr, censornode, tombstone) |
|
3847 | rewrite.v2_censor(self, tr, censor_nodes, tombstone) | |
3848 |
|
3848 | |||
3849 | def verifyintegrity(self, state): |
|
3849 | def verifyintegrity(self, state): | |
3850 | """Verifies the integrity of the revlog. |
|
3850 | """Verifies the integrity of the revlog. |
@@ -51,14 +51,14 b' from . import (' | |||||
51 | ) |
|
51 | ) | |
52 |
|
52 | |||
53 |
|
53 | |||
54 | def v1_censor(rl, tr, censornode, tombstone=b''): |
|
54 | def v1_censor(rl, tr, censor_nodes, tombstone=b''): | |
55 | """censors a revision in a "version 1" revlog""" |
|
55 | """censors a revision in a "version 1" revlog""" | |
56 | assert rl._format_version == constants.REVLOGV1, rl._format_version |
|
56 | assert rl._format_version == constants.REVLOGV1, rl._format_version | |
57 |
|
57 | |||
58 | # avoid cycle |
|
58 | # avoid cycle | |
59 | from .. import revlog |
|
59 | from .. import revlog | |
60 |
|
60 | |||
61 | censorrev = rl.rev(censornode) |
|
61 | censor_revs = set(rl.rev(node) for node in censor_nodes) | |
62 | tombstone = storageutil.packmeta({b'censored': tombstone}, b'') |
|
62 | tombstone = storageutil.packmeta({b'censored': tombstone}, b'') | |
63 |
|
63 | |||
64 | # Rewriting the revlog in place is hard. Our strategy for censoring is |
|
64 | # Rewriting the revlog in place is hard. Our strategy for censoring is | |
@@ -87,14 +87,14 b' def v1_censor(rl, tr, censornode, tombst' | |||||
87 | node = rl.node(rev) |
|
87 | node = rl.node(rev) | |
88 | p1, p2 = rl.parents(node) |
|
88 | p1, p2 = rl.parents(node) | |
89 |
|
89 | |||
90 |
if rev |
|
90 | if rev in censor_revs: | |
91 | newrl.addrawrevision( |
|
91 | newrl.addrawrevision( | |
92 | tombstone, |
|
92 | tombstone, | |
93 | tr, |
|
93 | tr, | |
94 |
rl.linkrev( |
|
94 | rl.linkrev(rev), | |
95 | p1, |
|
95 | p1, | |
96 | p2, |
|
96 | p2, | |
97 |
|
|
97 | node, | |
98 | constants.REVIDX_ISCENSORED, |
|
98 | constants.REVIDX_ISCENSORED, | |
99 | ) |
|
99 | ) | |
100 |
|
100 | |||
@@ -138,12 +138,12 b' def v1_censor(rl, tr, censornode, tombst' | |||||
138 | rl._load_inner(chunk_cache) |
|
138 | rl._load_inner(chunk_cache) | |
139 |
|
139 | |||
140 |
|
140 | |||
141 | def v2_censor(revlog, tr, censornode, tombstone=b''): |
|
141 | def v2_censor(revlog, tr, censor_nodes, tombstone=b''): | |
142 | """censors a revision in a "version 2" revlog""" |
|
142 | """censors a revision in a "version 2" revlog""" | |
143 | assert revlog._format_version != REVLOGV0, revlog._format_version |
|
143 | assert revlog._format_version != REVLOGV0, revlog._format_version | |
144 | assert revlog._format_version != REVLOGV1, revlog._format_version |
|
144 | assert revlog._format_version != REVLOGV1, revlog._format_version | |
145 |
|
145 | |||
146 |
censor_revs = {revlog.rev( |
|
146 | censor_revs = {revlog.rev(node) for node in censor_nodes} | |
147 | _rewrite_v2(revlog, tr, censor_revs, tombstone) |
|
147 | _rewrite_v2(revlog, tr, censor_revs, tombstone) | |
148 |
|
148 | |||
149 |
|
149 |
@@ -1280,7 +1280,7 b' class ifilemutationtests(basetestcase):' | |||||
1280 | node2 = f.add(b'foo\n' * 32, None, tr, 2, node1, f.nullid) |
|
1280 | node2 = f.add(b'foo\n' * 32, None, tr, 2, node1, f.nullid) | |
1281 |
|
1281 | |||
1282 | with self._maketransactionfn() as tr: |
|
1282 | with self._maketransactionfn() as tr: | |
1283 | f.censorrevision(tr, node1) |
|
1283 | f.censorrevision(tr, [node1]) | |
1284 |
|
1284 | |||
1285 | self.assertEqual(len(f), 3) |
|
1285 | self.assertEqual(len(f), 3) | |
1286 | self.assertEqual(list(f.revs()), [0, 1, 2]) |
|
1286 | self.assertEqual(list(f.revs()), [0, 1, 2]) |
@@ -80,7 +80,7 b' Censor revision with 2 offenses' | |||||
80 | $ hg --config extensions.censor= --cwd foo/bar/baz censor -r $C2 -t "remove password" ../../../target |
|
80 | $ hg --config extensions.censor= --cwd foo/bar/baz censor -r $C2 -t "remove password" ../../../target | |
81 | checking for the censored content in 2 heads |
|
81 | checking for the censored content in 2 heads | |
82 | checking for the censored content in the working directory |
|
82 | checking for the censored content in the working directory | |
83 | censoring 1 file revision |
|
83 | censoring 1 file revisions | |
84 | $ hg cat -r $H1 target | head -n 10 |
|
84 | $ hg cat -r $H1 target | head -n 10 | |
85 | Tainted file is now sanitized |
|
85 | Tainted file is now sanitized | |
86 | $ hg cat -r $H2 target | head -n 10 |
|
86 | $ hg cat -r $H2 target | head -n 10 | |
@@ -101,7 +101,7 b' Censor revision with 1 offense' | |||||
101 | $ hg --config extensions.censor= --cwd foo/bar/baz censor -r $C1 path:target |
|
101 | $ hg --config extensions.censor= --cwd foo/bar/baz censor -r $C1 path:target | |
102 | checking for the censored content in 2 heads |
|
102 | checking for the censored content in 2 heads | |
103 | checking for the censored content in the working directory |
|
103 | checking for the censored content in the working directory | |
104 | censoring 1 file revision |
|
104 | censoring 1 file revisions | |
105 | $ hg cat -r $H1 target | head -n 10 |
|
105 | $ hg cat -r $H1 target | head -n 10 | |
106 | Tainted file is now sanitized |
|
106 | Tainted file is now sanitized | |
107 | $ hg cat -r $H2 target | head -n 10 |
|
107 | $ hg cat -r $H2 target | head -n 10 | |
@@ -243,7 +243,7 b" with the file censored, but we can't cen" | |||||
243 | $ hg --config extensions.censor= censor -r $C3 target |
|
243 | $ hg --config extensions.censor= censor -r $C3 target | |
244 | checking for the censored content in 2 heads |
|
244 | checking for the censored content in 2 heads | |
245 | checking for the censored content in the working directory |
|
245 | checking for the censored content in the working directory | |
246 | censoring 1 file revision |
|
246 | censoring 1 file revisions | |
247 | $ hg update -r $H2 |
|
247 | $ hg update -r $H2 | |
248 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
248 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
249 | $ hg merge -r $C3 |
|
249 | $ hg merge -r $C3 | |
@@ -294,7 +294,7 b' Can re-add file after being deleted + ce' | |||||
294 | $ hg --config extensions.censor= censor -r $C4 target |
|
294 | $ hg --config extensions.censor= censor -r $C4 target | |
295 | checking for the censored content in 2 heads |
|
295 | checking for the censored content in 2 heads | |
296 | checking for the censored content in the working directory |
|
296 | checking for the censored content in the working directory | |
297 | censoring 1 file revision |
|
297 | censoring 1 file revisions | |
298 | $ hg cat -r $C4 target | head -n 10 |
|
298 | $ hg cat -r $C4 target | head -n 10 | |
299 | $ hg cat -r "$H2^^" target | head -n 10 |
|
299 | $ hg cat -r "$H2^^" target | head -n 10 | |
300 | Tainted file now super sanitized |
|
300 | Tainted file now super sanitized | |
@@ -329,7 +329,7 b' Can censor enough revision to move back ' | |||||
329 | $ hg --config extensions.censor= censor -r $C5 target |
|
329 | $ hg --config extensions.censor= censor -r $C5 target | |
330 | checking for the censored content in 2 heads |
|
330 | checking for the censored content in 2 heads | |
331 | checking for the censored content in the working directory |
|
331 | checking for the censored content in the working directory | |
332 | censoring 1 file revision |
|
332 | censoring 1 file revisions | |
333 |
|
333 | |||
334 | The important part is for the censor operation to not crash and the repository |
|
334 | The important part is for the censor operation to not crash and the repository | |
335 | to not be corrupted. Right now this involve keeping the revlog split. |
|
335 | to not be corrupted. Right now this involve keeping the revlog split. | |
@@ -410,7 +410,7 b' Censored nodes can be pushed if they cen' | |||||
410 | $ hg --config extensions.censor= censor -r $REV target |
|
410 | $ hg --config extensions.censor= censor -r $REV target | |
411 | checking for the censored content in 3 heads |
|
411 | checking for the censored content in 3 heads | |
412 | checking for the censored content in the working directory |
|
412 | checking for the censored content in the working directory | |
413 | censoring 1 file revision |
|
413 | censoring 1 file revisions | |
414 | $ hg cat -r $REV target | head -n 10 |
|
414 | $ hg cat -r $REV target | head -n 10 | |
415 | $ hg cat -r $CLEANREV target | head -n 10 |
|
415 | $ hg cat -r $CLEANREV target | head -n 10 | |
416 | Re-sanitized; nothing to see here |
|
416 | Re-sanitized; nothing to see here | |
@@ -513,7 +513,7 b' Can import bundle where first revision o' | |||||
513 | $ hg --config extensions.censor= censor -r 0 target |
|
513 | $ hg --config extensions.censor= censor -r 0 target | |
514 | checking for the censored content in 3 heads |
|
514 | checking for the censored content in 3 heads | |
515 | checking for the censored content in the working directory |
|
515 | checking for the censored content in the working directory | |
516 | censoring 1 file revision |
|
516 | censoring 1 file revisions | |
517 | $ hg bundle -r 0 --base null ../rinit/initbundle |
|
517 | $ hg bundle -r 0 --base null ../rinit/initbundle | |
518 | 1 changesets found |
|
518 | 1 changesets found | |
519 | $ cd ../rinit |
|
519 | $ cd ../rinit | |
@@ -530,7 +530,17 b' Can skip the head checking steps' | |||||
530 |
|
530 | |||
531 | $ hg --config extensions.censor= censor -r 0 --no-check-heads target |
|
531 | $ hg --config extensions.censor= censor -r 0 --no-check-heads target | |
532 | checking for the censored content in the working directory |
|
532 | checking for the censored content in the working directory | |
533 | censoring 1 file revision |
|
533 | censoring 1 file revisions | |
|
534 | ||||
|
535 | Can censor multiple revision in one go. | |||
|
536 | ||||
|
537 | $ cd ../r | |||
|
538 | $ hg --config extensions.censor= censor -r 0+1 target | |||
|
539 | checking for the censored content in 3 heads | |||
|
540 | checking for the censored content in the working directory | |||
|
541 | censoring 2 file revisions | |||
|
542 | ||||
|
543 | ||||
534 |
|
544 | |||
535 | #if revlogv2 |
|
545 | #if revlogv2 | |
536 |
|
546 | |||
@@ -572,7 +582,7 b' Censor the file' | |||||
572 | $ hg --config extensions.censor= censor -r $B1 target |
|
582 | $ hg --config extensions.censor= censor -r $B1 target | |
573 | checking for the censored content in 1 heads |
|
583 | checking for the censored content in 1 heads | |
574 | checking for the censored content in the working directory |
|
584 | checking for the censored content in the working directory | |
575 | censoring 1 file revision |
|
585 | censoring 1 file revisions | |
576 | $ hg cat -r $B1 target | wc -l |
|
586 | $ hg cat -r $B1 target | wc -l | |
577 | *0 (re) |
|
587 | *0 (re) | |
578 |
|
588 |
@@ -17,7 +17,7 b'' | |||||
17 | $ hg censor target --config extensions.censor= -r ".^^" |
|
17 | $ hg censor target --config extensions.censor= -r ".^^" | |
18 | checking for the censored content in 1 heads |
|
18 | checking for the censored content in 1 heads | |
19 | checking for the censored content in the working directory |
|
19 | checking for the censored content in the working directory | |
20 | censoring 1 file revision |
|
20 | censoring 1 file revisions | |
21 | $ hg update ".^" |
|
21 | $ hg update ".^" | |
22 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
22 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
23 | $ cat target |
|
23 | $ cat target |
General Comments 0
You need to be logged in to leave comments.
Login now