blob: f53bb43936d64bbc56eac7ec95719e1bfde12ff5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2014 Oak Ridge National Laboratory and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* John Eblen - initial implementation
*******************************************************************************/
package org.eclipse.ptp.internal.rdt.sync.git.core;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.ptp.internal.rdt.sync.git.core.CommandRunner.CommandResults;
import org.eclipse.ptp.internal.rdt.sync.git.core.messages.Messages;
import org.eclipse.ptp.rdt.sync.core.AbstractSyncFileFilter;
import org.eclipse.ptp.rdt.sync.core.RecursiveSubMonitor;
import org.eclipse.ptp.rdt.sync.core.RemoteLocation;
import org.eclipse.ptp.rdt.sync.core.SyncFlag;
import org.eclipse.ptp.rdt.sync.core.SyncManager;
import org.eclipse.ptp.rdt.sync.core.exceptions.MissingConnectionException;
import org.eclipse.ptp.rdt.sync.core.exceptions.RemoteSyncException;
import org.eclipse.ptp.rdt.sync.core.exceptions.RemoteSyncMergeConflictException;
import org.eclipse.ptp.rdt.sync.core.services.AbstractSynchronizeService;
import org.eclipse.ptp.rdt.sync.core.services.ISynchronizeServiceDescriptor;
/**
* A Git-based synchronization service for synchronized projects
*/
public class GitSyncService extends AbstractSynchronizeService {
public static final String gitDir = ".ptp-sync"; //$NON-NLS-1$
// Name of file placed in empty directories to force sync'ing of those directories.
public static final String emptyDirectoryFileName = ".ptp-sync-folder"; //$NON-NLS-1$
public static final String commitMessage = Messages.GitSyncService_0;
public static final String remotePushBranch = "ptp-push"; //$NON-NLS-1$
// Implement storage of local JGit repositories and Git repositories.
private static final Map<IPath, JGitRepo> localDirectoryToJGitRepoMap = new HashMap<IPath, JGitRepo>();
private static final Map<RemoteLocation, GitRepo> remoteLocationToGitRepoMap = new HashMap<RemoteLocation, GitRepo>();
// Variables for managing sync threads
private static final ReentrantLock syncLock = new ReentrantLock();
private static final ConcurrentMap<ProjectAndRemoteLocationPair, AtomicLong> syncLRPending =
new ConcurrentHashMap<ProjectAndRemoteLocationPair, AtomicLong>();
private static final ConcurrentMap<ProjectAndRemoteLocationPair, AtomicLong> syncRLPending =
new ConcurrentHashMap<ProjectAndRemoteLocationPair, AtomicLong>();
// Entry indicates that the remote location has a clean (up-to-date) file filter for the project
private static final Set<LocalAndRemoteLocationPair> cleanFileFilterMap = new HashSet<LocalAndRemoteLocationPair>();
// Entry indicates that the remote location contains the most recently committed local changes
private static final Set<LocalAndRemoteLocationPair> localChangesPushed = new HashSet<LocalAndRemoteLocationPair>();
// Boilerplate class for IPath and RemoteLocation Pair
private class LocalAndRemoteLocationPair {
IPath localDir;
RemoteLocation remoteLoc;
/**
* Create new pair
* @param ld
* local directory
* @param rl
* remote location
*/
public LocalAndRemoteLocationPair(IPath ld, RemoteLocation rl) {
localDir = ld;
remoteLoc = rl;
}
/**
* Get the local directory
* @return directory
*/
public IPath getLocal() {
return localDir;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result
+ ((localDir == null) ? 0 : localDir.hashCode());
result = prime * result
+ ((remoteLoc == null) ? 0 : remoteLoc.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
LocalAndRemoteLocationPair other = (LocalAndRemoteLocationPair) obj;
if (!getOuterType().equals(other.getOuterType())) {
return false;
}
if (localDir == null) {
if (other.localDir != null) {
return false;
}
} else if (!localDir.equals(other.localDir)) {
return false;
}
if (remoteLoc == null) {
if (other.remoteLoc != null) {
return false;
}
} else if (!remoteLoc.equals(other.remoteLoc)) {
return false;
}
return true;
}
private GitSyncService getOuterType() {
return GitSyncService.this;
}
}
// Boilerplate class for IProject and RemoteLocation Pair
private class ProjectAndRemoteLocationPair {
IProject project;
RemoteLocation remoteLoc;
/**
* Create new pair
* @param p
* project
* @param rl
* remote location
*/
public ProjectAndRemoteLocationPair(IProject p, RemoteLocation rl) {
project = p;
remoteLoc = rl;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result
+ ((project == null) ? 0 : project.hashCode());
result = prime * result
+ ((remoteLoc == null) ? 0 : remoteLoc.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ProjectAndRemoteLocationPair other = (ProjectAndRemoteLocationPair) obj;
if (!getOuterType().equals(other.getOuterType())) {
return false;
}
if (project == null) {
if (other.project != null) {
return false;
}
} else if (!project.equals(other.project)) {
return false;
}
if (remoteLoc == null) {
if (other.remoteLoc != null) {
return false;
}
} else if (!remoteLoc.equals(other.remoteLoc)) {
return false;
}
return true;
}
private GitSyncService getOuterType() {
return GitSyncService.this;
}
}
/**
* Get JGit repository instance for the given project, creating it if necessary.
* @param project - cannot be null
* @param monitor
*
* @return JGit repository instance - never null
* @throws RemoteSyncException
* on problems creating the repository
*/
static JGitRepo getLocalJGitRepo(IProject project, IProgressMonitor monitor) throws RemoteSyncException {
IPath localDir = project.getLocation();
if (localDir == null) {
throw new RemoteSyncException(Messages.GitSyncService_17 + project.getName());
}
JGitRepo repo = localDirectoryToJGitRepoMap.get(localDir);
try {
if (repo == null) {
try {
repo = new JGitRepo(localDir, monitor);
localDirectoryToJGitRepoMap.put(localDir, repo);
setRepoFilesAsDerived(project);
} catch (GitAPIException e) {
throw new RemoteSyncException(e);
} catch (IOException e) {
throw new RemoteSyncException(e);
}
}
} finally {
if (monitor != null) {
monitor.done();
}
}
return repo;
}
/**
* Get Git repository instance for given remote location, creating it if necessary.
*
* @param rl
* remote location - cannot be null
* @param monitor
*
* @return Git repo instance - is null only if connection could not be resolved.
* @throws RemoteSyncException
* on problems creating the repository
*/
static GitRepo getGitRepo(RemoteLocation rl, IProgressMonitor monitor) throws RemoteSyncException {
if (rl == null) {
throw new NullPointerException();
}
GitRepo repo = remoteLocationToGitRepoMap.get(rl);
try {
if (repo == null) {
RecursiveSubMonitor subMon = RecursiveSubMonitor.convert(monitor, 100);
subMon.subTask(Messages.GitSyncService_1);
try {
subMon.subTask(Messages.GitSyncService_2);
repo = new GitRepo(rl, subMon.newChild(90));
remoteLocationToGitRepoMap.put(rl, repo);
} catch (MissingConnectionException e) {
return null;
}
}
} finally {
if (monitor != null) {
monitor.done();
}
}
return repo;
}
private static boolean consCalled = false;
/**
* Create a new instance of the GitSyncService
* @param descriptor
* service descriptor
*/
public GitSyncService(ISynchronizeServiceDescriptor descriptor) {
super(descriptor);
// Constructor for each sync service should only be called once by design of synchronized projects
// See bug 410106
assert(!consCalled) : Messages.GitSyncService_3;
consCalled = true;
}
/*
* (non-Javadoc)
* @see org.eclipse.ptp.rdt.sync.core.services.ISynchronizeService#checkout(org.eclipse.core.resources.IProject,
* org.eclipse.core.runtime.IPath[])
*/
@Override
public void checkout(IProject project, IPath[] paths) throws RemoteSyncException {
JGitRepo repo = getLocalJGitRepo(project, null);
if (repo != null) {
try {
repo.checkout(paths);
doRefresh(project);
} catch (GitAPIException e) {
throw new RemoteSyncException(e);
}
}
}
/*
* (non-Javadoc)
* @see org.eclipse.ptp.rdt.sync.core.services.ISynchronizeService#checkoutRemoteCopy(org.eclipse.core.resources.IProject,
* org.eclipse.core.runtime.IPath[])
*/
@Override
public void checkoutRemoteCopy(IProject project, IPath[] paths) throws RemoteSyncException {
JGitRepo repo = getLocalJGitRepo(project, null);
if (repo != null) {
try {
repo.checkoutRemoteCopy(paths);
doRefresh(project);
} catch (GitAPIException e) {
throw new RemoteSyncException(e);
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ptp.rdt.sync.core.serviceproviders.ISyncServiceProvider#close(
* org.eclipse.core.resources.IProject)
*/
@Override
public synchronized void close(IProject project) throws RemoteSyncException {
JGitRepo repo = getLocalJGitRepo(project, null);
if (repo != null) {
repo.close();
}
Iterator<Map.Entry<IPath, JGitRepo>> it = localDirectoryToJGitRepoMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<IPath, JGitRepo> entry = it.next();
if (entry.getValue() == repo) {
it.remove();
}
}
}
/*
* (non-Javadoc)
* @see org.eclipse.ptp.rdt.sync.core.services.ISynchronizeService#getMergeConflictFiles(
* org.eclipse.core.resources.IProject)
*/
@Override
public Set<IPath> getMergeConflictFiles(IProject project) throws RemoteSyncException {
JGitRepo repo = getLocalJGitRepo(project, null);
if (repo == null) {
return new HashSet<IPath>();
} else {
try {
return repo.getMergeConflictFiles();
} catch (GitAPIException e) {
throw new RemoteSyncException(e);
} catch (IOException e) {
throw new RemoteSyncException(e);
}
}
}
/*
* (non-Javadoc)
* @see org.eclipse.ptp.rdt.sync.core.services.ISynchronizeService#getMergeConflictParts(org.eclipse.core.resources.IProject,
* org.eclipse.core.resources.IFile)
*/
@Override
public String[] getMergeConflictParts(IProject project, IFile file) throws RemoteSyncException {
JGitRepo repo = getLocalJGitRepo(project, null);
if (repo == null) {
return null;
} else {
try {
return repo.getMergeConflictParts(file.getProjectRelativePath());
} catch (GitAPIException e) {
throw new RemoteSyncException(e);
} catch (IOException e) {
throw new RemoteSyncException(e);
}
}
}
/*
* (non-Javadoc)
* @see org.eclipse.ptp.rdt.sync.core.services.ISynchronizeService#setMergeAsResolved(org.eclipse.core.resources.IProject,
* org.eclipse.core.runtime.IPath[])
*/
@Override
public void setMergeAsResolved(IProject project, IPath[] paths) throws RemoteSyncException {
JGitRepo repo = getLocalJGitRepo(project, null);
if (repo != null) {
try {
repo.setMergeAsResolved(paths);
} catch (GitAPIException e) {
throw new RemoteSyncException(e);
}
}
}
/*
* (non-Javadoc)
* @see org.eclipse.ptp.rdt.sync.core.services.ISynchronizeService#synchronize(org.eclipse.core.resources.IProject,
* org.eclipse.ptp.rdt.sync.core.RemoteLocation, org.eclipse.core.resources.IResourceDelta,
* org.eclipse.core.runtime.IProgressMonitor, java.util.EnumSet)
*/
@Override
public void synchronize(final IProject project, RemoteLocation rl, IResourceDelta delta, IProgressMonitor monitor,
Set<SyncFlag> syncFlags) throws CoreException {
if (project == null || rl == null) {
throw new NullPointerException();
}
RemoteLocation remoteLoc = new RemoteLocation(rl);
RecursiveSubMonitor subMon = RecursiveSubMonitor.convert(monitor, 100);
try {
/*
* A synchronize with SyncFlag.BOTH guarantees that both directories are in sync.
*
* More precise: it guarantees that all changes written to disk at the moment of the call are guaranteed to be
* synchronized between both directories. No guarantees are given for changes occurring during the synchronize call.
*
* To satisfy this guarantee, this call needs to make sure that both the current delta and all outstanding sync
* requests finish before this call returns.
*
* Example: Why sync if current delta is empty? The RemoteMakeBuilder forces a sync before and after building. In some
* cases, we want to ensure repos are synchronized regardless of the passed delta, which can be set to null.
*/
ProjectAndRemoteLocationPair syncTarget = new ProjectAndRemoteLocationPair(project, remoteLoc);
Boolean syncLR = syncFlags.contains(SyncFlag.SYNC_LR);
Boolean syncRL = syncFlags.contains(SyncFlag.SYNC_RL);
Set<SyncFlag> modifiedSyncFlags = new HashSet<SyncFlag>(syncFlags);
// Do not sync LR (local-to-remote) if another thread is already waiting to do it.
if (syncLR) {
AtomicLong threadCount = syncLRPending.putIfAbsent(syncTarget, new AtomicLong(1));
if (threadCount != null) {
if (threadCount.get() > 0) {
syncLR = false;
modifiedSyncFlags.remove(SyncFlag.SYNC_LR);
} else {
threadCount.incrementAndGet();
}
}
}
// Do not sync RL (remote-to-local) if another thread is already waiting to do it.
if (syncRL) {
AtomicLong threadCount = syncRLPending.putIfAbsent(syncTarget, new AtomicLong(1));
if (threadCount != null) {
if (threadCount.get() > 0) {
syncRL = false;
modifiedSyncFlags.remove(SyncFlag.SYNC_RL);
} else {
threadCount.incrementAndGet();
}
}
}
// Return if we have nothing to do.
if (!(syncLR || syncRL)) {
return;
}
// lock syncLock. interruptible by progress monitor
try {
while (!syncLock.tryLock(50, TimeUnit.MILLISECONDS)) {
if (subMon.isCanceled()) {
throw new CoreException(new Status(IStatus.CANCEL, Activator.PLUGIN_ID, Messages.GitSyncService_4));
}
}
} catch (InterruptedException e1) {
throw new CoreException(new Status(IStatus.CANCEL, Activator.PLUGIN_ID, Messages.GitSyncService_5));
} finally {
if (syncLR) {
AtomicLong LRPending = syncLRPending.get(syncTarget);
assert(LRPending != null) : Messages.GitSyncService_20;
LRPending.decrementAndGet();
}
if (syncRL) {
AtomicLong RLPending = syncRLPending.get(syncTarget);
assert(RLPending != null) : Messages.GitSyncService_21;
RLPending.decrementAndGet();
}
}
try {
subMon.subTask(Messages.GitSyncService_9);
doSync(project, remoteLoc, modifiedSyncFlags, subMon.newChild(95));
} catch (RemoteSyncMergeConflictException e) {
subMon.subTask(Messages.GitSyncService_10);
// Refresh after merge conflict since conflicted files are altered with markup.
doRefresh(project);
throw e;
} finally {
syncLock.unlock();
}
// Sync successful - re-enable error messages. This is really UI code, but there is no way at the moment to notify UI
// of a successful sync.
SyncManager.setShowErrors(project, true);
// Refresh after sync to display changes
subMon.subTask(Messages.GitSyncService_10);
doRefresh(project);
} finally {
if (monitor != null) {
monitor.done();
}
}
}
/**
* Synchronize the given project to the given remote.
* The sync strategy follows these three high-level steps:
* 1) Commit local changes.
* 2) If sync RL is requested, commit and fetch remote changes and then merge them locally.
* 3) If sync LR is requested, push local changes to remote and merge. Only fast-forward merging is allowed to prevent remote
* merge conflicts. On failure, do a sync RL, even if not requested, to capture remote changes, and repeat sync LR. It is
* important that merge conflicts happen locally, so they can be handled in Eclipse with JGit. If a merge conflict occurs,
* sync'ing is halted (no sync LR is done) and only re-enabled once the conflicts are resolved.
*
* @param project
* the local project
* @param remoteLoc
* the remote location
* @param syncFlags
* flags to modify sync behavior
* @param monitor
* @throws RemoteSyncException
* for various problems sync'ing. All exceptions are wrapped in a RemoteSyncException and thrown, so that clients
* can always detect when a sync fails and why.
*/
private void doSync(IProject project, RemoteLocation remoteLoc, Set<SyncFlag> syncFlags, IProgressMonitor monitor)
throws RemoteSyncException {
boolean syncLR = syncFlags.contains(SyncFlag.SYNC_LR);
boolean syncRL = syncFlags.contains(SyncFlag.SYNC_RL);
RecursiveSubMonitor subMon = RecursiveSubMonitor.convert(monitor, 100);
try {
subMon.subTask(Messages.GitSyncService_6);
JGitRepo localRepo = getLocalJGitRepo(project, subMon.newChild(5));
if (localRepo.inUnresolvedMergeState()) {
throw new RemoteSyncMergeConflictException(Messages.GitSyncService_8);
}
LocalAndRemoteLocationPair lrpair = new LocalAndRemoteLocationPair(localRepo.getDirectory(), remoteLoc);
// Commit local changes
subMon.subTask(Messages.GitSyncService_12);
if (localRepo.commit(subMon.newChild(5))) {
// New changes - mark all local/remote pairs with current local as needing to be updated.
Iterator<LocalAndRemoteLocationPair> it = localChangesPushed.iterator();
while (it.hasNext()) {
LocalAndRemoteLocationPair lrp = it.next();
if (lrp.getLocal().equals(localRepo.getDirectory())) {
it.remove();
}
}
}
// Return early if local changes have already been pushed and remote-to-local sync was not requested.
if ((!syncLR || localChangesPushed.contains(lrpair)) && (!syncRL)) {
return;
}
// Get remote repository. creating it if necessary
subMon.subTask(Messages.GitSyncService_7);
GitRepo remoteRepo = getGitRepo(remoteLoc, subMon.newChild(10));
// Unresolved connection - abort
if (remoteRepo == null) {
return;
}
// Update remote file filter
if (!cleanFileFilterMap.contains(lrpair)) {
subMon.subTask(Messages.GitSyncService_11);
remoteRepo.uploadFilter(localRepo, subMon.newChild(10));
cleanFileFilterMap.add(lrpair);
}
// Sync remote to local
if (syncRL) {
subMon.subTask(Messages.GitSyncService_24);
doSyncRL(localRepo, remoteRepo, subMon.newChild(30));
}
// Sync local to remote
if (syncLR && (!localChangesPushed.contains(lrpair))) {
subMon.subTask(Messages.GitSyncService_25);
CommandResults results = doSyncLR(localRepo, remoteRepo, subMon.newChild(30));
boolean success = (results.getExitCode() == 0);
// If the remote merge fails and sync RL was not done, then the failure *most likely* occurred because files
// changed on remote. So we do a sync RL to capture changes and then sync LR again.
if (!success && !syncRL) {
subMon.subTask(Messages.GitSyncService_26);
doSyncRL(localRepo, remoteRepo, subMon.newChild(30));
subMon.subTask(Messages.GitSyncService_27);
results = doSyncLR(localRepo, remoteRepo, subMon.newChild(10));
success = (results.getExitCode() == 0);
}
if (success) {
localChangesPushed.add(lrpair);
} else {
// This should never happen since sync RL was done prior to sync LR. Only remote changes during the sync or
// an unanticipated problem could lead to here.
throw new RemoteSyncException(Messages.GitSyncService_23 + results.getStderr());
}
}
} catch (final IOException e) {
throw new RemoteSyncException(e);
} catch (GitAPIException e) {
throw new RemoteSyncException(e);
} catch (MissingConnectionException e) {
// nothing to do
} finally {
if (monitor != null) {
monitor.done();
}
}
}
// Returns whether sync succeeds (true) or fails (false) in an expected way. Failure can occur normally if remote files
// were altered, and thus the remote merge cannot be completed.
private CommandResults doSyncLR(JGitRepo localRepo, GitRepo remoteRepo, IProgressMonitor monitor) throws RemoteSyncException,
MissingConnectionException {
RecursiveSubMonitor subMon = RecursiveSubMonitor.convert(monitor, 30);
try {
if (localRepo.getGit().branchList().call().size() > 0) { // check whether master was already created
subMon.subTask(Messages.GitSyncService_18);
localRepo.push(remoteRepo, subMon.newChild(20));
subMon.subTask(Messages.GitSyncService_28);
return remoteRepo.merge(subMon.newChild(10));
}
return new CommandResults(); // Successful result with no output
} catch (TransportException e) {
throw new RemoteSyncException(e);
} catch (GitAPIException e) {
throw new RemoteSyncException(e);
} finally {
if (monitor != null) {
monitor.done();
}
}
}
private void doSyncRL(JGitRepo localRepo, GitRepo remoteRepo, IProgressMonitor monitor) throws RemoteSyncException,
MissingConnectionException {
RecursiveSubMonitor subMon = RecursiveSubMonitor.convert(monitor, 90);
try {
// Commit remote changes
subMon.subTask(Messages.GitSyncService_13);
remoteRepo.commitRemoteFiles(subMon.newChild(40));
// Download remote changes
subMon.subTask(Messages.GitSyncService_14);
try {
localRepo.fetch(remoteRepo, subMon.newChild(40));
} catch (TransportException e) {
// Fetch can fail simply because the remote branch isn't set up yet. So just return in that case and throw an
// exception otherwise.
// Even for the first case, however, a proceeding merge will fail (remote ref is not created), so be sure to
// always abort and not attempt a merge.
if (e.getMessage().startsWith("Remote does not have ")) { //$NON-NLS-1$
return;
} else {
throw new RemoteSyncException(e);
}
}
// Merge remote changes into local repository
subMon.subTask(Messages.GitSyncService_15);
try {
org.eclipse.jgit.api.MergeResult mergeResult = localRepo.merge(subMon.newChild(10));
if (mergeResult.getFailingPaths() != null) {
String message = Messages.GitSyncService_16;
for (String s : mergeResult.getFailingPaths().keySet()) {
message += System.getProperty("line.separator") + s; //$NON-NLS-1$
}
throw new RemoteSyncException(message);
}
if (localRepo.inUnresolvedMergeState()) {
throw new RemoteSyncMergeConflictException(Messages.GitSyncService_8);
// Even if we later decide not to throw an exception, it is important not to proceed after a merge conflict.
// return;
}
} catch (GitAPIException e) {
throw new RemoteSyncException(e);
} catch (IOException e) {
throw new RemoteSyncException(e);
}
} finally {
if (monitor != null) {
monitor.done();
}
}
}
/*
* (non-Javadoc)
* @see org.eclipse.ptp.rdt.sync.core.services.ISynchronizeService#getSyncFileFilter(org.eclipse.core.resources.IProject)
*/
@Override
public AbstractSyncFileFilter getSyncFileFilter(IProject project) throws RemoteSyncException {
return getLocalJGitRepo(project, null).getFilter();
}
/*
* (non-Javadoc)
* @see org.eclipse.ptp.rdt.sync.core.services.ISynchronizeService#setSyncFileFilter(org.eclipse.core.resources.IProject,
* org.eclipse.ptp.rdt.sync.core.AbstractSyncFileFilter)
*/
@Override
public void setSyncFileFilter(IProject project, AbstractSyncFileFilter filter) throws RemoteSyncException {
Iterator<LocalAndRemoteLocationPair> it = cleanFileFilterMap.iterator();
IPath localDir = project.getLocation();
if (localDir == null) {
throw new RemoteSyncException(Messages.GitSyncService_17 + project.getName());
}
while (it.hasNext()) {
LocalAndRemoteLocationPair lp = it.next();
if (lp.getLocal().equals(localDir)) {
it.remove();
}
}
JGitRepo localRepo = getLocalJGitRepo(project, null);
localRepo.setFilter(filter);
}
// Refresh the workspace in a separate thread
// Bug 374409 - this prevents deadlock caused by locking both the sync lock and the workspace lock.
private static Thread doRefresh(final IProject project) {
Thread refreshWorkspaceThread = new Thread(new Runnable() {
@Override
public void run() {
try {
project.refreshLocal(IResource.DEPTH_INFINITE, null);
} catch (CoreException e) {
Activator.log(Messages.JGitRepo_16, e);
}
}
}, "Refresh workspace thread"); //$NON-NLS-1$
refreshWorkspaceThread.start();
return refreshWorkspaceThread;
}
// Set Git repository files as derived.
// This prevents user-level operations, such as searching, from considering the repository directory.
private static void setRepoFilesAsDerived(final IProject project) {
// First refresh project so that files appear
final Thread refreshThread = doRefresh(project);
// Set derived only after refresh completes
Thread setDerivedThread = new Thread(new Runnable() {
@Override
public void run() {
try {
refreshThread.join();
project.getFolder(GitSyncService.gitDir).setDerived(true, null);
} catch (InterruptedException e) {
Activator.log(e);
} catch (CoreException e) {
Activator.log(e);
}
}
}, "Set repository as derived thread"); //$NON-NLS-1$
setDerivedThread.start();
}
}