Package org.tmatesoft.hg.internal

Source Code of org.tmatesoft.hg.internal.RevlogDump

/*
* Copyright (c) 2010-2013 TMate Software Ltd
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* For information on how to redistribute this software under
* the terms of a license other than GNU General Public License
* contact TMate Software at support@hg4j.com
*/
package org.tmatesoft.hg.internal;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

/**
* Utility to test/debug/troubleshoot
* @author Artem Tikhomirov
* @author TMate Software Ltd.
*/
public class RevlogDump {

  /**
   * Takes 3 command line arguments -
   *   repository path,
   *   path to index file (i.e. store/data/hello.c.i) in the repository (relative)
   *   and "dumpData" whether to print actual content or just revlog headers
   */
  public static void main(String[] args) throws Exception {
    String repo = "/temp/hg/hello/.hg/";
    String filename = "store/00changelog.i";
//    String filename = "store/data/hello.c.i";
//    String filename = "store/data/docs/readme.i";
//    System.out.println(escape("abc\0def\nzxc\tmnb"));
    boolean dumpDataFull = true;
    boolean dumpDataStats = false;
    if (args.length > 1) {
      repo = args[0];
      filename = args[1];
      dumpDataFull = args.length > 2 ? "dumpData".equals(args[2]) : false;
      dumpDataStats = args.length > 2 ? "dumpDataStats".equals(args[2]) : false;
    }
    final boolean needRevData = dumpDataFull || dumpDataStats;
    //
    RevlogReader rr = new RevlogReader(new File(repo, filename)).needData(needRevData);
    rr.init(needRevData);
    System.out.printf("%#8x, inline: %b\n", rr.versionField, rr.inlineData);
    System.out.println("Index    Offset      Flags     Packed     Actual   Base Rev   Link Rev  Parent1  Parent2     nodeid");
    ByteBuffer data = null;
    while (rr.hasMore()) {
      rr.readNext();
      System.out.printf("%4d:%14d %6X %10d %10d %10d %10d %8d %8d     %040x\n", rr.entryIndex, rr.offset, rr.flags, rr.compressedLen, rr.actualLen, rr.baseRevision, rr.linkRevision, rr.parent1Revision, rr.parent2Revision, new BigInteger(rr.nodeid));
      if (needRevData) {
        String resultString;
        if (rr.getDataLength() == 0) {
          resultString = "<NO DATA>";
        } else {
          data = ensureCapacity(data, rr.getDataLength());
          rr.getData(data);
          data.flip();
          resultString = buildString(data, rr.isPatch(), dumpDataFull);
        }
        if (resultString.endsWith("\n")) {
          System.out.print(resultString);
        } else {
          System.out.println(resultString);
        }
      }
    }
    rr.done();
  }
 
  private static ByteBuffer ensureCapacity(ByteBuffer src, int requiredCap) {
    if (src == null || src.capacity() < requiredCap) {
      return ByteBuffer.allocate((1 + requiredCap) * 3 / 2);
    }
    src.clear();
    return src;
  }
 
