view test/org/tmatesoft/hg/test/TestRevlog.java @ 709:497e697636fc

Report merged lines as changed block if possible, not as a sequence of added/deleted blocks. To facilitate access to merge parent lines AddBlock got mergeLineAt() method that reports index of the line in the second parent (if any), while insertedAt() has been changed to report index in the first parent always
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 21 Aug 2013 16:23:27 +0200
parents 7839ff0bfd78
children
line wrap: on
line source
/*
 * Copyright (c) 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.test;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

import org.tmatesoft.hg.core.HgRepositoryNotFoundException;
import org.tmatesoft.hg.core.Nodeid;
import org.tmatesoft.hg.internal.ByteArrayDataAccess;
import org.tmatesoft.hg.internal.Patch;
import org.tmatesoft.hg.internal.RevlogDump.RevlogReader;
import org.tmatesoft.hg.internal.diff.DiffHelper;
import org.tmatesoft.hg.internal.diff.DiffHelper.LineSequence;
import org.tmatesoft.hg.repo.HgLookup;
import org.tmatesoft.hg.repo.HgManifest;
import org.tmatesoft.hg.repo.HgManifest.Flags;
import org.tmatesoft.hg.repo.HgRepository;
import org.tmatesoft.hg.repo.HgRuntimeException;
import org.tmatesoft.hg.util.Path;

/**
 * Not a real JUnit test now
 * 
 * @author Artem Tikhomirov
 * @author TMate Software Ltd.
 */
public class TestRevlog {
	private ByteBuffer patchData;

	public static void main(String[] args) throws Exception {
		File indexFile = new File("/home/artem/hg/cpython/.hg/store/00manifest.i");
		new TestRevlog().run(indexFile);
	}
	
	private void run(File indexFile) throws Exception {
		final boolean shallDumpDiff = Boolean.TRUE.booleanValue();
		final boolean thoroughCheck = Boolean.FALSE.booleanValue();
		//
		RevlogReader rr = new RevlogReader(indexFile);
		rr.init(true);
		rr.needData(true);
		int startEntryIndex = 76507; // 150--87 
		rr.startFrom(startEntryIndex);
		rr.readNext();
		final long s0 = System.currentTimeMillis();
		ByteBuffer baseRevision = null;
		if (rr.isPatch()) {
			byte[] cc = getRevisionTrueContent(indexFile.getParentFile(), rr.entryIndex, rr.linkRevision);
			baseRevision = ByteBuffer.wrap(cc);
		} else {
			baseRevision = ByteBuffer.allocate(rr.getDataLength());
			rr.getData(baseRevision);
			baseRevision.flip();
		}
		ByteArrayDataAccess baseRevisionContent = new ByteArrayDataAccess(baseRevision.array(), baseRevision.arrayOffset(), baseRevision.remaining());
		//
		final long start = System.currentTimeMillis();
		int n = 1419;
		Patch seqPatch = new Patch(false), normalizedPatch = new Patch(true);
		while (rr.hasMore() && n-- > 0) {
			rr.readNext();
			if (!rr.isPatch()) {
				break;
			}
			if (rr.getDataLength() == 0) {
				System.out.printf("Empty content of revision %d\n", rr.entryIndex);
				continue;
			}
			Patch p1 = createPatch(rr);
			if (n < 1) {
				System.out.println("+" + p1);
				System.currentTimeMillis();
			}
				seqPatch = seqPatch.apply(p1);
				normalizedPatch = normalizedPatch.apply(p1);
//				if (n <= 1) {
//					System.out.println("=" + seqPatch);
//				}
//				if (n == 0) {
//					System.out.println("A" + ppp);
//					System.out.println("N" + normalizedPatch);
//					normalizedPatch = ppp;
//				}
				//
			if (!thoroughCheck) {
				if (baseRevisionContent.length() + seqPatch.patchSizeDelta() != rr.actualLen) {
					System.out.printf("Sequential patches:\tPatchRevision #%d (+%d, cset:%d) failed\n", rr.entryIndex, rr.entryIndex - startEntryIndex, rr.linkRevision);
				}
				if (baseRevisionContent.length() + normalizedPatch.patchSizeDelta() != rr.actualLen) {
					System.out.printf("Normalized patches:\tPatchRevision #%d (+%d, cset:%d) failed\n", rr.entryIndex, rr.entryIndex - startEntryIndex, rr.linkRevision);
				}
			} else {
				byte[] origin = getRevisionTrueContent(indexFile.getParentFile(), rr.entryIndex, rr.linkRevision);
				try {
					byte[] result1 = seqPatch.apply(baseRevisionContent, rr.actualLen);
					if (!Arrays.equals(result1, origin)) {
						System.out.printf("Sequential patches:\tPatchRevision #%d (+%d, cset:%d) failed\n", rr.entryIndex, rr.entryIndex - startEntryIndex, rr.linkRevision);
					}
				} catch (ArrayIndexOutOfBoundsException ex) {
					System.err.printf("Failure at entry %d (+%d)\n", rr.entryIndex, rr.entryIndex - startEntryIndex);
					ex.printStackTrace();
				}
//					try {
//						byte[] result2 = normalizedPatch.apply(baseRevisionContent, rr.actualLen);
//						if (!Arrays.equals(result2, origin)) {
//							System.out.printf("Normalized patches:\tPatchRevision #%d (+%d, cset:%d) failed\n", rr.entryIndex, rr.entryIndex - startEntryIndex, rr.linkRevision);
//						}
//					} catch (ArrayIndexOutOfBoundsException ex) {
//						System.err.printf("Failure at entry %d (+%d)\n", rr.entryIndex, rr.entryIndex - startEntryIndex);
//						ex.printStackTrace();
//					}
			}
		}
		final long end1 = System.currentTimeMillis();
		//
		byte[] result = seqPatch.apply(baseRevisionContent, rr.actualLen);
//		byte[] result = normalizedPatch.apply(baseRevisionContent, rr.actualLen);
		final long end2 = System.currentTimeMillis();
		byte[] origin = getRevisionTrueContent(indexFile.getParentFile(), rr.entryIndex, rr.linkRevision);
		final long end3 = System.currentTimeMillis();
		rr.done();
		System.out.printf("Collected patches up to revision %d. Patches total: %d, sequentialPatch contains %d elements, normalized: %d\n", rr.entryIndex, rr.entryIndex - startEntryIndex + 1, seqPatch.count(), normalizedPatch.count());
		if (!Arrays.equals(result, origin)) {
			if (shallDumpDiff) {
				diff(result, origin);
				dumpLineDifference(result, origin);
			} else {
				System.out.println("FAILURE!");
			}
		} else {
			System.out.println("OK!");
			System.out.printf("Iterate: %d ms, read base:%d, apply collected: %d ms, total=%d ms; Conventional: %d ms\n", (end1-start), (start-s0), (end2-end1), (end2-s0), (end3-end2));
		}
		Patch normalized = seqPatch.normalize();
		System.out.printf("N%s\n%d => %d patch elements\n", normalized, seqPatch.count(), normalized.count());
//		System.out.println(rs);
	}

