timeaware: add Model.getRepository(node) (fixes #552362)
diff --git a/plugins/org.hawk.timeaware/src/org/hawk/timeaware/graph/TimeAwareIndexer.java b/plugins/org.hawk.timeaware/src/org/hawk/timeaware/graph/TimeAwareIndexer.java
index 65165e3..5b573a8 100644
--- a/plugins/org.hawk.timeaware/src/org/hawk/timeaware/graph/TimeAwareIndexer.java
+++ b/plugins/org.hawk.timeaware/src/org/hawk/timeaware/graph/TimeAwareIndexer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2018 Aston University.
+ * Copyright (c) 2018-2019 Aston University.
  * 
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License 2.0 which is available at
@@ -59,6 +59,7 @@
 
 		String lastRev;
 		try {
+			taGraph.setTime(0);
 			lastRev = getLastIndexedRevision(vcsManager);
 		} catch (Exception e) {
 			LOGGER.error("Could not fetch the last indexed revision", e);
@@ -89,17 +90,9 @@
 						console.println(String.format("Index revision %s (timepoint %d) of %s",
 							commit.getRevision(), epochMillis, commit.getDelta().getManager().getLocation()));
 
-						taGraph.setTime(0);
-						setLastIndexedRevision(vcsManager, commit.getRevision());
+						setIndexedRevision(vcsManager, commit);
 					}
 				}
