##// END OF EJS Templates
censor: make various path forms available like other Mercurial commands...
FUJIWARA Katsunori -
r25806:5e18f6e3 default
parent child Browse files
Show More
@@ -1,165 +1,170 b''
1 1 # Copyright (C) 2015 - Mike Edgar <adgar@google.com>
2 2 #
3 3 # This extension enables removal of file content at a given revision,
4 4 # rewriting the data/metadata of successive revisions to preserve revision log
5 5 # integrity.
6 6
7 7 """erase file content at a given revision
8 8
9 9 The censor command instructs Mercurial to erase all content of a file at a given
10 10 revision *without updating the changeset hash.* This allows existing history to
11 11 remain valid while preventing future clones/pulls from receiving the erased
12 12 data.
13 13
14 14 Typical uses for censor are due to security or legal requirements, including::
15 15
16 16 * Passwords, private keys, crytographic material
17 17 * Licensed data/code/libraries for which the license has expired
18 18 * Personally Identifiable Information or other private data
19 19
20 20 Censored nodes can interrupt mercurial's typical operation whenever the excised
21 21 data needs to be materialized. Some commands, like ``hg cat``/``hg revert``,
22 22 simply fail when asked to produce censored data. Others, like ``hg verify`` and
23 23 ``hg update``, must be capable of tolerating censored data to continue to
24 24 function in a meaningful way. Such commands only tolerate censored file
25 25 revisions if they are allowed by the "censor.policy=ignore" config option.
26 26 """
27 27
28 28 from mercurial.node import short
29 29 from mercurial import cmdutil, error, filelog, revlog, scmutil, util
30 30 from mercurial.i18n import _
31 31
32 32 cmdtable = {}
33 33 command = cmdutil.command(cmdtable)
34 34 # Note for extension authors: ONLY specify testedwith = 'internal' for
35 35 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
36 36 # be specifying the version(s) of Mercurial they are tested with, or
37 37 # leave the attribute unspecified.
38 38 testedwith = 'internal'
39 39
40 40 @command('censor',
41 41 [('r', 'rev', '', _('censor file from specified revision'), _('REV')),
42 42 ('t', 'tombstone', '', _('replacement tombstone data'), _('TEXT'))],
43 43 _('-r REV [-t TEXT] [FILE]'))
44 44 def censor(ui, repo, path, rev='', tombstone='', **opts):
45 45 if not path:
46 46 raise util.Abort(_('must specify file path to censor'))
47 47 if not rev:
48 48 raise util.Abort(_('must specify revision to censor'))
49 49
50 wctx = repo[None]
51
52 m = scmutil.match(wctx, (path,))
53 if m.anypats() or len(m.files()) != 1:
54 raise util.Abort(_('can only specify an explicit filename'))
55 path = m.files()[0]
50 56 flog = repo.file(path)
51 57 if not len(flog):
52 58 raise util.Abort(_('cannot censor file with no history'))
53 59
54 60 rev = scmutil.revsingle(repo, rev, rev).rev()
55 61 try:
56 62 ctx = repo[rev]
57 63 except KeyError:
58 64 raise util.Abort(_('invalid revision identifier %s') % rev)
59 65
60 66 try:
61 67 fctx = ctx.filectx(path)
62 68 except error.LookupError:
63 69 raise util.Abort(_('file does not exist at revision %s') % rev)
64 70
65 71 fnode = fctx.filenode()
66 72 headctxs = [repo[c] for c in repo.heads()]
67 73 heads = [c for c in headctxs if path in c and c.filenode(path) == fnode]
68 74 if heads:
69 75 headlist = ', '.join([short(c.node()) for c in heads])
70 76 raise util.Abort(_('cannot censor file in heads (%s)') % headlist,
71 77 hint=_('clean/delete and commit first'))
72 78
73 wctx = repo[None]
74 79 wp = wctx.parents()
75 80 if ctx.node() in [p.node() for p in wp]:
76 81 raise util.Abort(_('cannot censor working directory'),
77 82 hint=_('clean/delete/update first'))
78 83
79 84 flogv = flog.version & 0xFFFF
80 85 if flogv != revlog.REVLOGNG:
81 86 raise util.Abort(
82 87 _('censor does not support revlog version %d') % (flogv,))
83 88
84 89 tombstone = filelog.packmeta({"censored": tombstone}, "")
85 90
86 91 crev = fctx.filerev()
87 92
88 93 if len(tombstone) > flog.rawsize(crev):
89 94 raise util.Abort(_(
90 95 'censor tombstone must be no longer than censored data'))
91 96
92 97 # Using two files instead of one makes it easy to rewrite entry-by-entry
93 98 idxread = repo.svfs(flog.indexfile, 'r')
94 99 idxwrite = repo.svfs(flog.indexfile, 'wb', atomictemp=True)
95 100 if flog.version & revlog.REVLOGNGINLINEDATA:
96 101 dataread, datawrite = idxread, idxwrite
97 102 else:
98 103 dataread = repo.svfs(flog.datafile, 'r')
99 104 datawrite = repo.svfs(flog.datafile, 'wb', atomictemp=True)
100 105
101 106 # Copy all revlog data up to the entry to be censored.
102 107 rio = revlog.revlogio()
103 108 offset = flog.start(crev)
104 109
105 110 for chunk in util.filechunkiter(idxread, limit=crev * rio.size):
106 111 idxwrite.write(chunk)
107 112 for chunk in util.filechunkiter(dataread, limit=offset):
108 113 datawrite.write(chunk)
109 114
110 115 def rewriteindex(r, newoffs, newdata=None):
111 116 """Rewrite the index entry with a new data offset and optional new data.
112 117
113 118 The newdata argument, if given, is a tuple of three positive integers:
114 119 (new compressed, new uncompressed, added flag bits).
115 120 """
116 121 offlags, comp, uncomp, base, link, p1, p2, nodeid = flog.index[r]
117 122 flags = revlog.gettype(offlags)
118 123 if newdata:
119 124 comp, uncomp, nflags = newdata
120 125 flags |= nflags
121 126 offlags = revlog.offset_type(newoffs, flags)
122 127 e = (offlags, comp, uncomp, r, link, p1, p2, nodeid)
123 128 idxwrite.write(rio.packentry(e, None, flog.version, r))
124 129 idxread.seek(rio.size, 1)
125 130
126 131 def rewrite(r, offs, data, nflags=revlog.REVIDX_DEFAULT_FLAGS):
127 132 """Write the given full text to the filelog with the given data offset.
128 133
129 134 Returns:
130 135 The integer number of data bytes written, for tracking data offsets.
131 136 """
132 137 flag, compdata = flog.compress(data)
133 138 newcomp = len(flag) + len(compdata)
134 139 rewriteindex(r, offs, (newcomp, len(data), nflags))
135 140 datawrite.write(flag)
136 141 datawrite.write(compdata)
137 142 dataread.seek(flog.length(r), 1)
138 143 return newcomp
139 144
140 145 # Rewrite censored revlog entry with (padded) tombstone data.
141 146 pad = ' ' * (flog.rawsize(crev) - len(tombstone))
142 147 offset += rewrite(crev, offset, tombstone + pad, revlog.REVIDX_ISCENSORED)
143 148
144 149 # Rewrite all following filelog revisions fixing up offsets and deltas.
145 150 for srev in xrange(crev + 1, len(flog)):
146 151 if crev in flog.parentrevs(srev):
147 152 # Immediate children of censored node must be re-added as fulltext.
148 153 try:
149 154 revdata = flog.revision(srev)
150 155 except error.CensoredNodeError as e:
151 156 revdata = e.tombstone
152 157 dlen = rewrite(srev, offset, revdata)
153 158 else:
154 159 # Copy any other revision data verbatim after fixing up the offset.
155 160 rewriteindex(srev, offset)
156 161 dlen = flog.length(srev)
157 162 for chunk in util.filechunkiter(dataread, limit=dlen):
158 163 datawrite.write(chunk)
159 164 offset += dlen
160 165
161 166 idxread.close()
162 167 idxwrite.close()
163 168 if dataread is not idxread:
164 169 dataread.close()
165 170 datawrite.close()
@@ -1,480 +1,485 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > censor=
4 4 > EOF
5 5 $ cp $HGRCPATH $HGRCPATH.orig
6 6
7 7 Create repo with unimpeachable content
8 8
9 9 $ hg init r
10 10 $ cd r
11 11 $ echo 'Initially untainted file' > target
12 12 $ echo 'Normal file here' > bystander
13 13 $ hg add target bystander
14 14 $ hg ci -m init
15 15
16 16 Clone repo so we can test pull later
17 17
18 18 $ cd ..
19 19 $ hg clone r rpull
20 20 updating to branch default
21 21 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
22 22 $ cd r
23 23
24 24 Introduce content which will ultimately require censorship. Name the first
25 25 censored node C1, second C2, and so on
26 26
27 27 $ echo 'Tainted file' > target
28 28 $ echo 'Passwords: hunter2' >> target
29 29 $ hg ci -m taint target
30 30 $ C1=`hg id --debug -i`
31 31
32 32 $ echo 'hunter3' >> target
33 33 $ echo 'Normal file v2' > bystander
34 34 $ hg ci -m moretaint target bystander
35 35 $ C2=`hg id --debug -i`
36 36
37 37 Add a new sanitized versions to correct our mistake. Name the first head H1,
38 38 the second head H2, and so on
39 39
40 40 $ echo 'Tainted file is now sanitized' > target
41 41 $ hg ci -m sanitized target
42 42 $ H1=`hg id --debug -i`
43 43
44 44 $ hg update -r $C2
45 45 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 46 $ echo 'Tainted file now super sanitized' > target
47 47 $ hg ci -m 'super sanitized' target
48 48 created new head
49 49 $ H2=`hg id --debug -i`
50 50
51 51 Verify target contents before censorship at each revision
52 52
53 53 $ hg cat -r $H1 target
54 54 Tainted file is now sanitized
55 55 $ hg cat -r $H2 target
56 56 Tainted file now super sanitized
57 57 $ hg cat -r $C2 target
58 58 Tainted file
59 59 Passwords: hunter2
60 60 hunter3
61 61 $ hg cat -r $C1 target
62 62 Tainted file
63 63 Passwords: hunter2
64 64 $ hg cat -r 0 target
65 65 Initially untainted file
66 66
67 67 Try to censor revision with too large of a tombstone message
68 68
69 69 $ hg censor -r $C1 -t 'blah blah blah blah blah blah blah blah bla' target
70 70 abort: censor tombstone must be no longer than censored data
71 71 [255]
72 72
73 73 Censor revision with 2 offenses
74 74
75 $ hg censor -r $C2 -t "remove password" target
75 (this also tests file pattern matching: path relative to cwd case)
76
77 $ mkdir -p foo/bar/baz
78 $ hg --cwd foo/bar/baz censor -r $C2 -t "remove password" ../../../target
76 79 $ hg cat -r $H1 target
77 80 Tainted file is now sanitized
78 81 $ hg cat -r $H2 target
79 82 Tainted file now super sanitized
80 83 $ hg cat -r $C2 target
81 84 abort: censored node: 1e0247a9a4b7
82 85 (set censor.policy to ignore errors)
83 86 [255]
84 87 $ hg cat -r $C1 target
85 88 Tainted file
86 89 Passwords: hunter2
87 90 $ hg cat -r 0 target
88 91 Initially untainted file
89 92
90 93 Censor revision with 1 offense
91 94
92 $ hg censor -r $C1 target
95 (this also tests file pattern matching: with 'path:' scheme)
96
97 $ hg --cwd foo/bar/baz censor -r $C1 path:target
93 98 $ hg cat -r $H1 target
94 99 Tainted file is now sanitized
95 100 $ hg cat -r $H2 target
96 101 Tainted file now super sanitized
97 102 $ hg cat -r $C2 target
98 103 abort: censored node: 1e0247a9a4b7
99 104 (set censor.policy to ignore errors)
100 105 [255]
101 106 $ hg cat -r $C1 target
102 107 abort: censored node: 613bc869fceb
103 108 (set censor.policy to ignore errors)
104 109 [255]
105 110 $ hg cat -r 0 target
106 111 Initially untainted file
107 112
108 113 Can only checkout target at uncensored revisions, -X is workaround for --all
109 114
110 115 $ hg revert -r $C2 target
111 116 abort: censored node: 1e0247a9a4b7
112 117 (set censor.policy to ignore errors)
113 118 [255]
114 119 $ hg revert -r $C1 target
115 120 abort: censored node: 613bc869fceb
116 121 (set censor.policy to ignore errors)
117 122 [255]
118 123 $ hg revert -r $C1 --all
119 124 reverting bystander
120 125 reverting target
121 126 abort: censored node: 613bc869fceb
122 127 (set censor.policy to ignore errors)
123 128 [255]
124 129 $ hg revert -r $C1 --all -X target
125 130 $ cat target
126 131 Tainted file now super sanitized
127 132 $ hg revert -r 0 --all
128 133 reverting target
129 134 $ cat target
130 135 Initially untainted file
131 136 $ hg revert -r $H2 --all
132 137 reverting bystander
133 138 reverting target
134 139 $ cat target
135 140 Tainted file now super sanitized
136 141
137 142 Uncensored file can be viewed at any revision
138 143
139 144 $ hg cat -r $H1 bystander
140 145 Normal file v2
141 146 $ hg cat -r $C2 bystander
142 147 Normal file v2
143 148 $ hg cat -r $C1 bystander
144 149 Normal file here
145 150 $ hg cat -r 0 bystander
146 151 Normal file here
147 152
148 153 Can update to children of censored revision
149 154
150 155 $ hg update -r $H1
151 156 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 157 $ cat target
153 158 Tainted file is now sanitized
154 159 $ hg update -r $H2
155 160 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
156 161 $ cat target
157 162 Tainted file now super sanitized
158 163
159 164 Set censor policy to abort in trusted $HGRC so hg verify fails
160 165
161 166 $ cp $HGRCPATH.orig $HGRCPATH
162 167 $ cat >> $HGRCPATH <<EOF
163 168 > [censor]
164 169 > policy = abort
165 170 > EOF
166 171
167 172 Repo fails verification due to censorship
168 173
169 174 $ hg verify
170 175 checking changesets
171 176 checking manifests
172 177 crosschecking files in changesets and manifests
173 178 checking files
174 179 target@1: censored file data
175 180 target@2: censored file data
176 181 2 files, 5 changesets, 7 total revisions
177 182 2 integrity errors encountered!
178 183 (first damaged changeset appears to be 1)
179 184 [1]
180 185
181 186 Cannot update to revision with censored data
182 187
183 188 $ hg update -r $C2
184 189 abort: censored node: 1e0247a9a4b7
185 190 (set censor.policy to ignore errors)
186 191 [255]
187 192 $ hg update -r $C1
188 193 abort: censored node: 613bc869fceb
189 194 (set censor.policy to ignore errors)
190 195 [255]
191 196 $ hg update -r 0
192 197 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 198 $ hg update -r $H2
194 199 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 200
196 201 Set censor policy to ignore in trusted $HGRC so hg verify passes
197 202
198 203 $ cp $HGRCPATH.orig $HGRCPATH
199 204 $ cat >> $HGRCPATH <<EOF
200 205 > [censor]
201 206 > policy = ignore
202 207 > EOF
203 208
204 209 Repo passes verification with warnings with explicit config
205 210
206 211 $ hg verify
207 212 checking changesets
208 213 checking manifests
209 214 crosschecking files in changesets and manifests
210 215 checking files
211 216 2 files, 5 changesets, 7 total revisions
212 217
213 218 May update to revision with censored data with explicit config
214 219
215 220 $ hg update -r $C2
216 221 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
217 222 $ cat target
218 223 $ hg update -r $C1
219 224 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
220 225 $ cat target
221 226 $ hg update -r 0
222 227 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 228 $ cat target
224 229 Initially untainted file
225 230 $ hg update -r $H2
226 231 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
227 232 $ cat target
228 233 Tainted file now super sanitized
229 234
230 235 Can merge in revision with censored data. Test requires one branch of history
231 236 with the file censored, but we can't censor at a head, so advance H1.
232 237
233 238 $ hg update -r $H1
234 239 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
235 240 $ C3=$H1
236 241 $ echo 'advanced head H1' > target
237 242 $ hg ci -m 'advance head H1' target
238 243 $ H1=`hg id --debug -i`
239 244 $ hg censor -r $C3 target
240 245 $ hg update -r $H2
241 246 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
242 247 $ hg merge -r $C3
243 248 merging target
244 249 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
245 250 (branch merge, don't forget to commit)
246 251
247 252 Revisions present in repository heads may not be censored
248 253
249 254 $ hg update -C -r $H2
250 255 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 256 $ hg censor -r $H2 target
252 257 abort: cannot censor file in heads (78a8fc215e79)
253 258 (clean/delete and commit first)
254 259 [255]
255 260 $ echo 'twiddling thumbs' > bystander
256 261 $ hg ci -m 'bystander commit'
257 262 $ H2=`hg id --debug -i`
258 263 $ hg censor -r "$H2^" target
259 264 abort: cannot censor file in heads (efbe78065929)
260 265 (clean/delete and commit first)
261 266 [255]
262 267
263 268 Cannot censor working directory
264 269
265 270 $ echo 'seriously no passwords' > target
266 271 $ hg ci -m 'extend second head arbitrarily' target
267 272 $ H2=`hg id --debug -i`
268 273 $ hg update -r "$H2^"
269 274 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 275 $ hg censor -r . target
271 276 abort: cannot censor working directory
272 277 (clean/delete/update first)
273 278 [255]
274 279 $ hg update -r $H2
275 280 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
276 281
277 282 Can re-add file after being deleted + censored
278 283
279 284 $ C4=$H2
280 285 $ hg rm target
281 286 $ hg ci -m 'delete target so it may be censored'
282 287 $ H2=`hg id --debug -i`
283 288 $ hg censor -r $C4 target
284 289 $ hg cat -r $C4 target
285 290 $ hg cat -r "$H2^^" target
286 291 Tainted file now super sanitized
287 292 $ echo 'fresh start' > target
288 293 $ hg add target
289 294 $ hg ci -m reincarnated target
290 295 $ H2=`hg id --debug -i`
291 296 $ hg cat -r $H2 target
292 297 fresh start
293 298 $ hg cat -r "$H2^" target
294 299 target: no such file in rev 452ec1762369
295 300 [1]
296 301 $ hg cat -r $C4 target
297 302 $ hg cat -r "$H2^^^" target
298 303 Tainted file now super sanitized
299 304
300 305 Can censor after revlog has expanded to no longer permit inline storage
301 306
302 307 $ for x in `python $TESTDIR/seq.py 0 50000`
303 308 > do
304 309 > echo "Password: hunter$x" >> target
305 310 > done
306 311 $ hg ci -m 'add 100k passwords'
307 312 $ H2=`hg id --debug -i`
308 313 $ C5=$H2
309 314 $ hg revert -r "$H2^" target
310 315 $ hg ci -m 'cleaned 100k passwords'
311 316 $ H2=`hg id --debug -i`
312 317 $ hg censor -r $C5 target
313 318 $ hg cat -r $C5 target
314 319 $ hg cat -r $H2 target
315 320 fresh start
316 321
317 322 Repo with censored nodes can be cloned and cloned nodes are censored
318 323
319 324 $ cd ..
320 325 $ hg clone r rclone
321 326 updating to branch default
322 327 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
323 328 $ cd rclone
324 329 $ hg cat -r $H1 target
325 330 advanced head H1
326 331 $ hg cat -r $H2~5 target
327 332 Tainted file now super sanitized
328 333 $ hg cat -r $C2 target
329 334 $ hg cat -r $C1 target
330 335 $ hg cat -r 0 target
331 336 Initially untainted file
332 337 $ hg verify
333 338 checking changesets
334 339 checking manifests
335 340 crosschecking files in changesets and manifests
336 341 checking files
337 342 2 files, 12 changesets, 13 total revisions
338 343
339 344 Repo cloned before tainted content introduced can pull censored nodes
340 345
341 346 $ cd ../rpull
342 347 $ hg cat -r tip target
343 348 Initially untainted file
344 349 $ hg verify
345 350 checking changesets
346 351 checking manifests
347 352 crosschecking files in changesets and manifests
348 353 checking files
349 354 2 files, 1 changesets, 2 total revisions
350 355 $ hg pull -r $H1 -r $H2
351 356 pulling from $TESTTMP/r (glob)
352 357 searching for changes
353 358 adding changesets
354 359 adding manifests
355 360 adding file changes
356 361 added 11 changesets with 11 changes to 2 files (+1 heads)
357 362 (run 'hg heads' to see heads, 'hg merge' to merge)
358 363 $ hg update 4
359 364 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
360 365 $ cat target
361 366 Tainted file now super sanitized
362 367 $ hg cat -r $H1 target
363 368 advanced head H1
364 369 $ hg cat -r $H2~5 target
365 370 Tainted file now super sanitized
366 371 $ hg cat -r $C2 target
367 372 $ hg cat -r $C1 target
368 373 $ hg cat -r 0 target
369 374 Initially untainted file
370 375 $ hg verify
371 376 checking changesets
372 377 checking manifests
373 378 crosschecking files in changesets and manifests
374 379 checking files
375 380 2 files, 12 changesets, 13 total revisions
376 381
377 382 Censored nodes can be pushed if they censor previously unexchanged nodes
378 383
379 384 $ echo 'Passwords: hunter2hunter2' > target
380 385 $ hg ci -m 're-add password from clone' target
381 386 created new head
382 387 $ H3=`hg id --debug -i`
383 388 $ REV=$H3
384 389 $ echo 'Re-sanitized; nothing to see here' > target
385 390 $ hg ci -m 're-sanitized' target
386 391 $ H2=`hg id --debug -i`
387 392 $ CLEANREV=$H2
388 393 $ hg cat -r $REV target
389 394 Passwords: hunter2hunter2
390 395 $ hg censor -r $REV target
391 396 $ hg cat -r $REV target
392 397 $ hg cat -r $CLEANREV target
393 398 Re-sanitized; nothing to see here
394 399 $ hg push -f -r $H2
395 400 pushing to $TESTTMP/r (glob)
396 401 searching for changes
397 402 adding changesets
398 403 adding manifests
399 404 adding file changes
400 405 added 2 changesets with 2 changes to 1 files (+1 heads)
401 406
402 407 $ cd ../r
403 408 $ hg cat -r $REV target
404 409 $ hg cat -r $CLEANREV target
405 410 Re-sanitized; nothing to see here
406 411 $ hg update $CLEANREV
407 412 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
408 413 $ cat target
409 414 Re-sanitized; nothing to see here
410 415
411 416 Censored nodes can be bundled up and unbundled in another repo
412 417
413 418 $ hg bundle --base 0 ../pwbundle
414 419 13 changesets found
415 420 $ cd ../rclone
416 421 $ hg unbundle ../pwbundle
417 422 adding changesets
418 423 adding manifests
419 424 adding file changes
420 425 added 2 changesets with 2 changes to 2 files (+1 heads)
421 426 (run 'hg heads .' to see heads, 'hg merge' to merge)
422 427 $ hg cat -r $REV target
423 428 $ hg cat -r $CLEANREV target
424 429 Re-sanitized; nothing to see here
425 430 $ hg update $CLEANREV
426 431 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
427 432 $ cat target
428 433 Re-sanitized; nothing to see here
429 434 $ hg verify
430 435 checking changesets
431 436 checking manifests
432 437 crosschecking files in changesets and manifests
433 438 checking files
434 439 2 files, 14 changesets, 15 total revisions
435 440
436 441 Censored nodes can be imported on top of censored nodes, consecutively
437 442
438 443 $ hg init ../rimport
439 444 $ hg bundle --base 1 ../rimport/splitbundle
440 445 12 changesets found
441 446 $ cd ../rimport
442 447 $ hg pull -r $H1 -r $H2 ../r
443 448 pulling from ../r
444 449 adding changesets
445 450 adding manifests
446 451 adding file changes
447 452 added 8 changesets with 10 changes to 2 files (+1 heads)
448 453 (run 'hg heads' to see heads, 'hg merge' to merge)
449 454 $ hg unbundle splitbundle
450 455 adding changesets
451 456 adding manifests
452 457 adding file changes
453 458 added 6 changesets with 5 changes to 2 files (+1 heads)
454 459 (run 'hg heads .' to see heads, 'hg merge' to merge)
455 460 $ hg update $H2
456 461 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
457 462 $ cat target
458 463 Re-sanitized; nothing to see here
459 464 $ hg verify
460 465 checking changesets
461 466 checking manifests
462 467 crosschecking files in changesets and manifests
463 468 checking files
464 469 2 files, 14 changesets, 15 total revisions
465 470 $ cd ../r
466 471
467 472 Can import bundle where first revision of a file is censored
468 473
469 474 $ hg init ../rinit
470 475 $ hg censor -r 0 target
471 476 $ hg bundle -r 0 --base null ../rinit/initbundle
472 477 1 changesets found
473 478 $ cd ../rinit
474 479 $ hg unbundle initbundle
475 480 adding changesets
476 481 adding manifests
477 482 adding file changes
478 483 added 1 changesets with 2 changes to 2 files
479 484 (run 'hg update' to get a working copy)
480 485 $ hg cat -r 0 target
General Comments 0
You need to be logged in to leave comments. Login now