	private void dumpLineDifference(byte[] result, byte[] origin) {
		String rs = new String(result).replace('\0', '\t');
		String os = new String(origin).replace('\0', '\t');
		String[] rsLines = rs.split("\n");
		String[] osLines = os.split("\n");
		int approxPos = 0;
		for (int i = 0; i < Math.min(rsLines.length, osLines.length); i++) {
			if (!rsLines[i].equals(osLines[i])) {
				System.out.printf("@%d (offset ~%d)\n\t%s\n\t%s\n", i, approxPos, osLines[i], rsLines[i]);
			}
			approxPos += rsLines[i].length() + 1;
		}
	}

	private void diff(byte[] result, byte[] origin) {
		DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>();
		pg.init(LineSequence.newlines(origin), LineSequence.newlines(result));
		pg.findMatchingBlocks(new DiffHelper.DeltaDumpInspector<LineSequence>());
	}

	private Patch createPatch(RevlogReader rr) throws IOException {
		assert rr.isPatch();
		if (patchData == null || patchData.capacity() < rr.getDataLength()) {
			patchData = ByteBuffer.allocate(rr.getDataLength());
		} else {
			patchData.clear();
		}
		rr.getData(patchData);
		patchData.flip();
		Patch patch1 = new Patch();
		patch1.read(new ByteArrayDataAccess(patchData.array(), patchData.arrayOffset(), patchData.remaining()));
		return patch1;
	}
	
	private byte[] getRevisionTrueContent(File repoLoc, final int manifestRev, int clogRev) throws HgRepositoryNotFoundException, IllegalArgumentException, HgRuntimeException {
		HgRepository hgRepo = new HgLookup().detect(repoLoc);
		final ByteArrayOutputStream out = new ByteArrayOutputStream(1024 * 1000);
		hgRepo.getManifest().walk(clogRev, clogRev, new HgManifest.Inspector() {
			
			public boolean next(Nodeid nid, Path fname, Flags flags) {
				try {
					out.write(fname.toString().getBytes());
					out.write(0);
					out.write(nid.toString().getBytes());
					if (flags == Flags.Exec) {
						out.write('x');
					} else if (flags == Flags.Link) {
						out.write('l');
					}
					out.write('\n');
				} catch (IOException e) {
					throw new IllegalStateException(e);
				}
				return true;
			}
			
			public boolean end(int manifestRevisionIndex) {
				return false;
			}
			
			public boolean begin(int manifestRevisionIndex, Nodeid manifestRevision, int changelogRevisionIndex) {
				if (manifestRev != manifestRevisionIndex) {
					throw new IllegalStateException(String.valueOf(manifestRevisionIndex));
				}
				return true;
			}
		});
		return out.toByteArray();
	}
}