Bug 573421 - Avoid local history for derived files

Added a configuration whether history for derived files is kept.
Off by default.

Change-Id: I27eba608269c2d96827066bab86496bd1bfa20f2
Signed-off-by: Joerg Kubitz <jkubitz-eclipse@gmx.de>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.resources/+/180356
Tested-by: Lars Vogel <Lars.Vogel@vogella.com>
Reviewed-by: Lars Vogel <Lars.Vogel@vogella.com>
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java
index c4bf28a..ff9ba18 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/DeleteVisitor.java
@@ -118,7 +118,9 @@
 			IFileInfo info = node.fileInfo;
 			if (info == null)
 				info = new FileInfo(node.getLocalName());
-			store.addState(target.getFullPath(), node.getStore(), info, true);
+			if (FileSystemResourceManager.storeHistory(node.getResource())) {
+				store.addState(target.getFullPath(), node.getStore(), info, true);
+			}
 		}
 		monitor.worked(1);
 		ticks--;
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
index dbd60dd..9045f3a 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
@@ -1149,7 +1149,8 @@
 				}
 			}
 			// add entry to History Store.
-			if (BitMask.isSet(updateFlags, IResource.KEEP_HISTORY) && fileInfo.exists())
+			if (BitMask.isSet(updateFlags, IResource.KEEP_HISTORY) && fileInfo.exists()
+					&& FileSystemResourceManager.storeHistory(target))
 				//never move to the history store, because then the file is missing if write fails
 				getHistoryStore().addState(target.getFullPath(), store, fileInfo, false);
 			if (!fileInfo.exists()) {
@@ -1250,4 +1251,10 @@
 		//for backwards compatibility, ensure the old .prj file is deleted
 		getWorkspace().getMetaArea().clearOldDescription(target);
 	}
+
+	public static boolean storeHistory(IResource file) {
+		WorkspaceDescription description = ((Workspace) file.getWorkspace()).internalGetDescription();
+		return description.isKeepDerivedState() || !file.isDerived();
+	}
+
 }
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java
index ec9e5e7..d9a6efb 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/IModelObjectConstants.java
@@ -37,6 +37,8 @@
 	String FILE_STATE_LONGEVITY = "fileStateLongevity"; //$NON-NLS-1$
 	String MAX_FILE_STATE_SIZE = "maxFileStateSize"; //$NON-NLS-1$
 	String MAX_FILE_STATES = "maxFileStates"; //$NON-NLS-1$
+	String KEEP_DERIVED_STATE = "keepDerivedState"; //$NON-NLS-1$
+
 	/**
 	 * The project relative path is called the link name for backwards compatibility
 	 */
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java
index d3c2757..70c0f44 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ModelObjectWriter.java
@@ -294,6 +294,7 @@
 			writer.printSimpleTag(FILE_STATE_LONGEVITY, description.getFileStateLongevity());
 			writer.printSimpleTag(MAX_FILE_STATE_SIZE, description.getMaxFileStateSize());
 			writer.printSimpleTag(MAX_FILE_STATES, description.getMaxFileStates());
