blob: 7595710b394addf0ee365bd98ef1c1ac82f374dd [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2013, 2022 Robin Stocker <robin@nibor.org> and others
*
* 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.ui.internal.revision;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Objects;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
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.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.egit.ui.Activator;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.team.core.history.IFileRevision;
/**
* Editable revision backed by a symlink (inside or outside the workspace). Used
* for conflict resolutions with stage 2 (previous HEAD) as input; updating the
* working tree file (which is supposed to be a symlink).
* <p>
* If you have a backing {@link IFile}, use {@link ResourceEditableRevision}.
* </p>
* <p>
* Git always places a file in the working tree in link/file or file/link
* conflicts. Only in link/link conflicts, we will have a symlink in the working
* tree.
* </p>
*/
public class SymlinkEditableRevision extends EditableRevision {
private final IPath location;
private final IRunnableContext runnableContext;
/**
* @param fileRevision
* @param location
* @param runnableContext
*/
public SymlinkEditableRevision(IFileRevision fileRevision,
@NonNull IPath location,
@NonNull IRunnableContext runnableContext) {
super(fileRevision, null);
this.location = location;
this.runnableContext = runnableContext;
}
@Override
public void setContent(final byte[] newContent) {
try {
// Don't fork: if we are called from a thread which locked
// workspace our *forked* operation will never complete because it
// requires file lock which cannot be acquired from another thread
ISchedulingRule rule = Job.getJobManager().currentRule();
boolean fork = true;
if (rule instanceof IResource) {
IFile ourFile = ResourcesPlugin.getWorkspace().getRoot()
.getFile(location);
if (ourFile.exists()
&& ((IResource) rule).isConflicting(ourFile))
fork = false;
}
runnableContext.run(fork, false, monitor -> {
int eol = RawParseUtils.nextLF(newContent, 0);
String target = new String(newContent, 0, eol,
StandardCharsets.UTF_8).trim();
try {
File linkFile = location.toFile();
boolean wasBrokenLink = !linkFile.exists();
java.nio.file.Path link = FileUtils.createSymLink(linkFile,
target);
updateLinkResource(wasBrokenLink, link);
} catch (IOException e) {
throw new InvocationTargetException(e);
}
});
fireContentChanged();
} catch (InvocationTargetException e) {
Activator.handleError(e.getTargetException().getMessage(),
e.getTargetException(), true);
} catch (InterruptedException e) {
// ignore here
}
}
private void updateLinkResource(boolean wasBroken,
java.nio.file.Path link) {
boolean brokenNow = !Files.exists(link);
if (brokenNow == wasBroken) {
// If the state doesn't change, we don't care, either Eclipse
// doesn's see broken link and we can't do anything or it is not
// broken and Eclipse handles the change
return;
}
// refresh the parent if either the link was broken before or broken
// just now
IPath parentPath = location.removeLastSegments(1);
@SuppressWarnings("null")
final IContainer parent = ResourceUtil
.getContainerForLocation(parentPath, true);
if (parent != null) {
WorkspaceJob job = new WorkspaceJob("Refreshing " + parentPath) { //$NON-NLS-1$
@Override
public IStatus runInWorkspace(IProgressMonitor m)
throws CoreException {
parent.refreshLocal(IResource.DEPTH_ONE, m);
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
}
@Override
public int hashCode() {
return 31 * super.hashCode() + Objects.hash(location, runnableContext);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !super.equals(obj) || getClass() != obj.getClass()) {
return false;
}
SymlinkEditableRevision other = (SymlinkEditableRevision) obj;
return Objects.equals(location, other.location)
&& Objects.equals(runnableContext, other.runnableContext);
}
}