-
-				// The indexed repo might have commits in other paths - mark the last revision as done as well so
-				// we do not keep trying to index again and again.
-				synchronized (taGraph) {
-					taGraph.setTime(0);
-					setLastIndexedRevision(vcsManager, currentRevision);
-				}
 			}
 		} catch (Exception e) {
 			LOGGER.error("Failed to synchronise repository " + vcsManager.getLocation(), e);
@@ -116,19 +109,20 @@
 	protected String getLastIndexedRevision(IVcsManager vcsManager) throws Exception {
 		String lastRev;
 		try (IGraphTransaction tx = graph.beginTransaction()) {
-			VCSManagerIndex vcsIndex = new VCSManagerIndex(graph);
+			VCSManagerIndex vcsIndex = new VCSManagerIndex((ITimeAwareGraphDatabase) graph);
 			RepositoryNode repoNode = vcsIndex.getOrCreateRepositoryNode(vcsManager.getLocation());
-			lastRev = repoNode.getLastRevision();
+			lastRev = repoNode.getLatest().getRevision();
 			tx.success();
 		}
 		return lastRev;
 	}
 
-	protected void setLastIndexedRevision(IVcsManager vcsManager, String lastRev) throws Exception {
+	protected void setIndexedRevision(IVcsManager vcsManager, VcsCommit commit) throws Exception {
 		try (IGraphTransaction tx = graph.beginTransaction()) {
-			VCSManagerIndex vcsIndex = new VCSManagerIndex(graph);
+			VCSManagerIndex vcsIndex = new VCSManagerIndex((ITimeAwareGraphDatabase) graph);
 			RepositoryNode repoNode = vcsIndex.getOrCreateRepositoryNode(vcsManager.getLocation());
-			repoNode.setLastRevision(lastRev);
+			repoNode.setRevision(commit.getRevision());
+			repoNode.setMessage(commit.getMessage());
 			tx.success();
 		}
 	}
diff --git a/plugins/org.hawk.timeaware/src/org/hawk/timeaware/graph/VCSManagerIndex.java b/plugins/org.hawk.timeaware/src/org/hawk/timeaware/graph/VCSManagerIndex.java
index 024a887..013acb6 100644
--- a/plugins/org.hawk.timeaware/src/org/hawk/timeaware/graph/VCSManagerIndex.java
+++ b/plugins/org.hawk.timeaware/src/org/hawk/timeaware/graph/VCSManagerIndex.java
@@ -17,13 +17,12 @@
 package org.hawk.timeaware.graph;
 
 import java.util.Collections;
-import java.util.List;
 
-import org.hawk.core.graph.IGraphDatabase;
 import org.hawk.core.graph.IGraphIterable;
 import org.hawk.core.graph.IGraphNode;
-import org.hawk.core.graph.IGraphNodeIndex;
+import org.hawk.core.graph.timeaware.ITimeAwareGraphDatabase;
 import org.hawk.core.graph.timeaware.ITimeAwareGraphNode;
+import org.hawk.core.graph.timeaware.ITimeAwareGraphNodeIndex;
 
 /**
  * Keeps track of information in the graph about the various VCS. This class
@@ -32,17 +31,19 @@
 public class VCSManagerIndex {
 	private static final String URI_PROPERTY = "uri";
 
-	private final IGraphNodeIndex idx;
-	private final IGraphDatabase db;
+	private final ITimeAwareGraphNodeIndex idx;
+	private final ITimeAwareGraphDatabase db;
 
 	/**
 	 * Type-safe wrapper for the node we keep in the graph about a repository.
 	 */
 	public class RepositoryNode {
 		private static final String LASTREV_PROPERTY = "lastRevision";
-		private final IGraphNode node;
+		private static final String MESSAGE_PROPERTY = "message";
 
-		public RepositoryNode(IGraphNode n) {
+		private final ITimeAwareGraphNode node;
+
+		public RepositoryNode(ITimeAwareGraphNode n) {
 			this.node = n;
 		}
 
@@ -51,10 +52,10 @@
 		}
 
 		/**
-		 * Returns the last revision indexed for this VCS, or <code>null</code> if it
-		 * has not been indexed yet.
+		 * Returns the revision indexed for this VCS at its current timepoint, or
+		 * <code>null</code> if it has not been indexed yet.
 		 */
-		public String getLastRevision() {
+		public String getRevision() {
 			final Object lastRev = node.getProperty(LASTREV_PROPERTY);
 			if (lastRev == null) {
 				return null;
@@ -66,20 +67,55 @@
 		/**
 		 * Changes the last revision indexed for this VCS.
 		 */
-		public void setLastRevision(String lastRev) {
+		public void setRevision(String lastRev) {
 			node.setProperty(LASTREV_PROPERTY, lastRev);
 		}
 
-		public IGraphNode getNode() {
-			return node;
+		/**
+		 * Returns the latest version of this repository node.
+		 *
+		 * @throws Exception Error while fetching the latest version.
+		 */
+		public RepositoryNode getLatest() throws Exception {
+			return new RepositoryNode(node.getLatest());
 		}
+
+		/**
+		 * Returns the node at a different point in time.
+		 */
+		public RepositoryNode travelInTime(long instant) throws Exception {
+			return new RepositoryNode(node.travelInTime(instant));
+		}
+
+		/**
+		 * Returns the commit message associated with this revision.
+		 */
+		public String getMessage() {
+			final Object message = node.getProperty(MESSAGE_PROPERTY);
+			return message == null ? null : message.toString();
+		}
+
+		/**
+		 * Stores the commit message associated with this revision.
+		 */
+		public void setMessage(String message) {
+			node.setProperty(MESSAGE_PROPERTY, message);
+		}
+
+		/**
+		 * Returns the underlying node ID.
+		 */
+		public Object getId() {
+			return node.getId();
+		}
+		
 	}
 
 	/**
 	 * Creates a new instance. Retrieves the existing node index in the graph,
 	 * or creates a new one if it does not exist. 
 	 */
-	public VCSManagerIndex(IGraphDatabase db) {
+	public VCSManagerIndex(ITimeAwareGraphDatabase db) {
 		this.db = db;
 		this.idx = db.getOrCreateNodeIndex("_hawkVCSIndex");
 	}
@@ -91,9 +127,9 @@
 	public RepositoryNode getOrCreateRepositoryNode(String repoURI) {
 		IGraphIterable<? extends IGraphNode> iNode = idx.get(URI_PROPERTY, repoURI);
 		if (iNode.size() > 0) {
-			return new RepositoryNode(iNode.getSingle());
+			return new RepositoryNode((ITimeAwareGraphNode) iNode.getSingle());
 		} else {
-			final IGraphNode node = db.createNode(
+			final ITimeAwareGraphNode node = db.createNode(
 				Collections.singletonMap(URI_PROPERTY, repoURI), "_hawkRepo");
 			idx.add(node, URI_PROPERTY, repoURI);
 			return new RepositoryNode(node);
diff --git a/plugins/org.hawk.timeaware/src/org/hawk/timeaware/queries/TimeAwareEOLQueryEngine.java b/plugins/org.hawk.timeaware/src/org/hawk/timeaware/queries/TimeAwareEOLQueryEngine.java
index 2b580e9..f4ff8d9 100644
--- a/plugins/org.hawk.timeaware/src/org/hawk/timeaware/queries/TimeAwareEOLQueryEngine.java
+++ b/plugins/org.hawk.timeaware/src/org/hawk/timeaware/queries/TimeAwareEOLQueryEngine.java
@@ -47,6 +47,8 @@
 import org.hawk.graph.FileNode;
 import org.hawk.graph.GraphWrapper;
 import org.hawk.graph.ModelElementNode;
+import org.hawk.timeaware.graph.VCSManagerIndex;
+import org.hawk.timeaware.graph.VCSManagerIndex.RepositoryNode;
 import org.hawk.timeaware.queries.operations.reflective.TimeAwareNodeHistoryOperationContributor;
 import org.hawk.timeaware.queries.operations.reflective.TypeHistoryOperationContributor;
 import org.slf4j.Logger;
@@ -263,6 +265,29 @@
 		return allFNW;
 	}
 
+	/**
+	 * Returns the {@link RepositoryNode} associated with this instance node.
+	 *
+	 * @throws Exception Error while travelling in time to the matching instant on
+	 *                   the repository node.
+	 */
+	public RepositoryNode getRepository(Object oInstanceNode) throws Exception {
+		if (oInstanceNode instanceof GraphNodeWrapper) {
+			GraphNodeWrapper gnw = (GraphNodeWrapper) oInstanceNode;
+			if (gnw.getNode() instanceof ITimeAwareGraphNode) {
+				ITimeAwareGraphNode taNode = (ITimeAwareGraphNode) gnw.getNode();
+				ModelElementNode me = new ModelElementNode(taNode);
+				String repoURL = me.getFileNode().getRepositoryURL();
+
+				final ITimeAwareGraphDatabase taGraph = (ITimeAwareGraphDatabase) graph;
+				final RepositoryNode repoNode = new VCSManagerIndex(taGraph).getOrCreateRepositoryNode(repoURL);
+
+				return repoNode.travelInTime(taNode.getTime());
+			}
+		}
+		return null;
+	}
+
 	private void setAllFiles(Function<IGraphNode, Iterable<? extends IGraphNode>> allFiles) {
 		this.allFiles = allFiles;
 	}
diff --git a/tests/org.hawk.timeaware.tests/src/org/hawk/timeaware/tests/NodeHistoryTest.java b/tests/org.hawk.timeaware.tests/src/org/hawk/timeaware/tests/NodeHistoryTest.java
index a9cb661..6fdda35 100644
--- a/tests/org.hawk.timeaware.tests/src/org/hawk/timeaware/tests/NodeHistoryTest.java
+++ b/tests/org.hawk.timeaware.tests/src/org/hawk/timeaware/tests/NodeHistoryTest.java
@@ -24,7 +24,6 @@
 
 import java.io.File;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Callable;
 
@@ -33,7 +32,6 @@
 import org.hawk.backend.tests.BackendTestSuite;
 import org.hawk.backend.tests.factories.IGraphDatabaseFactory;
 import org.hawk.core.graph.timeaware.ITimeAwareGraphNode;
-import org.hawk.epsilon.emc.EOLQueryEngine;
 import org.hawk.epsilon.emc.wrappers.GraphNodeWrapper;
 import org.hawk.integration.tests.emf.EMFModelSupportFactory;
 import org.hawk.svn.tests.rules.TemporarySVNRepository;
@@ -225,6 +223,23 @@
 	}
 
 	@Test
+	public void commitMessages() throws Throwable {
+		keepAddingChildren();
+		waitForSync(() -> {
+			assertEquals("Create root",
+				timeAwareEOL("return Model.getRepository(Tree.latest.all.selectOne(t|t.label='Root').earliest).message;"));
+			assertEquals("Add T1",
+					timeAwareEOL("return Model.getRepository(Tree.latest.all.selectOne(t|t.label='T1').earliest).message;"));
+			assertEquals("Add T2",
+					timeAwareEOL("return Model.getRepository(Tree.latest.all.selectOne(t|t.label='T2').earliest).message;"));
+			assertEquals("Add T3",
+					timeAwareEOL("return Model.getRepository(Tree.latest.all.selectOne(t|t.label='T3').earliest).message;"));
+
+			return null;
+		});
+	}
+
+	@Test
 	public void rangesAreBothInclusive() throws Throwable {
 		keepAddingChildren();
 		waitForSync(() -> {
diff --git a/tests/org.hawk.timeaware.tests/src/org/hawk/timeaware/tests/VCSManagerIndexTest.java b/tests/org.hawk.timeaware.tests/src/org/hawk/timeaware/tests/VCSManagerIndexTest.java
index 8d1a0cb..648d720 100644
--- a/tests/org.hawk.timeaware.tests/src/org/hawk/timeaware/tests/VCSManagerIndexTest.java
+++ b/tests/org.hawk.timeaware.tests/src/org/hawk/timeaware/tests/VCSManagerIndexTest.java
@@ -26,6 +26,7 @@
 import org.hawk.backend.tests.factories.IGraphDatabaseFactory;
 import org.hawk.core.graph.IGraphDatabase;
 import org.hawk.core.graph.IGraphTransaction;
+import org.hawk.core.graph.timeaware.ITimeAwareGraphDatabase;
 import org.hawk.core.util.DefaultConsole;
 import org.hawk.timeaware.graph.VCSManagerIndex;
 import org.hawk.timeaware.graph.VCSManagerIndex.RepositoryNode;
@@ -70,7 +71,7 @@
 		db.run(dbFolder, new DefaultConsole());
 
 		try (IGraphTransaction tx = db.beginTransaction()) {
-			this.idx = new VCSManagerIndex(db);
+			this.idx = new VCSManagerIndex((ITimeAwareGraphDatabase) db);
 			tx.success();
 		}
 	}
@@ -86,8 +87,8 @@
 		}
 		try (IGraphTransaction tx = db.beginTransaction()) {
 			assertEquals(repoURI, node.getURI());
-			assertNull(node.getLastRevision());
-			assertEquals(node.getNode().getId(), idx.getOrCreateRepositoryNode(repoURI).getNode().getId());
+			assertNull(node.getRevision());
+			assertEquals(node.getId(), idx.getOrCreateRepositoryNode(repoURI).getId());
 			tx.success();
 		}
 	}
@@ -99,12 +100,12 @@
 
 		try (IGraphTransaction tx = db.beginTransaction()) {
 			RepositoryNode node = idx.getOrCreateRepositoryNode(repoURI);
-			node.setLastRevision(lastRev);
+			node.setRevision(lastRev);
 			tx.success();
 		}
 		try (IGraphTransaction tx = db.beginTransaction()) {
 			RepositoryNode node = idx.getOrCreateRepositoryNode(repoURI);
-			assertEquals(lastRev, node.getLastRevision());
+			assertEquals(lastRev, node.getRevision());
 			tx.success();
 		}
 	}
@@ -116,18 +117,18 @@
 		Object firstId;
 		try (IGraphTransaction tx = db.beginTransaction()) {
 			RepositoryNode node = idx.getOrCreateRepositoryNode(repoURI);
-			firstId = node.getNode().getId();
+			firstId = node.getId();
 			tx.success();
 		}
 		try (IGraphTransaction tx = db.beginTransaction()) {
 			RepositoryNode node = idx.getOrCreateRepositoryNode(repoURI);
-			assertEquals(firstId, node.getNode().getId());
+			assertEquals(firstId, node.getId());
 			idx.removeRepositoryNode(repoURI);
 			tx.success();
 		}
 		try (IGraphTransaction tx = db.beginTransaction()) {
 			RepositoryNode node = idx.getOrCreateRepositoryNode(repoURI);
-			assertNotEquals(firstId, node.getNode().getId());
+			assertNotEquals(firstId, node.getId());
 			tx.success();
 		}
 	}