Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.egit.core/src/org/eclipse/egit/core/internal/ResourceRefreshHandler.java')
-rw-r--r--org.eclipse.egit.core/src/org/eclipse/egit/core/internal/ResourceRefreshHandler.java269
1 files changed, 269 insertions, 0 deletions
diff --git a/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/ResourceRefreshHandler.java b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/ResourceRefreshHandler.java
new file mode 100644
index 0000000000..a563fb47c3
--- /dev/null
+++ b/org.eclipse.egit.core/src/org/eclipse/egit/core/internal/ResourceRefreshHandler.java
@@ -0,0 +1,269 @@
+/*******************************************************************************
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com>
+ *
+ * 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.internal;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.eclipse.core.filesystem.URIUtil;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.egit.core.Activator;
+import org.eclipse.egit.core.internal.job.RuleUtil;
+import org.eclipse.egit.core.internal.trace.GitTraceLocation;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
+import org.eclipse.jgit.events.WorkingTreeModifiedListener;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Refreshes parts of the workspace changed by JGit operations. This will
+ * not refresh any git-ignored resources since those are not reported in the
+ * {@link WorkingTreeModifiedEvent}.
+ */
+public class ResourceRefreshHandler implements WorkingTreeModifiedListener {
+
+ @Override
+ public void onWorkingTreeModified(WorkingTreeModifiedEvent event) {
+ if (event.isEmpty()) {
+ return;
+ }
+ Repository repo = event.getRepository();
+ if (repo == null || repo.isBare()) {
+ return; // Should never occur
+ }
+ if (GitTraceLocation.REFRESH.isActive()) {
+ GitTraceLocation.getTrace().trace(
+ GitTraceLocation.REFRESH.getLocation(),
+ "Triggered refresh for repo: " + repo); //$NON-NLS-1$
+ }
+ try {
+ refreshRepository(event);
+ } catch (OperationCanceledException oe) {
+ return;
+ } catch (CoreException e) {
+ Activator.error(CoreText.Activator_refreshFailed, e);
+ }
+ }
+
+ private void refreshRepository(WorkingTreeModifiedEvent event)
+ throws CoreException {
+ if (event.isEmpty()) {
+ return; // Should actually not occur
+ }
+ File workTree = event.getRepository().getWorkTree().getAbsoluteFile();
+ Map<IPath, IProject> roots = getProjectLocations(workTree);
+ if (roots.isEmpty()) {
+ // No open projects from this repository in the workspace
+ return;
+ }
+ IPath wt = new Path(workTree.getPath());
+ Map<IResource, Boolean> toRefresh = computeResources(
+ event.getModified(), event.getDeleted(), wt, roots);
+ if (toRefresh.isEmpty()) {
+ return;
+ }
+ if (GitTraceLocation.REFRESH.isActive()) {
+ GitTraceLocation.getTrace().trace(
+ GitTraceLocation.REFRESH.getLocation(),
+ "Refreshing repository " + workTree + ' ' //$NON-NLS-1$
+ + toRefresh.size());
+ }
+ for (Map.Entry<IResource, Boolean> entry : toRefresh.entrySet()) {
+ entry.getKey()
+ .refreshLocal(entry.getValue().booleanValue()
+ ? IResource.DEPTH_INFINITE
+ : IResource.DEPTH_ONE, null);
+ }
+ if (GitTraceLocation.REFRESH.isActive()) {
+ GitTraceLocation.getTrace().trace(
+ GitTraceLocation.REFRESH.getLocation(),
+ "Refreshed repository " + workTree + ' ' //$NON-NLS-1$
+ + toRefresh.size());
+ }
+ }
+
+ private Map<IPath, IProject> getProjectLocations(File workTree) {
+ IProject[] projects = RuleUtil.getProjects(workTree);
+ if (projects == null) {
+ return Collections.emptyMap();
+ }
+ Map<IPath, IProject> result = new HashMap<>();
+ for (IProject project : projects) {
+ if (project.isAccessible()) {
+ IPath path = project.getLocation();
+ if (path != null) {
+ IPath projectFilePath = path.append(
+ IProjectDescription.DESCRIPTION_FILE_NAME);
+ if (projectFilePath.toFile().exists()) {
+ result.put(path, project);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ private Map<IResource, Boolean> computeResources(
+ Collection<String> modified, Collection<String> deleted,
+ IPath workTree, Map<IPath, IProject> roots) {
+ // Attempt to minimize the refreshes by returning IContainers if
+ // more than one file in a container has changed.
+ if (GitTraceLocation.REFRESH.isActive()) {
+ GitTraceLocation.getTrace().trace(
+ GitTraceLocation.REFRESH.getLocation(),
+ "Calculating refresh for repository " + workTree + ' ' //$NON-NLS-1$
+ + modified.size() + ' ' + deleted.size());
+ }
+ Set<IPath> fullRefreshes = new HashSet<>();
+ Map<IPath, IFile> handled = new HashMap<>();
+ Map<IResource, Boolean> result = new HashMap<>();
+ Stream.concat(modified.stream(), deleted.stream()).forEach(path -> {
+ IPath filePath = "/".equals(path) ? workTree //$NON-NLS-1$
+ : workTree.append(path);
+ IProject project = roots.get(filePath);
+ if (project != null) {
+ // Eclipse knows this as a project. Make sure it gets
+ // refreshed as such. One can refresh a folder via an IFile,
+ // but not an IProject.
+ handled.put(filePath, null);
+ result.put(project, Boolean.FALSE);
+ return;
+ }
+ if (fullRefreshes.stream()
+ .anyMatch(full -> full.isPrefixOf(filePath))
+ || !roots.keySet().stream()
+ .anyMatch(root -> root.isPrefixOf(filePath))) {
+ // Not in workspace or covered by a full container refresh
+ return;
+ }
+ IPath containerPath;
+ boolean isFile;
+ if (path.endsWith("/")) { //$NON-NLS-1$
+ // It's already a directory
+ isFile = false;
+ containerPath = filePath.removeTrailingSeparator();
+ } else {
+ isFile = true;
+ containerPath = filePath.removeLastSegments(1);
+ }
+ if (!handled.containsKey(containerPath)) {
+ if (!isFile && containerPath != null) {
+ IContainer container = getContainerForLocation(
+ containerPath);
+ if (container != null) {
+ IFile file = handled.get(containerPath);
+ handled.put(containerPath, null);
+ if (file != null) {
+ result.remove(file);
+ }
+ result.put(container, Boolean.FALSE);
+ }
+ } else if (isFile) {
+ // First file in this container. Find the deepest
+ // existing container and record its child.
+ String lastPart = filePath.lastSegment();
+ while (containerPath != null
+ && workTree.isPrefixOf(containerPath)) {
+ IContainer container = getContainerForLocation(
+ containerPath);
+ if (container == null) {
+ lastPart = containerPath.lastSegment();
+ containerPath = containerPath
+ .removeLastSegments(1);
+ isFile = false;
+ continue;
+ }
+ if (container.getType() == IResource.ROOT) {
+ // Missing project... ignore it and anything
+ // beneath. The user or our own branch project
+ // tracker will have to properly add/import the
+ // project.
+ containerPath = containerPath.append(lastPart);
+ fullRefreshes.add(containerPath);
+ handled.put(containerPath, null);
+ } else if (isFile) {
+ IFile file = container
+ .getFile(new Path(lastPart));
+ handled.put(containerPath, file);
+ result.put(file, Boolean.FALSE);
+ } else {
+ // New or deleted folder.
+ container = container
+ .getFolder(new Path(lastPart));
+ containerPath = containerPath.append(lastPart);
+ fullRefreshes.add(containerPath);
+ handled.put(containerPath, null);
+ result.put(container, Boolean.TRUE);
+ }
+ break;
+ }
+ }
+ } else {
+ IFile file = handled.get(containerPath);
+ if (file != null) {
+ // Second file in this container: replace file by
+ // its container.
+ handled.put(containerPath, null);
+ result.remove(file);
+ result.put(file.getParent(), Boolean.FALSE);
+ }
+ // Otherwise we already have this container.
+ }
+ });
+
+ if (GitTraceLocation.REFRESH.isActive()) {
+ GitTraceLocation.getTrace().trace(
+ GitTraceLocation.REFRESH.getLocation(),
+ "Calculated refresh for repository " + workTree); //$NON-NLS-1$
+ }
+ return result;
+ }
+
+ private static IContainer getContainerForLocation(@NonNull IPath location) {
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ IContainer dir = root.getContainerForLocation(location);
+ if (dir == null) {
+ return null;
+ }
+ if (isValid(dir)) {
+ return dir;
+ }
+ URI uri = URIUtil.toURI(location);
+ IContainer[] containers = root.findContainersForLocationURI(uri);
+ return Arrays.stream(containers).filter(ResourceRefreshHandler::isValid)
+ .findFirst().orElse(null);
+ }
+
+ private static boolean isValid(@NonNull IResource resource) {
+ return resource.isAccessible()
+ && !resource.isLinked(IResource.CHECK_ANCESTORS);
+ }
+
+} \ No newline at end of file

Back to the top