view src/org/tmatesoft/hg/internal/ForwardAnnotateInspector.java @ 678:8625cba0a5a8

Towards better blame of merge revisions: refactor merge handling strategy
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 19 Jul 2013 15:36:29 +0200
parents 1c49c0cee540
children 58a6900f845d
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.internal;

import org.tmatesoft.hg.core.HgAnnotateCommand.Inspector;
import org.tmatesoft.hg.core.HgAnnotateCommand.LineInfo;
import org.tmatesoft.hg.core.HgBlameInspector;
import org.tmatesoft.hg.core.HgCallbackTargetException;
import org.tmatesoft.hg.core.HgDiffCommand;
import org.tmatesoft.hg.core.HgException;
import org.tmatesoft.hg.core.HgIterateDirection;
import org.tmatesoft.hg.repo.HgLookup;
import org.tmatesoft.hg.repo.HgRepository;
import org.tmatesoft.hg.util.CancelSupport;
import org.tmatesoft.hg.util.CancelledException;
import org.tmatesoft.hg.util.ProgressSupport;

/**
 * Annotate file history iterating from parents to children
 * 
 * At the moment, doesn't handle start from any revision but 0
 * 
 * (+) May report annotate for any revision in the visited range.
 * 
 * @see ReverseAnnotateInspector
 * @author Artem Tikhomirov
 * @author TMate Software Ltd.
 */
public class ForwardAnnotateInspector implements HgBlameInspector, HgBlameInspector.RevisionDescriptor.Recipient {
	final IntMap<IntSliceSeq> all = new IntMap<IntSliceSeq>(100);
	// revision->map(lineNumber->lineContent)
	private final IntMap<IntMap<byte[]>> lineContent = new IntMap<IntMap<byte[]>>(100);
	private IntSliceSeq current;
	private RevisionDescriptor revDescriptor;

	/**
	 * @return desired order of iteration for diff
	 */
	public HgIterateDirection iterateDirection() {
		return HgIterateDirection.OldToNew;
	}

	public void report(int revision, Inspector insp, ProgressSupport progress, CancelSupport cancel) throws HgCallbackTargetException, CancelledException {
		int totalLines = 0;
		for (IntTuple t : all.get(revision)) {
			totalLines += t.at(0);
		}
		progress.start(totalLines);
		LineImpl li = new LineImpl();
		int line = 1;
		for (IntTuple t : all.get(revision)) {
			IntMap<byte[]> revLines = lineContent.get(t.at(1));
			for (int i = 0, x = t.at(0); i < x; i++) {
				final int lineInRev = t.at(2) + i;
				final byte[] lc = revLines.get(lineInRev);
				li.init(line++, lineInRev+1, t.at(1), lc);
				insp.next(li);
				progress.worked(1);
				cancel.checkCancelled();
			}
		}
		progress.done();
	}

	public void start(RevisionDescriptor rd) throws HgCallbackTargetException {
		all.put(rd.targetChangesetIndex(), current = new IntSliceSeq(3));
		revDescriptor = rd;
	}

	public void done(RevisionDescriptor rd) throws HgCallbackTargetException {
		revDescriptor = null;
	}

	public void same(EqualBlock block) throws HgCallbackTargetException {
		copyBlock(block.originChangesetIndex(), block.originStart(), block.length());
	}

	public void added(AddBlock block) throws HgCallbackTargetException {
		if (revDescriptor.isMerge() && block.originChangesetIndex() == revDescriptor.mergeChangesetIndex()) {
			copyBlock(block.originChangesetIndex(), block.insertedAt(), block.totalAddedLines());
			return;
		}
		BlockData addedLines = block.addedLines();
		IntMap<byte[]> revLines = lineContent.get(block.targetChangesetIndex());
		if (revLines == null) {
			lineContent.put(block.targetChangesetIndex(), revLines = new IntMap<byte[]>(block.totalAddedLines()));
		}
		for (int i = 0; i < block.totalAddedLines(); i++) {
			revLines.put(block.firstAddedLine() + i, addedLines.elementAt(i).asArray());
		}
		current.add(block.totalAddedLines(), block.targetChangesetIndex(), block.firstAddedLine());
	}

	public void changed(ChangeBlock block) throws HgCallbackTargetException {
		added(block);
	}

	public void deleted(DeleteBlock block) throws HgCallbackTargetException {
	}
	
	private void copyBlock(int originChangesetIndex, int blockStart, int length) {
		IntSliceSeq origin = all.get(originChangesetIndex);
		assert origin != null; // shall visit parents before came to this child
		int originPos = 0;
		int targetBlockLen = length;
		for (IntTuple t : origin) {
			int originBlockLen = t.at(0);
			int originBlockEnd = originPos + originBlockLen;
			if (originBlockEnd > blockStart) {
				// part of origin block from blockStart up to originBlockEnd, but not more
				// than size of the block (when blockStart is out of block start, i.e. < originPos)
				int originBlockOverlap = Math.min(originBlockLen, originBlockEnd - blockStart);
				assert originBlockOverlap > 0;
				// eat as much as there's left in the block being copied
				int originBlockConsumed = Math.min(originBlockOverlap, targetBlockLen);
				int originBlockLine = t.at(2);
				if (originPos < blockStart) {
					originBlockLine += originBlockLen-originBlockOverlap;
				}
				// copy fragment of original block;
				current.add(originBlockConsumed, t.at(1), originBlockLine);
				targetBlockLen -= originBlockConsumed;
				if (targetBlockLen == 0) {
					break;
				}
			}
			originPos += originBlockLen;
		}
	}


	public static void main(String[] args) throws HgCallbackTargetException, CancelledException, HgException {
		HgRepository repo = new HgLookup().detect("/home/artem/hg/junit-test-repos/test-annotate/");
		HgDiffCommand cmd = new HgDiffCommand(repo);
		cmd.file(repo.getFileNode("file1")).order(HgIterateDirection.OldToNew);
		final int cset = 8;
		cmd.range(0, cset);
		final ForwardAnnotateInspector c2 = new ForwardAnnotateInspector();
		cmd.executeAnnotate(c2);
		for (IntTuple t : c2.all.get(cset)) {
			System.out.printf("Block %d lines from revision %d (starts with line %d in the origin)\n", t.at(0), t.at(1), 1+t.at(2));
		}
		c2.report(cset, new Inspector() {
			
			public void next(LineInfo lineInfo) throws HgCallbackTargetException {
				System.out.printf("%3d:%3d: %s", lineInfo.getChangesetIndex(), lineInfo.getOriginLineNumber(), new String(lineInfo.getContent()));
			}
		}, ProgressSupport.Factory.get(null), CancelSupport.Factory.get(null));
	}
}