Bug 426279 - Clean up sync flags and use them during synchronization

Change-Id: I90fa33a25a2317522a0b5538d0854f1e04fb2159
Signed-off-by: John Eblen <jeblen@acm.org>
diff --git a/rdt/org.eclipse.ptp.rdt.sync.cdt.core/src/org/eclipse/ptp/internal/rdt/sync/cdt/core/SyncGCCBuildCommandParser.java b/rdt/org.eclipse.ptp.rdt.sync.cdt.core/src/org/eclipse/ptp/internal/rdt/sync/cdt/core/SyncGCCBuildCommandParser.java
index a622644..bd7b07d 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.cdt.core/src/org/eclipse/ptp/internal/rdt/sync/cdt/core/SyncGCCBuildCommandParser.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.cdt.core/src/org/eclipse/ptp/internal/rdt/sync/cdt/core/SyncGCCBuildCommandParser.java
@@ -17,6 +17,7 @@
 import org.eclipse.cdt.core.settings.model.CIncludePathEntry;
 import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
 import org.eclipse.cdt.core.settings.model.ICSettingEntry;
+import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
 import org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuildCommandParser;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Path;
diff --git a/rdt/org.eclipse.ptp.rdt.sync.cdt.core/src/org/eclipse/ptp/internal/rdt/sync/cdt/core/remotemake/SyncCommandLauncher.java b/rdt/org.eclipse.ptp.rdt.sync.cdt.core/src/org/eclipse/ptp/internal/rdt/sync/cdt/core/remotemake/SyncCommandLauncher.java
index f7dd928..7add4d4 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.cdt.core/src/org/eclipse/ptp/internal/rdt/sync/cdt/core/remotemake/SyncCommandLauncher.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.cdt.core/src/org/eclipse/ptp/internal/rdt/sync/cdt/core/remotemake/SyncCommandLauncher.java
@@ -386,15 +386,7 @@
 	private void syncOnPostBuild(IProgressMonitor monitor) throws CoreException {
 		SyncConfig config = SyncConfigManager.getActive(getProject());
 		if (shouldSyncAfterRun && SyncManager.getSyncAuto() && config.isSyncOnPostBuild()) {
-			switch (SyncManager.getSyncMode(getProject())) {
-			case ACTIVE:
-				SyncManager.syncBlocking(null, getProject(), SyncFlag.FORCE, monitor, null);
-				break;
-
-			case ALL:
-				SyncManager.syncAllBlocking(null, getProject(), SyncFlag.FORCE, null);
-				break;
-			}
+			SyncManager.syncBlocking(null, getProject(), SyncFlag.RL_ONLY, monitor, null);
 		}
 	}
 
@@ -403,11 +395,11 @@
 		if (shouldSyncBeforeRun && config.isSyncOnPreBuild()) {
 			switch (SyncManager.getSyncMode(getProject())) {
 			case ACTIVE:
-				SyncManager.syncBlocking(null, getProject(), SyncFlag.FORCE, monitor, null);
+				SyncManager.syncBlocking(null, getProject(), SyncFlag.LR_ONLY, monitor, null);
 				break;
 
 			case ALL:
-				SyncManager.syncAllBlocking(null, getProject(), SyncFlag.FORCE, null);
+				SyncManager.syncAllBlocking(null, getProject(), SyncFlag.LR_ONLY, null);
 				break;
 			}
 		}
