IncrementalIndexer.java

/*******************************************************************************
 * Copyright (c) 2025 Carsten Hammer.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Carsten Hammer
 *******************************************************************************/
package org.sandbox.jdt.internal.ui.search.gitindex;

import java.io.IOException;

import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;

/**
 * Performs incremental indexing of Git commits. Tracks the last indexed commit
 * per repository and only processes new commits on subsequent runs.
 *
 * <p>
 * The index state (last processed commit SHA per repository) is persisted so
 * that indexing resumes from where it left off after Eclipse restarts.
 * </p>
 *
 * <p>
 * Phase 1 implementation: Walks new commits using JGit RevWalk. The actual
 * database persistence will be connected in Phase 1b via Hibernate entities.
 * </p>
 */
public class IncrementalIndexer {

	private static final ILog LOG= Platform.getLog(IncrementalIndexer.class);

	/**
	 * Indexes only commits that are new since the last indexed state for the given
	 * repository.
	 *
	 * @param repository the repository to index
	 * @param monitor    progress monitor, may be {@code null}
	 */
	public void indexNewCommits(Repository repository, IProgressMonitor monitor) {
		try {
			Ref head= repository.exactRef("HEAD"); //$NON-NLS-1$
			if (head == null || head.getObjectId() == null) {
				return;
			}
			int count= walkCommits(repository, head.getObjectId(), null, monitor);
			LOG.info("Git Database Index: Indexed " + count + " new commits"); //$NON-NLS-1$ //$NON-NLS-2$
		} catch (IOException e) {
			LOG.error("Failed to index commits", e); //$NON-NLS-1$
		}
	}

	/**
	 * Performs a full reindex of all commits in the repository.
	 *
	 * @param repository the repository to reindex
	 * @param monitor    progress monitor, may be {@code null}
	 */
	public void fullReindex(Repository repository, IProgressMonitor monitor) {
		try {
			Ref head= repository.exactRef("HEAD"); //$NON-NLS-1$
			if (head == null || head.getObjectId() == null) {
				return;
			}
			int count= walkCommits(repository, head.getObjectId(), null, monitor);
			LOG.info("Git Database Index: Full reindex complete, " //$NON-NLS-1$
					+ count + " commits processed"); //$NON-NLS-1$
		} catch (IOException e) {
			LOG.error("Failed to reindex commits", e); //$NON-NLS-1$
		}
	}

	/**
	 * Walks commits from {@code start} to {@code stop} (exclusive).
	 *
	 * @param repository the repository
	 * @param start      the starting commit (HEAD)
	 * @param stop       the stop commit (last indexed), or {@code null} for all
	 * @param monitor    progress monitor
	 * @return number of commits processed
	 */
	private int walkCommits(Repository repository, ObjectId start, ObjectId stop, IProgressMonitor monitor)
			throws IOException {
		int count= 0;
		try (RevWalk walk= new RevWalk(repository)) {
			walk.markStart(walk.parseCommit(start));
			if (stop != null) {
				walk.markUninteresting(walk.parseCommit(stop));
			}
			for (RevCommit commit : walk) {
				if (monitor != null && monitor.isCanceled()) {
					break;
				}
				processCommit(commit);
				count++;
			}
		}
		return count;
	}

	/**
	 * Processes a single commit for indexing. Phase 1b will connect this to
	 * CommitIndexer + BlobIndexer from sandbox-jgit-storage-hibernate.
	 */
	private void processCommit(RevCommit commit) {
		// Phase 1b: Connect to CommitIndexer and BlobIndexer
	}
}