blob: 2393e2268ebe5f4bf066264d17f342790daa4cd7 [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.core;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.events.ConfigChangedEvent;
import org.eclipse.jgit.events.IndexChangedEvent;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.ListenerList;
import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.events.RepositoryEvent;
import org.eclipse.jgit.events.RepositoryListener;
import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectDatabase;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.RebaseTodoLine;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefRename;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryBuilder;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.FS;
/**
* A {@code RepositoryHandle} completely wraps another {@link Repository}.
*/
class RepositoryHandle extends Repository {
private final Repository delegate;
private final List<ListenerHandle> listeners = new ArrayList<>();
/**
* Creates a new {@link RepositoryHandle} for the given {@code delegate}.
*
* @param delegate
* to wrap with the handle; must <em>not</em> be another
* {@link RepositoryHandle}
*/
RepositoryHandle(Repository delegate) {
super(new RepositoryBuilder());
// Internal error; no translation.
Assert.isLegal(!(delegate instanceof RepositoryHandle),
"Delegate of a RepositoryHandle must not be another RepositoryHandle"); //$NON-NLS-1$
this.delegate = delegate;
addListeners(listeners, delegate.getListenerList(),
new WeakReference<>(this));
}
private static void addListeners(List<ListenerHandle> listeners,
ListenerList list, WeakReference<RepositoryHandle> ref) {
// Add listeners that change the repository so that forwarded events
// have this handle set as repository. Since we can't change the
// repository in the original event we have to re-fire new ones.
//
// We use a static method and a WeakReference to the RepositoryHandle
// instance to avoid a hard reference loop from the delegate back to the
// RepositoryHandle:
//
// delegate->listerList->listenerHandle->repositoryHandle,
//
// which would prevent GC from collecting the RepositoryHandle because
// of the hard reference to the delegate inside the RepositoryCache's
// RepositoryReference.
listeners.add(list.addConfigChangedListener(event -> {
Repository repo = ref.get();
if (repo != null) {
fireEvent(repo, new ConfigChangedEvent());
}
}));
listeners.add(list.addIndexChangedListener(event -> {
Repository repo = ref.get();
if (repo != null) {
fireEvent(repo, new IndexChangedEvent(event.isInternal()));
}
}));
listeners.add(list.addRefsChangedListener(event -> {
Repository repo = ref.get();
if (repo != null) {
fireEvent(repo, new RefsChangedEvent());
}
}));
listeners.add(list.addWorkingTreeModifiedListener(event -> {
Repository repo = ref.get();
if (repo != null) {
fireEvent(repo,
new WorkingTreeModifiedEvent(event.getModified(),
event.getDeleted()));
}
}));
}
private static void fireEvent(Repository repo,
RepositoryEvent<? extends RepositoryListener> event) {
// Since the delegate will also dispatch its event through the global
// list, which we intercept in the RepositoryCache, we don't use
// repo.fireEvent() here but instead dispatch the event ourselves
// through the repo's listener list. Otherwise there'd be two
// notifications through the global list.
event.setRepository(repo);
repo.getListenerList().dispatch(event);
}
Repository getDelegate() {
return delegate;
}
@Override
public final ListenerList getListenerList() {
return super.getListenerList();
}
@Override
public final void fireEvent(RepositoryEvent<?> event) {
super.fireEvent(event);
}
@Override
public final void incrementOpen() {
super.incrementOpen();
}
@Override
public final void close() {
if (delegate instanceof CachingRepository) {
((CachingRepository) delegate).clearConfigCache();
}
super.close();
}
@Override
protected void doClose() {
listeners.forEach(ListenerHandle::remove);
listeners.clear();
delegate.close();
}
@Override
public String toString() {
return '[' + this.getClass().getSimpleName() + ':' + delegate.toString()
+ ']';
}
// Override all other methods and forward to the delegate
@Override
public void create() throws IOException {
delegate.create();
}
@Override
public void create(boolean bare) throws IOException {
delegate.create(bare);
}
@Override
public File getDirectory() {
return delegate.getDirectory();
}
@Override
public String getIdentifier() {
return delegate.getIdentifier();
}
@Override
public ObjectDatabase getObjectDatabase() {
return delegate.getObjectDatabase();
}
@Override
public ObjectInserter newObjectInserter() {
return delegate.newObjectInserter();
}
@Override
public ObjectReader newObjectReader() {
return delegate.newObjectReader();
}
@Override
public RefDatabase getRefDatabase() {
return delegate.getRefDatabase();
}
@Override
public StoredConfig getConfig() {
return delegate.getConfig();
}
@Override
public AttributesNodeProvider createAttributesNodeProvider() {
return delegate.createAttributesNodeProvider();
}
@Override
public FS getFS() {
return delegate.getFS();
}
@Override
public ObjectLoader open(AnyObjectId objectId)
throws MissingObjectException, IOException {
return delegate.open(objectId);
}
@Override
public ObjectLoader open(AnyObjectId objectId, int typeHint)
throws MissingObjectException, IncorrectObjectTypeException,
IOException {
return delegate.open(objectId, typeHint);
}
@Override
public RefUpdate updateRef(String ref) throws IOException {
return delegate.updateRef(ref);
}
@Override
public RefUpdate updateRef(String ref, boolean detach) throws IOException {
return delegate.updateRef(ref, detach);
}
@Override
public RefRename renameRef(String fromRef, String toRef)
throws IOException {
return delegate.renameRef(fromRef, toRef);
}
@Override
public ObjectId resolve(String revstr)
throws AmbiguousObjectException, IncorrectObjectTypeException,
RevisionSyntaxException, IOException {
return delegate.resolve(revstr);
}
@Override
public String simplify(String revstr)
throws AmbiguousObjectException, IOException {
return delegate.simplify(revstr);
}
@Override
public String getFullBranch() throws IOException {
return delegate.getFullBranch();
}
@Override
public String getBranch() throws IOException {
return delegate.getBranch();
}
@Override
public Set<ObjectId> getAdditionalHaves() {
return delegate.getAdditionalHaves();
}
@Override
public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
return delegate.getAllRefsByPeeledObjectId();
}
@Override
public File getIndexFile() throws NoWorkTreeException {
return delegate.getIndexFile();
}
@Override
public RevCommit parseCommit(AnyObjectId id)
throws IncorrectObjectTypeException, IOException,
MissingObjectException {
return delegate.parseCommit(id);
}
@Override
public DirCache readDirCache() throws NoWorkTreeException,
CorruptObjectException, IOException {
return delegate.readDirCache();
}
@Override
public DirCache lockDirCache() throws NoWorkTreeException,
CorruptObjectException, IOException {
return delegate.lockDirCache();
}
@Override
public RepositoryState getRepositoryState() {
return delegate.getRepositoryState();
}
@Override
public boolean isBare() {
return delegate.isBare();
}
@Override
public File getWorkTree() throws NoWorkTreeException {
return delegate.getWorkTree();
}
@Override
public void scanForRepoChanges() throws IOException {
delegate.scanForRepoChanges();
}
@Override
public void notifyIndexChanged(boolean internal) {
delegate.notifyIndexChanged(internal);
}
@Override
public String shortenRemoteBranchName(String refName) {
return delegate.shortenRemoteBranchName(refName);
}
@Override
public String getRemoteName(String refName) {
return delegate.getRemoteName(refName);
}
@Override
public String getGitwebDescription() throws IOException {
return delegate.getGitwebDescription();
}
@Override
public void setGitwebDescription(String description)
throws IOException {
delegate.setGitwebDescription(description);
}
@Override
public ReflogReader getReflogReader(String refName) throws IOException {
return delegate.getReflogReader(refName);
}
@Override
public String readMergeCommitMsg() throws IOException, NoWorkTreeException {
return delegate.readMergeCommitMsg();
}
@Override
public void writeMergeCommitMsg(String msg) throws IOException {
delegate.writeMergeCommitMsg(msg);
}
@Override
public String readCommitEditMsg() throws IOException, NoWorkTreeException {
return delegate.readCommitEditMsg();
}
@Override
public void writeCommitEditMsg(String msg) throws IOException {
delegate.writeCommitEditMsg(msg);
}
@Override
public List<ObjectId> readMergeHeads()
throws IOException, NoWorkTreeException {
return delegate.readMergeHeads();
}
@Override
public void writeMergeHeads(List<? extends ObjectId> heads)
throws IOException {
delegate.writeMergeHeads(heads);
}
@Override
public ObjectId readCherryPickHead()
throws IOException, NoWorkTreeException {
return delegate.readCherryPickHead();
}
@Override
public ObjectId readRevertHead() throws IOException, NoWorkTreeException {
return delegate.readRevertHead();
}
@Override
public void writeCherryPickHead(ObjectId head) throws IOException {
delegate.writeCherryPickHead(head);
}
@Override
public void writeRevertHead(ObjectId head) throws IOException {
delegate.writeRevertHead(head);
}
@Override
public void writeOrigHead(ObjectId head) throws IOException {
delegate.writeOrigHead(head);
}
@Override
public ObjectId readOrigHead() throws IOException, NoWorkTreeException {
return delegate.readOrigHead();
}
@Override
public String readSquashCommitMsg() throws IOException {
return delegate.readSquashCommitMsg();
}
@Override
public void writeSquashCommitMsg(String msg) throws IOException {
delegate.writeSquashCommitMsg(msg);
}
@Override
public List<RebaseTodoLine> readRebaseTodo(String path,
boolean includeComments) throws IOException {
return delegate.readRebaseTodo(path, includeComments);
}
@Override
public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps,
boolean append) throws IOException {
delegate.writeRebaseTodoFile(path, steps, append);
}
@Override
public Set<String> getRemoteNames() {
return delegate.getRemoteNames();
}
@Override
public void autoGC(ProgressMonitor monitor) {
delegate.autoGC(monitor);
}
}