diff src/org/tmatesoft/hg/core/HgDiffCommand.java @ 629:5f52074707b2

Diff/blame methods as command, their residence in HgDataFile was a mistake
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 22 May 2013 16:46:15 +0200
parents
children 72c979555cb8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgDiffCommand.java	Wed May 22 16:46:15 2013 +0200
@@ -0,0 +1,171 @@
+/*
+ * 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.core;
+
+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.internal.BlameHelper;
+import org.tmatesoft.hg.internal.Experimental;
+import org.tmatesoft.hg.internal.FileHistory;
+import org.tmatesoft.hg.internal.FileRevisionHistoryChunk;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRuntimeException;
+import org.tmatesoft.hg.util.CancelledException;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * 'hg diff' counterpart, with similar, diff-based, functionality
+ * 
+ * @since 1.1
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@Experimental(reason="#execute* methods might get renamed")
+public class HgDiffCommand extends HgAbstractCommand<HgDiffCommand> {
+
+	private final HgRepository repo;
+	private HgDataFile df;
+	private int clogRevIndexStart, clogRevIndexEnd;
+	private int clogRevIndexToParents;
+	private HgIterateDirection iterateDirection = HgIterateDirection.NewToOld;
+
+	public HgDiffCommand(HgRepository hgRepo) {
+		repo = hgRepo;
+	}
+	
+	public HgDiffCommand file(Path file) {
+		df = repo.getFileNode(file);
+		return this;
+	}
+
+	public HgDiffCommand file(HgDataFile file) {
+		df = file;
+		return this;
+	}
+
+	public HgDiffCommand range(int changelogRevIndexStart, int changelogRevIndexEnd) {
+		clogRevIndexStart = changelogRevIndexStart;
+		clogRevIndexEnd = changelogRevIndexEnd;
+		return this;
+	}
+	
+	// FIXME javadoc when needed and difference with range
+	public HgDiffCommand changeset(int changelogRevIndex) {
+		clogRevIndexToParents = changelogRevIndex;
+		return this;
+	}
+
+	// FIXME javadoc when needed
+	public HgDiffCommand order(HgIterateDirection order) {
+		iterateDirection = order;
+		return this;
+	}
+
+	// FIXME progress and cancellation
+	
+	/**
+	 * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2'
+	 */
+	public void executeDiff(HgBlameInspector insp) throws HgCallbackTargetException, CancelledException, HgException {
+		try {
+			int fileRevIndex1 = fileRevIndex(df, clogRevIndexStart);
+			int fileRevIndex2 = fileRevIndex(df, clogRevIndexEnd);
+			BlameHelper bh = new BlameHelper(insp);
+			bh.prepare(df, clogRevIndexStart, clogRevIndexEnd);
+			bh.diff(fileRevIndex1, clogRevIndexStart, fileRevIndex2, clogRevIndexEnd);
+		} catch (HgRuntimeException ex) {
+			throw new HgLibraryFailureException(ex);
+		}
+	}
+
+	/**
+	 * Walk file history range and report changes (diff) for each revision
+	 */
+	public void executeAnnotate(HgBlameInspector insp) throws HgCallbackTargetException, CancelledException, HgException {
+		if (wrongRevisionIndex(clogRevIndexStart) || wrongRevisionIndex(clogRevIndexEnd)) {
+			throw new IllegalArgumentException();
+		}
+		// FIXME check file and range are set
+		try {
+			// Note, changelogRevIndexEnd may be TIP, while the code below doesn't tolerate constants
+			//
+			int lastRevision = repo.getChangelog().getLastRevision();
+			if (clogRevIndexEnd == TIP) {
+				clogRevIndexEnd = lastRevision;
+			}
+			HgInternals.checkRevlogRange(clogRevIndexStart, clogRevIndexEnd, lastRevision);
+			if (!df.exists()) {
+				return;
+			}
+			BlameHelper bh = new BlameHelper(insp);
+			FileHistory fileHistory = bh.prepare(df, clogRevIndexStart, clogRevIndexEnd);
+	
+			int[] fileClogParentRevs = new int[2];
+			int[] fileParentRevs = new int[2];
+			for (FileRevisionHistoryChunk fhc : fileHistory.iterate(iterateDirection)) {
+				for (int fri : fhc.fileRevisions(iterateDirection)) {
+					int clogRevIndex = fhc.changeset(fri);
+					// the way we built fileHistory ensures we won't walk past [changelogRevIndexStart..changelogRevIndexEnd]
+					assert clogRevIndex >= clogRevIndexStart;
+					assert clogRevIndex <= clogRevIndexEnd;
+					fhc.fillFileParents(fri, fileParentRevs);
+					fhc.fillCsetParents(fri, fileClogParentRevs);
+					bh.annotateChange(fri, clogRevIndex, fileParentRevs, fileClogParentRevs);
+				}
+			}
+		} catch (HgRuntimeException ex) {
+			throw new HgLibraryFailureException(ex);
+		}
+	}
+
+	/**
+	 * 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 executeAnnotateSingleRevision(HgBlameInspector insp) throws HgCallbackTargetException, CancelledException, HgException {
+		try {
+			int changelogRevisionIndex = clogRevIndexToParents;
+			// 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);
+			}
+			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]);
+			BlameHelper bh = new BlameHelper(insp);
+			int clogIndexStart = fileClogParentRevs[0] == NO_REVISION ? (fileClogParentRevs[1] == NO_REVISION ? 0 : fileClogParentRevs[1]) : fileClogParentRevs[0];
+			bh.prepare(df, clogIndexStart, changelogRevisionIndex);
+			bh.annotateChange(fileRevIndex, changelogRevisionIndex, fileRevParents, fileClogParentRevs);
+		} catch (HgRuntimeException ex) {
+			throw new HgLibraryFailureException(ex);
+		}
+	}
+
+
+	private static int fileRevIndex(HgDataFile df, int csetRevIndex) throws HgRuntimeException {
+		Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath());
+		return df.getRevisionIndex(fileRev);
+	}
+}