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 org.tmatesoft.hg.internal.Callback; tikhomirov@566: import org.tmatesoft.hg.internal.CsetParamKeeper; tikhomirov@676: import org.tmatesoft.hg.internal.ForwardAnnotateInspector; tikhomirov@566: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@566: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@628: import org.tmatesoft.hg.repo.HgRuntimeException; 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: * 'hg annotate' counterpart, report origin revision and file line-by-line tikhomirov@566: * tikhomirov@613: * @since 1.1 tikhomirov@566: * @author Artem Tikhomirov tikhomirov@566: * @author TMate Software Ltd. tikhomirov@566: */ 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@680: tikhomirov@680: /** tikhomirov@680: * Select file to annotate, tikhomirov@680: * @param fileNode repository file to annotate tikhomirov@680: * @param followCopyRename true to follow copies/renames. tikhomirov@680: * @return this for convenience tikhomirov@680: */ tikhomirov@680: public HgAnnotateCommand file(HgDataFile fileNode, boolean followCopyRename) { tikhomirov@680: return file(fileNode.getPath(), followCopyRename); tikhomirov@680: } tikhomirov@680: tikhomirov@626: // TODO [post-1.1] set encoding and provide String line content from LineInfo tikhomirov@676: // TODO FWIW: diff algorithms: http://bramcohen.livejournal.com/73318.html 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@676: progress.start(200); tikhomirov@628: try { tikhomirov@628: HgDataFile df = repo.getFileNode(file); tikhomirov@628: if (!df.exists()) { tikhomirov@628: return; tikhomirov@628: } tikhomirov@628: final int changesetStart = followRename ? 0 : df.getChangesetRevisionIndex(0); tikhomirov@676: final int annotateRevIndex = annotateRevision.get(); tikhomirov@676: HgDiffCommand cmd = new HgDiffCommand(repo).file(df); tikhomirov@676: cmd.range(changesetStart, annotateRevIndex); tikhomirov@676: cmd.set(cancellation); tikhomirov@676: cmd.set(new ProgressSupport.Sub(progress, 100)); tikhomirov@676: // tikhomirov@676: // ReverseAnnotateInspector ai = new ReverseAnnotateInspector(); tikhomirov@676: ForwardAnnotateInspector ai = new ForwardAnnotateInspector(); tikhomirov@676: cmd.order(ai.iterateDirection()); tikhomirov@676: // tikhomirov@676: cmd.executeAnnotate(ai); tikhomirov@628: cancellation.checkCancelled(); tikhomirov@676: ai.report(annotateRevIndex, inspector, new ProgressSupport.Sub(progress, 100), cancellation); tikhomirov@628: } catch (HgRuntimeException ex) { tikhomirov@628: throw new HgLibraryFailureException(ex); tikhomirov@569: } 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@677: /** tikhomirov@677: * @return 1-based index of the line in the annotated revision tikhomirov@677: */ tikhomirov@566: int getLineNumber(); tikhomirov@677: tikhomirov@677: /** tikhomirov@677: * @return 1-based line number at the first appearance, at changeset {@link #getChangesetIndex()} tikhomirov@677: */ tikhomirov@677: int getOriginLineNumber(); tikhomirov@677: /** tikhomirov@677: * @return changeset revision this line was introduced at tikhomirov@677: */ tikhomirov@566: int getChangesetIndex(); tikhomirov@677: tikhomirov@677: /** tikhomirov@677: * @return line content tikhomirov@677: */ tikhomirov@566: byte[] getContent(); tikhomirov@566: } tikhomirov@566: }