# HG changeset patch # User Artem Tikhomirov # Date 1292809836 -3600 # Node ID 24bb4f365164e89ef2319d1bd344299a1f616a23 # Parent 08db726a0fb7914ac9d27ba26dc8bbf6385a0554 Rudimentary log functionality with basic infrastructure is in place diff -r 08db726a0fb7 -r 24bb4f365164 src/com/tmate/hgkit/console/Cat.java --- a/src/com/tmate/hgkit/console/Cat.java Sun Dec 19 05:41:31 2010 +0100 +++ b/src/com/tmate/hgkit/console/Cat.java Mon Dec 20 02:50:36 2010 +0100 @@ -19,5 +19,7 @@ System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation()); return; } + byte[] tipContent = hgRepo.getFileNode("hello.c").content(); + System.out.println(new String(tipContent)); } } diff -r 08db726a0fb7 -r 24bb4f365164 src/com/tmate/hgkit/console/Log.java --- a/src/com/tmate/hgkit/console/Log.java Sun Dec 19 05:41:31 2010 +0100 +++ b/src/com/tmate/hgkit/console/Log.java Mon Dec 20 02:50:36 2010 +0100 @@ -21,18 +21,22 @@ return; } System.out.println(hgRepo.getLocation()); - final Changeset.Callback callback = new Changeset.Callback() { + final Changeset.Inspector callback = new Changeset.Inspector() { public void next(Changeset cset) { - System.out.println(); + System.out.println("==>"); + cset.dump(); } }; HgDataFile f1 = hgRepo.getFileNode("hello.c"); System.out.println("Complete of a file:"); f1.history(callback); - System.out.println("Range 1-3:"); - f1.history(1,3, callback); // +// System.out.println("\n\n========================="); +// System.out.println("Range 1-3:"); +// f1.history(1,3, callback); + // + System.out.println("\n\n========================="); System.out.println("Complete of a repo:"); hgRepo.getChangelog().all(callback); //new ChangelogWalker().setFile("hello.c").setRevisionRange(1, 4).accept(new Visitor); diff -r 08db726a0fb7 -r 24bb4f365164 src/com/tmate/hgkit/console/Main.java --- a/src/com/tmate/hgkit/console/Main.java Sun Dec 19 05:41:31 2010 +0100 +++ b/src/com/tmate/hgkit/console/Main.java Mon Dec 20 02:50:36 2010 +0100 @@ -6,12 +6,7 @@ import java.io.File; import java.io.FileInputStream; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; import java.util.LinkedList; -import java.util.List; -import java.util.zip.Deflater; import java.util.zip.Inflater; import com.tmate.hgkit.ll.Changeset; @@ -23,9 +18,11 @@ public class Main { public static void main(String[] args) throws Exception { + //String filename = "store/00changelog.i"; + String filename = "store/data/hello.c.i"; LinkedList changelog = new LinkedList(); // - DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("/temp/hg/hello/" + ".hg/store/00changelog.i")))); + DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("/temp/hg/hello/.hg/" + filename)))); DataInput di = dis; dis.mark(10); int versionField = di.readInt(); @@ -51,6 +48,7 @@ dis.skip(12); System.out.printf("%14d %6X %10d %10d %10d %10d %8d %8d %040x\n", offset, flags, compressedLen, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, new BigInteger(buf)); if (inlineData) { + String resultString; byte[] data = new byte[compressedLen]; di.readFully(data); if (data[0] == 0x78 /* 'x' */) { @@ -59,35 +57,13 @@ byte[] result = new byte[actualLen*2]; int resultLen = zlib.inflate(result); zlib.end(); - if (resultLen != actualLen) { - System.err.printf("Expected:%d, decomressed to:%d bytes\n", actualLen, resultLen); - } - String resultString; - if (baseRevision != entryCount) { - // this is a patch - byte[] baseRevContent = changelog.get(baseRevision).rawData; - LinkedList bins = new LinkedList(); - int p1, p2, len, patchElementIndex = 0; - do { - final int x = patchElementIndex; - p1 = (result[x] << 24) | (result[x+1] << 16) | (result[x+2] << 8) | result[x+3]; - p2 = (result[x+4] << 24) | (result[x+5] << 16) | (result[x+6] << 8) | result[x+7]; - len = (result[x+8] << 24) | (result[x+9] << 16) | (result[x+10] << 8) | result[x+11]; - System.out.printf("%4d %4d %4d\n", p1, p2, len); - patchElementIndex += 12 + len; - bins.add(new PatchRecord(p1, p2, len, result, x+12)); - } while (patchElementIndex < resultLen); - // - result = apply(baseRevContent, bins); - resultLen = result.length; - } resultString = new String(result, 0, resultLen, "UTF-8"); - System.out.println(resultString); - entryCount++; - Changeset changeset = new Changeset(); - changeset.read(result, 0, resultLen); - changelog.add(changeset); - } // TODO else if uncompressed + } else if (data[0] == 0x75 /* 'u' */) { + resultString = new String(data, 1, data.length - 1, "UTF-8"); + } else { + resultString = new String(data); + } + System.out.println(resultString); } } dis.close(); @@ -100,36 +76,4 @@ System.out.println("<"); } } - - - // mpatch.c : apply() - private static byte[] apply(byte[] baseRevisionContent, List patch) { - byte[] tempBuf = new byte[512]; // XXX - int last = 0, destIndex = 0; - for (PatchRecord pr : patch) { - System.arraycopy(baseRevisionContent, last, tempBuf, destIndex, pr.start-last); - destIndex += pr.start - last; - System.arraycopy(pr.data, 0, tempBuf, destIndex, pr.data.length); - destIndex += pr.data.length; - last = pr.end; - } - System.arraycopy(baseRevisionContent, last, tempBuf, destIndex, baseRevisionContent.length - last); - destIndex += baseRevisionContent.length - last; // total length - byte[] rv = new byte[destIndex]; - System.arraycopy(tempBuf, 0, rv, 0, destIndex); - return rv; - } - - static class PatchRecord { // copy of struct frag from mpatch.c - int start, end, len; - byte[] data; - - public PatchRecord(int p1, int p2, int len, byte[] src, int srcOffset) { - start = p1; - end = p2; - this.len = len; - data = new byte[len]; - System.arraycopy(src, srcOffset, data, 0, len); - } - } } diff -r 08db726a0fb7 -r 24bb4f365164 src/com/tmate/hgkit/ll/Changelog.java --- a/src/com/tmate/hgkit/ll/Changelog.java Sun Dec 19 05:41:31 2010 +0100 +++ b/src/com/tmate/hgkit/ll/Changelog.java Mon Dec 20 02:50:36 2010 +0100 @@ -3,11 +3,8 @@ */ package com.tmate.hgkit.ll; -import java.io.DataInput; -import java.io.EOFException; -import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.List; /** @@ -16,35 +13,52 @@ */ public class Changelog extends Revlog { - private RevlogStream content; + private final RevlogStream content; - /*package-local*/ Changelog(HgRepository hgRepo) { + /*package-local*/ Changelog(HgRepository hgRepo, RevlogStream content) { super(hgRepo); - content = hgRepo.resolve(".hg/store/00changelog.i"); + this.content = content; } - public List all() { - throw HgRepository.notImplemented(); - } - - public void all(Changeset.Callback callback) { - throw HgRepository.notImplemented(); + public void all(final Changeset.Inspector inspector) { + Revlog.Inspector i = new Revlog.Inspector() { + + public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) { + Changeset cset = Changeset.parse(data, 0, data.length); + // XXX there's no guarantee for Changeset.Callback that distinct instance comes each time, consider instance reuse + inspector.next(cset); + } + }; + content.iterate(0, content.revisionCount() - 1, true, i); } public List range(int start, int end) { - //read from outline[start].start .. (outline[end].start + outline[end].length) - // parse changesets final ArrayList rv = new ArrayList(end - start + 1); Revlog.Inspector i = new Revlog.Inspector() { - public void next(int compressedLen, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) { - // TODO Auto-generated method stub - Changeset.parse(data); - i.add(); - throw HgRepository.notImplemented(); + public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) { + Changeset cset = Changeset.parse(data, 0, data.length); + rv.add(cset); } }; content.iterate(start, end, true, i); return rv; } + + public void range(final Changeset.Inspector inspector, final int... revisions) { + if (revisions == null || revisions.length == 0) { + return; + } + Revlog.Inspector i = new Revlog.Inspector() { + + public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) { + if (Arrays.binarySearch(revisions, revisionNumber) >= 0) { + Changeset cset = Changeset.parse(data, 0, data.length); + inspector.next(cset); + } + } + }; + Arrays.sort(revisions); + content.iterate(revisions[0], revisions[revisions.length - 1], true, i); + } } diff -r 08db726a0fb7 -r 24bb4f365164 src/com/tmate/hgkit/ll/Changeset.java --- a/src/com/tmate/hgkit/ll/Changeset.java Sun Dec 19 05:41:31 2010 +0100 +++ b/src/com/tmate/hgkit/ll/Changeset.java Mon Dec 20 02:50:36 2010 +0100 @@ -23,12 +23,12 @@ * @author artem */ public class Changeset { + // TODO immutable private /*final*/ Nodeid nodeid; private String user; private String comment; private ArrayList files; private String timezone; // FIXME - public byte[] rawData; // FIXME public void dump() { System.out.println("User:" + user); @@ -42,50 +42,50 @@ } } - public void read(byte[] buf, int offset, int length) { - rawData = new byte[length]; - System.arraycopy(buf, offset, rawData, 0, length); + public static Changeset parse(byte[] data, int offset, int length) { + Changeset rv = new Changeset(); final int bufferEndIndex = offset + length; final byte lineBreak = (byte) '\n'; - int breakIndex1 = indexOf(buf, lineBreak, offset, bufferEndIndex); + int breakIndex1 = indexOf(data, lineBreak, offset, bufferEndIndex); if (breakIndex1 == -1) { throw new IllegalArgumentException("Bad Changeset data"); } - nodeid = Nodeid.fromAscii(buf, 0, breakIndex1); - int breakIndex2 = indexOf(buf, lineBreak, breakIndex1+1, bufferEndIndex); + rv.nodeid = Nodeid.fromAscii(data, 0, breakIndex1); + int breakIndex2 = indexOf(data, lineBreak, breakIndex1+1, bufferEndIndex); if (breakIndex2 == -1) { throw new IllegalArgumentException("Bad Changeset data"); } - user = new String(buf, breakIndex1+1, breakIndex2 - breakIndex1 - 1); - int breakIndex3 = indexOf(buf, lineBreak, breakIndex2+1, bufferEndIndex); + rv.user = new String(data, breakIndex1+1, breakIndex2 - breakIndex1 - 1); + int breakIndex3 = indexOf(data, lineBreak, breakIndex2+1, bufferEndIndex); if (breakIndex3 == -1) { throw new IllegalArgumentException("Bad Changeset data"); } - timezone = new String(buf, breakIndex2+1, breakIndex3 - breakIndex2 - 1); + rv.timezone = new String(data, breakIndex2+1, breakIndex3 - breakIndex2 - 1); // int lastStart = breakIndex3 + 1; - int breakIndex4 = indexOf(buf, lineBreak, lastStart, bufferEndIndex); - files = new ArrayList(5); + int breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex); + rv.files = new ArrayList(5); while (breakIndex4 != -1 && breakIndex4 + 1 < bufferEndIndex) { - files.add(new String(buf, lastStart, breakIndex4 - lastStart)); + rv.files.add(new String(data, lastStart, breakIndex4 - lastStart)); lastStart = breakIndex4 + 1; - if (buf[breakIndex4 + 1] == lineBreak) { + if (data[breakIndex4 + 1] == lineBreak) { // found \n\n break; } else { - breakIndex4 = indexOf(buf, lineBreak, lastStart, bufferEndIndex); + breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex); } } if (breakIndex4 == -1 || breakIndex4 >= bufferEndIndex) { throw new IllegalArgumentException("Bad Changeset data"); } try { - comment = new String(buf, breakIndex4+2, bufferEndIndex - breakIndex4 - 2, "UTF-8"); + rv.comment = new String(data, breakIndex4+2, bufferEndIndex - breakIndex4 - 2, "UTF-8"); } catch (UnsupportedEncodingException ex) { - comment = ""; + rv.comment = ""; throw new IllegalStateException("Could hardly happen"); } + return rv; } private static int indexOf(byte[] src, byte what, int startOffset, int endIndex) { @@ -97,7 +97,7 @@ return -1; } - public interface Callback { + public interface Inspector { // first(), last(), single(). // void next(Changeset cset); diff -r 08db726a0fb7 -r 24bb4f365164 src/com/tmate/hgkit/ll/HgDataFile.java --- a/src/com/tmate/hgkit/ll/HgDataFile.java Sun Dec 19 05:41:31 2010 +0100 +++ b/src/com/tmate/hgkit/ll/HgDataFile.java Mon Dec 20 02:50:36 2010 +0100 @@ -10,10 +10,21 @@ */ public class HgDataFile extends Revlog { + private final RevlogStream content; // XXX move up to Revlog? + + // absolute from repo root? + // slashes, unix-style? + // repo location agnostic, just to give info to user, not to access real storage private final String path; - /*package-local*/HgDataFile(HgRepository hgRepo) { + /*package-local*/HgDataFile(HgRepository hgRepo, String path, RevlogStream content) { super(hgRepo); + this.path = path; + this.content = content; + } + + public boolean exists() { + return content != null; // XXX need better impl } public String getPath() { @@ -29,4 +40,24 @@ public byte[] content(int revision) { throw HgRepository.notImplemented(); } + + public void history(Changeset.Inspector inspector) { + if (!exists()) { + throw new IllegalStateException("Can't get history of invalid repository file node"); + } + final int[] commitRevisions = new int[content.revisionCount()]; + Revlog.Inspector insp = new Revlog.Inspector() { + int count = 0; + + public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) { + commitRevisions[count++] = linkRevision; + } + }; + content.iterate(0, -1, false, insp); + getRepo().getChangelog().range(inspector, commitRevisions); + } + + public void history(int start, int end, Changeset.Inspector i) { + throw HgRepository.notImplemented(); + } } diff -r 08db726a0fb7 -r 24bb4f365164 src/com/tmate/hgkit/ll/HgRepository.java --- a/src/com/tmate/hgkit/ll/HgRepository.java Sun Dec 19 05:41:31 2010 +0100 +++ b/src/com/tmate/hgkit/ll/HgRepository.java Mon Dec 20 02:50:36 2010 +0100 @@ -37,8 +37,8 @@ public final Changelog getChangelog() { if (this.changelog == null) { // might want delegate to protected createChangelog() some day - this.changelog = new Changelog(this); - // TODO init + RevlogStream content = resolve("store/00changelog.i"); // XXX perhaps, knowledge about filenames should be in LocalHgRepo? + this.changelog = new Changelog(this, content); } return this.changelog; } @@ -62,5 +62,5 @@ /** * Perhaps, should be separate interface, like ContentLookup */ - public abstract RevlogStream resolve(String string); + protected abstract RevlogStream resolve(String string); } diff -r 08db726a0fb7 -r 24bb4f365164 src/com/tmate/hgkit/ll/LocalHgRepo.java --- a/src/com/tmate/hgkit/ll/LocalHgRepo.java Sun Dec 19 05:41:31 2010 +0100 +++ b/src/com/tmate/hgkit/ll/LocalHgRepo.java Mon Dec 20 02:50:36 2010 +0100 @@ -5,6 +5,8 @@ import java.io.File; import java.io.IOException; +import java.lang.ref.SoftReference; +import java.util.HashMap; /** * @author artem @@ -30,4 +32,44 @@ public String getLocation() { return repoLocation; } + + private final HashMap> streamsCache = new HashMap>(); + + /** + * path - repository storage path (i.e. one usually with .i or .d) + */ + @Override + protected RevlogStream resolve(String path) { + final SoftReference ref = streamsCache.get(path); + RevlogStream cached = ref == null ? null : ref.get(); + if (cached != null) { + return cached; + } + File f = new File(repoDir, path); + if (f.exists()) { + RevlogStream s = new RevlogStream(f); + streamsCache.put(path, new SoftReference(s)); + return s; + } + return null; + } + + @Override + public HgDataFile getFileNode(String path) { + String nPath = normalize(path); + String storagePath = toStoragePath(nPath); + RevlogStream content = resolve(storagePath); + // XXX no content when no file? or HgDataFile.exists() to detect that? How about files that were removed in previous releases? + return new HgDataFile(this, nPath, content); + } + + // FIXME much more to be done, see store.py:_hybridencode + private static String toStoragePath(String path) { + // XXX works for lowercase names only + return "store/data/" + path.replace('\\', '/') + ".i"; + } + + private static String normalize(String path) { + return path.replace('\\', '/'); + } } diff -r 08db726a0fb7 -r 24bb4f365164 src/com/tmate/hgkit/ll/Revlog.java --- a/src/com/tmate/hgkit/ll/Revlog.java Sun Dec 19 05:41:31 2010 +0100 +++ b/src/com/tmate/hgkit/ll/Revlog.java Mon Dec 20 02:50:36 2010 +0100 @@ -23,6 +23,7 @@ } public interface Inspector { - void next(int compressedLen, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*32*/] nodeid, byte[] data); + // XXX boolean retVal to indicate whether to continue? + void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*32*/] nodeid, byte[] data); } } diff -r 08db726a0fb7 -r 24bb4f365164 src/com/tmate/hgkit/ll/RevlogStream.java --- a/src/com/tmate/hgkit/ll/RevlogStream.java Sun Dec 19 05:41:31 2010 +0100 +++ b/src/com/tmate/hgkit/ll/RevlogStream.java Mon Dec 20 02:50:36 2010 +0100 @@ -3,12 +3,19 @@ */ package com.tmate.hgkit.ll; +import java.io.BufferedInputStream; import java.io.DataInput; +import java.io.DataInputStream; import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedList; import java.util.List; +import java.util.zip.DataFormatException; import java.util.zip.Inflater; /** @@ -22,29 +29,54 @@ private List index; // indexed access highly needed private boolean inline = false; + private final File indexFile; + + RevlogStream(File indexFile) { + this.indexFile = indexFile; + } private void detectVersion() { } /*package*/ DataInput getIndexStream() { - // TODO Auto-generated method stub - return null; + DataInputStream dis = null; + try { + dis = new DataInputStream(new BufferedInputStream(new FileInputStream(indexFile))); + } catch (FileNotFoundException ex) { + ex.printStackTrace(); + // should not happen, we checked for existence + } + return dis; } /*package*/ DataInput getDataStream() { - // TODO Auto-generated method stub - return null; + final String indexName = indexFile.getName(); + File dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d"); + try { + return new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile))); + } catch (FileNotFoundException ex) { + ex.printStackTrace(); + return null; + } } + public int revisionCount() { initOutline(); return index.size(); } - // should be possible to use TIP, ALL + // should be possible to use TIP, ALL, or -1, -2, -n notation of Hg + // ? boolean needsNodeid public void iterate(int start, int end, boolean needData, Revlog.Inspector inspector) { initOutline(); final int indexSize = index.size(); + if (indexSize == 0) { + return; + } + if (end == -1 /*FIXME TIP*/) { + end = indexSize - 1; + } if (start < 0 || start >= indexSize) { throw new IllegalArgumentException("Bad left range boundary " + start); } @@ -55,12 +87,13 @@ DataInput diIndex = null, diData = null; diIndex = getIndexStream(); - if (needData) { + if (needData && !inline) { diData = getDataStream(); } try { diIndex.skipBytes(inline ? (int) index.get(start).offset : start * 64); - for (int i = start; i <= end && i < indexSize; i++ ) { + byte[] lastData = null; + for (int i = start; i <= end; i++ ) { IndexEntry ie = index.get(i); long l = diIndex.readLong(); long offset = l >>> 16; @@ -81,34 +114,71 @@ if (inline) { diIndex.readFully(dataBuf); } else { - diData.skipBytes((int) ie.offset); // FIXME not skip but seek!!! + diData.skipBytes((int) ie.offset); // FIXME not skip but seek!!! (skip would work only for the first time) diData.readFully(dataBuf); } if (dataBuf[0] == 0x78 /* 'x' */) { - Inflater zlib = new Inflater(); - zlib.setInput(dataBuf, 0, compressedLen); - byte[] result = new byte[actualLen*2]; // FIXME need to use zlib.finished() instead - int resultLen = zlib.inflate(result); - zlib.end(); - data = new byte[resultLen]; - System.arraycopy(result, 0, data, 0, resultLen); + try { + Inflater zlib = new Inflater(); + zlib.setInput(dataBuf, 0, compressedLen); + byte[] result = new byte[actualLen*2]; // FIXME need to use zlib.finished() instead + int resultLen = zlib.inflate(result); + zlib.end(); + data = new byte[resultLen]; + System.arraycopy(result, 0, data, 0, resultLen); + } catch (DataFormatException ex) { + ex.printStackTrace(); + data = new byte[0]; // FIXME need better failure strategy + } } else if (dataBuf[0] == 0x75 /* 'u' */) { data = new byte[dataBuf.length - 1]; System.arraycopy(dataBuf, 1, data, 0, data.length); } else { // XXX Python impl in fact throws exception when there's not 'x', 'u' or '0' - // but I don't see reason not to just return data as is + // but I don't see reason not to return data as is data = dataBuf; } - // FIXME if patch data (based on baseRevision), then apply patches to get true content + // XXX + if (baseRevision != i) { // XXX not sure if this is the right way to detect a patch + // this is a patch + LinkedList patches = new LinkedList(); + int patchElementIndex = 0; + do { + final int x = patchElementIndex; // shorthand + int p1 = (data[x] << 24) | (data[x+1] << 16) | (data[x+2] << 8) | data[x+3]; + int p2 = (data[x+4] << 24) | (data[x+5] << 16) | (data[x+6] << 8) | data[x+7]; + int len = (data[x+8] << 24) | (data[x+9] << 16) | (data[x+10] << 8) | data[x+11]; + patchElementIndex += 12 + len; + patches.add(new PatchRecord(p1, p2, len, data, x+12)); + } while (patchElementIndex < data.length); + // + byte[] baseRevContent; + if (baseRevision == i - 1) { + baseRevContent = lastData; + } else { + // FIXME implement delta collection from few revisions + // read baseRevision plus all deltas between this revision and base. Need to do this effectively. + throw HgRepository.notImplemented(); + } + + // FIXME need to collect all patches between baseRevision and current version + data = apply(baseRevContent, patches); + } + } else { + if (inline) { + diIndex.skipBytes(compressedLen); + } } - inspector.next(compressedLen, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, buf, data); + inspector.next(i, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, buf, data); + lastData = data; } } catch (EOFException ex) { // should not happen as long as we read inside known boundaries throw new IllegalStateException(ex); } catch (IOException ex) { - FIXME + throw new IllegalStateException(ex); // FIXME need better handling + } finally { + hackCloseFileStreams(diIndex, diData); // FIXME HACK!!! } } @@ -120,11 +190,11 @@ DataInput di = getIndexStream(); try { int versionField = di.readInt(); - // di.undreadInt(); + di.readInt(); // just to skip next 2 bytes of offset + flags final int INLINEDATA = 1 << 16; inline = (versionField & INLINEDATA) != 0; long offset = 0; // first offset is always 0, thus Hg uses it for other purposes - while(true) { // EOFExcepiton should get us outta here. FIXME Out inputstream should has explicit no-more-data indicator + while(true) { // EOFExcepiton should get us outta here. FIXME Our inputstream should has explicit no-more-data indicator int compressedLen = di.readInt(); // 8+4 = 12 bytes total read // int actualLen = di.readInt(); @@ -135,9 +205,9 @@ // byte[] nodeid = new byte[32]; res.add(new IndexEntry(offset, compressedLen)); if (inline) { - di.skipBytes(6*4 + 32 + compressedLen); // Check: 56 (skip) + 12 (read) = 64 (total RevlogNG record size) + di.skipBytes(5*4 + 32 + compressedLen); // Check: 52 (skip) + 12 (read) = 64 (total RevlogNG record size) } else { - di.skipBytes(6*4 + 32); + di.skipBytes(5*4 + 32); } long l = di.readLong(); offset = l >>> 16; @@ -150,6 +220,22 @@ // too bad, no outline then index = Collections.emptyList(); } + hackCloseFileStreams(di, null); // FIXME HACK!!! + } + + // FIXME HACK to deal with File/FileStream nature of out data source. Won't need this once implement + // own DataInput based on bytearray chunks or RandomAccessFile + private void hackCloseFileStreams(DataInput index, DataInput data) { + try { + if (index != null) { + ((DataInputStream) index).close(); + } + if (data != null) { + ((DataInputStream) data).close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } } @@ -163,4 +249,37 @@ length = l; } } + + // mpatch.c : apply() + // FIXME need to implement patch merge (fold, combine, gather and discard from aforementioned mpatch.[c|py]), also see Revlog and Mercurial PDF + private static byte[] apply(byte[] baseRevisionContent, List patch) { + byte[] tempBuf = new byte[512]; // XXX + int last = 0, destIndex = 0; + for (PatchRecord pr : patch) { + System.arraycopy(baseRevisionContent, last, tempBuf, destIndex, pr.start-last); + destIndex += pr.start - last; + System.arraycopy(pr.data, 0, tempBuf, destIndex, pr.data.length); + destIndex += pr.data.length; + last = pr.end; + } + System.arraycopy(baseRevisionContent, last, tempBuf, destIndex, baseRevisionContent.length - last); + destIndex += baseRevisionContent.length - last; // total length + byte[] rv = new byte[destIndex]; + System.arraycopy(tempBuf, 0, rv, 0, destIndex); + return rv; + } + + static class PatchRecord { // copy of struct frag from mpatch.c + int start, end, len; + byte[] data; + + public PatchRecord(int p1, int p2, int len, byte[] src, int srcOffset) { + start = p1; + end = p2; + this.len = len; + data = new byte[len]; + System.arraycopy(src, srcOffset, data, 0, len); + } + } + }