blob: 669fa10f74f26b7f372b2b2242527b6357e68dbd [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2019 Thomas Wolf <thomas.wolf@paranor.ch>
*
* All rights reserved. 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
*******************************************************************************/
package org.eclipse.egit.ui.internal;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.egit.core.UnitOfWork;
import org.eclipse.egit.ui.Activator;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
/**
* A cache of some state of repositories. Concrete subclasses are responsible
* for clearing this cache in response to some event.
*/
public abstract class RepositoryStateCache {
private enum RepositoryItem {
CONFIG, HEAD, HEAD_REF, HEAD_COMMIT, FULL_BRANCH_NAME, STATE
}
/** "null" marker in the maps. */
private static final Object NOTHING = new Object();
private final Map<File, Map<RepositoryItem, Object>> cache = new ConcurrentHashMap<>();
/**
* Initializes the {@link RepositoryStateCache} and makes it listen to changes
* that may affect the cache.
*/
public abstract void initialize();
/**
* Disposes the {@link RepositoryStateCache}.
*/
public void dispose() {
clear();
}
/**
* Completely clears the cache.
*/
public void clear() {
cache.clear();
}
/**
* Clears all cached entries for the given {@link Repository}.
*
* @param repository
* to remove cached items of
*/
public void clear(Repository repository) {
cache.remove(repository.getDirectory());
}
private Map<RepositoryItem, Object> getItems(Repository repository) {
return cache.computeIfAbsent(repository.getDirectory(),
gitDir -> new ConcurrentHashMap<>());
}
/**
* Retrieves the given repository's {@link StoredConfig}.
*
* @param repository
* @return the {@link StoredConfig} of the repository
*/
public StoredConfig getConfig(Repository repository) {
Object value = getItems(repository).computeIfAbsent(
RepositoryItem.CONFIG, key -> repository.getConfig());
return (StoredConfig) value;
}
private ObjectId getHead(Repository repository,
String[] fullName, Ref[] ref) {
return UnitOfWork.get(repository, () -> {
ObjectId head = ObjectId.zeroId();
String name = null;
Ref r = null;
try {
r = repository.exactRef(Constants.HEAD);
} catch (IOException e) {
Activator.logError(e.getLocalizedMessage(), e);
}
ref[0] = r;
if (r != null) {
if (r.isSymbolic()) {
name = r.getTarget().getName();
}
head = r.getObjectId();
if (head != null) {
if (name == null) {
name = head.name();
}
} else {
head = ObjectId.zeroId();
}
}
fullName[0] = name != null ? name : ""; //$NON-NLS-1$
return head;
});
}
/**
* Retrieves the {@link ObjectId} of the current HEAD.
*
* @param repository
* @return ObjectId of HEAD, or {@code null} if none
*/
public ObjectId getHead(Repository repository) {
if (repository == null) {
return null;
}
Map<RepositoryItem, Object> items = getItems(repository);
Object value = items.get(RepositoryItem.HEAD);
if (value == null) {
String[] fullName = { null };
Ref[] headRef = { null };
value = items.computeIfAbsent(RepositoryItem.HEAD,
key -> getHead(repository, fullName, headRef));
items.computeIfAbsent(RepositoryItem.FULL_BRANCH_NAME,
key -> fullName[0]);
items.computeIfAbsent(RepositoryItem.HEAD_REF,
key -> headRef[0] == null ? NOTHING : headRef[0]);
}
ObjectId head = (ObjectId) value;
if (head == null || head.equals(ObjectId.zeroId())) {
return null;
}
return head;
}
/**
* Retrieves the current HEAD ref.
*
* @param repository
* @return the HEAD ref, or {@code null} if none
*/
public Ref getHeadRef(Repository repository) {
if (repository == null) {
return null;
}
Map<RepositoryItem, Object> items = getItems(repository);
Object value = items.get(RepositoryItem.HEAD_REF);
if (value == null) {
String[] fullName = { null };
Ref[] headRef = { null };
items.computeIfAbsent(RepositoryItem.HEAD,
key -> getHead(repository, fullName, headRef));
items.computeIfAbsent(RepositoryItem.FULL_BRANCH_NAME,
key -> fullName[0]);
value = items.computeIfAbsent(RepositoryItem.HEAD_REF,
key -> headRef[0] == null ? NOTHING : headRef[0]);
}
if (value == null || value == NOTHING) {
return null;
}
return (Ref) value;
}
/**
* Retrieves the current HEAD commit of the repository.
*
* @param repository
* @return the commit, or {@code null} if none could be determined
*/
public RevCommit getHeadCommit(Repository repository) {
if (repository == null) {
return null;
}
Map<RepositoryItem, Object> items = getItems(repository);
Object value = items.get(RepositoryItem.HEAD_COMMIT);
if (value == null) {
ObjectId headId = getHead(repository);
if (headId != null) {
try (RevWalk w = new RevWalk(repository)) {
RevCommit commit = w.parseCommit(headId);
items.put(RepositoryItem.HEAD_COMMIT, commit);
return commit;
} catch (IOException e) {
// Ignore here
}
}
items.put(RepositoryItem.HEAD_COMMIT, NOTHING);
return null;
} else if (value == NOTHING) {
return null;
}
return (RevCommit) value;
}
/**
* Retrieves the full name of the current branch.
*
* @param repository
* @return the full branch name
*/
public String getFullBranchName(Repository repository) {
if (repository == null) {
return null;
}
Map<RepositoryItem, Object> items = getItems(repository);
Object fullBranchName = items.get(RepositoryItem.FULL_BRANCH_NAME);
if (fullBranchName == null) {
String[] fullName = { null };
Ref[] headRef = { null };
items.computeIfAbsent(RepositoryItem.HEAD,
key -> getHead(repository, fullName, headRef));
fullBranchName = items.computeIfAbsent(
RepositoryItem.FULL_BRANCH_NAME, key -> fullName[0]);
items.computeIfAbsent(RepositoryItem.HEAD_REF,
key -> headRef[0] == null ? NOTHING : headRef[0]);
}
String name = (String) fullBranchName;
if (name == null || name.isEmpty()) {
return null;
}
return name;
}
/**
* Retrieves the repository state.
*
* @param repository
* @return the {@link RepositoryState}
*/
public @NonNull RepositoryState getRepositoryState(Repository repository) {
RepositoryState state = UnitOfWork.get(repository, () -> {
Object value = getItems(repository).computeIfAbsent(
RepositoryItem.STATE,
key -> repository.getRepositoryState());
return (RepositoryState) value;
});
assert state != null; // Keep the compiler happy.
return state;
}
}