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;
|
package com.intellij.openapi.vcs.merge;
|
||||||
|
|
||||||
import com.intellij.openapi.vcs.VcsException;
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.util.ui.ColumnInfo;
|
import com.intellij.util.ui.ColumnInfo;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -58,15 +57,4 @@ public interface MergeSession {
|
|||||||
* @param resolution the used resolution.
|
* @param resolution the used resolution.
|
||||||
*/
|
*/
|
||||||
void conflictResolvedForFile(@NotNull VirtualFile file, @NotNull Resolution 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 {
|
private void acceptFileRevision(@NotNull VirtualFile file, @NotNull MergeSession.Resolution resolution) throws Exception {
|
||||||
if (myMergeSession != null && !myMergeSession.canMerge(file)) return;
|
if (myMergeSession != null && !myMergeSession.canMerge(file)) return;
|
||||||
|
|
||||||
if (myMergeSession != null && myMergeSession.acceptFileRevision(file, resolution)) return;
|
|
||||||
|
|
||||||
if (!DiffUtil.makeWritable(myProject, file)) {
|
if (!DiffUtil.makeWritable(myProject, file)) {
|
||||||
throw new IOException("File is read-only: " + file.getPresentableName());
|
throw new IOException("File is read-only: " + file.getPresentableName());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,6 +237,8 @@ open class MultipleFileMergeDialog2(
|
|||||||
override fun getDimensionServiceKey(): String = "MultipleFileMergeDialog"
|
override fun getDimensionServiceKey(): String = "MultipleFileMergeDialog"
|
||||||
|
|
||||||
private fun acceptRevision(resolution: MergeSession.Resolution) {
|
private fun acceptRevision(resolution: MergeSession.Resolution) {
|
||||||
|
assert(resolution == MergeSession.Resolution.AcceptedYours || resolution == MergeSession.Resolution.AcceptedTheirs)
|
||||||
|
|
||||||
FileDocumentManager.getInstance().saveAllDocuments()
|
FileDocumentManager.getInstance().saveAllDocuments()
|
||||||
val files = getSelectedFiles()
|
val files = getSelectedFiles()
|
||||||
if (!beforeResolve(files)) {
|
if (!beforeResolve(files)) {
|
||||||
@@ -244,10 +246,23 @@ open class MultipleFileMergeDialog2(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (file in files) {
|
if (mergeSession is MergeSessionEx) {
|
||||||
acceptFileRevision(file, resolution)
|
val supportedFiles = files.filter { file -> mergeSession.canMerge(file) }
|
||||||
checkMarkModifiedProject(file)
|
|
||||||
markFileProcessed(file, resolution)
|
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) {
|
catch (e: Exception) {
|
||||||
@@ -261,8 +276,6 @@ open class MultipleFileMergeDialog2(
|
|||||||
private fun acceptFileRevision(file: VirtualFile, resolution: MergeSession.Resolution) {
|
private fun acceptFileRevision(file: VirtualFile, resolution: MergeSession.Resolution) {
|
||||||
if (mergeSession?.canMerge(file) == false) return
|
if (mergeSession?.canMerge(file) == false) return
|
||||||
|
|
||||||
if (mergeSession?.acceptFileRevision(file, resolution) == true) return
|
|
||||||
|
|
||||||
if (!DiffUtil.makeWritable(project, file)) {
|
if (!DiffUtil.makeWritable(project, file)) {
|
||||||
throw IOException("File is read-only: " + file.presentableName)
|
throw IOException("File is read-only: " + file.presentableName)
|
||||||
}
|
}
|
||||||
@@ -280,16 +293,27 @@ open class MultipleFileMergeDialog2(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun markFileProcessed(file: VirtualFile, resolution: MergeSession.Resolution) {
|
private fun markFilesProcessed(files: List<VirtualFile>, resolution: MergeSession.Resolution) {
|
||||||
unresolvedFiles.remove(file)
|
unresolvedFiles.removeAll(files)
|
||||||
if (mergeSession != null) {
|
if (mergeSession is MergeSessionEx) {
|
||||||
mergeSession.conflictResolvedForFile(file, resolution)
|
mergeSession.conflictResolvedForFiles(files, resolution)
|
||||||
|
}
|
||||||
|
else if (mergeSession != null) {
|
||||||
|
files.forEach {
|
||||||
|
mergeSession.conflictResolvedForFile(it, resolution)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mergeProvider.conflictResolvedForFile(file)
|
files.forEach {
|
||||||
|
mergeProvider.conflictResolvedForFile(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
processedFiles.add(file)
|
processedFiles.addAll(files)
|
||||||
VcsDirtyScopeManager.getInstance(project).fileDirty(file)
|
VcsDirtyScopeManager.getInstance(project).filesDirty(files, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun markFileProcessed(file: VirtualFile, resolution: MergeSession.Resolution) {
|
||||||
|
markFilesProcessed(listOf(file), resolution)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateModelFromFiles() {
|
private fun updateModelFromFiles() {
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ import com.intellij.openapi.vcs.FilePath;
|
|||||||
import com.intellij.openapi.vcs.VcsException;
|
import com.intellij.openapi.vcs.VcsException;
|
||||||
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
|
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
|
||||||
import com.intellij.openapi.vcs.merge.*;
|
import com.intellij.openapi.vcs.merge.*;
|
||||||
|
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
|
||||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.util.ArrayUtil;
|
import com.intellij.util.ArrayUtil;
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
|
import com.intellij.util.containers.MultiMap;
|
||||||
import com.intellij.util.ui.ColumnInfo;
|
import com.intellij.util.ui.ColumnInfo;
|
||||||
import com.intellij.vcs.log.Hash;
|
import com.intellij.vcs.log.Hash;
|
||||||
import com.intellij.vcs.log.impl.HashImpl;
|
import com.intellij.vcs.log.impl.HashImpl;
|
||||||
@@ -528,7 +530,7 @@ public class GitMergeProvider implements MergeProvider2 {
|
|||||||
/**
|
/**
|
||||||
* The merge session, it queries conflict information.
|
* The merge session, it queries conflict information.
|
||||||
*/
|
*/
|
||||||
private class MyMergeSession implements MergeSession {
|
private class MyMergeSession implements MergeSessionEx {
|
||||||
Map<VirtualFile, Conflict> myConflicts = new HashMap<>();
|
Map<VirtualFile, Conflict> myConflicts = new HashMap<>();
|
||||||
String currentBranchName;
|
String currentBranchName;
|
||||||
String mergeHeadBranchName;
|
String mergeHeadBranchName;
|
||||||
@@ -613,74 +615,96 @@ public class GitMergeProvider implements MergeProvider2 {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void conflictResolvedForFile(@NotNull VirtualFile file, @NotNull Resolution resolution) {
|
public void conflictResolvedForFile(@NotNull VirtualFile file, @NotNull Resolution resolution) {
|
||||||
Conflict c = myConflicts.get(file);
|
conflictResolvedForFiles(Collections.singletonList(file), resolution);
|
||||||
if (c == null) {
|
}
|
||||||
LOG.error("Conflict was not loaded for the file: " + file.getPath());
|
|
||||||
return;
|
@Override
|
||||||
}
|
public void conflictResolvedForFiles(@NotNull List<VirtualFile> files, @NotNull Resolution resolution) {
|
||||||
try {
|
MultiMap<VirtualFile, Conflict> byRoot = groupConflictsByRoot(files);
|
||||||
Conflict.Status status;
|
|
||||||
switch (resolution) {
|
for (VirtualFile root : byRoot.keySet()) {
|
||||||
case AcceptedTheirs:
|
Collection<Conflict> conflicts = byRoot.get(root);
|
||||||
status = c.myStatusTheirs;
|
|
||||||
break;
|
List<VirtualFile> toAdd = new ArrayList<>();
|
||||||
case AcceptedYours:
|
List<VirtualFile> toDelete = new ArrayList<>();
|
||||||
status = c.myStatusYours;
|
|
||||||
break;
|
for (Conflict c: conflicts) {
|
||||||
case Merged:
|
Conflict.Status status;
|
||||||
status = Conflict.Status.MODIFIED;
|
switch (resolution) {
|
||||||
break;
|
case AcceptedTheirs:
|
||||||
default:
|
status = c.myStatusTheirs;
|
||||||
throw new IllegalArgumentException("Unsupported resolution for unmergable files(" + file.getPath() + "): " + resolution);
|
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:
|
try {
|
||||||
GitFileUtils.addFiles(myProject, c.myRoot, file);
|
GitFileUtils.addFiles(myProject, root, toAdd);
|
||||||
break;
|
GitFileUtils.deleteFiles(myProject, root, toDelete);
|
||||||
case DELETED:
|
}
|
||||||
GitFileUtils.deleteFiles(myProject, c.myRoot, file);
|
catch (VcsException e) {
|
||||||
break;
|
LOG.error(String.format("Unexpected exception during the git operation: modified - %s deleted - %s)", toAdd, toDelete), e);
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unsupported status(" + file.getPath() + "): " + status);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (VcsException e) {
|
|
||||||
LOG.error("Unexpected exception during the git operation (" + file.getPath() + ")", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean acceptFileRevision(@NotNull VirtualFile file, @NotNull MergeSession.Resolution resolution) throws VcsException {
|
public void acceptFilesRevisions(@NotNull List<VirtualFile> files, @NotNull Resolution resolution) throws VcsException {
|
||||||
if (resolution != Resolution.AcceptedYours && resolution != Resolution.AcceptedTheirs) return false;
|
assert resolution == Resolution.AcceptedYours || resolution == Resolution.AcceptedTheirs;
|
||||||
|
|
||||||
|
MultiMap<VirtualFile, Conflict> byRoot = groupConflictsByRoot(files);
|
||||||
boolean isCurrent = resolution == Resolution.AcceptedYours;
|
boolean isCurrent = resolution == Resolution.AcceptedYours;
|
||||||
|
|
||||||
Conflict c = myConflicts.get(file);
|
for (VirtualFile root : byRoot.keySet()) {
|
||||||
if (c == null) {
|
Collection<Conflict> conflicts = byRoot.get(root);
|
||||||
LOG.error("Conflict was not loaded for the file: " + file.getPath());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Conflict.Status status = isCurrent ? c.myStatusYours : c.myStatusTheirs;
|
List<VirtualFile> filesToCheckout = ContainerUtil.mapNotNull(conflicts, c -> {
|
||||||
switch (status) {
|
Conflict.Status status = isCurrent ? c.myStatusYours : c.myStatusTheirs;
|
||||||
case MODIFIED:
|
return status == Conflict.Status.MODIFIED ? c.myFile : null;
|
||||||
String parameter = myReverseRoots.contains(c.myRoot)
|
});
|
||||||
? isCurrent ? "--theirs" : "--ours"
|
|
||||||
: isCurrent ? "--ours" : "--theirs";
|
|
||||||
|
|
||||||
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.addParameters(parameter);
|
||||||
handler.endOptions();
|
handler.endOptions();
|
||||||
handler.addRelativeFiles(Collections.singletonList(c.myFile));
|
handler.addParameters(paths);
|
||||||
GitCommandResult result = Git.getInstance().runCommand(handler);
|
GitCommandResult result = Git.getInstance().runCommand(handler);
|
||||||
if (!result.success()) throw new VcsException(result.getErrorOutputAsJoinedString());
|
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