tikhomirov@629: /* tikhomirov@629: * Copyright (c) 2013 TMate Software Ltd tikhomirov@629: * tikhomirov@629: * This program is free software; you can redistribute it and/or modify tikhomirov@629: * it under the terms of the GNU General Public License as published by tikhomirov@629: * the Free Software Foundation; version 2 of the License. tikhomirov@629: * tikhomirov@629: * This program is distributed in the hope that it will be useful, tikhomirov@629: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@629: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@629: * GNU General Public License for more details. tikhomirov@629: * tikhomirov@629: * For information on how to redistribute this software under tikhomirov@629: * the terms of a license other than GNU General Public License tikhomirov@629: * contact TMate Software at support@hg4j.com tikhomirov@629: */ tikhomirov@629: package org.tmatesoft.hg.core; tikhomirov@629: tikhomirov@629: import static org.tmatesoft.hg.repo.HgInternals.wrongRevisionIndex; tikhomirov@629: import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; tikhomirov@629: import static org.tmatesoft.hg.repo.HgRepository.TIP; tikhomirov@629: tikhomirov@629: import org.tmatesoft.hg.internal.BlameHelper; tikhomirov@629: import org.tmatesoft.hg.internal.Experimental; tikhomirov@629: import org.tmatesoft.hg.internal.FileHistory; tikhomirov@629: import org.tmatesoft.hg.internal.FileRevisionHistoryChunk; tikhomirov@629: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@629: import org.tmatesoft.hg.repo.HgInternals; tikhomirov@629: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@629: import org.tmatesoft.hg.repo.HgRuntimeException; tikhomirov@629: import org.tmatesoft.hg.util.CancelledException; tikhomirov@629: import org.tmatesoft.hg.util.Path; tikhomirov@629: tikhomirov@629: /** tikhomirov@629: * 'hg diff' counterpart, with similar, diff-based, functionality tikhomirov@629: * tikhomirov@629: * @since 1.1 tikhomirov@629: * @author Artem Tikhomirov tikhomirov@629: * @author TMate Software Ltd. tikhomirov@629: */ tikhomirov@629: @Experimental(reason="#execute* methods might get renamed") tikhomirov@629: public class HgDiffCommand extends HgAbstractCommand { tikhomirov@629: tikhomirov@629: private final HgRepository repo; tikhomirov@629: private HgDataFile df; tikhomirov@629: private int clogRevIndexStart, clogRevIndexEnd; tikhomirov@629: private int clogRevIndexToParents; tikhomirov@629: private HgIterateDirection iterateDirection = HgIterateDirection.NewToOld; tikhomirov@629: tikhomirov@629: public HgDiffCommand(HgRepository hgRepo) { tikhomirov@629: repo = hgRepo; tikhomirov@629: } tikhomirov@629: tikhomirov@629: public HgDiffCommand file(Path file) { tikhomirov@629: df = repo.getFileNode(file); tikhomirov@629: return this; tikhomirov@629: } tikhomirov@629: tikhomirov@629: public HgDiffCommand file(HgDataFile file) { tikhomirov@629: df = file; tikhomirov@629: return this; tikhomirov@629: } tikhomirov@629: tikhomirov@629: public HgDiffCommand range(int changelogRevIndexStart, int changelogRevIndexEnd) { tikhomirov@629: clogRevIndexStart = changelogRevIndexStart; tikhomirov@629: clogRevIndexEnd = changelogRevIndexEnd; tikhomirov@629: return this; tikhomirov@629: } tikhomirov@629: tikhomirov@629: // FIXME javadoc when needed and difference with range tikhomirov@629: public HgDiffCommand changeset(int changelogRevIndex) { tikhomirov@629: clogRevIndexToParents = changelogRevIndex; tikhomirov@629: return this; tikhomirov@629: } tikhomirov@629: tikhomirov@629: // FIXME javadoc when needed tikhomirov@629: public HgDiffCommand order(HgIterateDirection order) { tikhomirov@629: iterateDirection = order; tikhomirov@629: return this; tikhomirov@629: } tikhomirov@629: tikhomirov@629: // FIXME progress and cancellation tikhomirov@629: tikhomirov@629: /** tikhomirov@629: * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2' tikhomirov@629: */ tikhomirov@629: public void executeDiff(HgBlameInspector insp) throws HgCallbackTargetException, CancelledException, HgException { tikhomirov@629: try { tikhomirov@629: int fileRevIndex1 = fileRevIndex(df, clogRevIndexStart); tikhomirov@629: int fileRevIndex2 = fileRevIndex(df, clogRevIndexEnd); tikhomirov@629: BlameHelper bh = new BlameHelper(insp); tikhomirov@629: bh.prepare(df, clogRevIndexStart, clogRevIndexEnd); tikhomirov@629: bh.diff(fileRevIndex1, clogRevIndexStart, fileRevIndex2, clogRevIndexEnd); tikhomirov@629: } catch (HgRuntimeException ex) { tikhomirov@629: throw new HgLibraryFailureException(ex); tikhomirov@629: } tikhomirov@629: } tikhomirov@629: tikhomirov@629: /** tikhomirov@629: * Walk file history range and report changes (diff) for each revision tikhomirov@629: */ tikhomirov@629: public void executeAnnotate(HgBlameInspector insp) throws HgCallbackTargetException, CancelledException, HgException { tikhomirov@629: if (wrongRevisionIndex(clogRevIndexStart) || wrongRevisionIndex(clogRevIndexEnd)) { tikhomirov@629: throw new IllegalArgumentException(); tikhomirov@629: } tikhomirov@629: // FIXME check file and range are set tikhomirov@629: try { tikhomirov@629: // Note, changelogRevIndexEnd may be TIP, while the code below doesn't tolerate constants tikhomirov@629: // tikhomirov@629: int lastRevision = repo.getChangelog().getLastRevision(); tikhomirov@629: if (clogRevIndexEnd == TIP) { tikhomirov@629: clogRevIndexEnd = lastRevision; tikhomirov@629: } tikhomirov@629: HgInternals.checkRevlogRange(clogRevIndexStart, clogRevIndexEnd, lastRevision); tikhomirov@629: if (!df.exists()) { tikhomirov@629: return; tikhomirov@629: } tikhomirov@629: BlameHelper bh = new BlameHelper(insp); tikhomirov@629: FileHistory fileHistory = bh.prepare(df, clogRevIndexStart, clogRevIndexEnd); tikhomirov@629: tikhomirov@629: int[] fileClogParentRevs = new int[2]; tikhomirov@629: int[] fileParentRevs = new int[2]; tikhomirov@629: for (FileRevisionHistoryChunk fhc : fileHistory.iterate(iterateDirection)) { tikhomirov@629: for (int fri : fhc.fileRevisions(iterateDirection)) { tikhomirov@629: int clogRevIndex = fhc.changeset(fri); tikhomirov@629: // the way we built fileHistory ensures we won't walk past [changelogRevIndexStart..changelogRevIndexEnd] tikhomirov@629: assert clogRevIndex >= clogRevIndexStart; tikhomirov@629: assert clogRevIndex <= clogRevIndexEnd; tikhomirov@629: fhc.fillFileParents(fri, fileParentRevs); tikhomirov@629: fhc.fillCsetParents(fri, fileClogParentRevs); tikhomirov@629: bh.annotateChange(fri, clogRevIndex, fileParentRevs, fileClogParentRevs); tikhomirov@629: } tikhomirov@629: } tikhomirov@629: } catch (HgRuntimeException ex) { tikhomirov@629: throw new HgLibraryFailureException(ex); tikhomirov@629: } tikhomirov@629: } tikhomirov@629: tikhomirov@629: /** tikhomirov@629: * Annotates changes of the file against its parent(s). tikhomirov@629: * Unlike {@link #annotate(HgDataFile, int, Inspector, HgIterateDirection)}, doesn't tikhomirov@629: * walk file history, looks at the specified revision only. Handles both parents (if merge revision). tikhomirov@629: */ tikhomirov@629: public void executeAnnotateSingleRevision(HgBlameInspector insp) throws HgCallbackTargetException, CancelledException, HgException { tikhomirov@629: try { tikhomirov@629: int changelogRevisionIndex = clogRevIndexToParents; tikhomirov@629: // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f tikhomirov@629: int fileRevIndex = fileRevIndex(df, changelogRevisionIndex); tikhomirov@629: int[] fileRevParents = new int[2]; tikhomirov@629: df.parents(fileRevIndex, fileRevParents, null, null); tikhomirov@629: if (changelogRevisionIndex == TIP) { tikhomirov@629: changelogRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex); tikhomirov@629: } tikhomirov@629: int[] fileClogParentRevs = new int[2]; tikhomirov@629: fileClogParentRevs[0] = fileRevParents[0] == NO_REVISION ? NO_REVISION : df.getChangesetRevisionIndex(fileRevParents[0]); tikhomirov@629: fileClogParentRevs[1] = fileRevParents[1] == NO_REVISION ? NO_REVISION : df.getChangesetRevisionIndex(fileRevParents[1]); tikhomirov@629: BlameHelper bh = new BlameHelper(insp); tikhomirov@629: int clogIndexStart = fileClogParentRevs[0] == NO_REVISION ? (fileClogParentRevs[1] == NO_REVISION ? 0 : fileClogParentRevs[1]) : fileClogParentRevs[0]; tikhomirov@629: bh.prepare(df, clogIndexStart, changelogRevisionIndex); tikhomirov@629: bh.annotateChange(fileRevIndex, changelogRevisionIndex, fileRevParents, fileClogParentRevs); tikhomirov@629: } catch (HgRuntimeException ex) { tikhomirov@629: throw new HgLibraryFailureException(ex); tikhomirov@629: } tikhomirov@629: } tikhomirov@629: tikhomirov@629: tikhomirov@629: private static int fileRevIndex(HgDataFile df, int csetRevIndex) throws HgRuntimeException { tikhomirov@629: Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath()); tikhomirov@629: return df.getRevisionIndex(fileRev); tikhomirov@629: } tikhomirov@629: }