Mercurial > jhg
changeset 603:707b5c7c6fa4
Refactor HgBlameFacility: relevant action methods moved to proper home (HgDataFile), as facility doesn't provide anything but packaging of relevant methods/interfaces
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Mon, 06 May 2013 18:29:57 +0200 (2013-05-06) |
parents | e3717fc7d26f |
children | c3505001a42a |
files | src/org/tmatesoft/hg/core/HgAnnotateCommand.java src/org/tmatesoft/hg/internal/BlameHelper.java src/org/tmatesoft/hg/internal/FileAnnotation.java src/org/tmatesoft/hg/repo/HgBlameFacility.java src/org/tmatesoft/hg/repo/HgBlameInspector.java src/org/tmatesoft/hg/repo/HgDataFile.java test/org/tmatesoft/hg/test/TestBlame.java |
diffstat | 7 files changed, 321 insertions(+), 352 deletions(-) [+] |
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgAnnotateCommand.java Mon May 06 17:11:29 2013 +0200 +++ b/src/org/tmatesoft/hg/core/HgAnnotateCommand.java Mon May 06 18:29:57 2013 +0200 @@ -26,8 +26,7 @@ import org.tmatesoft.hg.internal.FileAnnotation; import org.tmatesoft.hg.internal.FileAnnotation.LineDescriptor; import org.tmatesoft.hg.internal.FileAnnotation.LineInspector; -import org.tmatesoft.hg.repo.HgBlameFacility.BlockData; -import org.tmatesoft.hg.repo.HgBlameFacility; +import org.tmatesoft.hg.repo.HgBlameInspector.BlockData; import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.CancelSupport; @@ -42,6 +41,7 @@ * * @author Artem Tikhomirov * @author TMate Software Ltd. + * @since 1.1 */ @Experimental(reason="Work in progress. Unstable API") public class HgAnnotateCommand extends HgAbstractCommand<HgAnnotateCommand> { @@ -118,8 +118,7 @@ final int changesetStart = followRename ? 0 : df.getChangesetRevisionIndex(0); Collector c = new Collector(cancellation); FileAnnotation fa = new FileAnnotation(c); - HgBlameFacility af = new HgBlameFacility(df); - af.annotate(changesetStart, annotateRevision.get(), fa, HgIterateDirection.NewToOld); + df.annotate(changesetStart, annotateRevision.get(), fa, HgIterateDirection.NewToOld); progress.worked(1); c.throwIfCancelled(); cancellation.checkCancelled();
--- a/src/org/tmatesoft/hg/internal/BlameHelper.java Mon May 06 17:11:29 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/BlameHelper.java Mon May 06 18:29:57 2013 +0200 @@ -24,14 +24,13 @@ import org.tmatesoft.hg.core.HgCallbackTargetException; import org.tmatesoft.hg.internal.DiffHelper.LineSequence; import org.tmatesoft.hg.internal.DiffHelper.LineSequence.ByteChain; -import org.tmatesoft.hg.repo.HgBlameFacility.Block; -import org.tmatesoft.hg.repo.HgBlameFacility.BlockData; -import org.tmatesoft.hg.repo.HgBlameFacility.ChangeBlock; -import org.tmatesoft.hg.repo.HgBlameFacility.EqualBlock; -import org.tmatesoft.hg.repo.HgBlameFacility.Inspector; -import org.tmatesoft.hg.repo.HgBlameFacility.RevisionDescriptor; -import org.tmatesoft.hg.repo.HgBlameFacility.RevisionDescriptor.Recipient; -import org.tmatesoft.hg.repo.HgBlameFacility; +import org.tmatesoft.hg.repo.HgBlameInspector.Block; +import org.tmatesoft.hg.repo.HgBlameInspector.BlockData; +import org.tmatesoft.hg.repo.HgBlameInspector.ChangeBlock; +import org.tmatesoft.hg.repo.HgBlameInspector.EqualBlock; +import org.tmatesoft.hg.repo.HgBlameInspector.RevisionDescriptor; +import org.tmatesoft.hg.repo.HgBlameInspector.RevisionDescriptor.Recipient; +import org.tmatesoft.hg.repo.HgBlameInspector; import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgInvalidStateException; import org.tmatesoft.hg.util.Adaptable; @@ -40,18 +39,18 @@ /** * Blame implementation - * @see HgBlameFacility + * @see HgBlameInspector * @author Artem Tikhomirov * @author TMate Software Ltd. */ public class BlameHelper { - private final Inspector insp; + private final HgBlameInspector insp; private FileLinesCache linesCache; // FIXME exposing internals (use of FileLinesCache through cons arg and #useFileUpTo) smells bad, refactor! - public BlameHelper(Inspector inspector, int cacheHint) { + public BlameHelper(HgBlameInspector inspector, int cacheHint) { insp = inspector; linesCache = new FileLinesCache(cacheHint); } @@ -192,7 +191,7 @@ } private static class BlameBlockInspector extends DiffHelper.DeltaInspector<LineSequence> { - private final Inspector insp; + private final HgBlameInspector insp; private final int csetOrigin; private final int csetTarget; private EqualBlocksCollector p2MergeCommon; @@ -201,7 +200,7 @@ private final AnnotateRev annotatedRevision; private HgCallbackTargetException error; - public BlameBlockInspector(HgDataFile df, int fileRevIndex, Inspector inspector, int originCset, int targetCset) { + public BlameBlockInspector(HgDataFile df, int fileRevIndex, HgBlameInspector inspector, int originCset, int targetCset) { assert inspector != null; insp = inspector; annotatedRevision = new AnnotateRev();
--- a/src/org/tmatesoft/hg/internal/FileAnnotation.java Mon May 06 17:11:29 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/FileAnnotation.java Mon May 06 18:29:57 2013 +0200 @@ -19,15 +19,10 @@ import org.tmatesoft.hg.core.HgCallbackTargetException; import org.tmatesoft.hg.core.HgIterateDirection; -import org.tmatesoft.hg.repo.HgBlameFacility; +import org.tmatesoft.hg.repo.HgBlameInspector; +import org.tmatesoft.hg.repo.HgBlameInspector.RevisionDescriptor; +import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgInvalidStateException; -import org.tmatesoft.hg.repo.HgBlameFacility.AddBlock; -import org.tmatesoft.hg.repo.HgBlameFacility.BlockData; -import org.tmatesoft.hg.repo.HgBlameFacility.ChangeBlock; -import org.tmatesoft.hg.repo.HgBlameFacility.DeleteBlock; -import org.tmatesoft.hg.repo.HgBlameFacility.EqualBlock; -import org.tmatesoft.hg.repo.HgBlameFacility.RevisionDescriptor; -import org.tmatesoft.hg.repo.HgDataFile; /** * Produce output like 'hg annotate' does @@ -35,7 +30,7 @@ * @author Artem Tikhomirov * @author TMate Software Ltd. */ -public class FileAnnotation implements HgBlameFacility.Inspector, RevisionDescriptor.Recipient { +public class FileAnnotation implements HgBlameInspector, RevisionDescriptor.Recipient { @Experimental(reason="The line-by-line inspector likely to become part of core/command API") @Callback @@ -58,8 +53,7 @@ return; } FileAnnotation fa = new FileAnnotation(insp); - HgBlameFacility af = new HgBlameFacility(df); - af.annotate(changelogRevisionIndex, fa, HgIterateDirection.NewToOld); + df.annotate(changelogRevisionIndex, fa, HgIterateDirection.NewToOld); } // keeps <startSeq1, startSeq2, len> of equal blocks, origin to target, from some previous step
--- a/src/org/tmatesoft/hg/repo/HgBlameFacility.java Mon May 06 17:11:29 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,296 +0,0 @@ -/* - * 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.repo; - -import static org.tmatesoft.hg.core.HgIterateDirection.OldToNew; -import static org.tmatesoft.hg.repo.HgInternals.wrongRevisionIndex; -import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; -import static org.tmatesoft.hg.repo.HgRepository.TIP; - -import org.tmatesoft.hg.core.HgCallbackTargetException; -import org.tmatesoft.hg.core.HgIterateDirection; -import org.tmatesoft.hg.core.Nodeid; -import org.tmatesoft.hg.internal.BlameHelper; -import org.tmatesoft.hg.internal.Callback; -import org.tmatesoft.hg.internal.Experimental; -import org.tmatesoft.hg.internal.FileHistory; -import org.tmatesoft.hg.internal.FileRevisionHistoryChunk; -import org.tmatesoft.hg.util.Adaptable; - -/** - * Facility with diff/annotate functionality. - * - * @author Artem Tikhomirov - * @author TMate Software Ltd. - */ -@Experimental(reason="Unstable API") -public final class HgBlameFacility { - private final HgDataFile df; - - public HgBlameFacility(HgDataFile file) { - if (file == null) { - throw new IllegalArgumentException(); - } - df = file; - } - - /** - * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2' - */ - public void diff(int clogRevIndex1, int clogRevIndex2, Inspector insp) throws HgCallbackTargetException { - // FIXME clogRevIndex1 and clogRevIndex2 may point to different files, need to decide whether to throw an exception - // or to attempt to look up correct file node (tricky) - int fileRevIndex1 = fileRevIndex(df, clogRevIndex1); - int fileRevIndex2 = fileRevIndex(df, clogRevIndex2); - BlameHelper bh = new BlameHelper(insp, 5); - bh.useFileUpTo(df, clogRevIndex2); - bh.diff(fileRevIndex1, clogRevIndex1, fileRevIndex2, clogRevIndex2); - } - - /** - * Walk file history up/down to revision at given changeset and report changes for each revision - */ - public void annotate(int changelogRevisionIndex, Inspector insp, HgIterateDirection iterateOrder) throws HgCallbackTargetException { - annotate(0, changelogRevisionIndex, insp, iterateOrder); - } - - /** - * Walk file history range and report changes for each revision - */ - public void annotate(int changelogRevIndexStart, int changelogRevIndexEnd, Inspector insp, HgIterateDirection iterateOrder) throws HgCallbackTargetException { - if (wrongRevisionIndex(changelogRevIndexStart) || wrongRevisionIndex(changelogRevIndexEnd)) { - throw new IllegalArgumentException(); - } - // Note, changelogRevIndexEnd may be TIP, while the code below doesn't tolerate constants - // - int lastRevision = df.getRepo().getChangelog().getLastRevision(); - if (changelogRevIndexEnd == TIP) { - changelogRevIndexEnd = lastRevision; - } - HgInternals.checkRevlogRange(changelogRevIndexStart, changelogRevIndexEnd, lastRevision); - if (!df.exists()) { - return; - } - FileHistory fileHistory = new FileHistory(df, changelogRevIndexStart, changelogRevIndexEnd); - fileHistory.build(); - BlameHelper bh = new BlameHelper(insp, 10); - for (FileRevisionHistoryChunk fhc : fileHistory.iterate(OldToNew)) { - // iteration order is not important here - bh.useFileUpTo(fhc.getFile(), fhc.getEndChangeset()); - } - int[] fileClogParentRevs = new int[2]; - int[] fileParentRevs = new int[2]; - for (FileRevisionHistoryChunk fhc : fileHistory.iterate(iterateOrder)) { - for (int fri : fhc.fileRevisions(iterateOrder)) { - int clogRevIndex = fhc.changeset(fri); - // the way we built fileHistory ensures we won't walk past [changelogRevIndexStart..changelogRevIndexEnd] - assert clogRevIndex >= changelogRevIndexStart; - assert clogRevIndex <= changelogRevIndexEnd; - fhc.fillFileParents(fri, fileParentRevs); - fhc.fillCsetParents(fri, fileClogParentRevs); - bh.annotateChange(fri, clogRevIndex, fileParentRevs, fileClogParentRevs); - } - } - } - - /** - * Annotates changes of the file against its parent(s). - * Unlike {@link #annotate(HgDataFile, int, Inspector, HgIterateDirection)}, doesn't - * walk file history, looks at the specified revision only. Handles both parents (if merge revision). - */ - public void annotateSingleRevision(int changelogRevisionIndex, Inspector insp) throws HgCallbackTargetException { - // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f - int fileRevIndex = fileRevIndex(df, changelogRevisionIndex); - int[] fileRevParents = new int[2]; - df.parents(fileRevIndex, fileRevParents, null, null); - if (changelogRevisionIndex == TIP) { - changelogRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex); - } - BlameHelper bh = new BlameHelper(insp, 5); - bh.useFileUpTo(df, changelogRevisionIndex); - int[] fileClogParentRevs = new int[2]; - fileClogParentRevs[0] = fileRevParents[0] == NO_REVISION ? NO_REVISION : df.getChangesetRevisionIndex(fileRevParents[0]); - fileClogParentRevs[1] = fileRevParents[1] == NO_REVISION ? NO_REVISION : df.getChangesetRevisionIndex(fileRevParents[1]); - bh.annotateChange(fileRevIndex, changelogRevisionIndex, fileRevParents, fileClogParentRevs); - } - - /** - * Client's sink for revision differences. - * - * When implemented, clients shall not expect new {@link Block blocks} instances in each call. - * - * In case more information about annotated revision is needed, inspector instances may supply - * {@link RevisionDescriptor.Recipient} through {@link Adaptable}. - */ - @Callback - public interface Inspector { - void same(EqualBlock block) throws HgCallbackTargetException; - void added(AddBlock block) throws HgCallbackTargetException; - void changed(ChangeBlock block) throws HgCallbackTargetException; - void deleted(DeleteBlock block) throws HgCallbackTargetException; - } - - /** - * Represents content of a block, either as a sequence of bytes or a - * sequence of smaller blocks (lines), if appropriate (according to usage context). - * - * This approach allows line-by-line access to content data along with complete byte sequence for the whole block, i.e. - * <pre> - * BlockData bd = addBlock.addedLines() - * // bd describes data from the addition completely. - * // elements of the BlockData are lines - * bd.elementCount() == addBlock.totalAddedLines(); - * // one cat obtain complete addition with - * byte[] everythingAdded = bd.asArray(); - * // or iterate line by line - * for (int i = 0; i < bd.elementCount(); i++) { - * byte[] lineContent = bd.elementAt(i); - * String line = new String(lineContent, fileEncodingCharset); - * } - * where bd.elementAt(0) is the line at index addBlock.firstAddedLine() - * </pre> - * - * LineData or ChunkData? - */ - public interface BlockData { - BlockData elementAt(int index); - int elementCount(); - byte[] asArray(); - } - - /** - * {@link Inspector} may optionally request extra information about revisions - * being inspected, denoting itself as a {@link RevisionDescriptor.Recipient}. This class - * provides complete information about file revision under annotation now. - */ - public interface RevisionDescriptor { - /** - * @return complete source of the diff origin, never <code>null</code> - */ - BlockData origin(); - /** - * @return complete source of the diff target, never <code>null</code> - */ - BlockData target(); - /** - * @return changeset revision index of original file, or {@link HgRepository#NO_REVISION} if it's the very first revision - */ - int originChangesetIndex(); - /** - * @return changeset revision index of the target file - */ - int targetChangesetIndex(); - /** - * @return <code>true</code> if this revision is merge - */ - boolean isMerge(); - /** - * @return changeset revision index of the second, merged parent - */ - int mergeChangesetIndex(); - /** - * @return revision index of the change in target file's revlog - */ - int fileRevisionIndex(); - - /** - * @return file object under blame (target file) - */ - HgDataFile file(); - - /** - * Implement to indicate interest in {@link RevisionDescriptor}. - * - * Note, instance of {@link RevisionDescriptor} is the same for - * {@link #start(RevisionDescriptor)} and {@link #done(RevisionDescriptor)} - * methods, and not necessarily a new one (i.e. <code>==</code>) for the next - * revision announced. - */ - @Callback - public interface Recipient { - /** - * Comes prior to any change {@link Block blocks} - */ - void start(RevisionDescriptor revisionDescription) throws HgCallbackTargetException; - /** - * Comes after all change {@link Block blocks} were dispatched - */ - void done(RevisionDescriptor revisionDescription) throws HgCallbackTargetException; - } - } - - /** - * Each change block comes from a single origin, blocks that are result of a merge - * have {@link #originChangesetIndex()} equal to {@link RevisionDescriptor#mergeChangesetIndex()}. - */ - public interface Block { - int originChangesetIndex(); - int targetChangesetIndex(); - } - - public interface EqualBlock extends Block { - int originStart(); - int targetStart(); - int length(); - BlockData content(); - } - - public interface AddBlock extends Block { - /** - * @return line index in the origin where this block is inserted - */ - int insertedAt(); - /** - * @return line index of the first added line in the target revision - */ - int firstAddedLine(); - /** - * @return number of added lines in this block - */ - int totalAddedLines(); - /** - * @return content of added lines - */ - BlockData addedLines(); - } - public interface DeleteBlock extends Block { - /** - * @return line index in the target revision were this deleted block would be - */ - int removedAt(); - /** - * @return line index of the first removed line in the original revision - */ - int firstRemovedLine(); - /** - * @return number of deleted lines in this block - */ - int totalRemovedLines(); - /** - * @return content of deleted lines - */ - BlockData removedLines(); - } - public interface ChangeBlock extends AddBlock, DeleteBlock { - } - - - private static int fileRevIndex(HgDataFile df, int csetRevIndex) { - Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath()); - return df.getRevisionIndex(fileRev); - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/repo/HgBlameInspector.java Mon May 06 18:29:57 2013 +0200 @@ -0,0 +1,188 @@ +/* + * 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.repo; + +import org.tmatesoft.hg.core.HgCallbackTargetException; +import org.tmatesoft.hg.internal.Callback; +import org.tmatesoft.hg.internal.Experimental; +import org.tmatesoft.hg.util.Adaptable; + +/** + * Client's sink for revision differences, diff/annotate functionality. + * + * When implemented, clients shall not expect new {@link Block blocks} instances in each call. + * + * In case more information about annotated revision is needed, inspector instances may supply + * {@link RevisionDescriptor.Recipient} through {@link Adaptable}. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + * @since 1.1 + */ +@Callback +@Experimental(reason="Unstable API") +public interface HgBlameInspector { + + void same(EqualBlock block) throws HgCallbackTargetException; + void added(AddBlock block) throws HgCallbackTargetException; + void changed(ChangeBlock block) throws HgCallbackTargetException; + void deleted(DeleteBlock block) throws HgCallbackTargetException; + + /** + * Represents content of a block, either as a sequence of bytes or a + * sequence of smaller blocks (lines), if appropriate (according to usage context). + * + * This approach allows line-by-line access to content data along with complete byte sequence for the whole block, i.e. + * <pre> + * BlockData bd = addBlock.addedLines() + * // bd describes data from the addition completely. + * // elements of the BlockData are lines + * bd.elementCount() == addBlock.totalAddedLines(); + * // one cat obtain complete addition with + * byte[] everythingAdded = bd.asArray(); + * // or iterate line by line + * for (int i = 0; i < bd.elementCount(); i++) { + * byte[] lineContent = bd.elementAt(i); + * String line = new String(lineContent, fileEncodingCharset); + * } + * where bd.elementAt(0) is the line at index addBlock.firstAddedLine() + * </pre> + * + * LineData or ChunkData? + */ + public interface BlockData { + BlockData elementAt(int index); + int elementCount(); + byte[] asArray(); + } + + /** + * {@link HgBlameInspector} may optionally request extra information about revisions + * being inspected, denoting itself as a {@link RevisionDescriptor.Recipient}. This class + * provides complete information about file revision under annotation now. + */ + public interface RevisionDescriptor { + /** + * @return complete source of the diff origin, never <code>null</code> + */ + BlockData origin(); + /** + * @return complete source of the diff target, never <code>null</code> + */ + BlockData target(); + /** + * @return changeset revision index of original file, or {@link HgRepository#NO_REVISION} if it's the very first revision + */ + int originChangesetIndex(); + /** + * @return changeset revision index of the target file + */ + int targetChangesetIndex(); + /** + * @return <code>true</code> if this revision is merge + */ + boolean isMerge(); + /** + * @return changeset revision index of the second, merged parent + */ + int mergeChangesetIndex(); + /** + * @return revision index of the change in target file's revlog + */ + int fileRevisionIndex(); + + /** + * @return file object under blame (target file) + */ + HgDataFile file(); + + /** + * Implement to indicate interest in {@link RevisionDescriptor}. + * + * Note, instance of {@link RevisionDescriptor} is the same for + * {@link #start(RevisionDescriptor)} and {@link #done(RevisionDescriptor)} + * methods, and not necessarily a new one (i.e. <code>==</code>) for the next + * revision announced. + */ + @Callback + public interface Recipient { + /** + * Comes prior to any change {@link Block blocks} + */ + void start(RevisionDescriptor revisionDescription) throws HgCallbackTargetException; + /** + * Comes after all change {@link Block blocks} were dispatched + */ + void done(RevisionDescriptor revisionDescription) throws HgCallbackTargetException; + } + } + + /** + * Each change block comes from a single origin, blocks that are result of a merge + * have {@link #originChangesetIndex()} equal to {@link RevisionDescriptor#mergeChangesetIndex()}. + */ + public interface Block { + int originChangesetIndex(); + int targetChangesetIndex(); + } + + public interface EqualBlock extends Block { + int originStart(); + int targetStart(); + int length(); + BlockData content(); + } + + public interface AddBlock extends Block { + /** + * @return line index in the origin where this block is inserted + */ + int insertedAt(); + /** + * @return line index of the first added line in the target revision + */ + int firstAddedLine(); + /** + * @return number of added lines in this block + */ + int totalAddedLines(); + /** + * @return content of added lines + */ + BlockData addedLines(); + } + public interface DeleteBlock extends Block { + /** + * @return line index in the target revision were this deleted block would be + */ + int removedAt(); + /** + * @return line index of the first removed line in the original revision + */ + int firstRemovedLine(); + /** + * @return number of deleted lines in this block + */ + int totalRemovedLines(); + /** + * @return content of deleted lines + */ + BlockData removedLines(); + } + public interface ChangeBlock extends AddBlock, DeleteBlock { + } +}
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java Mon May 06 17:11:29 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Mon May 06 18:29:57 2013 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2012 TMate Software Ltd + * 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 @@ -16,6 +16,7 @@ */ package org.tmatesoft.hg.repo; +import static org.tmatesoft.hg.core.HgIterateDirection.OldToNew; import static org.tmatesoft.hg.repo.HgInternals.wrongRevisionIndex; import static org.tmatesoft.hg.repo.HgRepository.*; import static org.tmatesoft.hg.util.LogFacility.Severity.Info; @@ -28,14 +29,20 @@ import java.nio.channels.FileChannel; import java.util.Arrays; +import org.tmatesoft.hg.core.HgCallbackTargetException; import org.tmatesoft.hg.core.HgChangesetFileSneaker; +import org.tmatesoft.hg.core.HgIterateDirection; import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.internal.BlameHelper; import org.tmatesoft.hg.internal.DataAccess; +import org.tmatesoft.hg.internal.FileHistory; +import org.tmatesoft.hg.internal.FileRevisionHistoryChunk; import org.tmatesoft.hg.internal.FilterByteChannel; import org.tmatesoft.hg.internal.FilterDataAccess; import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.internal.Metadata; import org.tmatesoft.hg.internal.RevlogStream; +import org.tmatesoft.hg.repo.HgBlameInspector; import org.tmatesoft.hg.util.ByteChannel; import org.tmatesoft.hg.util.CancelSupport; import org.tmatesoft.hg.util.CancelledException; @@ -428,6 +435,86 @@ return getRepo().getManifest().getFileFlags(changesetRevIndex, getPath()); } + /** + * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2' + */ + public void diff(int clogRevIndex1, int clogRevIndex2, HgBlameInspector insp) throws HgCallbackTargetException { + // FIXME clogRevIndex1 and clogRevIndex2 may point to different files, need to decide whether to throw an exception + // or to attempt to look up correct file node (tricky) + int fileRevIndex1 = fileRevIndex(this, clogRevIndex1); + int fileRevIndex2 = fileRevIndex(this, clogRevIndex2); + BlameHelper bh = new BlameHelper(insp, 5); + bh.useFileUpTo(this, clogRevIndex2); + bh.diff(fileRevIndex1, clogRevIndex1, fileRevIndex2, clogRevIndex2); + } + + /** + * Walk file history up/down to revision at given changeset and report changes for each revision + */ + public void annotate(int changelogRevisionIndex, HgBlameInspector insp, HgIterateDirection iterateOrder) throws HgCallbackTargetException { + annotate(0, changelogRevisionIndex, insp, iterateOrder); + } + + /** + * Walk file history range and report changes for each revision + */ + public void annotate(int changelogRevIndexStart, int changelogRevIndexEnd, HgBlameInspector insp, HgIterateDirection iterateOrder) throws HgCallbackTargetException { + if (wrongRevisionIndex(changelogRevIndexStart) || wrongRevisionIndex(changelogRevIndexEnd)) { + throw new IllegalArgumentException(); + } + // Note, changelogRevIndexEnd may be TIP, while the code below doesn't tolerate constants + // + int lastRevision = getRepo().getChangelog().getLastRevision(); + if (changelogRevIndexEnd == TIP) { + changelogRevIndexEnd = lastRevision; + } + HgInternals.checkRevlogRange(changelogRevIndexStart, changelogRevIndexEnd, lastRevision); + if (!exists()) { + return; + } + FileHistory fileHistory = new FileHistory(this, changelogRevIndexStart, changelogRevIndexEnd); + fileHistory.build(); + BlameHelper bh = new BlameHelper(insp, 10); + for (FileRevisionHistoryChunk fhc : fileHistory.iterate(OldToNew)) { + // iteration order is not important here + bh.useFileUpTo(fhc.getFile(), fhc.getEndChangeset()); + } + int[] fileClogParentRevs = new int[2]; + int[] fileParentRevs = new int[2]; + for (FileRevisionHistoryChunk fhc : fileHistory.iterate(iterateOrder)) { + for (int fri : fhc.fileRevisions(iterateOrder)) { + int clogRevIndex = fhc.changeset(fri); + // the way we built fileHistory ensures we won't walk past [changelogRevIndexStart..changelogRevIndexEnd] + assert clogRevIndex >= changelogRevIndexStart; + assert clogRevIndex <= changelogRevIndexEnd; + fhc.fillFileParents(fri, fileParentRevs); + fhc.fillCsetParents(fri, fileClogParentRevs); + bh.annotateChange(fri, clogRevIndex, fileParentRevs, fileClogParentRevs); + } + } + } + + /** + * Annotates changes of the file against its parent(s). + * Unlike {@link #annotate(HgDataFile, int, Inspector, HgIterateDirection)}, doesn't + * walk file history, looks at the specified revision only. Handles both parents (if merge revision). + */ + public void annotateSingleRevision(int changelogRevisionIndex, HgBlameInspector insp) throws HgCallbackTargetException { + // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f + int fileRevIndex = fileRevIndex(this, changelogRevisionIndex); + int[] fileRevParents = new int[2]; + parents(fileRevIndex, fileRevParents, null, null); + if (changelogRevisionIndex == TIP) { + changelogRevisionIndex = getChangesetRevisionIndex(fileRevIndex); + } + BlameHelper bh = new BlameHelper(insp, 5); + bh.useFileUpTo(this, changelogRevisionIndex); + int[] fileClogParentRevs = new int[2]; + fileClogParentRevs[0] = fileRevParents[0] == NO_REVISION ? NO_REVISION : getChangesetRevisionIndex(fileRevParents[0]); + fileClogParentRevs[1] = fileRevParents[1] == NO_REVISION ? NO_REVISION : getChangesetRevisionIndex(fileRevParents[1]); + bh.annotateChange(fileRevIndex, changelogRevisionIndex, fileRevParents, fileClogParentRevs); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(getClass().getSimpleName()); @@ -445,6 +532,13 @@ RevlogStream.Inspector insp = new MetadataInspector(metadata, null); super.content.iterate(localRev, localRev, true, insp); } + + + private static int fileRevIndex(HgDataFile df, int csetRevIndex) { + Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath()); + return df.getRevisionIndex(fileRev); + } + private static class MetadataInspector extends ErrorHandlingInspector implements RevlogStream.Inspector { private final Metadata metadata;
--- a/test/org/tmatesoft/hg/test/TestBlame.java Mon May 06 17:11:29 2013 +0200 +++ b/test/org/tmatesoft/hg/test/TestBlame.java Mon May 06 18:29:57 2013 +0200 @@ -49,13 +49,8 @@ import org.tmatesoft.hg.internal.FileAnnotation.LineDescriptor; import org.tmatesoft.hg.internal.FileAnnotation.LineInspector; import org.tmatesoft.hg.internal.IntVector; -import org.tmatesoft.hg.repo.HgBlameFacility; -import org.tmatesoft.hg.repo.HgBlameFacility.AddBlock; -import org.tmatesoft.hg.repo.HgBlameFacility.Block; -import org.tmatesoft.hg.repo.HgBlameFacility.BlockData; -import org.tmatesoft.hg.repo.HgBlameFacility.ChangeBlock; -import org.tmatesoft.hg.repo.HgBlameFacility.DeleteBlock; -import org.tmatesoft.hg.repo.HgBlameFacility.EqualBlock; +import org.tmatesoft.hg.repo.HgBlameInspector; +import org.tmatesoft.hg.repo.HgBlameInspector.BlockData; import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgLookup; import org.tmatesoft.hg.repo.HgRepository; @@ -79,7 +74,7 @@ final int checkChangeset = 539; HgDataFile df = repo.getFileNode(fname); ByteArrayOutputStream bos = new ByteArrayOutputStream(); - new HgBlameFacility(df).annotateSingleRevision(checkChangeset, new DiffOutInspector(new PrintStream(bos))); + df.annotateSingleRevision(checkChangeset, new DiffOutInspector(new PrintStream(bos))); LineGrepOutputParser gp = new LineGrepOutputParser("^@@.+"); ExecHelper eh = new ExecHelper(gp, null); eh.run("hg", "diff", "-c", String.valueOf(checkChangeset), "-U", "0", fname); @@ -135,10 +130,9 @@ public void testComplexHistoryAnnotate() throws Exception { HgRepository repo = Configuration.get().find("test-annotate"); HgDataFile df = repo.getFileNode("file1"); - HgBlameFacility af = new HgBlameFacility(df); ByteArrayOutputStream bos = new ByteArrayOutputStream(); DiffOutInspector dump = new DiffOutInspector(new PrintStream(bos)); - af.annotate(TIP, dump, HgIterateDirection.OldToNew); + df.annotate(TIP, dump, HgIterateDirection.OldToNew); LinkedList<String> apiResult = new LinkedList<String>(Arrays.asList(splitLines(bos.toString()))); /* @@ -196,7 +190,6 @@ HgRepository repo = Configuration.get().find("test-annotate2"); HgDataFile df = repo.getFileNode("file1b.txt"); // rev3: file1 -> file1a, rev7: file1a -> file1b, tip: rev10 - HgBlameFacility bf = new HgBlameFacility(df); DiffOutInspector insp = new DiffOutInspector(new PrintStream(new OutputStream() { @Override public void write(int b) throws IOException { @@ -207,19 +200,19 @@ // earlier than rev2 shall be reported as new from change3 int[] change_2_8_new2old = new int[] {4, 6, 3, 4, -1, 3}; int[] change_2_8_old2new = new int[] {-1, 3, 3, 4, 4, 6 }; - bf.annotate(2, 8, insp, NewToOld); + df.annotate(2, 8, insp, NewToOld); Assert.assertArrayEquals(change_2_8_new2old, insp.getReportedRevisionPairs()); insp.reset(); - bf.annotate(2, 8, insp, OldToNew); + df.annotate(2, 8, insp, OldToNew); Assert.assertArrayEquals(change_2_8_old2new, insp.getReportedRevisionPairs()); // same as 2 to 8, with addition of rev9 changes rev7 (rev6 to rev7 didn't change content, only name) int[] change_3_9_new2old = new int[] {7, 9, 4, 6, 3, 4, -1, 3 }; int[] change_3_9_old2new = new int[] {-1, 3, 3, 4, 4, 6, 7, 9 }; insp.reset(); - bf.annotate(3, 9, insp, NewToOld); + df.annotate(3, 9, insp, NewToOld); Assert.assertArrayEquals(change_3_9_new2old, insp.getReportedRevisionPairs()); insp.reset(); - bf.annotate(3, 9, insp, OldToNew); + df.annotate(3, 9, insp, OldToNew); Assert.assertArrayEquals(change_3_9_old2new, insp.getReportedRevisionPairs()); } @@ -297,18 +290,17 @@ final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java"; final int checkChangeset = 539; HgDataFile df = repo.getFileNode(fname); - HgBlameFacility af = new HgBlameFacility(df); DiffOutInspector dump = new DiffOutInspector(System.out); System.out.println("541 -> 543"); - af.annotateSingleRevision(543, dump); + df.annotateSingleRevision(543, dump); System.out.println("539 -> 541"); - af.annotateSingleRevision(541, dump); + df.annotateSingleRevision(541, dump); System.out.println("536 -> 539"); - af.annotateSingleRevision(checkChangeset, dump); + df.annotateSingleRevision(checkChangeset, dump); System.out.println("531 -> 536"); - af.annotateSingleRevision(536, dump); + df.annotateSingleRevision(536, dump); System.out.println(" -1 -> 531"); - af.annotateSingleRevision(531, dump); + df.annotateSingleRevision(531, dump); FileAnnotateInspector fai = new FileAnnotateInspector(); FileAnnotation.annotate(df, 541, fai); @@ -322,7 +314,6 @@ final String fname = "src/org/tmatesoft/hg/repo/HgManifest.java"; final int checkChangeset = 415; HgDataFile df = repo.getFileNode(fname); - HgBlameFacility af = new HgBlameFacility(df); DiffOutInspector dump = new DiffOutInspector(System.out); // System.out.println("413 -> 415"); // af.diff(df, 413, 415, dump); @@ -332,16 +323,15 @@ // dump.needRevisions(true); // af.annotateChange(df, checkChangeset, dump); dump.needRevisions(true); - af.annotate(checkChangeset, dump, HgIterateDirection.OldToNew); + df.annotate(checkChangeset, dump, HgIterateDirection.OldToNew); } private void ccc() throws Throwable { HgRepository repo = new HgLookup().detect("/home/artem/hg/hgtest-annotate-merge/"); HgDataFile df = repo.getFileNode("file.txt"); - HgBlameFacility af = new HgBlameFacility(df); DiffOutInspector dump = new DiffOutInspector(System.out); dump.needRevisions(true); - af.annotate(8, dump, HgIterateDirection.NewToOld); + df.annotate(8, dump, HgIterateDirection.NewToOld); // af.annotateSingleRevision(df, 113, dump); // System.out.println(); // af.annotate(df, TIP, new LineDumpInspector(true), HgIterateDirection.NewToOld); @@ -372,7 +362,7 @@ tt.ccc(); } - private static class DiffOutInspector implements HgBlameFacility.Inspector { + private static class DiffOutInspector implements HgBlameInspector { private final PrintStream out; private boolean dumpRevs; private IntVector reportedRevisionPairs = new IntVector(); @@ -490,7 +480,8 @@ } } - private static class LineDumpInspector implements HgBlameFacility.Inspector { + @SuppressWarnings("unused") + private static class LineDumpInspector implements HgBlameInspector { private final boolean lineByLine;