diff --git a/rdt/org.eclipse.ptp.rdt.sync.cdt.ui/src/org/eclipse/ptp/internal/rdt/sync/cdt/ui/wizards/NewRemoteSyncProjectWizard.java b/rdt/org.eclipse.ptp.rdt.sync.cdt.ui/src/org/eclipse/ptp/internal/rdt/sync/cdt/ui/wizards/NewRemoteSyncProjectWizard.java
index 206960a..99c6754 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.cdt.ui/src/org/eclipse/ptp/internal/rdt/sync/cdt/ui/wizards/NewRemoteSyncProjectWizard.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.cdt.ui/src/org/eclipse/ptp/internal/rdt/sync/cdt/ui/wizards/NewRemoteSyncProjectWizard.java
@@ -121,7 +121,7 @@
 		// Force an initial sync
 		if (success && project != null) {
 			try {
-				SyncManager.sync(null, project, SyncFlag.FORCE, new CommonSyncExceptionHandler(false, true));
+				SyncManager.sync(null, project, SyncFlag.BOTH, new CommonSyncExceptionHandler(false, true));
 			} catch (CoreException e) {
 				// This should never happen because only a blocking sync can throw a core exception.
 				Activator.log(Messages.NewRemoteSyncProjectWizard_0, e);
diff --git a/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/internal/rdt/sync/core/SynchronizedResource.java b/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/internal/rdt/sync/core/SynchronizedResource.java
index af65629..2f1756a 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/internal/rdt/sync/core/SynchronizedResource.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/internal/rdt/sync/core/SynchronizedResource.java
@@ -44,7 +44,7 @@
 	 */
 	@Override
 	public void refresh(IProgressMonitor monitor) throws CoreException {
-		SyncManager.syncBlocking(null, fResource.getProject(), SyncFlag.FORCE, monitor, null);
+		SyncManager.syncBlocking(null, fResource.getProject(), SyncFlag.BOTH, monitor, null);
 	}
 
 	/*
diff --git a/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/SyncFlag.java b/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/SyncFlag.java
index 98621f5..209fd59 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/SyncFlag.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/SyncFlag.java
@@ -1,5 +1,5 @@
 /*******************************************************************************

- * Copyright (c) 2011 Oak Ridge National Laboratory and others.

+ * Copyright (c) 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

@@ -10,33 +10,43 @@
  *******************************************************************************/

 package org.eclipse.ptp.rdt.sync.core;

 

+import java.util.Collections;

 import java.util.EnumSet;

+import java.util.Set;

 

 /**

- * Flags to control the behavior of synchronization. Currently, the individual FORCE_SYNC_TO_LOCAL and FORCE_SYNC_TO_REMOTE flags

- * are not used. Instead, the EnumSets are used as defined below.

+ * Flags to control the behavior of synchronization.

  * 

- * Please note that the synchronization protocol is a work in progress, and the meanings of these flags are subject to change.

- * 

- * @since 3.0

+ * @since 4.0

  */

 public enum SyncFlag {

-	DISABLE_SYNC, FORCE_SYNC_TO_LOCAL, FORCE_SYNC_TO_REMOTE;

+	/**

+	 * Sync local to remote

+	 * @since 4.0

+	 */

+	SYNC_LR,

+	

+	/**

+	 * Sync remote to local

+	 * @since 4.0

+	 */

+	SYNC_RL;

 

 	/**

-	 * Do not actually transfer files. Just do any necessary bookkeeping. (This is used, for example, when files change on the

-	 * system, but the user has disabled sync'ing.)

+	 * Convenience flag set for sync'ing both directions (from local to remote and from remote to local).

+	 * @since 4.0

 	 */

-	public static final EnumSet<SyncFlag> NO_SYNC = EnumSet.of(SyncFlag.DISABLE_SYNC);

+	public static final Set<SyncFlag> BOTH = Collections.unmodifiableSet(EnumSet.allOf(SyncFlag.class));

 

 	/**

-	 * Transfer files "if needed". For example, if a resource change has affected the local repository. (The meaning of "if needed"

-	 * is subject to change.

+	 * Convenience flag set for sync'ing only from local to remote.

+	 * @since 4.0

 	 */

-	public static final EnumSet<SyncFlag> NO_FORCE = EnumSet.noneOf(SyncFlag.class);

+	public static final Set<SyncFlag> LR_ONLY = Collections.unmodifiableSet(EnumSet.of(SyncFlag.SYNC_LR));

 

 	/**

-	 * Force transferring of files. This could be necessary, for example, to download remote changes.

+	 * Convenience flag set for sync'ing only from remote to local.

+	 * @since 4.0

 	 */

-	public static final EnumSet<SyncFlag> FORCE = EnumSet.of(SyncFlag.FORCE_SYNC_TO_LOCAL, SyncFlag.FORCE_SYNC_TO_REMOTE);

-}

+	public static final Set<SyncFlag> RL_ONLY = Collections.unmodifiableSet(EnumSet.of(SyncFlag.SYNC_RL));

+}
\ No newline at end of file
diff --git a/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/SyncManager.java b/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/SyncManager.java
index a4e891b..d2e9c30 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/SyncManager.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/SyncManager.java
@@ -53,11 +53,11 @@
 		private final SyncConfig fSyncConfig;
 		private final IResourceDelta fDelta;
 		private final ISynchronizeService fSyncService;
-		private final EnumSet<SyncFlag> fSyncFlags;
+		private final Set<SyncFlag> fSyncFlags;
 		private final ISyncExceptionHandler fSyncExceptionHandler;
 
 		public SynchronizeJob(IProject project, SyncConfig syncConfig, IResourceDelta delta, ISynchronizeService syncService,
-				EnumSet<SyncFlag> syncFlags, ISyncExceptionHandler seHandler) {
+				Set<SyncFlag> syncFlags, ISyncExceptionHandler seHandler) {
 			super(Messages.SyncManager_4);
 			fProject = project;
 			fSyncConfig = syncConfig;
@@ -343,7 +343,7 @@
 	}
 
 	// Note that the monitor is ignored for non-blocking jobs since SynchronizeJob creates its own monitor
-	private static Job[] scheduleSyncJobs(IResourceDelta delta, IProject project, EnumSet<SyncFlag> syncFlags, boolean syncAll,
+	private static Job[] scheduleSyncJobs(IResourceDelta delta, IProject project, Set<SyncFlag> syncFlags, boolean syncAll,
 			boolean isBlocking, boolean useExceptionHandler, ISyncExceptionHandler seHandler, IProgressMonitor monitor)
 			throws CoreException {
 		int jobNum = 0;
@@ -486,7 +486,7 @@
 		SyncUtils.flushNode(node);
 	}
 
-	private static Job sync(IResourceDelta delta, IProject project, EnumSet<SyncFlag> syncFlags, boolean isBlocking,
+	private static Job sync(IResourceDelta delta, IProject project, Set<SyncFlag> syncFlags, boolean isBlocking,
 			boolean useExceptionHandler, ISyncExceptionHandler seHandler, IProgressMonitor monitor) throws CoreException {
 		if (getSyncMode(project) == SyncMode.UNAVAILABLE) {
 			return null;
@@ -509,8 +509,9 @@
 	 *            logic to handle exceptions
 	 * @return the scheduled sync job
 	 * @throws CoreException
+	 * @since 4.0
 	 */
-	public static Job sync(IResourceDelta delta, IProject project, EnumSet<SyncFlag> syncFlags, ISyncExceptionHandler seHandler)
+	public static Job sync(IResourceDelta delta, IProject project, Set<SyncFlag> syncFlags, ISyncExceptionHandler seHandler)
 			throws CoreException {
 		return sync(delta, project, syncFlags, false, true, seHandler, null);
 	}
@@ -529,8 +530,9 @@
 	 * @return array of sync jobs scheduled
 	 * @throws CoreException
 	 *             on problems sync'ing
+	 * @since 4.0
 	 */
-	public static Job[] syncAll(IResourceDelta delta, IProject project, EnumSet<SyncFlag> syncFlags, ISyncExceptionHandler seHandler)
+	public static Job[] syncAll(IResourceDelta delta, IProject project, Set<SyncFlag> syncFlags, ISyncExceptionHandler seHandler)
 			throws CoreException {
 		if (getSyncMode(project) == SyncMode.UNAVAILABLE) {
 			return new Job[0];
@@ -554,8 +556,9 @@
 	 * @return array of sync jobs scheduled
 	 * @throws CoreException
 	 *             on problems sync'ing
+	 * @since 4.0
 	 */
-	public static Job[] syncAllBlocking(IResourceDelta delta, IProject project, EnumSet<SyncFlag> syncFlags,
+	public static Job[] syncAllBlocking(IResourceDelta delta, IProject project, Set<SyncFlag> syncFlags,
 			ISyncExceptionHandler seHandler) throws CoreException {
 		if (getSyncMode(project) == SyncMode.UNAVAILABLE) {
 			return new Job[0];
@@ -579,8 +582,9 @@
 	 * @return the scheduled sync job
 	 * @throws CoreException
 	 *             on problems sync'ing
+	 * @since 4.0
 	 */
-	public static Job syncBlocking(IResourceDelta delta, IProject project, EnumSet<SyncFlag> syncFlags, IProgressMonitor monitor)
+	public static Job syncBlocking(IResourceDelta delta, IProject project, Set<SyncFlag> syncFlags, IProgressMonitor monitor)
 			throws CoreException {
 		return sync(delta, project, syncFlags, true, false, null, monitor);
 	}
@@ -602,8 +606,9 @@
 	 * @return the scheduled sync job
 	 * @throws CoreException
 	 *             on problems sync'ing
+	 * @since 4.0
 	 */
-	public static Job syncBlocking(IResourceDelta delta, IProject project, EnumSet<SyncFlag> syncFlags, IProgressMonitor monitor,
+	public static Job syncBlocking(IResourceDelta delta, IProject project, Set<SyncFlag> syncFlags, IProgressMonitor monitor,
 			ISyncExceptionHandler seHandler) throws CoreException {
 		return sync(delta, project, syncFlags, true, true, seHandler, monitor);
 	}
diff --git a/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/services/ISynchronizeService.java b/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/services/ISynchronizeService.java
index 12855e4..6eab809 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/services/ISynchronizeService.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.core/src/org/eclipse/ptp/rdt/sync/core/services/ISynchronizeService.java
@@ -118,7 +118,7 @@
 	 * @since 4.0
 	 */
 	public void synchronize(IProject project, RemoteLocation remoteLoc, IResourceDelta delta,
-			IProgressMonitor monitor, EnumSet<SyncFlag> syncFlags) throws CoreException;
+			IProgressMonitor monitor, Set<SyncFlag> syncFlags) throws CoreException;
 
 	/**
 	 * Get SyncFileFilter for given project
diff --git a/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/GitRepo.java b/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/GitRepo.java
index c01f378..2645c20 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/GitRepo.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/GitRepo.java
@@ -320,9 +320,13 @@
      */
     public void merge(IProgressMonitor monitor) throws RemoteSyncException, MissingConnectionException {
 		CommandResults mergeResults;
-		// ff-only prevents accidental corruption of the remote repository but is supported only in recent Git versions.
-		// final String command = gitCommand + " merge --ff-only " + remotePushBranch; //$NON-NLS-1$
-		final String command = gitCommand() + " merge " + GitSyncService.remotePushBranch; //$NON-NLS-1$
+		// ff-only was introduced in Git 1.6.6 and prevents accidental corruption of the remote repository.
+		String command;
+		if (remoteGitVersion >= 1060600) {
+			command = gitCommand() + " merge --ff-only " + GitSyncService.remotePushBranch; //$NON-NLS-1$
+		} else {
+			command = gitCommand() + " merge " + GitSyncService.remotePushBranch; //$NON-NLS-1$
+		}
 
 		try {
 			mergeResults = this.executeRemoteCommand(command, monitor);
@@ -367,6 +371,37 @@
 	}
 
 	/**
+	 * Get the SHA-1 of the current head commit
+     *
+     * @param monitor
+	 * @return SHA-1 hash code or null if no HEAD commit (can happen between initialization and first commit)
+     *
+     * @throws RemoteSyncException
+	 * 			on problems executing the necessary remote commands.
+	 * @throws MissingConnectionException
+	 * 			if the connection is unresolved
+	 */
+	public String getHead(IProgressMonitor monitor) throws RemoteSyncException, MissingConnectionException {
+		String command = gitCommand() + " rev-parse HEAD"; //$NON-NLS-1$
+		CommandResults headResults;
+		try {
+			headResults = this.executeRemoteCommand(command, monitor);
+		} catch (IOException e) {
+			throw new RemoteSyncException(e);
+		} catch (InterruptedException e) {
+			throw new RemoteSyncException(e);
+		} catch (RemoteConnectionException e) {
+			throw new RemoteSyncException(e);
+		}
+		// Assume failure occurs because there is no head.
+		if (headResults.getExitCode() > 0) {
+			return null;
+		} else {
+			return headResults.getStdout().trim();
+		}
+	}
+
+	/**
 	 * Get the remote location of this repository
 	 * @return remote location
 	 */
@@ -378,7 +413,7 @@
 	 * Return the Git version used for this repository
 	 *
 	 * @param monitor
-	 * @return Git version as a single int in the format: MMMmmmrrr (Major, minor, and revision)
+	 * @return Git version as a single int in the format: MMmmrrpp (Major, minor, revision, and patch)
 	 *
 	 * @throws RemoteSyncException
 	 * @throws MissingConnectionException
diff --git a/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/GitSyncService.java b/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/GitSyncService.java
index f19e664..47d1adc 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/GitSyncService.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/GitSyncService.java
@@ -11,7 +11,6 @@
 package org.eclipse.ptp.internal.rdt.sync.git.core;
 
 import java.io.IOException;
-import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -62,11 +61,15 @@
 
 	// Variables for managing sync threads
 	private static final ReentrantLock syncLock = new ReentrantLock();
-	private static final ConcurrentMap<ProjectAndRemoteLocationPair, AtomicLong> syncThreadsWaiting =
+	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 {
@@ -408,7 +411,7 @@
 	 */
 	@Override
 	public void synchronize(final IProject project, RemoteLocation rl, IResourceDelta delta, IProgressMonitor monitor,
-			EnumSet<SyncFlag> syncFlags) throws CoreException {
+			Set<SyncFlag> syncFlags) throws CoreException {
 		if (project == null || rl == null) {
 			throw new NullPointerException();
 		}
@@ -417,7 +420,7 @@
 
 		try {
 			/*
-			 * A synchronize with SyncFlag.FORCE guarantees that both directories are in sync.
+			 * 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.
@@ -428,24 +431,41 @@
 			 * 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.
 			 */
-			// TODO: We are not using the individual "sync to local" and "sync to remote" flags yet.
-			if (syncFlags.contains(SyncFlag.DISABLE_SYNC)) {
-				return;
+			
+			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();
+					}
+				}
 			}
 
-			ProjectAndRemoteLocationPair syncTarget = new ProjectAndRemoteLocationPair(project, remoteLoc);
-		    AtomicLong threadCount = syncThreadsWaiting.get(syncTarget);
-		    if (threadCount != null && threadCount.get() > 0 && syncFlags == SyncFlag.NO_FORCE) {
-		    	return; // the queued thread will do the work for us. And we don't have to wait because of NO_FORCE
-		    }
+			// 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();
+					}
+				}
+			}
 
-		    // Increment the value, initializing it if necessary. See:
-		    // http://stackoverflow.com/questions/2539654/java-concurrency-many-writers-one-reader/2539761#2539761
-		    if (threadCount == null){ 
-		    	threadCount = syncThreadsWaiting.putIfAbsent(syncTarget, new AtomicLong(1)); 
-		    } 
-		    if(threadCount != null){
-		    	threadCount.incrementAndGet();      
+		    // Return if we have nothing to do.
+		    if (!(syncLR || syncRL)) {
+		    	return;
 		    }
 
 			// lock syncLock. interruptible by progress monitor
@@ -458,14 +478,21 @@
 			} catch (InterruptedException e1) {
 				throw new CoreException(new Status(IStatus.CANCEL, Activator.PLUGIN_ID, Messages.GitSyncService_5));
 			} finally {
-				threadCount = syncThreadsWaiting.get(syncTarget);
-				assert(threadCount != null) : Messages.GitSyncService_19;
-				threadCount.decrementAndGet();
+				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, syncFlags, subMon.newChild(95));
+				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.
@@ -490,12 +517,12 @@
 	}
 
 	/**
-	 * Synchronize the given project to the given remote. Currently both directions are always synchronized.
+	 * Synchronize the given project to the given remote.
 	 * The sync strategy follows these three high-level steps:
 	 * 1) Commit local and remote changes. These are independent operations.
-	 * 2) Fetch remote changes and merge them locally. Thus, all merge conflicts should occur locally and thus easily managed.
-	 * 3) Push local changes to remote and merge them remotely. This final merge should never fail assuming files are
-	 *    unchanged during the sync.
+	 * 2) Fetch remote changes and merge them locally. Thus, all merge conflicts should occur locally and can be easily managed.
+	 * 3) Push local changes to remote and merge them remotely. This final merge should never fail assuming files are unchanged
+	 *    during the sync.
 	 *
 	 * @param project
 	 * 				the local project
@@ -508,7 +535,7 @@
 	 *             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, EnumSet<SyncFlag> syncFlags, IProgressMonitor monitor)
+	private void doSync(IProject project, RemoteLocation remoteLoc, Set<SyncFlag> syncFlags, IProgressMonitor monitor)
 			throws RemoteSyncException {
 		RecursiveSubMonitor subMon = RecursiveSubMonitor.convert(monitor, 100);
 		try {
@@ -519,60 +546,83 @@
 				throw new RemoteSyncMergeConflictException(Messages.GitSyncService_8);
 			}
 
+			LocalAndRemoteLocationPair lrpair = new LocalAndRemoteLocationPair(localRepo.getDirectory(), remoteLoc);
+
 			// Commit local changes
 			subMon.subTask(Messages.GitSyncService_12);
-			boolean hasChanges = localRepo.commit(subMon.newChild(5));
-			if ((!hasChanges) && (syncFlags == SyncFlag.NO_FORCE)) {
+			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 ((localChangesPushed.contains(lrpair)) && (!syncFlags.contains(SyncFlag.SYNC_RL))) {
 				return;
 			}
 
 			// Get remote repository. creating it if necessary
 			subMon.subTask(Messages.GitSyncService_7);
 			GitRepo remoteRepo = getGitRepo(remoteLoc, subMon.newChild(15));
+			
 			// Unresolved connection - abort
 			if (remoteRepo == null) {
 				return;
 			}
 
 			// Update remote file filter
-			LocalAndRemoteLocationPair lp = new LocalAndRemoteLocationPair(localRepo.getDirectory(), remoteRepo.getRemoteLocation());
-			int commitWork = 20;
-			if (!cleanFileFilterMap.contains(lp)) {
+			int commitWork = 15;
+			if (!cleanFileFilterMap.contains(lrpair)) {
 				commitWork -= 10;
 				subMon.subTask(Messages.GitSyncService_11);
 				remoteRepo.uploadFilter(localRepo, subMon.newChild(10));
-				cleanFileFilterMap.add(lp);
+				cleanFileFilterMap.add(lrpair);
 			}
 
+			// Commit remote changes
 			subMon.subTask(Messages.GitSyncService_13);
 			remoteRepo.commitRemoteFiles(subMon.newChild(commitWork));
 
-			try {
-				// Fetch the remote repository
-				subMon.subTask(Messages.GitSyncService_14);
-				localRepo.fetch(remoteRepo.getRemoteLocation(), subMon.newChild(20));
+			// Get hash code of the head of the remote repository
+			subMon.subTask(Messages.GitSyncService_22);
+			String remoteHead = remoteRepo.getHead(subMon.newChild(5));
 
-				// Merge it with local
-				subMon.subTask(Messages.GitSyncService_15);
-				org.eclipse.jgit.api.MergeResult mergeResult = localRepo.merge(subMon.newChild(5));
-				if (mergeResult.getFailingPaths() != null) {
-					String message = Messages.GitSyncService_16;
-					for (String s : mergeResult.getFailingPaths().keySet()) {
-						message += System.getProperty("line.separator") + s; //$NON-NLS-1$
+			// Sync remote-to-local if and only if the remote head commit is null or not in the local repository.
+			// Note that the sync flag settings are irrelevant here. If only an LR sync is requested, we still need to update the
+			// local with remote changes, and if RL sync is requested, it is still unnecessary if there are no remote changes.
+			try {
+				if ((remoteHead == null) || (!localRepo.commitExists(remoteHead))) {
+					// Fetch the remote repository
+					subMon.subTask(Messages.GitSyncService_14);
+					localRepo.fetch(remoteRepo.getRemoteLocation(), subMon.newChild(20));
+
+					// Merge it with local
+					subMon.subTask(Messages.GitSyncService_15);
+					org.eclipse.jgit.api.MergeResult mergeResult = localRepo.merge(subMon.newChild(5));
+					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);
 					}
-					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;
+					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 (TransportException e) {
 				if (e.getMessage().startsWith("Remote does not have ")) { //$NON-NLS-1$
 					// Means that the remote branch isn't set up yet (and thus nothing to fetch). Can be ignored and local to
 					// remote sync can proceed.
-					// Note: It is important, though, that we do not merge if fetch fails. Merge will fail because remote ref is
-					// not created.
+					// Note: It is important, though, that we do not merge if fetch fails. Merge will fail because remote ref
+					// is not created.
 				} else {
 					throw new RemoteSyncException(e);
 				}
@@ -581,10 +631,13 @@
 			}
 
 			// Push local repository to remote
-			if (localRepo.getGit().branchList().call().size() > 0) { // check whether master was already created
-				subMon.subTask(Messages.GitSyncService_18);
-				localRepo.push(remoteRepo.getRemoteLocation(), subMon.newChild(20));
-				remoteRepo.merge(subMon.newChild(10));
+			if ((!localChangesPushed.contains(lrpair)) && (syncFlags.contains(SyncFlag.SYNC_LR))) {
+				if (localRepo.getGit().branchList().call().size() > 0) { // check whether master was already created
+					subMon.subTask(Messages.GitSyncService_18);
+					localRepo.push(remoteRepo.getRemoteLocation(), subMon.newChild(20));
+					remoteRepo.merge(subMon.newChild(10));
+					localChangesPushed.add(lrpair);
+				}
 			}
 		} catch (final IOException e) {
 			throw new RemoteSyncException(e);
diff --git a/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/JGitRepo.java b/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/JGitRepo.java
index 96d6f68..8dcb617 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/JGitRepo.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/JGitRepo.java
@@ -33,7 +33,9 @@
 import org.eclipse.jgit.api.StatusCommand;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.errors.AmbiguousObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.RevisionSyntaxException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
@@ -293,6 +295,23 @@
 	}
 
 	/**
+	 * Check if the given commit exists.
+	 *
+	 * @return whether the commit exists
+	 * @throws IOException
+	 * 					on problems accessing the file system
+	 * @throws IncorrectObjectTypeException 
+	 * @throws AmbiguousObjectException 
+	 * @throws RevisionSyntaxException
+	 * 					exceptions that most likely indicate JGit had problems handling the passed id
+	 */
+	boolean commitExists(String commitId) throws RevisionSyntaxException, AmbiguousObjectException, IncorrectObjectTypeException,
+	IOException {
+		ObjectId commitObjectId = this.getRepository().resolve(commitId);
+		return this.getRepository().hasObject(commitObjectId);
+	}
+
+	/**
 	 * Fetch files from the given remote. This only transmits the files. It does not update the local repository.
 	 *
 	 * @param remoteLoc
diff --git a/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/messages/Messages.java b/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/messages/Messages.java
index f7e9039..2bf9f13 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/messages/Messages.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/messages/Messages.java
@@ -47,6 +47,9 @@
 	public static String GitSyncService_18;
 	public static String GitSyncService_19;
 	public static String GitSyncService_2;
+	public static String GitSyncService_20;
+	public static String GitSyncService_21;
+	public static String GitSyncService_22;
 	public static String GitSyncService_3;
 	public static String GitSyncService_4;
 	public static String GitSyncService_5;
diff --git a/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/messages/messages.properties b/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/messages/messages.properties
index 54b1389..c5ce84d 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/messages/messages.properties
+++ b/rdt/org.eclipse.ptp.rdt.sync.git.core/src/org/eclipse/ptp/internal/rdt/sync/git/core/messages/messages.properties
@@ -41,6 +41,9 @@
 GitSyncService_18=Pushing local changes to remote
 GitSyncService_19=Missing sync thread waiting count that should have already been initialized.
 GitSyncService_2=Creating remote Git repository
+GitSyncService_20=Missing LR sync thread count that should have already been initialized.
+GitSyncService_21=Missing RL sync thread count that should have already been initialized.
+GitSyncService_22=Check if there are unknown remote changes
 GitSyncService_3=Internal error - Git sync service constructor called more than once
 GitSyncService_4=Synchronization canceled
 GitSyncService_5=Synchronization interrupted
diff --git a/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/ResourceChangeListener.java b/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/ResourceChangeListener.java
index 2786e9a..a69f7f4 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/ResourceChangeListener.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/ResourceChangeListener.java
@@ -12,6 +12,9 @@
  *******************************************************************************/
 package org.eclipse.ptp.internal.rdt.sync.ui;
 
+import java.util.EnumSet;
+import java.util.Set;
+
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResourceChangeEvent;
 import org.eclipse.core.resources.IResourceChangeListener;
@@ -76,19 +79,17 @@
 					 */
 					if (syncConfig != null) {
 						try {
+							Set<SyncFlag> f = SyncFlag.RL_ONLY;
 							if (delta.getKind() == IResourceDelta.CHANGED && syncConfig.isSyncOnSave()) {
-								// Do a non-forced sync to update any changes reported in delta. Sync'ing is necessary even if user
-								// has turned it off. This allows for some bookkeeping but no files are transferred.
-								if (syncMode == SyncMode.UNAVAILABLE) {
+								// Do a local-to-remote sync to update any changes reported in delta.
+								if ((syncMode == SyncMode.UNAVAILABLE) || (!syncOn)) {
 									continue;
-								} else if (!syncOn) {
-									SyncManager.sync(delta, project, SyncFlag.NO_SYNC, null);
 								} else if (syncMode == SyncMode.ALL) {
-									SyncManager.syncAll(delta, project, SyncFlag.NO_FORCE, new CommonSyncExceptionHandler(true,
+									SyncManager.syncAll(delta, project, SyncFlag.LR_ONLY, new CommonSyncExceptionHandler(true,
 											false));
 								} else if (syncMode == SyncMode.ACTIVE) {
 									SyncManager
-											.sync(delta, project, SyncFlag.NO_FORCE, new CommonSyncExceptionHandler(true, false));
+											.sync(delta, project, SyncFlag.LR_ONLY, new CommonSyncExceptionHandler(true, false));
 								}
 							}
 						} catch (CoreException e) {
diff --git a/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/menus/SyncMenuOperation.java b/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/menus/SyncMenuOperation.java
index a720120..7339c9c 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/menus/SyncMenuOperation.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/menus/SyncMenuOperation.java
@@ -73,16 +73,16 @@
 		// On sync request, sync regardless of the flags
 		try {
 			if (command.equals(syncActiveCommand)) {
-				SyncManager.sync(null, project, SyncFlag.FORCE, syncExceptionHandler);
+				SyncManager.sync(null, project, SyncFlag.BOTH, syncExceptionHandler);
 			} else if (command.equals(syncAllCommand)) {
-				SyncManager.syncAll(null, project, SyncFlag.FORCE, syncExceptionHandler);
+				SyncManager.syncAll(null, project, SyncFlag.BOTH, syncExceptionHandler);
 				// If user switches to active or all, assume the user wants to sync right away
 			} else if (command.equals(setActiveCommand)) {
 				SyncManager.setSyncMode(project, SyncMode.ACTIVE);
-				SyncManager.sync(null, project, SyncFlag.FORCE, syncExceptionHandler);
+				SyncManager.sync(null, project, SyncFlag.BOTH, syncExceptionHandler);
 			} else if (command.equals(setAllCommand)) {
 				SyncManager.setSyncMode(project, SyncMode.ALL);
-				SyncManager.syncAll(null, project, SyncFlag.FORCE, syncExceptionHandler);
+				SyncManager.syncAll(null, project, SyncFlag.BOTH, syncExceptionHandler);
 			} else if (command.equals(setNoneCommand)) {
 				SyncManager.setSyncMode(project, SyncMode.NONE);
 			} else if (command.equals(syncAutoCommand)) {
@@ -91,9 +91,9 @@
 				if (SyncManager.getSyncAuto()) {
 					SyncMode syncMode = SyncManager.getSyncMode(project);
 					if (syncMode == SyncMode.ACTIVE) {
-						SyncManager.sync(null, project, SyncFlag.FORCE, syncExceptionHandler);
+						SyncManager.sync(null, project, SyncFlag.BOTH, syncExceptionHandler);
 					} else if (syncMode == SyncMode.ALL) {
-						SyncManager.syncAll(null, project, SyncFlag.FORCE, syncExceptionHandler);
+						SyncManager.syncAll(null, project, SyncFlag.BOTH, syncExceptionHandler);
 					}
 				}
 			} else if (command.equals(syncExcludeCommand) || command.equals(syncIncludeCommand)) {
diff --git a/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/wizards/NewSyncProjectWizard.java b/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/wizards/NewSyncProjectWizard.java
index 348ba8f..1c9baa8 100644
--- a/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/wizards/NewSyncProjectWizard.java
+++ b/rdt/org.eclipse.ptp.rdt.sync.ui/src/org/eclipse/ptp/internal/rdt/sync/ui/wizards/NewSyncProjectWizard.java
@@ -409,7 +409,7 @@
 
 		// Force an initial sync
 		try {
-			SyncManager.sync(null, project, SyncFlag.FORCE, new CommonSyncExceptionHandler(false, true));
+			SyncManager.sync(null, project, SyncFlag.BOTH, new CommonSyncExceptionHandler(false, true));
 		} catch (CoreException e) {
 			// This should never happen because only a blocking sync can throw a core exception.
 			RDTSyncUIPlugin.log(Messages.NewSyncProjectWizard_Unexpected_core_exception, e);
diff --git a/tools/gem/org.eclipse.ptp.gem/src/org/eclipse/ptp/internal/gem/util/GemUtilities.java b/tools/gem/org.eclipse.ptp.gem/src/org/eclipse/ptp/internal/gem/util/GemUtilities.java
index 5f4607b..473efc9 100644
--- a/tools/gem/org.eclipse.ptp.gem/src/org/eclipse/ptp/internal/gem/util/GemUtilities.java
+++ b/tools/gem/org.eclipse.ptp.gem/src/org/eclipse/ptp/internal/gem/util/GemUtilities.java
@@ -1347,7 +1347,7 @@
 		final IProgressMonitor monitor = new NullProgressMonitor();
 		final IProject project = getCurrentProject(gemActiveResource);
 		try {
-			SyncManager.syncBlocking(null, project, SyncFlag.FORCE, monitor);
+			SyncManager.syncBlocking(null, project, SyncFlag.BOTH, monitor);
 		} catch (final CoreException e) {
 			logExceptionDetail(e);
 		}