+			writer.printSimpleTag(KEEP_DERIVED_STATE, description.isKeepDerivedState() ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$
 			String[] order = description.getBuildOrder(false);
 			if (order != null)
 				write(BUILD_ORDER, PROJECT, order, writer);
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java
index 1e6599b..156f03f 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java
@@ -41,6 +41,7 @@
 	public static final boolean PREF_APPLY_FILE_STATE_POLICY_DEFAULT = true;
 	public static final long PREF_FILE_STATE_LONGEVITY_DEFAULT = 7 * 24 * 3600 * 1000l; // 7 days
 	public static final long PREF_MAX_FILE_STATE_SIZE_DEFAULT = 1024 * 1024l; // 1 MB
+	public static final boolean PREF_KEEP_DERIVED_STATE_DEFAULT = false;
 	public static final int PREF_MAX_FILE_STATES_DEFAULT = 50;
 	public static final long PREF_DELTA_EXPIRATION_DEFAULT = 30 * 24 * 3600 * 1000l; // 30 days
 	/**
@@ -76,11 +77,13 @@
 		node.putBoolean(ResourcesPlugin.PREF_DEFAULT_BUILD_ORDER, PREF_DEFAULT_BUILD_ORDER_DEFAULT);
 		node.putInt(ResourcesPlugin.PREF_MISSING_NATURE_MARKER_SEVERITY, PREF_MISSING_NATURE_MARKER_SEVERITY_DEFAULT);
 
+
 		// history store defaults
 		node.putBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY, PREF_APPLY_FILE_STATE_POLICY_DEFAULT);
 		node.putLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, PREF_FILE_STATE_LONGEVITY_DEFAULT);
 		node.putLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, PREF_MAX_FILE_STATE_SIZE_DEFAULT);
 		node.putInt(ResourcesPlugin.PREF_MAX_FILE_STATES, PREF_MAX_FILE_STATES_DEFAULT);
+		node.putBoolean(ResourcesPlugin.PREF_KEEP_DERIVED_STATE, PREF_KEEP_DERIVED_STATE_DEFAULT);
 
 		// save manager defaults
 		node.putLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, PREF_SNAPSHOT_INTERVAL_DEFAULT);
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java
index b0b0418..909aaad 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java
@@ -61,6 +61,9 @@
 	 */
 	@Override
 	public void addToLocalHistory(IFile file) {
+		if (!FileSystemResourceManager.storeHistory(file)) {
+			return;
+		}
 		Assert.isLegal(isValid);
 		try {
 			lock.acquire();
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java
index 87687de..0859d78 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescription.java
@@ -28,6 +28,7 @@
 	protected int maxBuildIterations;
 	protected int maxFileStates;
 	protected long maxFileStateSize;
+	private boolean keepDerivedState;
 	protected boolean applyFileStatePolicy;
 	private long snapshotInterval;
 	protected int operationsPerSnapshot;
@@ -44,6 +45,8 @@
 		fileStateLongevity = node.getLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY, PreferenceInitializer.PREF_FILE_STATE_LONGEVITY_DEFAULT);
 		maxFileStates = node.getInt(ResourcesPlugin.PREF_MAX_FILE_STATES, PreferenceInitializer.PREF_MAX_FILE_STATES_DEFAULT);
 		maxFileStateSize = node.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE, PreferenceInitializer.PREF_MAX_FILE_STATE_SIZE_DEFAULT);
+		keepDerivedState = node.getBoolean(ResourcesPlugin.PREF_KEEP_DERIVED_STATE,
+				PreferenceInitializer.PREF_KEEP_DERIVED_STATE_DEFAULT);
 		snapshotInterval = node.getLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL, PreferenceInitializer.PREF_SNAPSHOT_INTERVAL_DEFAULT);
 		operationsPerSnapshot = node.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT, PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT_DEFAULT);
 		deltaExpiration = node.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION, PreferenceInitializer.PREF_DELTA_EXPIRATION_DEFAULT);
@@ -213,4 +216,14 @@
 	public void setMaxConcurrentBuilds(int n) {
 		this.parallelBuildsCount = n;
 	}
+
+	@Override
+	public boolean isKeepDerivedState() {
+		return keepDerivedState;
+	}
+
+	@Override
+	public void setKeepDerivedState(boolean keepDerivedState) {
+		this.keepDerivedState = keepDerivedState;
+	}
 }
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java
index 9a2aa02..06fa124 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspaceDescriptionReader.java
@@ -116,6 +116,8 @@
 		String fileStateLongevity = getString(node, FILE_STATE_LONGEVITY);
 		String maxFileStateSize = getString(node, MAX_FILE_STATE_SIZE);
 		String maxFileStates = getString(node, MAX_FILE_STATES);
+		String keepDerivedState = getString(node, KEEP_DERIVED_STATE);
+
 		String[] buildOrder = getStrings(searchNode(node, BUILD_ORDER));
 
 		// build instance
@@ -145,6 +147,9 @@
 		} catch (NumberFormatException e) {
 			logNumberFormatException(maxFileStates, e);
 		}
+		if (keepDerivedState != null)
+			// if in doubt keep derived off
+			description.setKeepDerivedState(keepDerivedState.equals(Integer.toString(1)));
 		if (buildOrder != null)
 			description.internalSetBuildOrder(buildOrder);
 		try {
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java
index d5cb26b..65fc7d8 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/WorkspacePreferences.java
@@ -75,6 +75,7 @@
 		target.setFileStateLongevity(source.getFileStateLongevity());
 		target.setMaxFileStates(source.getMaxFileStates());
 		target.setMaxFileStateSize(source.getMaxFileStateSize());
+		target.setKeepDerivedState(source.isKeepDerivedState());
 		target.setSnapshotInterval(source.getSnapshotInterval());
 		target.setOperationsPerSnapshot(source.getOperationsPerSnapshot());
 		target.setDeltaExpiration(source.getDeltaExpiration());
@@ -100,6 +101,7 @@
 		super.setOperationsPerSnapshot(preferences.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT));
 		super.setDeltaExpiration(preferences.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION));
 		super.setMaxConcurrentBuilds(preferences.getInt(ResourcesPlugin.PREF_MAX_CONCURRENT_BUILDS));
