| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * |
| * 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 |
| * |
| * 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.SubMonitor; |
| 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<Object, IPath> retargetedDiffs = new HashMap<>(); |
| |
| public WorkspacePatcher() { |
| // nothing to do |
| } |
| |
| public WorkspacePatcher(IResource target) { |
| setTarget(target); |
| } |
| |
| @Override |
| 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 |
| |
| @Override |
| 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<IFile> list= new ArrayList<>(); |
| for (DiffProject diffProject : fDiffProjects) { |
| if (Utilities.getProject(diffProject).isAccessible()) |
| list.addAll(Arrays.asList(getTargetFiles(diffProject))); |
| } |
| // validate the files for editing |
| if (!validator.validateResources(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 (FilePatch2 diff : diffs) { |
| int workTicks= WORK_UNIT; |
| if (isAccessible(diff)) { |
| IFile file= getTargetFile(diff); |
| IPath path= file.getProjectRelativePath(); |
| if (pm != null) |
| pm.subTask(path.toString()); |
| createPath(file.getProject(), path); |
| |
| List<Hunk> failed= new ArrayList<>(); |
| |
| int type= diff.getDiffType(isReversed()); |
| switch (type) { |
| case FilePatch2.ADDITION : |
| // patch it and collect rejected hunks |
| List<String> result= apply(diff, file, true, failed); |
| if (result != null) |
| store(LineReader.createString(isPreserveLineDelimeters(), result), file, SubMonitor.convert(pm, workTicks)); |
| workTicks -= WORK_UNIT; |
| break; |
| case FilePatch2.DELETION : |
| file.delete(true, true, SubMonitor.convert(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, SubMonitor.convert(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<IFile> files= new ArrayList<>(); |
| FilePatch2[] diffs = project.getFileDiffs(); |
| for (FilePatch2 diff : diffs) { |
| if (isEnabled(diff)) { |
| files.add(getTargetFile(diff)); |
| } |
| } |
| return files.toArray(new IFile[files.size()]); |
| } |
| |
| @Override |
| 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<ISchedulingRule> projects= new ArrayList<>(); |
| IResourceRuleFactory ruleFactory= ResourcesPlugin.getWorkspace().getRuleFactory(); |
| // Determine the appropriate scheduling rules |
| for (DiffProject diffProject : fDiffProjects) { |
| IProject tempProject = Utilities.getProject(diffProject); |
| // 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 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 (DiffProject diffProject : fDiffProjects) { |
| if (diffProject != project) { |
| temp[counter++] = diffProject; |
| } |
| } |
| fDiffProjects = temp; |
| } |
| |
| @Override |
| protected Object getElementParent(Object element) { |
| if (element instanceof FilePatch2 && fDiffProjects != null) { |
| FilePatch2 diff = (FilePatch2) element; |
| for (DiffProject project : fDiffProjects) { |
| if (project.contains(diff)) |
| return project; |
| } |
| } |
| return null; |
| } |
| |
| public boolean isRetargeted(Object object) { |
| return retargetedDiffs.containsKey(object); |
| } |
| |
| public IPath getOriginalPath(Object object) { |
| return 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 (IHunk h : hunks) { |
| Hunk hunk = (Hunk) h; |
| 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 (DiffProject d : diffProjects) { |
| if (Utilities.getProject(d).equals(project)) { |
| diffProject = d; |
| 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 (FilePatch2 fileDiff : diffsToCheck) { |
| 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 (FilePatch2 diff : diffs) { |
| selectedProject.add(diff); |
| } |
| // 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 (DiffProject p : projects) { |
| if (Utilities.getProject(p).equals(project)) { |
| return p; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| 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 (FilePatch2 diff : diffs) { |
| IPath oldPath = diff.getPath(false); |
| IPath newPath = diff.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 (String[] segment : segments) { |
| for (int j = 0; j < paths.length; j++) { |
| if (!paths[j].segment(0).equals(segment[j])) { |
| continue SEGMENTS; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean projectExists(final String name) { |
| return ResourcesPlugin.getWorkspace().getRoot().getProject(name).exists(); |
| } |
| } |