# HG changeset patch # User Artem Tikhomirov # Date 1292733691 -3600 # Node ID 08db726a0fb7914ac9d27ba26dc8bbf6385a0554 # Parent a3576694a4d1edaa681cab15b89d6b556b02aff4 Shaping out low-level Hg structures diff -r a3576694a4d1 -r 08db726a0fb7 design.txt --- a/design.txt Sat Dec 18 05:47:35 2010 +0100 +++ b/design.txt Sun Dec 19 05:41:31 2010 +0100 @@ -5,6 +5,15 @@ Log --rev Log +HgDataFile.history() or Changelog.history(file)? + + +Changelog.all() to return list with placeholder, not-parsed elements (i.e. read only compressedLen field and skip to next record), so that +total number of elements in the list is correct hg cat -Implementation: logic to find file by name in the repository is the same with Log and other commands \ No newline at end of file +Implementation: logic to find file by name in the repository is the same with Log and other commands + + +Revlog +What happens when big entry is added to a file - when it detects it can't longer fit into .i and needs .d? Inline flag and .i format changes? \ No newline at end of file diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/console/Cat.java --- a/src/com/tmate/hgkit/console/Cat.java Sat Dec 18 05:47:35 2010 +0100 +++ b/src/com/tmate/hgkit/console/Cat.java Sun Dec 19 05:41:31 2010 +0100 @@ -3,7 +3,7 @@ */ package com.tmate.hgkit.console; -import com.tmate.hgkit.fs.RepositoryFinder; +import com.tmate.hgkit.fs.RepositoryLookup; import com.tmate.hgkit.ll.HgRepository; /** @@ -13,7 +13,7 @@ public class Cat { public static void main(String[] args) throws Exception { - RepositoryFinder repoLookup = new RepositoryFinder(); + RepositoryLookup repoLookup = new RepositoryLookup(); HgRepository hgRepo = repoLookup.detect(args); if (hgRepo.isInvalid()) { System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation()); diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/console/Log.java --- a/src/com/tmate/hgkit/console/Log.java Sat Dec 18 05:47:35 2010 +0100 +++ b/src/com/tmate/hgkit/console/Log.java Sun Dec 19 05:41:31 2010 +0100 @@ -3,7 +3,9 @@ */ package com.tmate.hgkit.console; -import com.tmate.hgkit.fs.RepositoryFinder; +import com.tmate.hgkit.fs.RepositoryLookup; +import com.tmate.hgkit.ll.Changeset; +import com.tmate.hgkit.ll.HgDataFile; import com.tmate.hgkit.ll.HgRepository; /** @@ -12,15 +14,28 @@ public class Log { public static void main(String[] args) throws Exception { - RepositoryFinder repoLookup = new RepositoryFinder(); + RepositoryLookup repoLookup = new RepositoryLookup(); HgRepository hgRepo = repoLookup.detect(args); if (hgRepo.isInvalid()) { System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation()); return; } System.out.println(hgRepo.getLocation()); + final Changeset.Callback callback = new Changeset.Callback() { + + public void next(Changeset cset) { + System.out.println(); + } + }; + 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("Complete of a repo:"); + hgRepo.getChangelog().all(callback); //new ChangelogWalker().setFile("hello.c").setRevisionRange(1, 4).accept(new Visitor); - } } diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/console/Main.java --- a/src/com/tmate/hgkit/console/Main.java Sat Dec 18 05:47:35 2010 +0100 +++ b/src/com/tmate/hgkit/console/Main.java Sun Dec 19 05:41:31 2010 +0100 @@ -23,29 +23,6 @@ public class Main { public static void main(String[] args) throws Exception { - Deflater zip1 = new Deflater(6, true); - final byte[] input = "Abstractions are valueless".getBytes(); - zip1.setInput(input); - zip1.finish(); - byte[] result1 = new byte[100]; - int resLen1 = zip1.deflate(result1); - System.out.printf("%3d:", resLen1); - for (int i = 0; i < resLen1; i++) { - System.out.printf("%02X", result1[i]); - } - System.out.println(); - // - Deflater zip2 = new Deflater(6, false); - zip2.setInput(input); - zip2.finish(); - byte[] result2 = new byte[100]; - int resLen2 = zip2.deflate(result2); - System.out.printf("%3d:", resLen2); - for (int i = 0; i < resLen2; i++) { - System.out.printf("%02X", result2[i]); - } - System.out.println(); - // LinkedList changelog = new LinkedList(); // DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("/temp/hg/hello/" + ".hg/store/00changelog.i")))); diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/fs/RepositoryFinder.java --- a/src/com/tmate/hgkit/fs/RepositoryFinder.java Sat Dec 18 05:47:35 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2010 Artem Tikhomirov - */ -package com.tmate.hgkit.fs; - -import java.io.File; - -import com.tmate.hgkit.ll.HgRepository; -import com.tmate.hgkit.ll.LocalHgRepo; - -/** - * @author artem - */ -public class RepositoryFinder { - - public HgRepository detect(String[] commandLineArgs) throws Exception { - if (commandLineArgs.length == 0) { - return detectFromWorkingDir(); - } - return detect(commandLineArgs[0]); - } - - public HgRepository detectFromWorkingDir() throws Exception { - return detect(System.getProperty("user.dir")); - } - - public HgRepository detect(String location) throws Exception /*FIXME Exception type, RepoInitException? */ { - File dir = new File(location); - File repository; - do { - repository = new File(dir, ".hg"); - if (repository.exists() && repository.isDirectory()) { - break; - } - repository = null; - dir = dir.getParentFile(); - - } while(dir != null); - if (repository == null) { - return new LocalHgRepo(location); - } - return new LocalHgRepo(repository); - } -} diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/fs/RepositoryLookup.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/fs/RepositoryLookup.java Sun Dec 19 05:41:31 2010 +0100 @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.fs; + +import java.io.File; + +import com.tmate.hgkit.ll.HgRepository; +import com.tmate.hgkit.ll.LocalHgRepo; + +/** + * @author artem + */ +public class RepositoryLookup { + + public HgRepository detect(String[] commandLineArgs) throws Exception { + if (commandLineArgs.length == 0) { + return detectFromWorkingDir(); + } + return detect(commandLineArgs[0]); + } + + public HgRepository detectFromWorkingDir() throws Exception { + return detect(System.getProperty("user.dir")); + } + + public HgRepository detect(String location) throws Exception /*FIXME Exception type, RepoInitException? */ { + File dir = new File(location); + File repository; + do { + repository = new File(dir, ".hg"); + if (repository.exists() && repository.isDirectory()) { + break; + } + repository = null; + dir = dir.getParentFile(); + + } while(dir != null); + if (repository == null) { + return new LocalHgRepo(location); + } + return new LocalHgRepo(repository); + } +} diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/ll/Changelog.java --- a/src/com/tmate/hgkit/ll/Changelog.java Sat Dec 18 05:47:35 2010 +0100 +++ b/src/com/tmate/hgkit/ll/Changelog.java Sun Dec 19 05:41:31 2010 +0100 @@ -3,10 +3,48 @@ */ 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.List; + /** * Representation of the Mercurial changelog file (list of ChangeSets) * @author artem */ -public class Changelog { +public class Changelog extends Revlog { + private RevlogStream content; + + /*package-local*/ Changelog(HgRepository hgRepo) { + super(hgRepo); + content = hgRepo.resolve(".hg/store/00changelog.i"); + } + + public List all() { + throw HgRepository.notImplemented(); + } + + public void all(Changeset.Callback callback) { + throw HgRepository.notImplemented(); + } + + 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(); + } + }; + content.iterate(start, end, true, i); + return rv; + } } diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/ll/Changeset.java --- a/src/com/tmate/hgkit/ll/Changeset.java Sat Dec 18 05:47:35 2010 +0100 +++ b/src/com/tmate/hgkit/ll/Changeset.java Sun Dec 19 05:41:31 2010 +0100 @@ -96,4 +96,10 @@ } return -1; } + + public interface Callback { + // first(), last(), single(). + // + void next(Changeset cset); + } } diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/ll/HgDataFile.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/ll/HgDataFile.java Sun Dec 19 05:41:31 2010 +0100 @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.ll; + +/** + * Extends Revlog/uses RevlogStream? + * ? name:HgFileNode? + * @author artem + */ +public class HgDataFile extends Revlog { + + private final String path; + + /*package-local*/HgDataFile(HgRepository hgRepo) { + super(hgRepo); + } + + public String getPath() { + return path; // hgRepo.backresolve(this) -> name? + } + + private static final int TIP = -2; + + public byte[] content() { + return content(TIP); + } + + public byte[] content(int revision) { + throw HgRepository.notImplemented(); + } +} diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/ll/HgManifest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/ll/HgManifest.java Sun Dec 19 05:41:31 2010 +0100 @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.ll; + +/** + * + * @author artem + */ +public class HgManifest extends Revlog { + + /*package-local*/ HgManifest(HgRepository hgRepo) { + super(hgRepo); + } +} diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/ll/HgRepository.java --- a/src/com/tmate/hgkit/ll/HgRepository.java Sat Dec 18 05:47:35 2010 +0100 +++ b/src/com/tmate/hgkit/ll/HgRepository.java Sun Dec 19 05:41:31 2010 +0100 @@ -6,12 +6,18 @@ /** * @author artem - * */ public abstract class HgRepository { + // temp aux marker method + public static IllegalStateException notImplemented() { + return new IllegalStateException("Not implemented"); + } + private Changelog changelog; + private HgManifest manifest; + private boolean isInvalid = true; public boolean isInvalid() { @@ -23,21 +29,38 @@ } public void log() { - Changelog clog = getChangelog(); + Revlog clog = getChangelog(); assert clog != null; // TODO get data to the client } - /** - * @return - */ - private Changelog getChangelog() { + public final Changelog getChangelog() { if (this.changelog == null) { - this.changelog = new Changelog(); + // might want delegate to protected createChangelog() some day + this.changelog = new Changelog(this); // TODO init } return this.changelog; } + + public final HgManifest getManifest() { + if (this.manifest == null) { + this.manifest = new HgManifest(this); + } + return this.manifest; + } + + public final Object/*HgDirstate*/ getDirstate() { + throw notImplemented(); + } + + public abstract HgDataFile getFileNode(String path); public abstract String getLocation(); + + + /** + * Perhaps, should be separate interface, like ContentLookup + */ + public abstract RevlogStream resolve(String string); } diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/ll/Revlog.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/tmate/hgkit/ll/Revlog.java Sun Dec 19 05:41:31 2010 +0100 @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010 Artem Tikhomirov + */ +package com.tmate.hgkit.ll; + +/** + * + * @author artem + */ +public abstract class Revlog { + + private final HgRepository hgRepo; + + protected Revlog(HgRepository hgRepo) { + if (hgRepo == null) { + throw new NullPointerException(); + } + this.hgRepo = hgRepo; + } + + public final HgRepository getRepo() { + return hgRepo; + } + + public interface Inspector { + void next(int compressedLen, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[/*32*/] nodeid, byte[] data); + } +} diff -r a3576694a4d1 -r 08db726a0fb7 src/com/tmate/hgkit/ll/RevlogStream.java --- a/src/com/tmate/hgkit/ll/RevlogStream.java Sat Dec 18 05:47:35 2010 +0100 +++ b/src/com/tmate/hgkit/ll/RevlogStream.java Sun Dec 19 05:41:31 2010 +0100 @@ -4,6 +4,12 @@ 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.List; +import java.util.zip.Inflater; /** * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), @@ -13,7 +19,10 @@ * @see http://mercurial.selenic.com/wiki/RevlogNG */ public class RevlogStream { - + + private List index; // indexed access highly needed + private boolean inline = false; + private void detectVersion() { } @@ -27,5 +36,131 @@ // TODO Auto-generated method stub return null; } + public int revisionCount() { + initOutline(); + return index.size(); + } + + // should be possible to use TIP, ALL + public void iterate(int start, int end, boolean needData, Revlog.Inspector inspector) { + initOutline(); + final int indexSize = index.size(); + if (start < 0 || start >= indexSize) { + throw new IllegalArgumentException("Bad left range boundary " + start); + } + if (end < start || end >= indexSize) { + throw new IllegalArgumentException("Bad right range boundary " + end); + } + // XXX may cache [start .. end] from index with a single read (pre-read) + + DataInput diIndex = null, diData = null; + diIndex = getIndexStream(); + if (needData) { + diData = getDataStream(); + } + try { + diIndex.skipBytes(inline ? (int) index.get(start).offset : start * 64); + for (int i = start; i <= end && i < indexSize; i++ ) { + IndexEntry ie = index.get(i); + long l = diIndex.readLong(); + long offset = l >>> 16; + int flags = (int) (l & 0X0FFFF); + int compressedLen = diIndex.readInt(); + int actualLen = diIndex.readInt(); + int baseRevision = diIndex.readInt(); + int linkRevision = diIndex.readInt(); + int parent1Revision = diIndex.readInt(); + int parent2Revision = diIndex.readInt(); + byte[] buf = new byte[32]; + // XXX Hg keeps 12 last bytes empty, we move them into front here + diIndex.readFully(buf, 12, 20); + diIndex.skipBytes(12); + byte[] data = null; + if (needData) { + byte[] dataBuf = new byte[compressedLen]; + if (inline) { + diIndex.readFully(dataBuf); + } else { + diData.skipBytes((int) ie.offset); // FIXME not skip but seek!!! + 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); + } 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 + data = dataBuf; + } + // FIXME if patch data (based on baseRevision), then apply patches to get true content + } + inspector.next(compressedLen, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, buf, data); + } + } catch (EOFException ex) { + // should not happen as long as we read inside known boundaries + throw new IllegalStateException(ex); + } catch (IOException ex) { + FIXME + } + } + private void initOutline() { + if (index != null && !index.isEmpty()) { + return; + } + ArrayList res = new ArrayList(); + DataInput di = getIndexStream(); + try { + int versionField = di.readInt(); + // di.undreadInt(); + 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 + int compressedLen = di.readInt(); + // 8+4 = 12 bytes total read +// int actualLen = di.readInt(); +// int baseRevision = di.readInt(); +// int linkRevision = di.readInt(); +// int parent1Revision = di.readInt(); +// int parent2Revision = di.readInt(); +// 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) + } else { + di.skipBytes(6*4 + 32); + } + long l = di.readLong(); + offset = l >>> 16; + } + } catch (EOFException ex) { + // fine, done then + index = res; + } catch (IOException ex) { + ex.printStackTrace(); + // too bad, no outline then + index = Collections.emptyList(); + } + } + + + // perhaps, package-local or protected, if anyone else from low-level needs them + private static class IndexEntry { + public final long offset; + public final int length; // data past fixed record (need to decide whether including header size or not), and whether length is of compressed data or not + + public IndexEntry(long o, int l) { + offset = o; + length = l; + } + } }