Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Wolf2021-04-28 20:45:00 +0000
committerMatthias Sohn2021-06-01 10:32:04 +0000
commit2824d51899189f7fc5d4b799ef9efd33d2bbf361 (patch)
tree52569efe3068dc4edea4b6a0b740f6fa4091884c /org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/merge
parent7f4234445bfc112939b14e6f8397cce10515dd20 (diff)
downloadegit-2824d51899189f7fc5d4b799ef9efd33d2bbf361.tar.gz
egit-2824d51899189f7fc5d4b799ef9efd33d2bbf361.tar.xz
egit-2824d51899189f7fc5d4b799ef9efd33d2bbf361.zip
[merge] Use EFS-linked IResources for non-workspace files
LocationEditableRevision caused JDT to log exceptions and to not initialize the merge viewer fully. Fix this by creating a hidden resource linked to the non-workspace file instead. These hidden resources are all created in a hidden project, and they are cleaned up when the merge editor is disposed. In case some of them remain all the same, which can happen when the IDE shuts down, EGit core removes any linked resources it finds in that hidden project on startup. JDT uses similar mechanisms for external folders, and so does the Compare UI's CompareWithOtherResourceDialog. Bug: 573232 Change-Id: If7c76b9480fc6dc14e888c7a21440146d37a8ad1 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Diffstat (limited to 'org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/merge')
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/merge/GitMergeEditorInput.java219
1 files changed, 156 insertions, 63 deletions
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/merge/GitMergeEditorInput.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/merge/GitMergeEditorInput.java
index 12f8125a50..a685f3b137 100644
--- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/merge/GitMergeEditorInput.java
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/merge/GitMergeEditorInput.java
@@ -12,6 +12,8 @@ package org.eclipse.egit.ui.internal.merge;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
+import java.net.URI;
+import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.time.Instant;
import java.util.ArrayList;
@@ -32,13 +34,21 @@ import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.resources.ResourcesPlugin;
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.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.egit.core.internal.CompareCoreUtils;
import org.eclipse.egit.core.internal.CoreText;
+import org.eclipse.egit.core.internal.efs.HiddenResources;
import org.eclipse.egit.core.internal.storage.GitFileRevision;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.egit.core.util.RevCommitUtils;
@@ -48,7 +58,6 @@ import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.revision.EditableRevision;
import org.eclipse.egit.ui.internal.revision.FileRevisionTypedElement;
import org.eclipse.egit.ui.internal.revision.GitCompareFileRevisionEditorInput.EmptyTypedElement;
-import org.eclipse.egit.ui.internal.revision.LocationEditableRevision;
import org.eclipse.egit.ui.internal.revision.ResourceEditableRevision;
import org.eclipse.egit.ui.internal.synchronize.compare.LocalNonWorkspaceTypedElement;
import org.eclipse.jface.operation.IRunnableContext;
@@ -73,13 +82,12 @@ import org.eclipse.swt.widgets.Display;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.internal.ui.synchronize.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener;
import org.eclipse.team.internal.ui.synchronize.LocalResourceTypedElement;
-import org.eclipse.team.ui.synchronize.SaveableCompareEditorInput;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE.SharedImages;
/**
- * A Git-specific {@link CompareEditorInput}
+ * A Git-specific {@link CompareEditorInput} for merging conflicting files.
*/
@SuppressWarnings("restriction")
public class GitMergeEditorInput extends CompareEditorInput {
@@ -95,6 +103,8 @@ public class GitMergeEditorInput extends CompareEditorInput {
private final IPath[] locations;
+ private List<IFile> toDelete;
+
/**
* @param useWorkspace
* if <code>true</code>, use the workspace content (i.e. the
@@ -131,6 +141,90 @@ public class GitMergeEditorInput extends CompareEditorInput {
return super.getAdapter(adapter);
}
+ @Override
+ protected void contentsCreated() {
+ super.contentsCreated();
+ // select the first conflict
+ getNavigator().selectChange(true);
+ }
+
+ @Override
+ protected void handleDispose() {
+ super.handleDispose();
+ // We do NOT dispose the images, as these are shared.
+ //
+ // We need to remove the temporary resources. A CompareEditorInput is
+ // supposed to be the very last thing that is disposed in a compare
+ // viewer, but this is not always true. If content merge viewers add
+ // additional widgets, for instance for the Java structure comparison,
+ // we're suddenly no longer the last item to be disposed. The various
+ // viewers (left, right, structure, and so on) are all disposed when
+ // their widgets are disposed. Widget disposal happens recursively
+ // top-down on the UI thread, so an asyncExec should be safe here to
+ // ensure that we remove the files only once everything else has been
+ // disposed of. If we delete temporary resources before all viewers had
+ // disconnected the Document, some might not disconnect because
+ // SharedDocumentAdapter.getDocumentKey() returns null if the file has
+ // been deleted. If this happens the framework will find that still
+ // connected document the next time this resource is opened and show
+ // that instead of the true resource contents. This is wrong and is very
+ // annoying if this cached document is dirty: one can open only this
+ // dirty version from then on, until the next restart of Eclipse.
+ PlatformUI.getWorkbench().getDisplay().asyncExec(this::cleanUp);
+ }
+
+ private void cleanUp() {
+ if (toDelete == null || toDelete.isEmpty()) {
+ return;
+ }
+ List<IFile> toClean = toDelete;
+ toDelete = null;
+ // Don't clean up if the workbench is shutting down; we would exit with
+ // unsaved workspace changes. Instead, EGit core cleans the project on
+ // start.
+ Job job = new Job(UIText.GitMergeEditorInput_ResourceCleanupJobName) {
+
+ @Override
+ public boolean shouldSchedule() {
+ return super.shouldSchedule()
+ && !PlatformUI.getWorkbench().isClosing();
+ }
+
+ @Override
+ public boolean shouldRun() {
+ return super.shouldRun()
+ && !PlatformUI.getWorkbench().isClosing();
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ IWorkspaceRunnable remove = m -> {
+ SubMonitor progress = SubMonitor.convert(m, toClean.size());
+ for (IFile tmp : toClean) {
+ if (PlatformUI.getWorkbench().isClosing()) {
+ return;
+ }
+ try {
+ tmp.delete(true, progress.newChild(1));
+ } catch (CoreException e) {
+ // Ignore
+ }
+ }
+ };
+ try {
+ ResourcesPlugin.getWorkspace().run(remove, null,
+ IWorkspace.AVOID_UPDATE, monitor);
+ } catch (CoreException e) {
+ return e.getStatus();
+ }
+ return Status.OK_STATUS;
+ }
+ };
+ job.setSystem(true);
+ job.setUser(false);
+ job.schedule();
+ }
+
private static boolean isUIThread() {
return Display.getCurrent() != null;
}
@@ -269,19 +363,6 @@ public class GitMergeEditorInput extends CompareEditorInput {
}
}
- @Override
- protected void contentsCreated() {
- super.contentsCreated();
- // select the first conflict
- getNavigator().selectChange(true);
- }
-
- @Override
- protected void handleDispose() {
- super.handleDispose();
- // we do NOT dispose the images, as these are shared
- }
-
@SuppressWarnings("unused")
private IDiffContainer buildDiffContainer(Repository repository,
RevCommit headCommit, RevCommit ancestorCommit,
@@ -381,20 +462,22 @@ public class GitMergeEditorInput extends CompareEditorInput {
.equals(dirCacheEntry.getLastModifiedInstant());
}
if (useWorkingTree) {
+ LocalResourceTypedElement item;
if (file != null) {
- left = SaveableCompareEditorInput
- .createFileElement(file);
+ item = new LocalResourceTypedElement(file);
} else {
- left = new LocalNonWorkspaceTypedElement(repository,
+ item = new LocalNonWorkspaceTypedElement(repository,
location);
}
- if (left instanceof LocalResourceTypedElement) {
- ((LocalResourceTypedElement) left)
- .setSharedDocumentListener(
- new LocalResourceSaver(
- (LocalResourceTypedElement) left));
- }
+ item.setSharedDocumentListener(
+ new LocalResourceSaver(item));
+ left = item;
} else {
+ IFile rsc = file != null ? file
+ : createHiddenResource(location.toFile().toURI(),
+ tw.getNameString(), null);
+ assert rsc != null;
+ // Stage 2 from index with backing IResource
rev = GitFileRevision.inIndex(repository, gitPath,
DirCacheEntry.STAGE_2);
IRunnableContext runnableContext = getContainer();
@@ -403,48 +486,16 @@ public class GitMergeEditorInput extends CompareEditorInput {
.getProgressService();
assert runnableContext != null;
}
- if (file != null) {
- left = new ResourceEditableRevision(rev, file,
- runnableContext);
- } else {
- left = new LocationEditableRevision(rev, location,
- runnableContext);
- }
+ left = new ResourceEditableRevision(rev, rsc,
+ runnableContext);
// 'left' saves to the working tree. Update the index entry
// with the current time. Normal conflict stages have a
// timestamp of zero, so this is a non-invasive fully
// compatible way to mark this conflict stage so that the
// next time we do take the file contents.
- ((EditableRevision) left)
- .addContentChangeListener(source -> {
- DirCache cache = null;
- try {
- cache = repository.lockDirCache();
- DirCacheEditor editor = cache.editor();
- editor.add(new PathEdit(gitPath) {
-
- private boolean done;
-
- @Override
- public void apply(DirCacheEntry ent) {
- if (!done && ent.getStage() > 0) {
- ent.setLastModified(
- Instant.now());
- done = true;
- }
- }
- });
- editor.commit();
- } catch (RuntimeException | IOException e) {
- Activator.logError(MessageFormat.format(
- UIText.GitMergeEditorInput_ErrorUpdatingIndex,
- gitPath), e);
- } finally {
- if (cache != null) {
- cache.unlock();
- }
- }
- });
+ ((EditableRevision) left).addContentChangeListener(
+ source -> updateIndexTimestamp(repository,
+ gitPath));
// make sure we don't need a round trip later
try {
((EditableRevision) left).cacheContents(monitor);
@@ -487,6 +538,49 @@ public class GitMergeEditorInput extends CompareEditorInput {
}
}
+ private IFile createHiddenResource(URI uri, String name, Charset encoding)
+ throws IOException {
+ try {
+ IFile tmp = HiddenResources.INSTANCE.createFile(uri, name, encoding,
+ null);
+ if (toDelete == null) {
+ toDelete = new ArrayList<>();
+ }
+ toDelete.add(tmp);
+ return tmp;
+ } catch (CoreException e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ private void updateIndexTimestamp(Repository repository, String gitPath) {
+ DirCache cache = null;
+ try {
+ cache = repository.lockDirCache();
+ DirCacheEditor editor = cache.editor();
+ editor.add(new PathEdit(gitPath) {
+
+ private boolean done;
+
+ @Override
+ public void apply(DirCacheEntry ent) {
+ if (!done && ent.getStage() > 0) {
+ ent.setLastModified(Instant.now());
+ done = true;
+ }
+ }
+ });
+ editor.commit();
+ } catch (IOException e) {
+ Activator.logError(MessageFormat.format(
+ UIText.GitMergeEditorInput_ErrorUpdatingIndex, gitPath), e);
+ } finally {
+ if (cache != null) {
+ cache.unlock();
+ }
+ }
+ }
+
private IDiffContainer getFileParent(IDiffContainer root,
IPath repositoryPath, IFile file, IPath location) {
int projectSegment = -1;
@@ -580,6 +674,5 @@ public class GitMergeEditorInput extends CompareEditorInput {
public void handleDocumentSaved() {
// Nothing
}
-
}
}

Back to the top