blob: 11c00bc94764e07c810fff1239a832ae11d110fe [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.history;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.*;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.encoding.EncodingManager;
import com.intellij.openapi.vfs.encoding.EncodingProjectManager;
import com.intellij.util.WaitForProgressToShow;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
public class VcsHistoryUtil {
private static final Logger LOG = Logger.getInstance(VcsHistoryUtil.class);
private VcsHistoryUtil() {
}
public static int compare(VcsFileRevision first, VcsFileRevision second) {
if (first instanceof CurrentRevision && second instanceof CurrentRevision) {
return compareNumbers(first, second);
}
if (second instanceof CurrentRevision) return -1 * compare(second, first);
if (first instanceof CurrentRevision) {
int result = compareNumbers(first, second);
if (result == 0) {
return 1;
}
else {
return result;
}
}
else {
return compareNumbers(first, second);
}
}
public static int compareNumbers(VcsFileRevision first, VcsFileRevision second) {
return first.getRevisionNumber().compareTo(second.getRevisionNumber());
}
/**
* Invokes {@link com.intellij.openapi.diff.DiffManager#getDiffTool()} to show difference between the given revisions of the given file.
* @param project project under vcs control.
* @param filePath file which revisions are compared.
* @param revision1 first revision - 'before', to the left.
* @param revision2 second revision - 'after', to the right.
* @throws com.intellij.openapi.vcs.VcsException
* @throws java.io.IOException
*/
public static void showDiff(@NotNull final Project project, @NotNull FilePath filePath,
@NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2,
@NotNull String title1, @NotNull String title2) throws VcsException, IOException {
final byte[] content1 = loadRevisionContent(revision1);
final byte[] content2 = loadRevisionContent(revision2);
final SimpleDiffRequest diffData = new SimpleDiffRequest(project, filePath.getPresentableUrl());
diffData.addHint(DiffTool.HINT_SHOW_FRAME);
final Document doc = filePath.getDocument();
final Charset charset = filePath.getCharset();
final FileType fileType = filePath.getFileType();
diffData.setContentTitles(title1, title2);
final Ref<VirtualFile> f1 = new Ref<VirtualFile>(null);
final Ref<VirtualFile> f2 = new Ref<VirtualFile>(null);
if (fileType.isBinary()) {
final File file1 = FileUtil.createTempFile(revision1.getRevisionNumber().asString(), filePath.getName());
final File file2 = FileUtil.createTempFile(revision2.getRevisionNumber().asString(), filePath.getName());
try {
final FileOutputStream fos1 = new FileOutputStream(file1);
fos1.write(content1);
final FileOutputStream fos2 = new FileOutputStream(file2);
fos2.write(content2);
fos1.close();
fos2.close();
f1.set(LocalFileSystem.getInstance().findFileByIoFile(file1));
f2.set(LocalFileSystem.getInstance().findFileByIoFile(file2));
} catch(Exception e) {//
}
}
if (f1.isNull() || f2.isNull()) {
diffData.setContents(createContent(project, content1, revision1, doc, charset, fileType, filePath.getPath()),
createContent(project, content2, revision2, doc, charset, fileType, filePath.getPath()));
} else {
diffData.setContents(createFileContent(project, f1.get(), revision1), createFileContent(project, f2.get(), revision2));
}
WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() {
public void run() {
DiffManager.getInstance().getDiffTool().show(diffData);
if (!f1.isNull() || !f2.isNull()) {
Disposer.register(project, new Disposable() {
@Override
public void dispose() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
try {
if (!f1.isNull()) {
f1.get().delete(this);
}
if (!f2.isNull()) {
f2.get().delete(this);
}
}
catch (IOException e) {//
}
}
});
}
});
}
}
}, null, project);
}
public static byte[] loadRevisionContent(@NotNull VcsFileRevision revision) throws VcsException, IOException {
byte[] content = revision.getContent();
if (content == null) {
revision.loadContent();
content = revision.getContent();
}
if (content == null) throw new VcsException("Failed to load content for revision " + revision.getRevisionNumber().asString());
return content;
}
public static String loadRevisionContentGuessEncoding(@NotNull final VcsFileRevision revision, @Nullable final VirtualFile file,
@Nullable final Project project) throws VcsException, IOException {
final byte[] bytes = loadRevisionContent(revision);
if (file != null) {
return new String(bytes, file.getCharset());
}
EncodingManager e = project != null ? EncodingProjectManager.getInstance(project) : null;
if (e == null) {
e = EncodingManager.getInstance();
}
return CharsetToolkit.bytesToString(bytes, e.getDefaultCharset());
}
public static String loadRevisionContentGuessEncoding(final VcsFileRevision revision, @Nullable final Project project) throws VcsException, IOException {
final byte[] bytes = loadRevisionContent(revision);
EncodingManager e = project != null ? EncodingProjectManager.getInstance(project) : null;
if (e == null) {
e = EncodingManager.getInstance();
}
return CharsetToolkit.bytesToString(bytes, e.getDefaultCharset());
}
private static DiffContent createContent(@NotNull Project project, byte[] content1, VcsFileRevision revision, Document doc, Charset charset, FileType fileType, String filePath) {
if (isCurrent(revision) && (doc != null)) { return new DocumentContent(project, doc); }
if (isEmpty(revision)) { return SimpleContent.createEmpty(); }
return new BinaryContent(project, content1, charset, fileType, filePath);
}
private static DiffContent createFileContent(@NotNull Project project, VirtualFile file, VcsFileRevision revision) {
if (isEmpty(revision)) { return SimpleContent.createEmpty(); }
return new FileContent(project, file);
}
private static boolean isCurrent(VcsFileRevision revision) {
return revision instanceof CurrentRevision;
}
private static boolean isEmpty(VcsFileRevision revision) {
return revision == null || VcsFileRevision.NULL.equals(revision);
}
/**
* Shows difference between two revisions of a file in a diff tool.
* The content of revisions is queried in a background thread.
* If {@code findOlderNewer} is set to {@code true}, revisions may be specified in any order:
* this method will sort them so that the older revision is at the left, and the newer one is at the right.
* @param findOlderNewer specify {@code true} to let method compare revisions, and put the older revision at the left, and newer revision
* at the right.<br/>
* Specify {@code false} to put {@code revision1} at the left, and {@code revision2} at the right.
* @see #showDiff(Project, FilePath, VcsFileRevision, VcsFileRevision, String, String)
*/
public static void showDifferencesInBackground(@NotNull final Project project, @NotNull final FilePath filePath,
@NotNull final VcsFileRevision revision1, @NotNull final VcsFileRevision revision2,
final boolean findOlderNewer) {
new Task.Backgroundable(project, "Loading revisions to compare") {
@Override
public void run(@NotNull ProgressIndicator indicator) {
VcsFileRevision left = revision1;
VcsFileRevision right = revision2;
if (findOlderNewer) {
Pair<VcsFileRevision, VcsFileRevision> pair = sortRevisions(revision1, revision2);
left = pair.first;
right = pair.second;
}
try {
final String leftTitle = left.getRevisionNumber().asString() +
(left instanceof CurrentRevision ? " (" + VcsBundle.message("diff.title.local") + ")" : "");
final String rightTitle = right.getRevisionNumber().asString() +
(right instanceof CurrentRevision ? " (" + VcsBundle.message("diff.title.local") + ")" : "");
showDiff(project, filePath, left, right, leftTitle, rightTitle);
}
catch (final VcsException e) {
LOG.info(e);
WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() {
public void run() {
Messages.showErrorDialog(VcsBundle.message("message.text.cannot.show.differences", e.getLocalizedMessage()),
VcsBundle.message("message.title.show.differences"));
}
}, null, project);
}
catch (IOException e) {
LOG.info(e);
}
catch (ProcessCanceledException ex) {
LOG.info(ex);
}
}
}.queue();
}
/**
* Compares the given revisions and returns a pair of them, where the first one is older, and second is newer.
*/
@NotNull
public static Couple<VcsFileRevision> sortRevisions(@NotNull VcsFileRevision revision1,
@NotNull VcsFileRevision revision2) {
VcsFileRevision left = revision1;
VcsFileRevision right = revision2;
if (compare(revision1, revision2) > 0) {
left = revision2;
right = revision1;
}
return Couple.of(left, right);
}
}