mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-131357 git: speedup bulk merge resolve using "accept ours/theirs"
It is notably faster to checkout files in one git command.
This commit is contained in:
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.intellij.openapi.vcs.merge;
|
||||
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ui.ColumnInfo;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -58,15 +57,4 @@ public interface MergeSession {
|
||||
* @param resolution the used resolution.
|
||||
*/
|
||||
void conflictResolvedForFile(@NotNull VirtualFile file, @NotNull Resolution resolution);
|
||||
|
||||
/**
|
||||
* Called before {@link #conflictResolvedForFile} when the user executes "Accept Theirs" or "Accept Ours" action
|
||||
* to update file content to the selected version.
|
||||
*
|
||||
* @param resolution AcceptedYours or AcceptedTheirs
|
||||
* @return true if operation was performed, false if we should fall back to generic implementation.
|
||||
*/
|
||||
default boolean acceptFileRevision(@NotNull VirtualFile file, @NotNull MergeSession.Resolution resolution) throws VcsException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.vcs.merge;
|
||||
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the state of a multiple file merge operation.
|
||||
*
|
||||
* @author yole
|
||||
* @see MergeProvider2#createMergeSession
|
||||
* @since 8.1
|
||||
*/
|
||||
public interface MergeSessionEx extends MergeSession {
|
||||
/**
|
||||
* Called when user executes "Accept Theirs" or "Accept Ours" action to update file content on disk to the selected version.
|
||||
*
|
||||
* @param resolution AcceptedYours or AcceptedTheirs
|
||||
*/
|
||||
void acceptFilesRevisions(@NotNull List<VirtualFile> files, @NotNull MergeSessionEx.Resolution resolution) throws VcsException;
|
||||
|
||||
/**
|
||||
* Called after conflict resolution for conflicting files.
|
||||
* @see #conflictResolvedForFile
|
||||
*
|
||||
* @param file the conflicting file.
|
||||
* @param resolution the used resolution.
|
||||
*/
|
||||
void conflictResolvedForFiles(@NotNull List<VirtualFile> files, @NotNull Resolution resolution);
|
||||
}
|
||||
@@ -271,8 +271,6 @@ public class MultipleFileMergeDialog extends DialogWrapper {
|
||||
private void acceptFileRevision(@NotNull VirtualFile file, @NotNull MergeSession.Resolution resolution) throws Exception {
|
||||
if (myMergeSession != null && !myMergeSession.canMerge(file)) return;
|
||||
|
||||
if (myMergeSession != null && myMergeSession.acceptFileRevision(file, resolution)) return;
|
||||
|
||||
if (!DiffUtil.makeWritable(myProject, file)) {
|
||||
throw new IOException("File is read-only: " + file.getPresentableName());
|
||||
}
|
||||
|
||||
@@ -237,6 +237,8 @@ open class MultipleFileMergeDialog2(
|
||||
override fun getDimensionServiceKey(): String = "MultipleFileMergeDialog"
|
||||
|
||||
private fun acceptRevision(resolution: MergeSession.Resolution) {
|
||||
assert(resolution == MergeSession.Resolution.AcceptedYours || resolution == MergeSession.Resolution.AcceptedTheirs)
|
||||
|
||||
FileDocumentManager.getInstance().saveAllDocuments()
|
||||
val files = getSelectedFiles()
|
||||
if (!beforeResolve(files)) {
|
||||
@@ -244,10 +246,23 @@ open class MultipleFileMergeDialog2(
|
||||
}
|
||||
|
||||
try {
|
||||
for (file in files) {
|
||||
acceptFileRevision(file, resolution)
|
||||
checkMarkModifiedProject(file)
|
||||
markFileProcessed(file, resolution)
|
||||
if (mergeSession is MergeSessionEx) {
|
||||
val supportedFiles = files.filter { file -> mergeSession.canMerge(file) }
|
||||
|
||||
mergeSession.acceptFilesRevisions(supportedFiles, resolution)
|
||||
|
||||
for (file in supportedFiles) {
|
||||
checkMarkModifiedProject(file)
|
||||
}
|
||||
|
||||
markFilesProcessed(supportedFiles, resolution)
|
||||
}
|
||||
else {
|
||||
for (file in files) {
|
||||
acceptFileRevision(file, resolution)
|
||||
checkMarkModifiedProject(file)
|
||||
markFileProcessed(file, resolution)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
@@ -261,8 +276,6 @@ open class MultipleFileMergeDialog2(
|
||||
private fun acceptFileRevision(file: VirtualFile, resolution: MergeSession.Resolution) {
|
||||
if (mergeSession?.canMerge(file) == false) return
|
||||
|
||||
if (mergeSession?.acceptFileRevision(file, resolution) == true) return
|
||||
|
||||
if (!DiffUtil.makeWritable(project, file)) {
|
||||
throw IOException("File is read-only: " + file.presentableName)
|
||||
}
|
||||
@@ -280,16 +293,27 @@ open class MultipleFileMergeDialog2(
|
||||
}
|
||||
}
|
||||
|
||||
private fun markFileProcessed(file: VirtualFile, resolution: MergeSession.Resolution) {
|
||||
unresolvedFiles.remove(file)
|
||||
if (mergeSession != null) {
|
||||
mergeSession.conflictResolvedForFile(file, resolution)
|
||||
private fun markFilesProcessed(files: List<VirtualFile>, resolution: MergeSession.Resolution) {
|
||||
unresolvedFiles.removeAll(files)
|
||||
if (mergeSession is MergeSessionEx) {
|
||||
mergeSession.conflictResolvedForFiles(files, resolution)
|
||||
}
|
||||
else if (mergeSession != null) {
|
||||
files.forEach {
|
||||
mergeSession.conflictResolvedForFile(it, resolution)
|
||||
}
|
||||
}
|
||||
else {
|
||||
mergeProvider.conflictResolvedForFile(file)
|
||||
files.forEach {
|
||||
mergeProvider.conflictResolvedForFile(it)
|
||||
}
|
||||
}
|
||||
processedFiles.add(file)
|
||||
VcsDirtyScopeManager.getInstance(project).fileDirty(file)
|
||||
processedFiles.addAll(files)
|
||||
VcsDirtyScopeManager.getInstance(project).filesDirty(files, emptyList())
|
||||
}
|
||||
|
||||
private fun markFileProcessed(file: VirtualFile, resolution: MergeSession.Resolution) {
|
||||
markFilesProcessed(listOf(file), resolution)
|
||||
}
|
||||
|
||||
private fun updateModelFromFiles() {
|
||||
|
||||
@@ -13,10 +13,12 @@ import com.intellij.openapi.vcs.FilePath;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
|
||||
import com.intellij.openapi.vcs.merge.*;
|
||||
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.ui.ColumnInfo;
|
||||
import com.intellij.vcs.log.Hash;
|
||||
import com.intellij.vcs.log.impl.HashImpl;
|
||||
@@ -528,7 +530,7 @@ public class GitMergeProvider implements MergeProvider2 {
|
||||
/**
|
||||
* The merge session, it queries conflict information.
|
||||
*/
|
||||
private class MyMergeSession implements MergeSession {
|
||||
private class MyMergeSession implements MergeSessionEx {
|
||||
Map<VirtualFile, Conflict> myConflicts = new HashMap<>();
|
||||
String currentBranchName;
|
||||
String mergeHeadBranchName;
|
||||
@@ -613,74 +615,96 @@ public class GitMergeProvider implements MergeProvider2 {
|
||||
|
||||
@Override
|
||||
public void conflictResolvedForFile(@NotNull VirtualFile file, @NotNull Resolution resolution) {
|
||||
Conflict c = myConflicts.get(file);
|
||||
if (c == null) {
|
||||
LOG.error("Conflict was not loaded for the file: " + file.getPath());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Conflict.Status status;
|
||||
switch (resolution) {
|
||||
case AcceptedTheirs:
|
||||
status = c.myStatusTheirs;
|
||||
break;
|
||||
case AcceptedYours:
|
||||
status = c.myStatusYours;
|
||||
break;
|
||||
case Merged:
|
||||
status = Conflict.Status.MODIFIED;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported resolution for unmergable files(" + file.getPath() + "): " + resolution);
|
||||
conflictResolvedForFiles(Collections.singletonList(file), resolution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void conflictResolvedForFiles(@NotNull List<VirtualFile> files, @NotNull Resolution resolution) {
|
||||
MultiMap<VirtualFile, Conflict> byRoot = groupConflictsByRoot(files);
|
||||
|
||||
for (VirtualFile root : byRoot.keySet()) {
|
||||
Collection<Conflict> conflicts = byRoot.get(root);
|
||||
|
||||
List<VirtualFile> toAdd = new ArrayList<>();
|
||||
List<VirtualFile> toDelete = new ArrayList<>();
|
||||
|
||||
for (Conflict c: conflicts) {
|
||||
Conflict.Status status;
|
||||
switch (resolution) {
|
||||
case AcceptedTheirs:
|
||||
status = c.myStatusTheirs;
|
||||
break;
|
||||
case AcceptedYours:
|
||||
status = c.myStatusYours;
|
||||
break;
|
||||
case Merged:
|
||||
status = Conflict.Status.MODIFIED;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported resolution: " + resolution);
|
||||
}
|
||||
|
||||
if (status == Conflict.Status.MODIFIED) {
|
||||
toAdd.add(c.myFile);
|
||||
}
|
||||
else {
|
||||
toDelete.add(c.myFile);
|
||||
}
|
||||
}
|
||||
switch (status) {
|
||||
case MODIFIED:
|
||||
GitFileUtils.addFiles(myProject, c.myRoot, file);
|
||||
break;
|
||||
case DELETED:
|
||||
GitFileUtils.deleteFiles(myProject, c.myRoot, file);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported status(" + file.getPath() + "): " + status);
|
||||
|
||||
try {
|
||||
GitFileUtils.addFiles(myProject, root, toAdd);
|
||||
GitFileUtils.deleteFiles(myProject, root, toDelete);
|
||||
}
|
||||
catch (VcsException e) {
|
||||
LOG.error(String.format("Unexpected exception during the git operation: modified - %s deleted - %s)", toAdd, toDelete), e);
|
||||
}
|
||||
}
|
||||
catch (VcsException e) {
|
||||
LOG.error("Unexpected exception during the git operation (" + file.getPath() + ")", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptFileRevision(@NotNull VirtualFile file, @NotNull MergeSession.Resolution resolution) throws VcsException {
|
||||
if (resolution != Resolution.AcceptedYours && resolution != Resolution.AcceptedTheirs) return false;
|
||||
public void acceptFilesRevisions(@NotNull List<VirtualFile> files, @NotNull Resolution resolution) throws VcsException {
|
||||
assert resolution == Resolution.AcceptedYours || resolution == Resolution.AcceptedTheirs;
|
||||
|
||||
MultiMap<VirtualFile, Conflict> byRoot = groupConflictsByRoot(files);
|
||||
boolean isCurrent = resolution == Resolution.AcceptedYours;
|
||||
|
||||
Conflict c = myConflicts.get(file);
|
||||
if (c == null) {
|
||||
LOG.error("Conflict was not loaded for the file: " + file.getPath());
|
||||
return false;
|
||||
}
|
||||
for (VirtualFile root : byRoot.keySet()) {
|
||||
Collection<Conflict> conflicts = byRoot.get(root);
|
||||
|
||||
Conflict.Status status = isCurrent ? c.myStatusYours : c.myStatusTheirs;
|
||||
switch (status) {
|
||||
case MODIFIED:
|
||||
String parameter = myReverseRoots.contains(c.myRoot)
|
||||
? isCurrent ? "--theirs" : "--ours"
|
||||
: isCurrent ? "--ours" : "--theirs";
|
||||
List<VirtualFile> filesToCheckout = ContainerUtil.mapNotNull(conflicts, c -> {
|
||||
Conflict.Status status = isCurrent ? c.myStatusYours : c.myStatusTheirs;
|
||||
return status == Conflict.Status.MODIFIED ? c.myFile : null;
|
||||
});
|
||||
|
||||
GitLineHandler handler = new GitLineHandler(myProject, c.myRoot, GitCommand.CHECKOUT);
|
||||
String parameter = myReverseRoots.contains(root)
|
||||
? isCurrent ? "--theirs" : "--ours"
|
||||
: isCurrent ? "--ours" : "--theirs";
|
||||
|
||||
for (List<String> paths : VcsFileUtil.chunkFiles(root, filesToCheckout)) {
|
||||
GitLineHandler handler = new GitLineHandler(myProject, root, GitCommand.CHECKOUT);
|
||||
handler.addParameters(parameter);
|
||||
handler.endOptions();
|
||||
handler.addRelativeFiles(Collections.singletonList(c.myFile));
|
||||
handler.addParameters(paths);
|
||||
GitCommandResult result = Git.getInstance().runCommand(handler);
|
||||
if (!result.success()) throw new VcsException(result.getErrorOutputAsJoinedString());
|
||||
break;
|
||||
case DELETED:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported status(" + file.getPath() + "): " + status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@NotNull
|
||||
private MultiMap<VirtualFile, Conflict> groupConflictsByRoot(@NotNull List<VirtualFile> files) {
|
||||
MultiMap<VirtualFile, Conflict> byRoot = MultiMap.create();
|
||||
for (VirtualFile file: files) {
|
||||
Conflict c = myConflicts.get(file);
|
||||
if (c == null) {
|
||||
LOG.error("Conflict was not loaded for the file: " + file.getPath());
|
||||
continue;
|
||||
}
|
||||
|
||||
byRoot.putValue(c.myRoot, c);
|
||||
}
|
||||
return byRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user