HibernateReflogReader.java

/*******************************************************************************
 * Copyright (c) 2026 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.eclipse.jgit.storage.hibernate.refs;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.jgit.lib.CheckoutEntry;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.storage.hibernate.entity.GitReflogEntity;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

/**
 * Reads reflog entries from the {@code git_reflog} database table.
 * <p>
 * Returns entries in reverse chronological order (newest first),
 * consistent with the standard reflog reader contract.
 */
public class HibernateReflogReader implements ReflogReader {

	private final SessionFactory sessionFactory;

	private final String repositoryName;

	private final String refName;

	/**
	 * Create a new reflog reader.
	 *
	 * @param sessionFactory
	 *            the Hibernate session factory
	 * @param repositoryName
	 *            the repository name
	 * @param refName
	 *            the reference name to read reflog for
	 */
	public HibernateReflogReader(SessionFactory sessionFactory,
			String repositoryName, String refName) {
		this.sessionFactory = sessionFactory;
		this.repositoryName = repositoryName;
		this.refName = refName;
	}

	@Override
	public ReflogEntry getLastEntry() throws IOException {
		List<ReflogEntry> entries = getReverseEntries(1);
		return entries.isEmpty() ? null : entries.get(0);
	}

	@Override
	public List<ReflogEntry> getReverseEntries() throws IOException {
		return getReverseEntries(Integer.MAX_VALUE);
	}

	@Override
	public ReflogEntry getReverseEntry(int number) throws IOException {
		List<ReflogEntry> entries = getReverseEntries(number + 1);
		return number < entries.size() ? entries.get(number) : null;
	}

	@Override
	public List<ReflogEntry> getReverseEntries(int max) throws IOException {
		try (Session session = sessionFactory.openSession()) {
			List<GitReflogEntity> entities = session.createQuery(
					"FROM GitReflogEntity r WHERE r.repositoryName = :repo AND r.refName = :ref ORDER BY r.id DESC", //$NON-NLS-1$
					GitReflogEntity.class)
					.setParameter("repo", repositoryName) //$NON-NLS-1$
					.setParameter("ref", refName) //$NON-NLS-1$
					.setMaxResults(max).getResultList();
			List<ReflogEntry> result = new ArrayList<>(entities.size());
			for (GitReflogEntity e : entities) {
				result.add(new DbReflogEntry(e));
			}
			return Collections.unmodifiableList(result);
		}
	}

	/**
	 * A {@link ReflogEntry} backed by a database entity.
	 */
	static class DbReflogEntry implements ReflogEntry {
		private final ObjectId oldId;

		private final ObjectId newId;

		private final PersonIdent who;

		private final String comment;

		DbReflogEntry(GitReflogEntity entity) {
			this.oldId = entity.getOldId() != null
					? ObjectId.fromString(entity.getOldId())
					: ObjectId.zeroId();
			this.newId = entity.getNewId() != null
					? ObjectId.fromString(entity.getNewId())
					: ObjectId.zeroId();
			this.who = new PersonIdent(
					entity.getWhoName() != null ? entity.getWhoName() : "", //$NON-NLS-1$
					entity.getWhoEmail() != null ? entity.getWhoEmail() : "", //$NON-NLS-1$
					entity.getWhen(),
					java.time.ZoneOffset.UTC); //$NON-NLS-1$
			this.comment = entity.getMessage() != null ? entity.getMessage()
					: ""; //$NON-NLS-1$
		}

		@Override
		public ObjectId getOldId() {
			return oldId;
		}

		@Override
		public ObjectId getNewId() {
			return newId;
		}

		@Override
		public PersonIdent getWho() {
			return who;
		}

		@Override
		public String getComment() {
			return comment;
		}

		@Override
		public CheckoutEntry parseCheckout() {
			return null;
		}
	}
}