  private static String buildString(ByteBuffer data, boolean isPatch, boolean completeDataDump) throws IOException, UnsupportedEncodingException {
    if (isPatch) {
      DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data.array(), data.arrayOffset(), data.remaining()));
      StringBuilder sb = new StringBuilder();
      sb.append("<PATCH>:\n");
      while (dis.available() > 0) {
        int s = dis.readInt();
        int e = dis.readInt();
        int l = dis.readInt();
        sb.append(String.format("%d..%d, %d", s, e, l));
        if (completeDataDump) {
          byte[] src = new byte[l];
          dis.read(src, 0, l);
          sb.append(":");
          sb.append(escape(new String(src, 0, l, "UTF-8")));
        } else {
          dis.skipBytes(l);
        }
        sb.append('\n');
      }
      return sb.toString();
    } else {
      if (completeDataDump) {
        return escape(new String(data.array(), data.arrayOffset(), data.remaining(), "UTF-8"));
      }
      return String.format("<DATA>:%d bytes", data.remaining());
    }
  }
 
  private static Pattern controlCharPattern = Pattern.compile("\\p{Cntrl}");
  // \p{Cntrl}  A control character: [\x00-\x1F\x7F]
  private static String[] replacements = new String[33];
  static {
    for (int i = 0; i < 32; i++) {
      // no idea why need FOUR backslashes to get only one in printout
      replacements[i] = String.format("\\\\%X", i);
    }
    replacements[32] = String.format("\\\\%X", 127);
  }
  // handy to get newline-separated data printed on newlines.
  // set to false for non-printable data (e.g. binaries, where \n doesn't make sense)
  private static boolean leaveNewlineInData = true;
 
  private static String escape(CharSequence possiblyWithBinary) {
    Matcher m = controlCharPattern.matcher(possiblyWithBinary);
    StringBuffer rv = new StringBuffer();
    while (m.find()) {
      char c = m.group().charAt(0);
      if (leaveNewlineInData && c == '\n') {
        continue;
      }
      int x = (int) c;
      m.appendReplacement(rv, replacements[x == 127 ? 32 : x]);
    }
    m.appendTail(rv);
    return rv.toString();
  }

  public static class RevlogReader {
   
    private final File file;
    private boolean needRevData;
    private DataInputStream dis;
    private boolean inlineData;
    public int versionField;
    private FileChannel dataStream;
    public int entryIndex;
    private byte[] data;
    private int dataOffset, dataLen;
    public long offset;
    public int flags;
    public int baseRevision;
    public int linkRevision;
    public int parent1Revision;
    public int parent2Revision;
    public int compressedLen;
    public int actualLen;
    public byte[] nodeid = new byte[21]; // need 1 byte in the front to be 0 to avoid negative BigInts

    public RevlogReader(File f) {
      assert f.getName().endsWith(".i");
      file = f;
    }

    // affects #readNext()
    public RevlogReader needData(boolean needData) {
      needRevData = needData;
      return this;
    }
   
    public void init(boolean mayRequireData) throws IOException {
      dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
      DataInput di = dis;
      dis.mark(10);
      versionField = di.readInt();
      dis.reset();
      final int INLINEDATA = 1 << 16;
      inlineData = (versionField & INLINEDATA) != 0;
     
      dataStream = null;
      if (!inlineData && mayRequireData) {
        String fname = file.getAbsolutePath();
        dataStream = new FileInputStream(new File(fname.substring(0, fname.length()-2) + ".d")).getChannel();
      }
     
      entryIndex = -1;
    }
   
    public void startFrom(int startEntryIndex) throws IOException {
      if (dis == null) {
        throw new IllegalStateException("Call #init() first");
      }
      if (entryIndex != -1 && startEntryIndex != 0) {
        throw new IllegalStateException("Can't seek once iteration has started");
      }
      if (dataStream == null) {
        throw new IllegalStateException("Sorry, initial seek is now supported for separate .i/.d only");
      }
      long newPos = startEntryIndex * Internals.REVLOGV1_RECORD_SIZE, actualSkip;
      do {
        actualSkip = dis.skip(newPos);
        if (actualSkip <= 0) {
          throw new IllegalStateException(String.valueOf(actualSkip));
        }
        newPos -= actualSkip;
      } while (newPos > 0);
      entryIndex = startEntryIndex - 1;
    }
   
    public boolean hasMore() throws IOException {
      return dis.available() > 0;
    }
   
    public void readNext() throws IOException, DataFormatException {
      entryIndex++;
      DataInput di = dis;
      long l = di.readLong();
      offset = entryIndex == 0 ? 0 : (l >>> 16);
      flags = (int) (l & 0x0FFFF);
      compressedLen = di.readInt();
      actualLen = di.readInt();
      baseRevision = di.readInt();
      linkRevision = di.readInt();
      parent1Revision = di.readInt();
      parent2Revision = di.readInt();
      di.readFully(nodeid, 1, 20);
      dis.skipBytes(12);
      // CAN'T USE skip() here without extra precautions. E.g. I ran into situation when
      // buffer was 8192 and BufferedInputStream was at position 8182 before attempt to skip(12).
      // BIS silently skips available bytes and leaves me two extra bytes that ruin the rest of the code.
      data = new byte[compressedLen];
      if (inlineData) {
        di.readFully(data);
      } else if (needRevData) {
        dataStream.position(offset);
        dataStream.read(ByteBuffer.wrap(data));
      }
      if (needRevData) {
        if (compressedLen == 0) {
          data = null;
          dataOffset = dataLen = 0;
        } else {
          if (data[0] == 0x78 /* 'x' */) {
            Inflater zlib = new Inflater();
            zlib.setInput(data, 0, compressedLen);
            byte[] result = new byte[actualLen * 3];
            int resultLen = zlib.inflate(result);
            zlib.end();
            data = result;
            dataOffset = 0;
            dataLen = resultLen;
          } else if (data[0] == 0x75 /* 'u' */) {
            dataOffset = 1;
            dataLen = data.length - 1;
          } else {
            dataOffset = 0;
            dataLen = data.length;
          }
        }
      }
    }
   
    public int getDataLength() {
      // NOT actualLen - there are empty patch revisions (dataLen == 0, but actualLen == previous length)
      // NOT compressedLen - zip data is uncompressed
      return dataLen;
    }
   
    public void getData(ByteBuffer bb) {
      assert bb.remaining() >= dataLen;
      bb.put(data, dataOffset, dataLen);
    }
   
    public boolean isPatch() {
      assert entryIndex != -1;
      return baseRevision != entryIndex;
    }
   
    public boolean isInline() {
      assert dis != null;
      return inlineData;
    }

    public void done() throws IOException {
      dis.close();
      dis = null;
      if (dataStream != null) {
        dataStream.close();
        dataStream = null;
      }
    }
  }
}
TOP

Related Classes of org.tmatesoft.hg.internal.RevlogDump

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.