EGitRepositoryTracker.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.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Platform;
import org.eclipse.egit.core.RepositoryCache;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;

/**
 * Tracks EGit-managed repositories and triggers indexing when changes are
 * detected. Listens for workspace resource changes that affect {@code .git}
 * directories (refs, objects) to detect new commits from EGit operations.
 *
 * <p>
 * Integration points with EGit:
 * </p>
 * <ul>
 * <li>{@code RepositoryCache.INSTANCE.getAllRepositories()} — all known
 * repos</li>
 * <li>{@code IResourceChangeListener} on .git/refs and .git/objects — detects
 * new commits</li>
 * </ul>
 */
public class EGitRepositoryTracker implements IResourceChangeListener {

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

	private static final String GIT_DIR= ".git"; //$NON-NLS-1$

	/**
	 * Starts tracking EGit repositories by registering a resource change listener.
	 */
	public void start() {
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
		LOG.info("Git Database Index: Repository tracker started"); //$NON-NLS-1$
	}

	/**
	 * Stops tracking by removing the resource change listener.
	 */
	public void stop() {
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
		LOG.info("Git Database Index: Repository tracker stopped"); //$NON-NLS-1$
	}

	@Override
	public void resourceChanged(IResourceChangeEvent event) {
		IResourceDelta delta= event.getDelta();
		if (delta == null) {
			return;
		}
		if (containsGitChanges(delta)) {
			scheduleIndexUpdate();
		}
	}

	private boolean containsGitChanges(IResourceDelta delta) {
		if (delta.getResource() != null && delta.getResource().getName().equals(GIT_DIR)) {
			return true;
		}
		for (IResourceDelta child : delta.getAffectedChildren()) {
			if (containsGitChanges(child)) {
				return true;
			}
		}
		return false;
	}

	private void scheduleIndexUpdate() {
		Collection<File> repositories= getAllRepositoryDirs();
		if (repositories.isEmpty()) {
			return;
		}
		LOG.info("Git Database Index: Detected Git changes, " //$NON-NLS-1$
				+ repositories.size() + " repositories known"); //$NON-NLS-1$
	}

	/**
	 * Returns all EGit-managed repository directories in the workspace.
	 *
	 * @return collection of .git directories
	 */
	public static Collection<File> getAllRepositoryDirs() {
		try {
			Repository[] repos= RepositoryCache.INSTANCE.getAllRepositories();
			List<File> dirs= new ArrayList<>();
			for (Repository repo : repos) {
				File dir= repo.getDirectory();
				if (dir != null) {
					dirs.add(dir);
				}
			}
			return dirs;
		} catch (Exception e) {
			LOG.error("Failed to get EGit repositories", e); //$NON-NLS-1$
			return Collections.emptyList();
		}
	}

	/**
	 * Opens a JGit Repository from a .git directory.
	 *
	 * @param gitDir the .git directory
	 * @return the repository, or {@code null} on failure
	 */
	public static Repository openRepository(File gitDir) {
		try {
			return FileRepositoryBuilder.create(gitDir);
		} catch (Exception e) {
			LOG.error("Failed to open repository: " + gitDir, e); //$NON-NLS-1$
			return null;
		}
	}
}