tikhomirov@566: /* tikhomirov@566: * Copyright (c) 2013 TMate Software Ltd tikhomirov@566: * tikhomirov@566: * This program is free software; you can redistribute it and/or modify tikhomirov@566: * it under the terms of the GNU General Public License as published by tikhomirov@566: * the Free Software Foundation; version 2 of the License. tikhomirov@566: * tikhomirov@566: * This program is distributed in the hope that it will be useful, tikhomirov@566: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@566: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@566: * GNU General Public License for more details. tikhomirov@566: * tikhomirov@566: * For information on how to redistribute this software under tikhomirov@566: * the terms of a license other than GNU General Public License tikhomirov@566: * contact TMate Software at support@hg4j.com tikhomirov@566: */ tikhomirov@566: package org.tmatesoft.hg.core; tikhomirov@566: tikhomirov@566: import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; tikhomirov@566: tikhomirov@566: import java.util.Arrays; tikhomirov@566: tikhomirov@566: import org.tmatesoft.hg.internal.Callback; tikhomirov@566: import org.tmatesoft.hg.internal.CsetParamKeeper; tikhomirov@566: import org.tmatesoft.hg.internal.Experimental; tikhomirov@566: import org.tmatesoft.hg.internal.FileAnnotation; tikhomirov@566: import org.tmatesoft.hg.internal.FileAnnotation.LineDescriptor; tikhomirov@566: import org.tmatesoft.hg.internal.FileAnnotation.LineInspector; tikhomirov@566: import org.tmatesoft.hg.repo.HgBlameFacility.BlockData; tikhomirov@569: import org.tmatesoft.hg.repo.HgBlameFacility; tikhomirov@566: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@566: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@581: import org.tmatesoft.hg.util.CancelSupport; tikhomirov@566: import org.tmatesoft.hg.util.CancelledException; tikhomirov@569: import org.tmatesoft.hg.util.Path; tikhomirov@581: import org.tmatesoft.hg.util.ProgressSupport; tikhomirov@566: tikhomirov@566: /** tikhomirov@566: * WORK IN PROGRESS. UNSTABLE API tikhomirov@566: * tikhomirov@566: * 'hg annotate' counterpart, report origin revision and file line-by-line tikhomirov@566: * tikhomirov@566: * @author Artem Tikhomirov tikhomirov@566: * @author TMate Software Ltd. tikhomirov@566: */ tikhomirov@566: @Experimental(reason="Work in progress. Unstable API") tikhomirov@566: public class HgAnnotateCommand extends HgAbstractCommand { tikhomirov@566: tikhomirov@566: private final HgRepository repo; tikhomirov@566: private final CsetParamKeeper annotateRevision; tikhomirov@569: private Path file; tikhomirov@569: private boolean followRename; tikhomirov@566: tikhomirov@566: public HgAnnotateCommand(HgRepository hgRepo) { tikhomirov@566: repo = hgRepo; tikhomirov@566: annotateRevision = new CsetParamKeeper(repo); tikhomirov@566: annotateRevision.doSet(HgRepository.TIP); tikhomirov@566: } tikhomirov@566: tikhomirov@566: public HgAnnotateCommand changeset(Nodeid nodeid) throws HgBadArgumentException { tikhomirov@566: annotateRevision.set(nodeid); tikhomirov@566: return this; tikhomirov@566: } tikhomirov@566: tikhomirov@566: public HgAnnotateCommand changeset(int changelogRevIndex) throws HgBadArgumentException { tikhomirov@566: annotateRevision.set(changelogRevIndex); tikhomirov@566: return this; tikhomirov@566: } tikhomirov@566: tikhomirov@569: /** tikhomirov@569: * Select file to annotate, origin of renamed/copied file would be followed, too. tikhomirov@569: * tikhomirov@569: * @param filePath path relative to repository root tikhomirov@569: * @return this for convenience tikhomirov@569: */ tikhomirov@569: public HgAnnotateCommand file(Path filePath) { tikhomirov@569: return file(filePath, true); tikhomirov@569: } tikhomirov@569: tikhomirov@569: /** tikhomirov@569: * Select file to annotate. tikhomirov@569: * tikhomirov@569: * @param filePath path relative to repository root tikhomirov@569: * @param followCopyRename true to follow copies/renames. tikhomirov@569: * @return this for convenience tikhomirov@569: */ tikhomirov@569: public HgAnnotateCommand file(Path filePath, boolean followCopyRename) { tikhomirov@569: file = filePath; tikhomirov@569: followRename = followCopyRename; tikhomirov@566: return this; tikhomirov@566: } tikhomirov@566: tikhomirov@566: // TODO [1.1] set encoding and provide String line content from LineInfo tikhomirov@566: tikhomirov@586: /** tikhomirov@586: * Annotate selected file tikhomirov@586: * tikhomirov@586: * @param inspector tikhomirov@586: * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state tikhomirov@586: * @throws HgCallbackTargetException tikhomirov@586: * @throws CancelledException if execution of the command was cancelled tikhomirov@586: */ tikhomirov@566: public void execute(Inspector inspector) throws HgException, HgCallbackTargetException, CancelledException { tikhomirov@566: if (inspector == null) { tikhomirov@566: throw new IllegalArgumentException(); tikhomirov@566: } tikhomirov@566: if (file == null) { tikhomirov@566: throw new HgBadArgumentException("Command needs file argument", null); tikhomirov@566: } tikhomirov@581: final ProgressSupport progress = getProgressSupport(inspector); tikhomirov@581: final CancelSupport cancellation = getCancelSupport(inspector, true); tikhomirov@581: cancellation.checkCancelled(); tikhomirov@581: progress.start(2); tikhomirov@569: HgDataFile df = repo.getFileNode(file); tikhomirov@569: if (!df.exists()) { tikhomirov@569: return; tikhomirov@569: } tikhomirov@569: final int changesetStart = followRename ? 0 : df.getChangesetRevisionIndex(0); tikhomirov@582: Collector c = new Collector(cancellation); tikhomirov@569: FileAnnotation fa = new FileAnnotation(c); tikhomirov@569: HgBlameFacility af = new HgBlameFacility(df); tikhomirov@569: af.annotate(changesetStart, annotateRevision.get(), fa, HgIterateDirection.NewToOld); tikhomirov@581: progress.worked(1); tikhomirov@582: c.throwIfCancelled(); tikhomirov@582: cancellation.checkCancelled(); tikhomirov@581: ProgressSupport.Sub subProgress = new ProgressSupport.Sub(progress, 1); tikhomirov@584: subProgress.start(c.lineRevisions.length); tikhomirov@566: LineImpl li = new LineImpl(); tikhomirov@566: for (int i = 0; i < c.lineRevisions.length; i++) { tikhomirov@566: li.init(i+1, c.lineRevisions[i], c.line(i)); tikhomirov@566: inspector.next(li); tikhomirov@581: subProgress.worked(1); tikhomirov@581: cancellation.checkCancelled(); tikhomirov@566: } tikhomirov@581: subProgress.done(); tikhomirov@581: progress.done(); tikhomirov@566: } tikhomirov@566: tikhomirov@566: /** tikhomirov@566: * Callback to receive annotated lines tikhomirov@566: */ tikhomirov@566: @Callback tikhomirov@566: public interface Inspector { tikhomirov@569: // start(FileDescriptor) throws HgCallbackTargetException; tikhomirov@569: void next(LineInfo lineInfo) throws HgCallbackTargetException; tikhomirov@569: // end(FileDescriptor) throws HgCallbackTargetException; tikhomirov@566: } tikhomirov@566: tikhomirov@566: /** tikhomirov@566: * Describes a line reported through {@link Inspector#next(LineInfo)} tikhomirov@566: * tikhomirov@566: * Clients shall not implement this interface tikhomirov@566: */ tikhomirov@566: public interface LineInfo { tikhomirov@566: int getLineNumber(); tikhomirov@566: int getChangesetIndex(); tikhomirov@566: byte[] getContent(); tikhomirov@566: } tikhomirov@566: tikhomirov@566: // FIXME there's no need in FileAnnotation.LineInspector, merge it here tikhomirov@566: private static class Collector implements LineInspector { tikhomirov@566: private int[] lineRevisions; tikhomirov@566: private byte[][] lines; tikhomirov@582: private final CancelSupport cancelSupport; tikhomirov@582: private CancelledException cancelEx; tikhomirov@566: tikhomirov@582: Collector(CancelSupport cancellation) { tikhomirov@582: cancelSupport = cancellation; tikhomirov@566: } tikhomirov@566: tikhomirov@566: public void line(int lineNumber, int changesetRevIndex, BlockData lineContent, LineDescriptor ld) { tikhomirov@582: if (cancelEx != null) { tikhomirov@582: return; tikhomirov@582: } tikhomirov@566: if (lineRevisions == null) { tikhomirov@566: lineRevisions = new int [ld.totalLines()]; tikhomirov@566: Arrays.fill(lineRevisions, NO_REVISION); tikhomirov@566: lines = new byte[ld.totalLines()][]; tikhomirov@566: } tikhomirov@566: lineRevisions[lineNumber] = changesetRevIndex; tikhomirov@566: lines[lineNumber] = lineContent.asArray(); tikhomirov@582: try { tikhomirov@582: cancelSupport.checkCancelled(); tikhomirov@582: } catch (CancelledException ex) { tikhomirov@582: cancelEx = ex; tikhomirov@582: } tikhomirov@566: } tikhomirov@566: tikhomirov@566: public byte[] line(int i) { tikhomirov@566: return lines[i]; tikhomirov@566: } tikhomirov@582: tikhomirov@582: public void throwIfCancelled() throws CancelledException { tikhomirov@582: if (cancelEx != null) { tikhomirov@582: throw cancelEx; tikhomirov@582: } tikhomirov@582: } tikhomirov@566: } tikhomirov@566: tikhomirov@566: tikhomirov@566: private static class LineImpl implements LineInfo { tikhomirov@566: private int ln; tikhomirov@566: private int rev; tikhomirov@566: private byte[] content; tikhomirov@566: tikhomirov@566: void init(int line, int csetRev, byte[] cnt) { tikhomirov@566: ln = line; tikhomirov@566: rev = csetRev; tikhomirov@566: content = cnt; tikhomirov@566: } tikhomirov@566: tikhomirov@566: public int getLineNumber() { tikhomirov@566: return ln; tikhomirov@566: } tikhomirov@566: tikhomirov@566: public int getChangesetIndex() { tikhomirov@566: return rev; tikhomirov@566: } tikhomirov@566: tikhomirov@566: public byte[] getContent() { tikhomirov@566: return content; tikhomirov@566: } tikhomirov@566: } tikhomirov@566: }