Mercurial > hg
comparison hgext/censor.py @ 51270:ceeb8fa23cc8
censor: accept multiple revision in a single call
This is useful when dealing with corruption, as all the corrupted revision can
be dealt with in one go.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Fri, 01 Dec 2023 22:56:08 +0100 |
parents | db121ddd171e |
children |
comparison
equal
deleted
inserted
replaced
51269:db121ddd171e | 51270:ceeb8fa23cc8 |
---|---|
34 from mercurial.i18n import _ | 34 from mercurial.i18n import _ |
35 from mercurial.node import short | 35 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 ) |
43 | 42 |
44 cmdtable = {} | 43 cmdtable = {} |
54 b'censor', | 53 b'censor', |
55 [ | 54 [ |
56 ( | 55 ( |
57 b'r', | 56 b'r', |
58 b'rev', | 57 b'rev', |
59 b'', | 58 [], |
60 _(b'censor file from specified revision'), | 59 _(b'censor file from specified revision'), |
61 _(b'REV'), | 60 _(b'REV'), |
62 ), | 61 ), |
63 ( | 62 ( |
64 b'', | 63 b'', |
69 (b't', b'tombstone', b'', _(b'replacement tombstone data'), _(b'TEXT')), | 68 (b't', b'tombstone', b'', _(b'replacement tombstone data'), _(b'TEXT')), |
70 ], | 69 ], |
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=b'', tombstone=b'', check_heads=True, **opts): | 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, |
78 repo, | 77 repo, |
79 path, | 78 path, |
82 check_heads=check_heads, | 81 check_heads=check_heads, |
83 **opts, | 82 **opts, |
84 ) | 83 ) |
85 | 84 |
86 | 85 |
87 def _docensor(ui, repo, path, rev=b'', tombstone=b'', check_heads=True, **opts): | 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 |
95 m = scmutil.match(wctx, (path,)) | 94 m = scmutil.match(wctx, (path,)) |
96 if m.anypats() or len(m.files()) != 1: | 95 if m.anypats() or len(m.files()) != 1: |
98 path = m.files()[0] | 97 path = m.files()[0] |
99 flog = repo.file(path) | 98 flog = repo.file(path) |
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 = logcmdutil.revsingle(repo, rev, rev).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() |
118 msg = b'checking for the censored content in %d heads\n' | 116 msg = b'checking for the censored content in %d heads\n' |
119 msg %= len(repo_heads) | 117 msg %= len(repo_heads) |
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) == fnode: | 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]) |
127 raise error.Abort( | 125 raise error.Abort( |
128 _(b'cannot censor file in heads (%s)') % headlist, | 126 _(b'cannot censor file in heads (%s)') % headlist, |
136 raise error.Abort( | 134 raise error.Abort( |
137 _(b'cannot censor working directory'), | 135 _(b'cannot censor working directory'), |
138 hint=_(b'clean/delete/update first'), | 136 hint=_(b'clean/delete/update first'), |
139 ) | 137 ) |
140 | 138 |
141 msg = b'censoring 1 file revision\n' | 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) |