blob: 611a7aaa6b1815ebe076e8fb5530996fd4fd50f9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2012 IBM Corporation 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.compare.internal.patch;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.compare.internal.core.Messages;
import org.eclipse.compare.internal.core.patch.DiffProject;
import org.eclipse.compare.internal.core.patch.FilePatch2;
import org.eclipse.compare.internal.core.patch.Hunk;
import org.eclipse.compare.internal.core.patch.PatchReader;
import org.eclipse.compare.patch.IHunk;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceRuleFactory;
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.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.MultiRule;
/**
* A Patcher
* - knows how to parse various patch file formats into some in-memory structure,
* - holds onto the parsed data and the options to use when applying the patches,
* - knows how to apply the patches to files and folders.
*/
public class WorkspacePatcher extends Patcher {
private DiffProject[] fDiffProjects;
private boolean fIsWorkspacePatch= false;
private boolean fIsGitPatch = false;
private final Map retargetedDiffs = new HashMap();
public WorkspacePatcher() {
// nothing to do
}
public WorkspacePatcher(IResource target) {
setTarget(target);
}
protected void patchParsed(PatchReader patchReader) {
super.patchParsed(patchReader);
fDiffProjects = patchReader.getDiffProjects();
fIsWorkspacePatch = patchReader.isWorkspacePatch();
fIsGitPatch = patchReader.isGitPatch() && calculateStripGitPrefixSegments() > -1;
}
public DiffProject[] getDiffProjects() {
return fDiffProjects;
}
public boolean isWorkspacePatch() {
return fIsWorkspacePatch;
}
public boolean isGitPatch() {
return fIsGitPatch;
}
//---- parsing patch files
public void applyAll(IProgressMonitor pm, IFileValidator validator) throws CoreException {
if (pm == null)
pm = new NullProgressMonitor();
if (!fIsWorkspacePatch) {
super.applyAll(pm, validator);
} else {
final int WORK_UNIT= 10;
// get all files to be modified in order to call validateEdit
List list= new ArrayList();
for (int j= 0; j < fDiffProjects.length; j++) {
DiffProject diffProject= fDiffProjects[j];
if (Utilities.getProject(diffProject).isAccessible())
list.addAll(Arrays.asList(getTargetFiles(diffProject)));
}
// validate the files for editing
if (!validator.validateResources((IFile[])list.toArray(new IFile[list.size()]))) {
return;
}
FilePatch2[] diffs = getDiffs();
if (pm != null) {
String message= Messages.WorkspacePatcher_0;
pm.beginTask(message, diffs.length * WORK_UNIT);
}
for (int i= 0; i < diffs.length; i++) {
int workTicks= WORK_UNIT;
FilePatch2 diff= diffs[i];
if (isAccessible(diff)) {
IFile file= getTargetFile(diff);
IPath path= file.getProjectRelativePath();
if (pm != null)
pm.subTask(path.toString());
createPath(file.getProject(), path);
List failed= new ArrayList();
int type= diff.getDiffType(isReversed());
switch (type) {
case FilePatch2.ADDITION :
// patch it and collect rejected hunks
List result= apply(diff, file, true, failed);
if (result != null)
store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks));
workTicks -= WORK_UNIT;
break;
case FilePatch2.DELETION :
file.delete(true, true, new SubProgressMonitor(pm, workTicks));
workTicks -= WORK_UNIT;
break;
case FilePatch2.CHANGE :
// patch it and collect rejected hunks
result= apply(diff, file, false, failed);
if (result != null)
store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks));
workTicks -= WORK_UNIT;
break;
}
if (isGenerateRejectFile() && failed.size() > 0) {
IPath pp= null;
if (path.segmentCount() > 1) {
pp= path.removeLastSegments(1);
pp= pp.append(path.lastSegment() + REJECT_FILE_EXTENSION);
} else
pp= new Path(path.lastSegment() + REJECT_FILE_EXTENSION);
file= createPath(file.getProject(), pp);
if (file != null) {
store(getRejected(failed), file, pm);
try {
IMarker marker= file.createMarker(MARKER_TYPE);
marker.setAttribute(IMarker.MESSAGE, Messages.WorkspacePatcher_1);
marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
} catch (CoreException ex) {
// NeedWork
}
}
}
}
if (pm != null) {
if (pm.isCanceled())
break;
if (workTicks > 0)
pm.worked(workTicks);
}
}
}
}
private boolean isAccessible(FilePatch2 diff) {
return isEnabled(diff) && Utilities.getProject(diff.getProject()).isAccessible();
}
/**
* Returns the target files of all the Diffs contained by this
* DiffProject.
* @param project
* @return An array of IFiles that are targeted by the Diffs
*/
public IFile[] getTargetFiles(DiffProject project) {
List files= new ArrayList();
FilePatch2[] diffs = project.getFileDiffs();
for (int i = 0; i < diffs.length; i++) {
FilePatch2 diff = diffs[i];
if (isEnabled(diff)) {
files.add(getTargetFile(diff));
}
}
return (IFile[]) files.toArray(new IFile[files.size()]);
}
public IFile getTargetFile(FilePatch2 diff) {
IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed());
DiffProject project = getProject(diff);
if (project != null)
return Utilities.getProject(project).getFile(path);
return super.getTargetFile(diff);
}
private IPath getFullPath(FilePatch2 diff) {
IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed());
DiffProject project = getProject(diff);
if (project != null)
return Utilities.getProject(project).getFile(path).getFullPath();
return getTarget().getFullPath().append(path);
}
public ISchedulingRule[] getTargetProjects() {
List projects= new ArrayList();
IResourceRuleFactory ruleFactory= ResourcesPlugin.getWorkspace().getRuleFactory();
// Determine the appropriate scheduling rules
for (int i= 0; i < fDiffProjects.length; i++) {
IProject tempProject= Utilities.getProject(fDiffProjects[i]);
// The goal here is to lock as little of the workspace as necessary
// but still allow the patcher to obtain the locks it needs.
// As such, we need to get the modify rules from the rule factory for the .project file. A pessimistic
// rule factory will return the root, while others might return just the project. Combining
// this rule with the project will result in the smallest possible locking set.
ISchedulingRule scheduleRule= ruleFactory.modifyRule(tempProject.getFile(IProjectDescription.DESCRIPTION_FILE_NAME));
MultiRule multiRule= new MultiRule(new ISchedulingRule[] { scheduleRule, tempProject } );
projects.add(multiRule);
}
return (ISchedulingRule[]) projects.toArray(new ISchedulingRule[projects.size()]);
}
public void setDiffProjects(DiffProject[] newProjectArray) {
fDiffProjects = new DiffProject[newProjectArray.length];
System.arraycopy(newProjectArray,0, fDiffProjects, 0, newProjectArray.length);
}
public void removeProject(DiffProject project) {
DiffProject[] temp = new DiffProject[fDiffProjects.length - 1];
int counter = 0;
for (int i = 0; i < fDiffProjects.length; i++) {
if (fDiffProjects[i] != project){
temp[counter++] = fDiffProjects[i];
}
}
fDiffProjects = temp;
}
protected Object getElementParent(Object element) {
if (element instanceof FilePatch2 && fDiffProjects != null) {
FilePatch2 diff = (FilePatch2) element;
for (int i = 0; i < fDiffProjects.length; i++) {
DiffProject project = fDiffProjects[i];
if (project.contains(diff))
return project;
}
}
return null;
}
public boolean isRetargeted(Object object) {
return retargetedDiffs.containsKey(object);
}
public IPath getOriginalPath(Object object) {
return (IPath)retargetedDiffs.get(object);
}
public void retargetDiff(FilePatch2 diff, IFile file) {
retargetedDiffs.put(diff, diff.getPath(false));
IHunk[] hunks = diff.getHunks();
if (isWorkspacePatch()){
//since the diff has no more hunks to apply, remove it from the parent and the patcher
diff.getProject().remove(diff);
}
removeDiff(diff);
FilePatch2 newDiff = getDiffForFile(file);
for (int i = 0; i < hunks.length; i++) {
Hunk hunk = (Hunk) hunks[i];
newDiff.add(hunk);
}
}
private FilePatch2 getDiffForFile(IFile file) {
DiffProject diffProject = null;
FilePatch2[] diffsToCheck;
if (isWorkspacePatch()){
// Check if the diff project already exists for the file
IProject project = file.getProject();
DiffProject[] diffProjects = getDiffProjects();
for (int i = 0; i < diffProjects.length; i++) {
if (Utilities.getProject(diffProjects[i]).equals(project)){
diffProject = diffProjects[i];
break;
}
}
// If the project doesn't exist yet, create it and add it to the project list
if (diffProject == null){
diffProject = addDiffProjectForProject(project);
}
diffsToCheck = diffProject.getFileDiffs();
} else {
diffsToCheck = getDiffs();
}
// Check to see if a diff already exists for the file
for (int i = 0; i < diffsToCheck.length; i++) {
FilePatch2 fileDiff = diffsToCheck[i];
if (isDiffForFile(fileDiff, file)) {
return fileDiff;
}
}
// Create a new diff for the file
IPath path = getDiffPath(file);
FilePatch2 newDiff = new FilePatch2(path, 0, path, 0);
if (diffProject != null){
diffProject.add(newDiff);
}
addDiff(newDiff);
return newDiff;
}
private IPath getDiffPath(IFile file) {
DiffProject project = getDiffProject(file.getProject());
if (project != null) {
return file.getProjectRelativePath();
}
return file.getFullPath().removeFirstSegments(getTarget().getFullPath().segmentCount());
}
private boolean isDiffForFile(FilePatch2 fileDiff, IFile file) {
return getFullPath(fileDiff).equals(file.getFullPath());
}
private DiffProject addDiffProjectForProject(IProject project) {
DiffProject[] diffProjects = getDiffProjects();
DiffProject diffProject = new DiffProject(project.getName());
DiffProject[] newProjectArray = new DiffProject[diffProjects.length + 1];
System.arraycopy(diffProjects, 0, newProjectArray, 0, diffProjects.length);
newProjectArray[diffProjects.length] = diffProject;
setDiffProjects(newProjectArray);
return diffProject;
}
public void retargetHunk(Hunk hunk, IFile file) {
FilePatch2 newDiff = getDiffForFile(file);
newDiff.add(hunk);
}
public void retargetProject(DiffProject project, IProject targetProject) {
retargetedDiffs.put(project, Utilities.getProject(project).getFullPath());
FilePatch2[] diffs = project.getFileDiffs();
DiffProject selectedProject = getDiffProject(targetProject);
if (selectedProject == null)
selectedProject = addDiffProjectForProject(targetProject);
// Copy over the diffs to the new project
for (int i = 0; i < diffs.length; i++) {
selectedProject.add(diffs[i]);
}
// Since the project has been retargeted, remove it from the patcher
removeProject(project);
}
/**
* Return the diff project for the given project
* or <code>null</code> if the diff project doesn't exist
* or if the patch is not a workspace patch.
* @param project the project
* @return the diff project for the given project
* or <code>null</code>
*/
private DiffProject getDiffProject(IProject project) {
if (!isWorkspacePatch())
return null;
DiffProject[] projects = getDiffProjects();
for (int i = 0; i < projects.length; i++) {
if (Utilities.getProject(projects[i]).equals(project))
return projects[i];
}
return null;
}
public int getStripPrefixSegments() {
// Segments are never stripped from a workspace patch
if (isWorkspacePatch())
return 0;
return super.getStripPrefixSegments();
}
int calculateStripGitPrefixSegments() {
FilePatch2[] diffs = getDiffs();
if (diffs.length == 0)
return -1;
int skip = -1;
for (int i = 0; i < diffs.length; i++) {
IPath oldPath = diffs[i].getPath(false);
IPath newPath = diffs[i].getPath(true);
if (checkFirstSegments(new IPath[] { oldPath, newPath },
new String[][] { { "a", "b" }, // change //$NON-NLS-1$ //$NON-NLS-2$
{ "b", "b" }, // addition //$NON-NLS-1$ //$NON-NLS-2$
{ "a", "a" } }) // deletion //$NON-NLS-1$ //$NON-NLS-2$
&& oldPath.segmentCount() > 2 && newPath.segmentCount() > 2) {
for (int j = 1; j < Math.min(oldPath.segmentCount(),
newPath.segmentCount()); j++) {
if (projectExists(oldPath.segment(j))
|| projectExists(newPath.segment(j))) {
if (skip == -1)
skip = j;
else if (skip != j)
return -1; // a different number of segments to be
// skipped, abort
break;
}
}
} else
return -1; // not a git diff or custom prefixes used
}
return skip;
}
private boolean checkFirstSegments(IPath[] paths, String[][] segments) {
SEGMENTS: for (int i = 0; i < segments.length; i++) {
for (int j = 0; j < paths.length; j++) {
if (!paths[j].segment(0).equals(segments[i][j]))
continue SEGMENTS;
}
return true;
}
return false;
}
private boolean projectExists(final String name) {
return ResourcesPlugin.getWorkspace().getRoot().getProject(name).exists();
}
}