blob: f4770e4e77f050940c62bc9df6522e8488dc3f8a [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.vcs.changes.patch;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diff.impl.patch.FilePatch;
import com.intellij.openapi.diff.impl.patch.TextFilePatch;
import com.intellij.openapi.diff.impl.patch.formove.PathMerger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Getter;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FilePathImpl;
import com.intellij.openapi.vcs.FileStatus;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vcs.changes.CurrentContentRevision;
import com.intellij.openapi.vcs.changes.SimpleContentRevision;
import com.intellij.openapi.vcs.changes.actions.ChangeDiffRequestPresentable;
import com.intellij.openapi.vcs.changes.actions.DiffRequestPresentable;
import com.intellij.openapi.vcs.changes.actions.DiffRequestPresentableProxy;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.vcsUtil.VcsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class FilePatchInProgress implements Strippable {
private final TextFilePatch myPatch;
private final PatchStrippable myStrippable;
private final FilePatchStatus myStatus;
private VirtualFile myBase;
private File myIoCurrentBase;
private VirtualFile myCurrentBase;
private boolean myBaseExists;
private ContentRevision myNewContentRevision;
private ContentRevision myCurrentRevision;
private final List<VirtualFile> myAutoBases;
private volatile Boolean myConflicts;
private File myAfterFile;
public FilePatchInProgress(final TextFilePatch patch, final Collection<VirtualFile> autoBases, final VirtualFile baseDir) {
myPatch = patch.pathsOnlyCopy();
myStrippable = new PatchStrippable(patch);
myAutoBases = new ArrayList<VirtualFile>();
if (autoBases != null) {
setAutoBases(autoBases);
}
myStatus = getStatus(myPatch);
if (myAutoBases.isEmpty()) {
setNewBase(baseDir);
} else {
setNewBase(myAutoBases.get(0));
}
}
public void setAutoBases(@NotNull final Collection<VirtualFile> autoBases) {
final String path = myPatch.getBeforeName() == null ? myPatch.getAfterName() : myPatch.getBeforeName();
for (VirtualFile autoBase : autoBases) {
final VirtualFile willBeBase = PathMerger.getBase(autoBase, path);
if (willBeBase != null) {
myAutoBases.add(willBeBase);
}
}
}
private static FilePatchStatus getStatus(final TextFilePatch patch) {
final String beforeName = patch.getBeforeName().replace("\\", "/");
final String afterName = patch.getAfterName().replace("\\", "/");
if (patch.isNewFile() || (beforeName == null)) {
return FilePatchStatus.ADDED;
} else if (patch.isDeletedFile() || (afterName == null)) {
return FilePatchStatus.DELETED;
}
if (beforeName.equals(afterName)) return FilePatchStatus.MODIFIED;
return FilePatchStatus.MOVED_OR_RENAMED;
}
public PatchChange getChange() {
return new PatchChange(getCurrentRevision(), getNewContentRevision(), this);
}
public void setNewBase(final VirtualFile base) {
myBase = base;
myNewContentRevision = null;
myCurrentRevision = null;
myAfterFile = null;
myConflicts = null;
final String beforeName = myPatch.getBeforeName();
if (beforeName != null) {
myIoCurrentBase = PathMerger.getFile(new File(myBase.getPath()), beforeName);
myCurrentBase = myIoCurrentBase == null ? null : VcsUtil.getVirtualFileWithRefresh(myIoCurrentBase);
myBaseExists = (myCurrentBase != null) && myCurrentBase.exists();
} else {
// creation
final String afterName = myPatch.getAfterName();
myBaseExists = true;
myIoCurrentBase = PathMerger.getFile(new File(myBase.getPath()), afterName);
myCurrentBase = VcsUtil.getVirtualFileWithRefresh(myIoCurrentBase);
}
}
public void setCreatedCurrentBase(final VirtualFile vf) {
myCurrentBase = vf;
}
public FilePatchStatus getStatus() {
return myStatus;
}
public File getIoCurrentBase() {
return myIoCurrentBase;
}
public VirtualFile getCurrentBase() {
return myCurrentBase;
}
public VirtualFile getBase() {
return myBase;
}
public TextFilePatch getPatch() {
return myPatch;
}
public boolean isBaseExists() {
return myBaseExists;
}
public boolean baseExistsOrAdded() {
return myBaseExists || FilePatchStatus.ADDED.equals(myStatus);
}
public ContentRevision getNewContentRevision() {
if (FilePatchStatus.DELETED.equals(myStatus)) return null;
if (myNewContentRevision == null) {
myConflicts = null;
if (FilePatchStatus.ADDED.equals(myStatus)) {
final FilePath newFilePath = FilePathImpl.createNonLocal(myIoCurrentBase.getAbsolutePath(), false);
final String content = myPatch.getNewFileText();
myNewContentRevision = new SimpleContentRevision(content, newFilePath, myPatch.getAfterVersionId());
} else {
final FilePath newFilePath;
if (FilePatchStatus.MOVED_OR_RENAMED.equals(myStatus)) {
newFilePath = new FilePathImpl(PathMerger.getFile(new File(myBase.getPath()), myPatch.getAfterName()), false);
} else {
newFilePath = (myCurrentBase != null) ? new FilePathImpl(myCurrentBase) : new FilePathImpl(myIoCurrentBase, false);
}
myNewContentRevision = new LazyPatchContentRevision(myCurrentBase, newFilePath, myPatch.getAfterVersionId(), myPatch);
if (myCurrentBase != null) {
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
public void run() {
((LazyPatchContentRevision) myNewContentRevision).getContent();
}
});
}
}
}
return myNewContentRevision;
}
public boolean isConflictingChange() {
if (myConflicts == null) {
if ((myCurrentBase != null) && (myNewContentRevision instanceof LazyPatchContentRevision)) {
((LazyPatchContentRevision) myNewContentRevision).getContent();
myConflicts = ((LazyPatchContentRevision) myNewContentRevision).isPatchApplyFailed();
} else {
myConflicts = false;
}
}
return myConflicts;
}
public ContentRevision getCurrentRevision() {
if (FilePatchStatus.ADDED.equals(myStatus)) return null;
if (myCurrentRevision == null) {
final FilePathImpl filePath = (myCurrentBase != null) ? new FilePathImpl(myCurrentBase) : new FilePathImpl(myIoCurrentBase, false);
myCurrentRevision = new CurrentContentRevision(filePath);
}
return myCurrentRevision;
}
public static class PatchChange extends Change {
private final FilePatchInProgress myPatchInProgress;
public PatchChange(ContentRevision beforeRevision, ContentRevision afterRevision, FilePatchInProgress patchInProgress) {
super(beforeRevision, afterRevision,
patchInProgress.isBaseExists() || FilePatchStatus.ADDED.equals(patchInProgress.getStatus()) ? null : FileStatus.MERGED_WITH_CONFLICTS);
myPatchInProgress = patchInProgress;
}
public FilePatchInProgress getPatchInProgress() {
return myPatchInProgress;
}
@Nullable
public DiffRequestPresentable createDiffRequestPresentable(final Project project, final Getter<CharSequence> baseContents) {
return new DiffRequestPresentableProxy() {
@NotNull
@Override
protected DiffRequestPresentable init() throws VcsException {
if (myPatchInProgress.isConflictingChange()) {
final Getter<ApplyPatchForBaseRevisionTexts> revisionTextsGetter = new Getter<ApplyPatchForBaseRevisionTexts>() {
@Override
public ApplyPatchForBaseRevisionTexts get() {
return ApplyPatchForBaseRevisionTexts.create(project, myPatchInProgress.getCurrentBase(),
new FilePathImpl(myPatchInProgress.getCurrentBase()), myPatchInProgress.getPatch(), baseContents);
}
};
return new MergedDiffRequestPresentable(project, revisionTextsGetter,
myPatchInProgress.getCurrentBase(), myPatchInProgress.getPatch().getAfterVersionId());
} else {
return new ChangeDiffRequestPresentable(project, PatchChange.this);
}
}
@Override
public String getPathPresentation() {
final File ioCurrentBase = myPatchInProgress.getIoCurrentBase();
return ioCurrentBase == null ? myPatchInProgress.getCurrentPath() : ioCurrentBase.getPath();
}
};
}
}
public List<VirtualFile> getAutoBasesCopy() {
final ArrayList<VirtualFile> result = new ArrayList<VirtualFile>(myAutoBases.size() + 1);
result.addAll(myAutoBases);
return result;
}
public Couple<String> getKey() {
return Couple.of(myPatch.getBeforeName(), myPatch.getAfterName());
}
private void refresh() {
myStrippable.applyBackToPatch(myPatch);
setNewBase(myBase);
}
public void reset() {
myStrippable.reset();
refresh();
}
public boolean canDown() {
return myStrippable.canDown();
}
public boolean canUp() {
return myStrippable.canUp();
}
public void up() {
myStrippable.up();
refresh();
}
public void down() {
myStrippable.down();
refresh();
}
public void setZero() {
myStrippable.setZero();
refresh();
}
public String getCurrentPath() {
return myStrippable.getCurrentPath();
}
public int getCurrentStrip() {
return myStrippable.getCurrentStrip();
}
private static class StripCapablePath implements Strippable {
private final int myStripMax;
private int myCurrentStrip;
private final StringBuilder mySourcePath;
private final int[] myParts;
private StripCapablePath(final String path) {
final String corrected = path.trim().replace('\\', '/');
mySourcePath = new StringBuilder(corrected);
final String[] steps = corrected.split("/");
myStripMax = steps.length - 1;
myParts = new int[steps.length];
int pos = 0;
for (int i = 0; i < steps.length; i++) {
final String step = steps[i];
myParts[i] = pos;
pos += step.length() + 1; // plus 1 for separator
}
myCurrentStrip = 0;
}
public void reset() {
myCurrentStrip = 0;
}
public int getCurrentStrip() {
return myCurrentStrip;
}
// down - restore dirs...
public boolean canDown() {
return myCurrentStrip > 0;
}
public boolean canUp() {
return myCurrentStrip < myStripMax;
}
public void up() {
if (canUp()) {
++ myCurrentStrip;
}
}
public void down() {
if (canDown()) {
-- myCurrentStrip;
}
}
public void setZero() {
myCurrentStrip = myStripMax;
}
public String getCurrentPath() {
return mySourcePath.substring(myParts[myCurrentStrip]);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StripCapablePath that = (StripCapablePath)o;
if (!mySourcePath.equals(that.mySourcePath)) return false;
return true;
}
@Override
public int hashCode() {
return mySourcePath.hashCode();
}
}
private static class PatchStrippable implements Strippable {
private final Strippable[] myParts;
private final int myBeforeIdx;
private final int myAfterIdx;
private PatchStrippable(final FilePatch patch) {
final boolean onePath = patch.isDeletedFile() || patch.isNewFile() || Comparing.equal(patch.getAfterName(), patch.getBeforeName());
final int size = onePath ? 1 : 2;
myParts = new Strippable[size];
int cnt = 0;
if (patch.getAfterName() != null) {
myAfterIdx = 0;
myParts[cnt] = new StripCapablePath(patch.getAfterName());
++ cnt;
} else {
myAfterIdx = -1;
}
if (cnt < size) {
myParts[cnt] = new StripCapablePath(patch.getBeforeName());
myBeforeIdx = cnt;
} else {
myBeforeIdx = 0;
}
}
public void reset() {
for (Strippable part : myParts) {
part.reset();
}
}
public boolean canDown() {
boolean result = true;
for (Strippable part : myParts) {
result &= part.canDown();
}
return result;
}
public boolean canUp() {
boolean result = true;
for (Strippable part : myParts) {
result &= part.canUp();
}
return result;
}
public void up() {
for (Strippable part : myParts) {
part.up();
}
}
public void down() {
for (Strippable part : myParts) {
part.down();
}
}
public void setZero() {
for (Strippable part : myParts) {
part.setZero();
}
}
public String getCurrentPath() {
return myParts[0].getCurrentPath();
}
public int getCurrentStrip() {
return myParts[0].getCurrentStrip();
}
public void applyBackToPatch(final FilePatch patch) {
final String beforeName = patch.getBeforeName();
if (beforeName != null) {
patch.setBeforeName(myParts[myBeforeIdx].getCurrentPath());
}
final String afterName = patch.getAfterName();
if (afterName != null) {
patch.setAfterName(myParts[myAfterIdx].getCurrentPath());
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PatchStrippable that = (PatchStrippable)o;
if (!Arrays.equals(myParts, that.myParts)) return false;
return true;
}
@Override
public int hashCode() {
return Arrays.hashCode(myParts);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FilePatchInProgress that = (FilePatchInProgress)o;
if (!myStrippable.equals(that.myStrippable)) return false;
return true;
}
@Override
public int hashCode() {
return myStrippable.hashCode();
}
}