comparison src/org/tmatesoft/hg/internal/BundleGenerator.java @ 644:1deea2f33218

Push: phase1 - prepare bundle with changes
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 19 Jun 2013 16:04:24 +0200
parents
children 14dac192aa26
comparison
equal deleted inserted replaced
643:a8ce405da1f5 644:1deea2f33218
1 /*
2 * Copyright (c) 2013 TMate Software Ltd
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * For information on how to redistribute this software under
14 * the terms of a license other than GNU General Public License
15 * contact TMate Software at support@hg4j.com
16 */
17 package org.tmatesoft.hg.internal;
18
19 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
20 import static org.tmatesoft.hg.repo.HgRepository.TIP;
21
22 import java.io.File;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStream;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.List;
32
33 import org.tmatesoft.hg.console.Bundle;
34 import org.tmatesoft.hg.core.HgIOException;
35 import org.tmatesoft.hg.core.Nodeid;
36 import org.tmatesoft.hg.internal.Patch.PatchDataSource;
37 import org.tmatesoft.hg.repo.HgBundle;
38 import org.tmatesoft.hg.repo.HgChangelog;
39 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
40 import org.tmatesoft.hg.repo.HgDataFile;
41 import org.tmatesoft.hg.repo.HgInternals;
42 import org.tmatesoft.hg.repo.HgInvalidControlFileException;
43 import org.tmatesoft.hg.repo.HgLookup;
44 import org.tmatesoft.hg.repo.HgManifest;
45 import org.tmatesoft.hg.repo.HgRepository;
46 import org.tmatesoft.hg.repo.HgRuntimeException;
47
48 /**
49 * @see http://mercurial.selenic.com/wiki/BundleFormat
50 * @author Artem Tikhomirov
51 * @author TMate Software Ltd.
52 */
53 public class BundleGenerator {
54
55 private final Internals repo;
56
57 public BundleGenerator(Internals hgRepo) {
58 repo = hgRepo;
59 }
60
61 public File create(List<Nodeid> changesets) throws HgIOException, IOException {
62 final HgChangelog clog = repo.getRepo().getChangelog();
63 final HgManifest manifest = repo.getRepo().getManifest();
64 IntVector clogRevsVector = new IntVector(changesets.size(), 0);
65 for (Nodeid n : changesets) {
66 clogRevsVector.add(clog.getRevisionIndex(n));
67 }
68 clogRevsVector.sort(true);
69 final int[] clogRevs = clogRevsVector.toArray();
70 System.out.printf("Changelog: %s\n", Arrays.toString(clogRevs));
71 final IntMap<Nodeid> clogMap = new IntMap<Nodeid>(changesets.size());
72 final IntVector manifestRevs = new IntVector(changesets.size(), 0);
73 final List<HgDataFile> files = new ArrayList<HgDataFile>();
74 clog.range(new HgChangelog.Inspector() {
75 public void next(int revisionIndex, Nodeid nodeid, RawChangeset cset) throws HgRuntimeException {
76 clogMap.put(revisionIndex, nodeid);
77 manifestRevs.add(manifest.getRevisionIndex(cset.manifest()));
78 for (String f : cset.files()) {
79 HgDataFile df = repo.getRepo().getFileNode(f);
80 if (!files.contains(df)) {
81 files.add(df);
82 }
83 }
84 }
85 }, clogRevs);
86 manifestRevs.sort(true);
87 System.out.printf("Manifest: %s\n", Arrays.toString(manifestRevs.toArray(true)));
88 ///////////////
89 for (HgDataFile df : sortedByName(files)) {
90 RevlogStream s = repo.getImplAccess().getStream(df);
91 final IntVector fileRevs = new IntVector();
92 s.iterate(0, TIP, false, new RevlogStream.Inspector() {
93
94 public void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgRuntimeException {
95 if (Arrays.binarySearch(clogRevs, linkRevision) >= 0) {
96 fileRevs.add(revisionIndex);
97 }
98 }
99 });
100 fileRevs.sort(true);
101 System.out.printf("%s: %s\n", df.getPath(), Arrays.toString(fileRevs.toArray(true)));
102 }
103 if (Boolean.FALSE.booleanValue()) {
104 return null;
105 }
106 ///////////////
107 //
108 final File bundleFile = File.createTempFile("hg4j-", "bundle");
109 final OutputStreamSerializer outRaw = new OutputStreamSerializer(new FileOutputStream(bundleFile));
110 outRaw.write("HG10UN".getBytes(), 0, 6);
111 //
112 RevlogStream clogStream = repo.getImplAccess().getChangelogStream();
113 new ChunkGenerator(outRaw, clogMap).iterate(clogStream, clogRevs);
114 outRaw.writeInt(0); // null chunk for changelog group
115 //
116 RevlogStream manifestStream = repo.getImplAccess().getManifestStream();
117 new ChunkGenerator(outRaw, clogMap).iterate(manifestStream, manifestRevs.toArray(true));
118 outRaw.writeInt(0); // null chunk for manifest group
119 //
120 for (HgDataFile df : sortedByName(files)) {
121 RevlogStream s = repo.getImplAccess().getStream(df);
122 final IntVector fileRevs = new IntVector();
123 s.iterate(0, TIP, false, new RevlogStream.Inspector() {
124
125 public void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgRuntimeException {
126 if (Arrays.binarySearch(clogRevs, linkRevision) >= 0) {
127 fileRevs.add(revisionIndex);
128 }
129 }
130 });
131 fileRevs.sort(true);
132 if (!fileRevs.isEmpty()) {
133 // although BundleFormat page says "filename length, filename" for a file,
134 // in fact there's a sort of 'filename chunk', i.e. filename length field includes
135 // not only length of filename, but also length of the field itseld, i.e. filename.length+sizeof(int)
136 byte[] fnameBytes = df.getPath().toString().getBytes(); // FIXME check encoding in native hg (and fix accordingly in HgBundle)
137 outRaw.writeInt(fnameBytes.length + 4);
138 outRaw.writeByte(fnameBytes);
139 new ChunkGenerator(outRaw, clogMap).iterate(s, fileRevs.toArray(true));
140 outRaw.writeInt(0); // null chunk for file group
141 }
142 }
143 outRaw.done();
144 //return new HgBundle(repo.getSessionContext(), repo.getDataAccess(), bundleFile);
145 return bundleFile;
146 }
147
148 private static Collection<HgDataFile> sortedByName(List<HgDataFile> files) {
149 Collections.sort(files, new Comparator<HgDataFile>() {
150
151 public int compare(HgDataFile o1, HgDataFile o2) {
152 return o1.getPath().compareTo(o2.getPath());
153 }
154 });
155 return files;
156 }
157
158
159 public static void main(String[] args) throws Exception {
160 final HgLookup hgLookup = new HgLookup();
161 HgRepository hgRepo = hgLookup.detectFromWorkingDir();
162 BundleGenerator bg = new BundleGenerator(HgInternals.getImplementationRepo(hgRepo));
163 ArrayList<Nodeid> l = new ArrayList<Nodeid>();
164 l.add(Nodeid.fromAscii("9ef1fab9f5e3d51d70941121dc27410e28069c2d")); // 640
165 l.add(Nodeid.fromAscii("2f33f102a8fa59274a27ebbe1c2903cecac6c5d5")); // 639
166 l.add(Nodeid.fromAscii("d074971287478f69ab0a64176ce2284d8c1e91c3")); // 638
167 File bundleFile = bg.create(l);
168 HgBundle b = hgLookup.loadBundle(bundleFile);
169 // Bundle.dump(b); // FIXME dependency from dependant code
170 }
171
172 private static class ChunkGenerator implements RevlogStream.Inspector {
173
174 private final DataSerializer ds;
175 private final IntMap<Nodeid> parentMap;
176 private final IntMap<Nodeid> clogMap;
177 private byte[] prevContent;
178 private int startParent;
179
180 public ChunkGenerator(DataSerializer dataSerializer, IntMap<Nodeid> clogNodeidMap) {
181 ds = dataSerializer;
182 parentMap = new IntMap<Nodeid>(clogNodeidMap.size());;
183 clogMap = clogNodeidMap;
184 }
185
186 public void iterate(RevlogStream s, int[] revisions) throws HgRuntimeException {
187 int[] p = s.parents(revisions[0], new int[2]);
188 startParent = p[0];
189 int[] revs2read;
190 if (startParent == NO_REVISION) {
191 revs2read = revisions;
192 prevContent = new byte[0];
193 } else {
194 revs2read = new int[revisions.length + 1];
195 revs2read[0] = startParent;
196 System.arraycopy(revisions, 0, revs2read, 1, revisions.length);
197 }
198 s.iterate(revs2read, true, this);
199 }
200
201 public void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgRuntimeException {
202 try {
203 parentMap.put(revisionIndex, Nodeid.fromBinary(nodeid, 0));
204 byte[] nextContent = data.byteArray();
205 data.done();
206 if (revisionIndex == startParent) {
207 prevContent = nextContent;
208 return;
209 }
210 Patch p = GeneratePatchInspector.delta(prevContent, nextContent);
211 prevContent = nextContent;
212 nextContent = null;
213 PatchDataSource pds = p.new PatchDataSource();
214 int len = pds.serializeLength() + 84;
215 ds.writeInt(len);
216 ds.write(nodeid, 0, Nodeid.SIZE);
217 // TODO assert parents match those in previous group elements
218 if (parent1Revision != NO_REVISION) {
219 ds.writeByte(parentMap.get(parent1Revision).toByteArray());
220 } else {
221 ds.writeByte(Nodeid.NULL.toByteArray());
222 }
223 if (parent2Revision != NO_REVISION) {
224 ds.writeByte(parentMap.get(parent2Revision).toByteArray());
225 } else {
226 ds.writeByte(Nodeid.NULL.toByteArray());
227 }
228 ds.writeByte(clogMap.get(linkRevision).toByteArray());
229 pds.serialize(ds);
230 } catch (IOException ex) {
231 // XXX odd to have object with IOException to use where no checked exception is allowed
232 throw new HgInvalidControlFileException(ex.getMessage(), ex, null);
233 } catch (HgIOException ex) {
234 throw new HgInvalidControlFileException(ex, true); // XXX any way to refactor ChunkGenerator not to get checked exception here?
235 }
236 }
237 }
238
239 private static class OutputStreamSerializer extends DataSerializer {
240 private final OutputStream out;
241 public OutputStreamSerializer(OutputStream outputStream) {
242 out = outputStream;
243 }
244
245 @Override
246 public void write(byte[] data, int offset, int length) throws HgIOException {
247 try {
248 out.write(data, offset, length);
249 } catch (IOException ex) {
250 throw new HgIOException(ex.getMessage(), ex, null);
251 }
252 }
253
254 @Override
255 public void done() throws HgIOException {
256 try {
257 out.close();
258 super.done();
259 } catch (IOException ex) {
260 throw new HgIOException(ex.getMessage(), ex, null);
261 }
262 }
263 }
264 }