+		super.setKeepDerivedState(preferences.getBoolean(ResourcesPlugin.PREF_KEEP_DERIVED_STATE));
 
 		// This property listener ensures we are being updated properly when changes
 		// are done directly to the preference store.
@@ -215,6 +217,11 @@
 		preferences.setValue(ResourcesPlugin.PREF_MAX_CONCURRENT_BUILDS, n);
 	}
 
+	@Override
+	public void setKeepDerivedState(boolean keepDerivedState) {
+		preferences.setValue(ResourcesPlugin.PREF_KEEP_DERIVED_STATE, keepDerivedState);
+	}
+
 	/**
 	 * @see org.eclipse.core.resources.IWorkspaceDescription#setSnapshotInterval(long)
 	 */
@@ -245,8 +252,10 @@
 			super.setOperationsPerSnapshot(preferences.getInt(PreferenceInitializer.PREF_OPERATIONS_PER_SNAPSHOT));
 		else if (property.equals(PreferenceInitializer.PREF_DELTA_EXPIRATION))
 			super.setDeltaExpiration(preferences.getLong(PreferenceInitializer.PREF_DELTA_EXPIRATION));
-		else if (property.equals(ResourcesPlugin.PREF_MAX_CONCURRENT_BUILDS)) {
+		else if (property.equals(ResourcesPlugin.PREF_MAX_CONCURRENT_BUILDS))
 			super.setMaxConcurrentBuilds(preferences.getInt(ResourcesPlugin.PREF_MAX_CONCURRENT_BUILDS));
+		else if (property.equals(ResourcesPlugin.PREF_KEEP_DERIVED_STATE)) {
+			super.setKeepDerivedState(preferences.getBoolean(ResourcesPlugin.PREF_KEEP_DERIVED_STATE));
 		}
 	}
 
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java
index 339aec0..df1d6ec 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IWorkspaceDescription.java
@@ -89,6 +89,16 @@
 	long getMaxFileStateSize();
 
 	/**
+	 * Returns whether derived files are tracked in the local history.
+	 *
+	 * @return <code>true</code> if local history for derived files is created
+	 * @see #setKeepDerivedState(boolean)
+	 * @see ResourcesPlugin#PREF_KEEP_DERIVED_STATE
+	 * @since 3.15
+	 */
+	boolean isKeepDerivedState();
+
+	/**
 	 * Returns whether file states are discarded according to the policy specified by
 	 * <code>setFileStateLongevity(long)</code>, <code>setMaxFileStates(int)</code>
 	 * and <code>setMaxFileStateSize(long)</code> methods.
@@ -233,6 +243,22 @@
 	void setMaxFileStateSize(long size);
 
 	/**
+	 * Sets whether derived files are tracked in the local history.
+	 * <p>
+	 * Users must call <code>IWorkspace.setDescription</code> before changes made to
+	 * this description take effect.
+	 * </p>
+	 *
+	 * @param keepDerivedState <code>true</code> if a history of derived files is
+	 *                         needed.
+	 * @see IWorkspace#setDescription(IWorkspaceDescription)
+	 * @see #isKeepDerivedState()
+	 * @see ResourcesPlugin#PREF_KEEP_DERIVED_STATE
+	 * @since 3.15
+	 */
+	void setKeepDerivedState(boolean keepDerivedState);
+
+	/**
 	 * Sets whether file states are discarded according to the policy specified by
 	 * <code>setFileStateLongevity(long)</code>, <code>setMaxFileStates(int)</code>
 	 * and <code>setMaxFileStateSize(long)</code> methods.
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java
index 0b06c2f..927093d 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/ResourcesPlugin.java
@@ -263,6 +263,16 @@
 	public static final String PREF_MAX_FILE_STATE_SIZE = PREF_DESCRIPTION_PREFIX + "maxfilestatesize"; //$NON-NLS-1$
 
 	/**
+	 * Name of a preference for configuring whether derived files should be stored
+	 * in the local history.
+	 *
+	 * @see IWorkspaceDescription#isKeepDerivedState()
+	 * @see IWorkspaceDescription#setKeepDerivedState(boolean)
+	 * @since 3.15
+	 */
+	public static final String PREF_KEEP_DERIVED_STATE = PREF_DESCRIPTION_PREFIX + "keepDerivedState"; //$NON-NLS-1$
+
+	/**
 	 * Name of a preference for configuring the maximum number of states per
 	 * file that can be stored in the local history.
 	 *
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/WorkspacePreferencesTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/WorkspacePreferencesTest.java
index 47296c6..23b8945 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/WorkspacePreferencesTest.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/WorkspacePreferencesTest.java
@@ -101,6 +101,11 @@
 		assertEquals("3.2", defaultSnapshotInterval, workspace.getDescription().getSnapshotInterval());
 		assertEquals("Description not synchronized", workspace.getDescription(), preferences);
 
+		preferences.setValue(ResourcesPlugin.PREF_KEEP_DERIVED_STATE, false);
+		assertFalse("4.0", workspace.getDescription().isKeepDerivedState());
+
+		preferences.setValue(ResourcesPlugin.PREF_KEEP_DERIVED_STATE, true);
+		assertTrue("4.1", workspace.getDescription().isKeepDerivedState());
 	}
 
 	/**
@@ -126,6 +131,8 @@
 		modified.setMaxFileStateSize((original.getMaxFileStateSize() + 1) * 2);
 		// 8 - PREF_SNAPSHOT_INTERVAL
 		modified.setSnapshotInterval((original.getSnapshotInterval() + 1) * 2);
+		// 9 - PREF_SNAPSHOT_INTERVAL
+		modified.setKeepDerivedState(!original.isKeepDerivedState());
 
 		final List<String> changedProperties = new LinkedList<>();
 		Preferences.IPropertyChangeListener listener = event -> changedProperties.add(event.getProperty());
@@ -144,7 +151,7 @@
 				fail("2.0", e);
 			}
 			// the right number of events should have been fired
-			assertEquals("2.1 - wrong number of properties changed ", 9, changedProperties.size());
+			assertEquals("2.1 - wrong number of properties changed ", 10, changedProperties.size());
 		} finally {
 			preferences.removePropertyChangeListener(listener);
 		}
@@ -181,6 +188,7 @@
 			modified.setMaxFileStates((original.getMaxFileStates() + 1) * 2);
 			modified.setMaxFileStateSize((original.getMaxFileStateSize() + 1) * 2);
 			modified.setSnapshotInterval((original.getSnapshotInterval() + 1) * 2);
+			modified.setKeepDerivedState(!original.isKeepDerivedState());
 
 			// sets modified description
 			try {
@@ -234,6 +242,7 @@
 		description.setMaxFileStates(16);
 		description.setMaxFileStateSize(100050);
 		description.setSnapshotInterval(1234567);
+		description.setKeepDerivedState(true);
 		try {
 			workspace.setDescription(description);
 		} catch (CoreException ce) {
@@ -270,6 +279,7 @@
 		description.setMaxFileStates(Math.abs((int) (Math.random() * 100000L)));
 		description.setMaxFileStateSize(Math.abs((long) (Math.random() * 100000L)));
 		description.setSnapshotInterval(Math.abs((long) (Math.random() * 100000L)));
+		description.setKeepDerivedState(true);
 		LocalMetaArea localMetaArea = ((Workspace) workspace).getMetaArea();
 		try {
 			localMetaArea.write(description);
@@ -296,6 +306,8 @@
 		assertEquals(message + " - 7", description.getMaxFileStateSize(), preferences.getLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE));
 		assertEquals(message + " - 8", description.getSnapshotInterval(), preferences.getLong(ResourcesPlugin.PREF_SNAPSHOT_INTERVAL));
 		assertEquals(message + " - 9", description.getMaxBuildIterations(), preferences.getLong(ResourcesPlugin.PREF_MAX_BUILD_ITERATIONS));
+		assertEquals(message + " -10", description.isKeepDerivedState(),
+				preferences.getBoolean(ResourcesPlugin.PREF_KEEP_DERIVED_STATE));
 	}
 
 	/**
@@ -311,5 +323,6 @@
 		assertEquals(message + " - 7", description1.getMaxFileStateSize(), description2.getMaxFileStateSize());
 		assertEquals(message + " - 8", description1.getSnapshotInterval(), description2.getSnapshotInterval());
 		assertEquals(message + " - 9", description1.getMaxBuildIterations(), description2.getMaxBuildIterations());
+		assertEquals(message + " -10", description1.isKeepDerivedState(), description2.isKeepDerivedState());
 	}
 }