diff src/org/tmatesoft/hg/internal/FileHistory.java @ 596:43cfa08ff3fd

HgBlameFacility refactoring: extract code to build file history that spans renames
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 02 May 2013 19:23:53 +0200
parents
children 507602cb4fb3
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/FileHistory.java	Thu May 02 19:23:53 2013 +0200
@@ -0,0 +1,105 @@
+/*
+ * 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.internal;
+
+import static org.tmatesoft.hg.core.HgIterateDirection.NewToOld;
+
+import java.util.Collections;
+import java.util.LinkedList;
+
+import org.tmatesoft.hg.core.HgIterateDirection;
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ * History of a file, with copy/renames, and corresponding revision information.
+ * Facility for file history iteration. 
+ * 
+ * FIXME [post-1.1] Utilize in HgLogCommand and anywhere else we need to follow file history
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class FileHistory {
+	
+	private LinkedList<FileRevisionHistoryChunk> fileCompleteHistory = new LinkedList<FileRevisionHistoryChunk>();
+	private final HgDataFile df;
+	private final int csetTo;
+	private final int csetFrom;
+	
+	public FileHistory(HgDataFile file, int fromChangeset, int toChangeset) {
+		df = file;
+		csetFrom = fromChangeset;
+		csetTo = toChangeset;
+	}
+	
+	public int getStartChangeset() {
+		return csetFrom;
+	}
+	
+	public int getEndChangeset() {
+		return csetTo;
+	}
+
+	public void build() {
+		assert fileCompleteHistory.isEmpty();
+		HgDataFile currentFile = df;
+		final int changelogRevIndexEnd = csetTo;
+		final int changelogRevIndexStart = csetFrom;
+		int fileLastClogRevIndex = changelogRevIndexEnd;
+		FileRevisionHistoryChunk nextChunk = null;
+		fileCompleteHistory.clear(); // just in case, #build() is not expected to be called more than once
+		do {
+			FileRevisionHistoryChunk fileHistory = new FileRevisionHistoryChunk(currentFile);
+			fileHistory.init(fileLastClogRevIndex);
+			fileHistory.linkTo(nextChunk);
+			fileCompleteHistory.addFirst(fileHistory); // to get the list in old-to-new order
+			nextChunk = fileHistory;
+			if (fileHistory.changeset(0) > changelogRevIndexStart && currentFile.isCopy()) {
+				// fileHistory.changeset(0) is the earliest revision we know about so far,
+				// once we get to revisions earlier than the requested start, stop digging.
+				// The reason there's NO == (i.e. not >=) because:
+				// (easy): once it's equal, we've reached our intended start
+				// (hard): if changelogRevIndexStart happens to be exact start of one of renames in the 
+				// chain of renames (test-annotate2 repository, file1->file1a->file1b, i.e. points 
+				// to the very start of file1a or file1 history), presence of == would get us to the next 
+				// chunk and hence changed parents of present chunk's first element. Our annotate alg 
+				// relies on parents only (i.e. knows nothing about 'last iteration element') to find out 
+				// what to compare, and hence won't report all lines of 'last iteration element' (which is the
+				// first revision of the renamed file) as "added in this revision", leaving gaps in annotate
+				HgRepository repo = currentFile.getRepo();
+				Nodeid originLastRev = currentFile.getCopySourceRevision();
+				currentFile = repo.getFileNode(currentFile.getCopySourceName());
+				fileLastClogRevIndex = currentFile.getChangesetRevisionIndex(currentFile.getRevisionIndex(originLastRev));
+				// XXX perhaps, shall fail with meaningful exception if new file doesn't exist (.i/.d not found for whatever reason)
+				// or source revision is missing?
+			} else {
+				fileHistory.chopAtChangeset(changelogRevIndexStart);
+				currentFile = null; // stop iterating
+			}
+		} while (currentFile != null && fileLastClogRevIndex > changelogRevIndexStart);
+		// fileCompleteHistory is in (origin, intermediate target, ultimate target) order
+	}
+	
+	public Iterable<FileRevisionHistoryChunk> iterate(HgIterateDirection order) {
+		if (order == NewToOld) {
+			return ReverseIterator.reversed(fileCompleteHistory);
+		}
+		return Collections.unmodifiableList(fileCompleteHistory);
+	}
+}