Mercurial > jhg
comparison src/org/tmatesoft/hg/repo/HgBundle.java @ 169:8c8e3f372fa1
Towards initial clone: refactor HgBundle to provide slightly higher-level structure of the bundle
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Wed, 23 Mar 2011 14:13:11 +0100 |
parents | d5268ca7715b |
children | 71ddbf8603e8 |
comparison
equal
deleted
inserted
replaced
168:dd525ca65de8 | 169:8c8e3f372fa1 |
---|---|
14 * the terms of a license other than GNU General Public License | 14 * the terms of a license other than GNU General Public License |
15 * contact TMate Software at support@hg4j.com | 15 * contact TMate Software at support@hg4j.com |
16 */ | 16 */ |
17 package org.tmatesoft.hg.repo; | 17 package org.tmatesoft.hg.repo; |
18 | 18 |
19 import static org.tmatesoft.hg.core.Nodeid.NULL; | |
20 | |
19 import java.io.File; | 21 import java.io.File; |
20 import java.io.IOException; | 22 import java.io.IOException; |
21 import java.util.LinkedList; | 23 import java.util.LinkedList; |
22 import java.util.List; | 24 import java.util.List; |
23 | 25 |
26 import org.tmatesoft.hg.core.HgBadStateException; | |
24 import org.tmatesoft.hg.core.HgException; | 27 import org.tmatesoft.hg.core.HgException; |
25 import org.tmatesoft.hg.core.Nodeid; | 28 import org.tmatesoft.hg.core.Nodeid; |
26 import org.tmatesoft.hg.internal.ByteArrayChannel; | 29 import org.tmatesoft.hg.internal.ByteArrayChannel; |
27 import org.tmatesoft.hg.internal.ByteArrayDataAccess; | 30 import org.tmatesoft.hg.internal.ByteArrayDataAccess; |
28 import org.tmatesoft.hg.internal.DataAccess; | 31 import org.tmatesoft.hg.internal.DataAccess; |
29 import org.tmatesoft.hg.internal.DataAccessProvider; | 32 import org.tmatesoft.hg.internal.DataAccessProvider; |
30 import org.tmatesoft.hg.internal.DigestHelper; | 33 import org.tmatesoft.hg.internal.DigestHelper; |
34 import org.tmatesoft.hg.internal.InflaterDataAccess; | |
31 import org.tmatesoft.hg.internal.RevlogStream; | 35 import org.tmatesoft.hg.internal.RevlogStream; |
32 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; | 36 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; |
33 import org.tmatesoft.hg.util.CancelledException; | 37 import org.tmatesoft.hg.util.CancelledException; |
34 | 38 |
35 | |
36 /** | 39 /** |
37 * @see http://mercurial.selenic.com/wiki/BundleFormat | 40 * @see http://mercurial.selenic.com/wiki/BundleFormat |
38 * | 41 * |
39 * @author Artem Tikhomirov | 42 * @author Artem Tikhomirov |
40 * @author TMate Software Ltd. | 43 * @author TMate Software Ltd. |
41 */ | 44 */ |
42 public class HgBundle { | 45 public class HgBundle { |
43 | 46 |
44 private final File bundleFile; | 47 private final File bundleFile; |
45 private final DataAccessProvider accessProvider; | 48 private final DataAccessProvider accessProvider; |
46 | 49 |
47 public HgBundle(DataAccessProvider dap, File bundle) { | 50 HgBundle(DataAccessProvider dap, File bundle) { |
48 accessProvider = dap; | 51 accessProvider = dap; |
49 bundleFile = bundle; | 52 bundleFile = bundle; |
50 } | 53 } |
51 | 54 |
52 public void changes(HgRepository hgRepo) throws HgException, IOException { | 55 private DataAccess getDataStream() throws IOException { |
53 DataAccess da = accessProvider.create(bundleFile); | 56 DataAccess da = accessProvider.create(bundleFile); |
54 DigestHelper dh = new DigestHelper(); | 57 byte[] signature = new byte[6]; |
58 if (da.length() > 6) { | |
59 da.readBytes(signature, 0, 6); | |
60 if (signature[0] == 'H' && signature[1] == 'G' && signature[2] == '1' && signature[3] == '0') { | |
61 if (signature[4] == 'G' && signature[5] == 'Z') { | |
62 return new InflaterDataAccess(da, 6, da.length() - 6); | |
63 } | |
64 if (signature[4] == 'B' && signature[5] == 'Z') { | |
65 throw HgRepository.notImplemented(); | |
66 } | |
67 if (signature[4] != 'U' || signature[5] != 'N') { | |
68 throw new HgBadStateException("Bad bundle signature:" + new String(signature)); | |
69 } | |
70 // "...UN", fall-through | |
71 } else { | |
72 da.reset(); | |
73 } | |
74 } | |
75 return da; | |
76 } | |
77 | |
78 // shows changes recorded in the bundle that are missing from the supplied repository | |
79 public void changes(final HgRepository hgRepo) throws HgException, IOException { | |
80 Inspector insp = new Inspector() { | |
81 DigestHelper dh = new DigestHelper(); | |
82 boolean emptyChangelog = true; | |
83 private DataAccess prevRevContent; | |
84 | |
85 public void changelogStart() { | |
86 emptyChangelog = true; | |
87 | |
88 } | |
89 | |
90 public void changelogEnd() { | |
91 if (emptyChangelog) { | |
92 throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log? | |
93 } | |
94 } | |
95 | |
96 /* | |
97 * Despite that BundleFormat wiki says: "Each Changelog entry patches the result of all previous patches | |
98 * (the previous, or parent patch of a given patch p is the patch that has a node equal to p's p1 field)", | |
99 * it seems not to hold true. Instead, each entry patches previous one, regardless of whether the one | |
100 * before is its parent (i.e. ge.firstParent()) or not. | |
101 * | |
102 Actual state in the changelog.i | |
103 Index Offset Flags Packed Actual Base Rev Link Rev Parent1 Parent2 nodeid | |
104 50: 9212 0 209 329 48 50 49 -1 f1db8610da62a3e0beb8d360556ee1fd6eb9885e | |
105 51: 9421 0 278 688 48 51 50 -1 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 | |
106 52: 9699 0 154 179 52 52 50 -1 30bd389788464287cee22ccff54c330a4b715de5 | |
107 53: 9853 0 133 204 52 53 51 52 a6f39e595b2b54f56304470269a936ead77f5725 | |
108 54: 9986 0 156 182 54 54 52 -1 fd4f2c98995beb051070630c272a9be87bef617d | |
109 | |
110 Excerpt from bundle (nodeid, p1, p2, cs): | |
111 f1db8610da62a3e0beb8d360556ee1fd6eb9885e 26e3eeaa39623de552b45ee1f55c14f36460f220 0000000000000000000000000000000000000000 f1db8610da62a3e0beb8d360556ee1fd6eb9885e; patches:4 | |
112 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 f1db8610da62a3e0beb8d360556ee1fd6eb9885e 0000000000000000000000000000000000000000 9429c7bd1920fab164a9d2b621d38d57bcb49ae0; patches:3 | |
113 > 30bd389788464287cee22ccff54c330a4b715de5 f1db8610da62a3e0beb8d360556ee1fd6eb9885e 0000000000000000000000000000000000000000 30bd389788464287cee22ccff54c330a4b715de5; patches:3 | |
114 a6f39e595b2b54f56304470269a936ead77f5725 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 30bd389788464287cee22ccff54c330a4b715de5 a6f39e595b2b54f56304470269a936ead77f5725; patches:3 | |
115 fd4f2c98995beb051070630c272a9be87bef617d 30bd389788464287cee22ccff54c330a4b715de5 0000000000000000000000000000000000000000 fd4f2c98995beb051070630c272a9be87bef617d; patches:3 | |
116 | |
117 To recreate 30bd..e5, one have to take content of 9429..e0, not its p1 f1db..5e | |
118 */ | |
119 public boolean element(GroupElement ge) { | |
120 emptyChangelog = false; | |
121 HgChangelog changelog = hgRepo.getChangelog(); | |
122 try { | |
123 if (prevRevContent == null) { | |
124 if (NULL.equals(ge.firstParent()) && NULL.equals(ge.secondParent())) { | |
125 prevRevContent = new ByteArrayDataAccess(new byte[0]); | |
126 } else { | |
127 final Nodeid base = ge.firstParent(); | |
128 if (!changelog.isKnown(base) /*only first parent, that's Bundle contract*/) { | |
129 throw new IllegalStateException(String.format("Revision %s needs a parent %s, which is missing in the supplied repo %s", ge.node().shortNotation(), base.shortNotation(), hgRepo.toString())); | |
130 } | |
131 ByteArrayChannel bac = new ByteArrayChannel(); | |
132 changelog.rawContent(base, bac); // FIXME get DataAccess directly, to avoid | |
133 // extra byte[] (inside ByteArrayChannel) duplication just for the sake of subsequent ByteArrayDataChannel wrap. | |
134 prevRevContent = new ByteArrayDataAccess(bac.toArray()); | |
135 } | |
136 } | |
137 // | |
138 byte[] csetContent = ge.apply(prevRevContent); | |
139 dh = dh.sha1(ge.firstParent(), ge.secondParent(), csetContent); // XXX ge may give me access to byte[] content of nodeid directly, perhaps, I don't need DH to be friend of Nodeid? | |
140 if (!ge.node().equalsTo(dh.asBinary())) { | |
141 throw new IllegalStateException("Integrity check failed on " + bundleFile + ", node:" + ge.node()); | |
142 } | |
143 ByteArrayDataAccess csetDataAccess = new ByteArrayDataAccess(csetContent); | |
144 if (changelog.isKnown(ge.node())) { | |
145 System.out.print("+"); | |
146 } else { | |
147 System.out.print("-"); | |
148 } | |
149 RawChangeset cs = RawChangeset.parse(csetDataAccess); | |
150 System.out.println(cs.toString()); | |
151 prevRevContent = csetDataAccess.reset(); | |
152 } catch (CancelledException ex) { | |
153 return false; | |
154 } catch (Exception ex) { | |
155 throw new HgBadStateException(ex); // FIXME | |
156 } | |
157 return true; | |
158 } | |
159 | |
160 public void manifestStart() {} | |
161 public void manifestEnd() {} | |
162 public void fileStart(String name) {} | |
163 public void fileEnd(String name) {} | |
164 | |
165 }; | |
166 inspectChangelog(insp); | |
167 } | |
168 | |
169 public void dump() throws IOException { | |
170 Dump dump = new Dump(); | |
171 inspectAll(dump); | |
172 System.out.println("Total files:" + dump.names.size()); | |
173 for (String s : dump.names) { | |
174 System.out.println(s); | |
175 } | |
176 } | |
177 | |
178 // callback to minimize amount of Strings and Nodeids instantiated | |
179 public interface Inspector { | |
180 void changelogStart(); | |
181 | |
182 void changelogEnd(); | |
183 | |
184 void manifestStart(); | |
185 | |
186 void manifestEnd(); | |
187 | |
188 void fileStart(String name); | |
189 | |
190 void fileEnd(String name); | |
191 | |
192 /** | |
193 * @param element | |
194 * data element, instance might be reused | |
195 * @return <code>true</code> to continue | |
196 */ | |
197 boolean element(GroupElement element); | |
198 } | |
199 | |
200 public static class Dump implements Inspector { | |
201 public final LinkedList<String> names = new LinkedList<String>(); | |
202 | |
203 public void changelogStart() { | |
204 System.out.println("Changelog group"); | |
205 } | |
206 | |
207 public void changelogEnd() { | |
208 } | |
209 | |
210 public void manifestStart() { | |
211 System.out.println("Manifest group"); | |
212 } | |
213 | |
214 public void manifestEnd() { | |
215 } | |
216 | |
217 public void fileStart(String name) { | |
218 names.add(name); | |
219 System.out.println(name); | |
220 } | |
221 | |
222 public void fileEnd(String name) { | |
223 } | |
224 | |
225 public boolean element(GroupElement ge) { | |
226 try { | |
227 System.out.printf(" %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches().size()); | |
228 } catch (Exception ex) { | |
229 ex.printStackTrace(); // FIXME | |
230 } | |
231 return true; | |
232 } | |
233 } | |
234 | |
235 public void inspectChangelog(Inspector inspector) throws IOException { | |
236 if (inspector == null) { | |
237 throw new IllegalArgumentException(); | |
238 } | |
239 DataAccess da = getDataStream(); | |
55 try { | 240 try { |
56 List<GroupElement> changelogGroup = readGroup(da); | 241 if (da.isEmpty()) { |
57 if (changelogGroup.isEmpty()) { | 242 return; |
58 throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log? | 243 } |
59 } | 244 inspector.changelogStart(); |
60 // XXX in fact, bundle not necessarily starts with the first revision missing in hgRepo | 245 readGroup(da, inspector); |
61 // need to 'scroll' till the last one common. | 246 inspector.changelogEnd(); |
62 final Nodeid base = changelogGroup.get(0).firstParent(); | |
63 if (!hgRepo.getChangelog().isKnown(base)) { | |
64 throw new IllegalArgumentException("unknown parent"); | |
65 } | |
66 // BundleFormat wiki says: | |
67 // Each Changelog entry patches the result of all previous patches | |
68 // (the previous, or parent patch of a given patch p is the patch that has a node equal to p's p1 field) | |
69 ByteArrayChannel bac = new ByteArrayChannel(); | |
70 hgRepo.getChangelog().rawContent(base, bac); // FIXME get DataAccess directly, to avoid | |
71 // extra byte[] (inside ByteArrayChannel) duplication just for the sake of subsequent ByteArrayDataChannel wrap. | |
72 ByteArrayDataAccess baseRevContent = new ByteArrayDataAccess(bac.toArray()); | |
73 for (GroupElement ge : changelogGroup) { | |
74 byte[] csetContent = RevlogStream.apply(baseRevContent, -1, ge.patches); | |
75 dh = dh.sha1(ge.firstParent(), ge.secondParent(), csetContent); // XXX ge may give me access to byte[] content of nodeid directly, perhaps, I don't need DH to be friend of Nodeid? | |
76 if (!ge.node().equalsTo(dh.asBinary())) { | |
77 throw new IllegalStateException("Integrity check failed on " + bundleFile + ", node:" + ge.node()); | |
78 } | |
79 ByteArrayDataAccess csetDataAccess = new ByteArrayDataAccess(csetContent); | |
80 RawChangeset cs = RawChangeset.parse(csetDataAccess); | |
81 System.out.println(cs.toString()); | |
82 baseRevContent = csetDataAccess.reset(); | |
83 } | |
84 } catch (CancelledException ex) { | |
85 System.out.println("Operation cancelled"); | |
86 } finally { | 247 } finally { |
87 da.done(); | 248 da.done(); |
88 } | 249 } |
89 } | 250 } |
90 | 251 |
91 public void dump() throws IOException { | 252 public void inspectManifest(Inspector inspector) throws IOException { |
92 DataAccess da = accessProvider.create(bundleFile); | 253 if (inspector == null) { |
254 throw new IllegalArgumentException(); | |
255 } | |
256 DataAccess da = getDataStream(); | |
93 try { | 257 try { |
94 LinkedList<String> names = new LinkedList<String>(); | 258 if (da.isEmpty()) { |
259 return; | |
260 } | |
261 skipGroup(da); // changelog | |
95 if (!da.isEmpty()) { | 262 if (!da.isEmpty()) { |
96 System.out.println("Changelog group"); | 263 inspector.manifestStart(); |
97 List<GroupElement> changelogGroup = readGroup(da); | 264 readGroup(da, inspector); |
98 for (GroupElement ge : changelogGroup) { | 265 inspector.manifestEnd(); |
99 System.out.printf(" %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches.size()); | |
100 } | |
101 System.out.println("Manifest group"); | |
102 List<GroupElement> manifestGroup = readGroup(da); | |
103 for (GroupElement ge : manifestGroup) { | |
104 System.out.printf(" %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches.size()); | |
105 } | |
106 while (!da.isEmpty()) { | |
107 int fnameLen = da.readInt(); | |
108 if (fnameLen <= 4) { | |
109 break; // null chunk, the last one. | |
110 } | |
111 byte[] fname = new byte[fnameLen - 4]; | |
112 da.readBytes(fname, 0, fname.length); | |
113 names.add(new String(fname)); | |
114 List<GroupElement> fileGroup = readGroup(da); | |
115 System.out.println(names.getLast()); | |
116 for (GroupElement ge : fileGroup) { | |
117 System.out.printf(" %s %s %s %s; patches:%d\n", ge.node(), ge.firstParent(), ge.secondParent(), ge.cset(), ge.patches.size()); | |
118 } | |
119 } | |
120 } | |
121 System.out.println(names.size()); | |
122 for (String s : names) { | |
123 System.out.println(s); | |
124 } | 266 } |
125 } finally { | 267 } finally { |
126 da.done(); | 268 da.done(); |
127 } | 269 } |
128 } | 270 } |
129 | 271 |
130 private static List<GroupElement> readGroup(DataAccess da) throws IOException { | 272 public void inspectFiles(Inspector inspector) throws IOException { |
273 if (inspector == null) { | |
274 throw new IllegalArgumentException(); | |
275 } | |
276 DataAccess da = getDataStream(); | |
277 try { | |
278 if (!da.isEmpty()) { | |
279 skipGroup(da); // changelog | |
280 } | |
281 if (!da.isEmpty()) { | |
282 skipGroup(da); // manifest | |
283 } | |
284 while (!da.isEmpty()) { | |
285 int fnameLen = da.readInt(); | |
286 if (fnameLen <= 4) { | |
287 break; // null chunk, the last one. | |
288 } | |
289 byte[] nameBuf = new byte[fnameLen - 4]; | |
290 da.readBytes(nameBuf, 0, nameBuf.length); | |
291 String fname = new String(nameBuf); | |
292 inspector.fileStart(fname); | |
293 readGroup(da, inspector); | |
294 inspector.fileEnd(fname); | |
295 } | |
296 } finally { | |
297 da.done(); | |
298 } | |
299 } | |
300 | |
301 public void inspectAll(Inspector inspector) throws IOException { | |
302 if (inspector == null) { | |
303 throw new IllegalArgumentException(); | |
304 } | |
305 DataAccess da = getDataStream(); | |
306 try { | |
307 if (da.isEmpty()) { | |
308 return; | |
309 } | |
310 inspector.changelogStart(); | |
311 readGroup(da, inspector); | |
312 inspector.changelogEnd(); | |
313 // | |
314 if (da.isEmpty()) { | |
315 return; | |
316 } | |
317 inspector.manifestStart(); | |
318 readGroup(da, inspector); | |
319 inspector.manifestEnd(); | |
320 // | |
321 while (!da.isEmpty()) { | |
322 int fnameLen = da.readInt(); | |
323 if (fnameLen <= 4) { | |
324 break; // null chunk, the last one. | |
325 } | |
326 byte[] fnameBuf = new byte[fnameLen - 4]; | |
327 da.readBytes(fnameBuf, 0, fnameBuf.length); | |
328 String name = new String(fnameBuf); | |
329 inspector.fileStart(name); | |
330 readGroup(da, inspector); | |
331 inspector.fileEnd(name); | |
332 } | |
333 } finally { | |
334 da.done(); | |
335 } | |
336 } | |
337 | |
338 private static void readGroup(DataAccess da, Inspector inspector) throws IOException { | |
131 int len = da.readInt(); | 339 int len = da.readInt(); |
132 LinkedList<GroupElement> rv = new LinkedList<HgBundle.GroupElement>(); | 340 boolean good2go = true; |
133 while (len > 4 && !da.isEmpty()) { | 341 while (len > 4 && !da.isEmpty() && good2go) { |
134 byte[] nb = new byte[80]; | 342 byte[] nb = new byte[80]; |
135 da.readBytes(nb, 0, 80); | 343 da.readBytes(nb, 0, 80); |
136 int dataLength = len-84; | 344 int dataLength = len - 84 /* length field + 4 nodeids */; |
137 LinkedList<RevlogStream.PatchRecord> patches = new LinkedList<RevlogStream.PatchRecord>(); | 345 byte[] data = new byte[dataLength]; |
138 while (dataLength > 0) { | 346 da.readBytes(data, 0, dataLength); |
139 RevlogStream.PatchRecord pr = RevlogStream.PatchRecord.read(da); | 347 DataAccess slice = new ByteArrayDataAccess(data); // XXX in fact, may pass a slicing DataAccess. |
140 patches.add(pr); | 348 // Just need to make sure that we seek to proper location afterwards (where next GroupElement starts), |
141 dataLength -= pr.len + 12; | 349 // regardless whether that slice has read it or not. |
142 } | 350 GroupElement ge = new GroupElement(nb, slice); |
143 rv.add(new GroupElement(nb, patches)); | 351 good2go = inspector.element(ge); |
144 len = da.isEmpty() ? 0 : da.readInt(); | 352 len = da.isEmpty() ? 0 : da.readInt(); |
145 } | 353 } |
146 return rv; | 354 // need to skip up to group end if inspector told he don't want to continue with the group, |
147 } | 355 // because outer code may try to read next group immediately as we return back. |
148 | 356 while (len > 4 && !da.isEmpty()) { |
149 static class GroupElement { | 357 da.skip(len - 4 /* length field */); |
150 private byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192 | 358 len = da.isEmpty() ? 0 : da.readInt(); |
359 } | |
360 } | |
361 | |
362 private static void skipGroup(DataAccess da) throws IOException { | |
363 int len = da.readInt(); | |
364 while (len > 4 && !da.isEmpty()) { | |
365 da.skip(len - 4); // sizeof(int) | |
366 len = da.isEmpty() ? 0 : da.readInt(); | |
367 } | |
368 } | |
369 | |
370 public static class GroupElement { | |
371 private final byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192 | |
372 private final DataAccess dataAccess; | |
151 private List<RevlogStream.PatchRecord> patches; | 373 private List<RevlogStream.PatchRecord> patches; |
152 | 374 |
153 GroupElement(byte[] fourNodeids, List<RevlogStream.PatchRecord> patchList) { | 375 GroupElement(byte[] fourNodeids, DataAccess rawDataAccess) { |
154 assert fourNodeids != null && fourNodeids.length == 80; | 376 assert fourNodeids != null && fourNodeids.length == 80; |
155 // patchList.size() > 0 | |
156 header = fourNodeids; | 377 header = fourNodeids; |
157 patches = patchList; | 378 dataAccess = rawDataAccess; |
158 } | 379 } |
380 | |
159 public Nodeid node() { | 381 public Nodeid node() { |
160 return Nodeid.fromBinary(header, 0); | 382 return Nodeid.fromBinary(header, 0); |
161 } | 383 } |
384 | |
162 public Nodeid firstParent() { | 385 public Nodeid firstParent() { |
163 return Nodeid.fromBinary(header, 20); | 386 return Nodeid.fromBinary(header, 20); |
164 } | 387 } |
388 | |
165 public Nodeid secondParent() { | 389 public Nodeid secondParent() { |
166 return Nodeid.fromBinary(header, 40); | 390 return Nodeid.fromBinary(header, 40); |
167 } | 391 } |
392 | |
168 public Nodeid cset() { // cs seems to be changeset | 393 public Nodeid cset() { // cs seems to be changeset |
169 return Nodeid.fromBinary(header, 60); | 394 return Nodeid.fromBinary(header, 60); |
170 } | 395 } |
396 | |
397 public DataAccess rawData() { | |
398 return dataAccess; | |
399 } | |
400 | |
401 public List<RevlogStream.PatchRecord> patches() throws IOException { | |
402 if (patches == null) { | |
403 dataAccess.reset(); | |
404 LinkedList<RevlogStream.PatchRecord> p = new LinkedList<RevlogStream.PatchRecord>(); | |
405 while (!dataAccess.isEmpty()) { | |
406 RevlogStream.PatchRecord pr = RevlogStream.PatchRecord.read(dataAccess); | |
407 p.add(pr); | |
408 } | |
409 patches = p; | |
410 } | |
411 return patches; | |
412 } | |
413 | |
414 public byte[] apply(DataAccess baseContent) throws IOException { | |
415 return RevlogStream.apply(baseContent, -1, patches()); | |
416 } | |
171 } | 417 } |
172 } | 418 } |