mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[git] IDEA-84771 don't propose to commit empty changelist on cherry-pick
This commit is contained in:
@@ -21,6 +21,7 @@ import com.intellij.notification.NotificationListener;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vcs.FilePath;
|
||||
import com.intellij.openapi.vcs.VcsNotifier;
|
||||
import com.intellij.openapi.vcs.changes.*;
|
||||
@@ -42,11 +43,15 @@ import git4idea.merge.GitConflictResolver;
|
||||
import git4idea.repo.GitRepository;
|
||||
import git4idea.util.UntrackedFilesNotifier;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -80,18 +85,19 @@ public class GitCherryPicker {
|
||||
}
|
||||
|
||||
public void cherryPick(@NotNull Map<GitRepository, List<VcsFullCommitDetails>> commitsInRoots) {
|
||||
List<GitCommitWrapper> successfulCommits = new ArrayList<GitCommitWrapper>();
|
||||
List<GitCommitWrapper> successfulCommits = ContainerUtil.newArrayList();
|
||||
List<GitCommitWrapper> alreadyPicked = ContainerUtil.newArrayList();
|
||||
DvcsUtil.workingTreeChangeStarted(myProject);
|
||||
try {
|
||||
for (Map.Entry<GitRepository, List<VcsFullCommitDetails>> entry : commitsInRoots.entrySet()) {
|
||||
GitRepository repository = entry.getKey();
|
||||
boolean result = cherryPick(repository, entry.getValue(), successfulCommits);
|
||||
boolean result = cherryPick(repository, entry.getValue(), successfulCommits, alreadyPicked);
|
||||
repository.update();
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
notifySuccess(successfulCommits);
|
||||
notifyResult(successfulCommits, alreadyPicked);
|
||||
}
|
||||
finally {
|
||||
DvcsUtil.workingTreeChangeFinished(myProject);
|
||||
@@ -100,7 +106,7 @@ public class GitCherryPicker {
|
||||
|
||||
// return true to continue with other roots, false to break execution
|
||||
private boolean cherryPick(@NotNull GitRepository repository, @NotNull List<VcsFullCommitDetails> commits,
|
||||
@NotNull List<GitCommitWrapper> successfulCommits) {
|
||||
@NotNull List<GitCommitWrapper> successfulCommits, @NotNull List<GitCommitWrapper> alreadyPicked) {
|
||||
for (VcsFullCommitDetails commit : commits) {
|
||||
GitSimpleEventDetector conflictDetector = new GitSimpleEventDetector(CHERRY_PICK_CONFLICT);
|
||||
GitSimpleEventDetector localChangesOverwrittenDetector = new GitSimpleEventDetector(LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK);
|
||||
@@ -115,7 +121,7 @@ public class GitCherryPicker {
|
||||
}
|
||||
else {
|
||||
boolean committed = updateChangeListManagerShowCommitDialogAndRemoveChangeListOnSuccess(repository, commitWrapper,
|
||||
successfulCommits);
|
||||
successfulCommits, alreadyPicked);
|
||||
if (!committed) {
|
||||
notifyCommitCancelled(commitWrapper, successfulCommits);
|
||||
return false;
|
||||
@@ -129,7 +135,7 @@ public class GitCherryPicker {
|
||||
|
||||
if (mergeCompleted) {
|
||||
boolean committed = updateChangeListManagerShowCommitDialogAndRemoveChangeListOnSuccess(repository, commitWrapper,
|
||||
successfulCommits);
|
||||
successfulCommits, alreadyPicked);
|
||||
if (!committed) {
|
||||
notifyCommitCancelled(commitWrapper, successfulCommits);
|
||||
return false;
|
||||
@@ -156,6 +162,10 @@ public class GitCherryPicker {
|
||||
commitWrapper, successfulCommits);
|
||||
return false;
|
||||
}
|
||||
else if (isNothingToCommitMessage(result)) {
|
||||
alreadyPicked.add(commitWrapper);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
notifyError(result.getErrorOutputAsHtmlString(), commitWrapper, successfulCommits);
|
||||
return false;
|
||||
@@ -164,10 +174,23 @@ public class GitCherryPicker {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isNothingToCommitMessage(@NotNull GitCommandResult result) {
|
||||
if (!result.getErrorOutputAsJoinedString().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String stdout = result.getOutputAsJoinedString();
|
||||
return stdout.contains("nothing to commit") || stdout.contains("previous cherry-pick is now empty");
|
||||
}
|
||||
|
||||
private boolean updateChangeListManagerShowCommitDialogAndRemoveChangeListOnSuccess(@NotNull GitRepository repository,
|
||||
@NotNull GitCommitWrapper commit,
|
||||
@NotNull List<GitCommitWrapper> successfulCommits) {
|
||||
@NotNull List<GitCommitWrapper> successfulCommits,
|
||||
@NotNull List<GitCommitWrapper> alreadyPicked) {
|
||||
CherryPickData data = updateChangeListManager(commit.getCommit());
|
||||
if (data == null) {
|
||||
alreadyPicked.add(commit);
|
||||
return true;
|
||||
}
|
||||
boolean committed = showCommitDialogAndWaitForCommit(repository, commit, data.myChangeList, data.myCommitMessage);
|
||||
if (committed) {
|
||||
myChangeListManager.removeChangeList(data.myChangeList);
|
||||
@@ -199,15 +222,16 @@ public class GitCherryPicker {
|
||||
VcsNotifier.getInstance(myProject).notifyMinorWarning("Cherry-pick cancelled", description, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CherryPickData updateChangeListManager(@NotNull final VcsFullCommitDetails commit) {
|
||||
final Collection<FilePath> paths = ChangesUtil.getPaths(commit.getChanges());
|
||||
refreshChangedFiles(paths);
|
||||
final String commitMessage = createCommitMessage(commit);
|
||||
LocalChangeList changeList = createChangeListAfterUpdate(commit, paths, commitMessage);
|
||||
return new CherryPickData(changeList, commitMessage);
|
||||
return changeList == null ? null : new CherryPickData(changeList, commitMessage);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Nullable
|
||||
private LocalChangeList createChangeListAfterUpdate(@NotNull final VcsFullCommitDetails commit, @NotNull final Collection<FilePath> paths,
|
||||
@NotNull final String commitMessage) {
|
||||
final AtomicReference<LocalChangeList> changeList = new AtomicReference<LocalChangeList>();
|
||||
@@ -338,9 +362,37 @@ public class GitCherryPicker {
|
||||
return description;
|
||||
}
|
||||
|
||||
private void notifySuccess(@NotNull List<GitCommitWrapper> successfulCommits) {
|
||||
String description = getCommitsDetails(successfulCommits);
|
||||
VcsNotifier.getInstance(myProject).notifySuccess("Cherry-pick successful", description);
|
||||
private void notifyResult(@NotNull List<GitCommitWrapper> successfulCommits, @NotNull List<GitCommitWrapper> alreadyPicked) {
|
||||
if (alreadyPicked.isEmpty()) {
|
||||
VcsNotifier.getInstance(myProject).notifySuccess("Cherry-pick successful", getCommitsDetails(successfulCommits));
|
||||
}
|
||||
else if (!successfulCommits.isEmpty()) {
|
||||
String title = String.format("Cherry-picked %d commits from %d", successfulCommits.size(),
|
||||
successfulCommits.size() + alreadyPicked.size());
|
||||
String description = getCommitsDetails(successfulCommits) + "<p/>" + formAlreadyPickedDescription(alreadyPicked, true);
|
||||
VcsNotifier.getInstance(myProject).notifyImportantWarning(title, description);
|
||||
}
|
||||
else {
|
||||
VcsNotifier.getInstance(myProject).notifyImportantWarning("Nothing to cherry-pick",
|
||||
formAlreadyPickedDescription(alreadyPicked, false));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String formAlreadyPickedDescription(@NotNull List<GitCommitWrapper> alreadyPicked, boolean but) {
|
||||
|
||||
String hashes = StringUtil.join(alreadyPicked, new Function<GitCommitWrapper, String>() {
|
||||
@Override
|
||||
public String fun(GitCommitWrapper commit) {
|
||||
return commit.getCommit().getId().toShortString();
|
||||
}
|
||||
}, ", ");
|
||||
if (but) {
|
||||
String wasnt = alreadyPicked.size() == 1 ? "wasn't" : "weren't";
|
||||
String it = alreadyPicked.size() == 1 ? "it" : "them";
|
||||
return String.format("%s %s picked, because all changes from %s have already been applied.", hashes, wasnt, it);
|
||||
}
|
||||
return String.format("All changes from %s have already been applied", hashes);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -365,9 +417,10 @@ public class GitCherryPicker {
|
||||
}
|
||||
}));
|
||||
VfsUtil.markDirtyAndRefresh(false, false, false, ArrayUtil.toObjectArray(virtualFiles, VirtualFile.class));
|
||||
VcsDirtyScopeManager.getInstance(myProject).filePathsDirty(filePaths, null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Nullable
|
||||
private LocalChangeList createChangeList(@NotNull VcsFullCommitDetails commit, @NotNull String commitMessage) {
|
||||
Collection<Change> changes = commit.getChanges();
|
||||
String changeListName = createNameForChangeList(commitMessage, 0).replace('\n', ' ');
|
||||
@@ -375,7 +428,11 @@ public class GitCherryPicker {
|
||||
if (!changes.isEmpty()) {
|
||||
myChangeListManager.moveChangesTo(changeList, changes.toArray(new Change[changes.size()]));
|
||||
}
|
||||
return changeList;
|
||||
if (!changeList.getChanges().isEmpty()) {
|
||||
return changeList;
|
||||
}
|
||||
myChangeListManager.removeChangeList(changeList);
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -393,10 +450,10 @@ public class GitCherryPicker {
|
||||
}
|
||||
|
||||
private static class CherryPickData {
|
||||
private final LocalChangeList myChangeList;
|
||||
private final String myCommitMessage;
|
||||
@NotNull private final LocalChangeList myChangeList;
|
||||
@NotNull private final String myCommitMessage;
|
||||
|
||||
private CherryPickData(LocalChangeList list, String message) {
|
||||
private CherryPickData(@NotNull LocalChangeList list, @NotNull String message) {
|
||||
myChangeList = list;
|
||||
myCommitMessage = message;
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@ Background:
|
||||
Given enabled auto-commit in the settings
|
||||
Given new committed files file.txt, a.txt, conflict.txt with initial content
|
||||
Given branch feature
|
||||
|
||||
Given commit f5027a3 on branch feature
|
||||
"""
|
||||
fix #1
|
||||
Author: John Bro
|
||||
M file.txt "feature changes"
|
||||
M file.txt "initial content\nfeature changes"
|
||||
"""
|
||||
|
||||
Scenario: Simple cherry-pick
|
||||
@@ -220,53 +219,53 @@ Background:
|
||||
"""
|
||||
And merge dialog should be shown
|
||||
|
||||
#Scenario: Notify if changes have already been applied (IDEA-73548)
|
||||
# Given commit eef9832 on branch master
|
||||
# """
|
||||
# fix #1 manually incorporated
|
||||
# M file.txt "feature changes"
|
||||
# """
|
||||
# When I cherry-pick the commit f5027a3
|
||||
# Then the last commit is eef9832
|
||||
# And warning notification is shown 'Nothing to cherry-pick'
|
||||
# """
|
||||
# All changes from f5027a3 fix #1 have already been applied
|
||||
# """
|
||||
#
|
||||
#Scenario: Cherry-pick 3 commits, second commit have already been applied (IDEA-73548)
|
||||
# Given commit c123abc on branch feature
|
||||
# """
|
||||
# fix #2
|
||||
# M file.txt "feature changes\nmore feature changes"
|
||||
# """
|
||||
# Given commit d123abc on branch feature
|
||||
# """
|
||||
# fix #3
|
||||
# M file.txt "feature changes\nmore feature changes\nmore feature changes"
|
||||
# """
|
||||
# Given commit e123abc on branch feature
|
||||
# """
|
||||
# fix for f2
|
||||
# M a.txt "feature changes"
|
||||
# """
|
||||
# Given commit e098fed on branch master
|
||||
# """
|
||||
# fix for f2 manually incorporated
|
||||
# M a.txt "feature changes"
|
||||
# """
|
||||
# When I cherry-pick commits c123abc, d123abc and e123abc
|
||||
# Then `git log -2` should return
|
||||
# """
|
||||
# fix #3
|
||||
# (cherry picked from commit c123abc)
|
||||
# -----
|
||||
# fix #2
|
||||
# (cherry picked from commit f5027a3)
|
||||
# """
|
||||
# And warning notification is shown 'Cherry-picked 2 commits'
|
||||
# """
|
||||
# c123abc fix #2
|
||||
# d123abc fix #3
|
||||
# <hr/>
|
||||
# Commit e123abc wasn't picked, because all changes from it have already been applied.
|
||||
# """
|
||||
Scenario: Notify if changes have already been applied (IDEA-73548)
|
||||
Given commit eef9832 on branch master
|
||||
"""
|
||||
fix #1 manually incorporated
|
||||
M file.txt "feature changes"
|
||||
"""
|
||||
When I cherry-pick the commit f5027a3
|
||||
Then the last commit is eef9832
|
||||
And warning notification is shown 'Nothing to cherry-pick'
|
||||
"""
|
||||
All changes from f5027a3 fix #1 have already been applied
|
||||
"""
|
||||
|
||||
Scenario: Cherry-pick 3 commits, second commit have already been applied (IDEA-73548)
|
||||
Given commit c123abc on branch feature
|
||||
"""
|
||||
fix #2
|
||||
M file.txt "feature changes\nmore feature changes"
|
||||
"""
|
||||
Given commit d123abc on branch feature
|
||||
"""
|
||||
fix #3
|
||||
M file.txt "feature changes\nmore feature changes\nmore feature changes"
|
||||
"""
|
||||
Given commit e123abc on branch feature
|
||||
"""
|
||||
fix for f2
|
||||
M a.txt "feature changes"
|
||||
"""
|
||||
Given commit e098fed on branch master
|
||||
"""
|
||||
fix for f2 manually incorporated
|
||||
M a.txt "feature changes"
|
||||
"""
|
||||
When I cherry-pick commits c123abc, d123abc and e123abc
|
||||
Then `git log -2` should return
|
||||
"""
|
||||
fix #3
|
||||
(cherry picked from commit c123abc)
|
||||
-----
|
||||
fix #2
|
||||
(cherry picked from commit f5027a3)
|
||||
"""
|
||||
And warning notification is shown 'Cherry-picked 2 commits'
|
||||
"""
|
||||
c123abc fix #2
|
||||
d123abc fix #3
|
||||
<hr/>
|
||||
Commit e123abc wasn't picked, because all changes from it have already been applied.
|
||||
"""
|
||||
|
||||
@@ -70,14 +70,15 @@ public class GeneralStepdefs {
|
||||
notificationType.equals("error") ? NotificationType.ERROR : null;
|
||||
Notification actualNotification = lastNotification();
|
||||
assertNotNull("Notification should be shown", actualNotification);
|
||||
assertEquals("Notification type is incorrect", type, actualNotification.getType());
|
||||
assertEquals("Notification title is incorrect", title, actualNotification.getTitle());
|
||||
assertEquals("Notification type is incorrect in " + actualNotification, type, actualNotification.getType());
|
||||
assertEquals("Notification title is incorrect in" + actualNotification, title, actualNotification.getTitle());
|
||||
assertNotificationContent(content, actualNotification.getContent());
|
||||
}
|
||||
|
||||
private static void assertNotificationContent(String expected, String actual) {
|
||||
expected = virtualCommits.replaceVirtualHashes(expected);
|
||||
assertEquals("Notification content is incorrect", StringUtil.convertLineSeparators(expected), StringUtil.convertLineSeparators(adjustNotificationContent(actual)));
|
||||
assertEquals("Notification content is incorrect", StringUtil.convertLineSeparators(expected),
|
||||
StringUtil.convertLineSeparators(adjustNotificationContent(actual)));
|
||||
}
|
||||
|
||||
private static String adjustNotificationContent(String content) {
|
||||
|
||||
@@ -15,33 +15,30 @@
|
||||
*/
|
||||
package git4idea;
|
||||
|
||||
import com.intellij.mock.MockVirtualFile;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.ThrowableComputable;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vcs.FilePathImpl;
|
||||
import com.intellij.openapi.vcs.changes.Change;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.changes.LocalChangeList;
|
||||
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
|
||||
import com.intellij.openapi.vfs.newvfs.impl.NullVirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.testFramework.vcs.MockChangeListManager;
|
||||
import com.intellij.testFramework.vcs.MockContentRevision;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.vcs.log.Hash;
|
||||
import com.intellij.vcs.log.VcsFullCommitDetails;
|
||||
import com.intellij.vcs.log.VcsLogObjectsFactory;
|
||||
import com.intellij.vcs.log.impl.HashImpl;
|
||||
import cucumber.annotation.en.And;
|
||||
import cucumber.annotation.en.Given;
|
||||
import cucumber.annotation.en.Then;
|
||||
import cucumber.annotation.en.When;
|
||||
import git4idea.cherrypick.GitCherryPicker;
|
||||
import git4idea.config.GitVersionSpecialty;
|
||||
import git4idea.history.GitHistoryUtils;
|
||||
import git4idea.test.MockVcsHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.openapi.vcs.Executor.echo;
|
||||
import static git4idea.GitCucumberWorld.*;
|
||||
@@ -63,7 +60,7 @@ public class GitCherryPickStepdefs {
|
||||
}
|
||||
|
||||
@When("^I cherry-pick the commit (\\w+)$")
|
||||
public void I_cherry_pick_the_commit(String hash) {
|
||||
public void I_cherry_pick_the_commit(String hash) throws VcsException {
|
||||
cherryPick(hash);
|
||||
}
|
||||
|
||||
@@ -232,36 +229,27 @@ public class GitCherryPickStepdefs {
|
||||
assertEquals("Default changelist is not active", DEFAULT, myChangeListManager.getDefaultChangeList().getName());
|
||||
}
|
||||
|
||||
private static void cherryPick(List<String> virtualHashes) {
|
||||
List<VcsFullCommitDetails> commits = ContainerUtil.newArrayList();
|
||||
for (String virtualHash : virtualHashes) {
|
||||
commits.add(createMockCommit(virtualHash));
|
||||
}
|
||||
private static void cherryPick(final List<String> virtualHashes) throws VcsException {
|
||||
List<VcsFullCommitDetails> commits = loadDetails(ContainerUtil.map(virtualHashes, new Function<String, String>() {
|
||||
@Override
|
||||
public String fun(String virtualHash) {
|
||||
return virtualCommits.getRealCommit(virtualHash).getHash();
|
||||
}
|
||||
}), myProjectDir);
|
||||
|
||||
new GitCherryPicker(myProject, myGit, myPlatformFacade, mySettings.isAutoCommitOnCherryPick())
|
||||
.cherryPick(Collections.singletonMap(myRepository, commits));
|
||||
.cherryPick(Collections.singletonMap(myRepository, commits));
|
||||
}
|
||||
|
||||
private static void cherryPick(String... virtualHashes) {
|
||||
private static List<VcsFullCommitDetails> loadDetails(List<String> hashes, @NotNull VirtualFile root) throws VcsException {
|
||||
String noWalk = GitVersionSpecialty.NO_WALK_UNSORTED.existsIn(myVcs.getVersion()) ? "--no-walk=unsorted" : "--no-walk";
|
||||
List<String> params = new ArrayList<String>();
|
||||
params.add(noWalk);
|
||||
params.addAll(hashes);
|
||||
return new ArrayList<VcsFullCommitDetails>(GitHistoryUtils.history(myProject, root, ArrayUtil.toStringArray(params)));
|
||||
}
|
||||
|
||||
private static void cherryPick(String... virtualHashes) throws VcsException {
|
||||
cherryPick(Arrays.asList(virtualHashes));
|
||||
}
|
||||
|
||||
private static VcsFullCommitDetails createMockCommit(String virtualHash) {
|
||||
CommitDetails realCommit = virtualCommits.getRealCommit(virtualHash);
|
||||
return mockCommit(realCommit.getHash(), realCommit.getMessage());
|
||||
}
|
||||
|
||||
private static VcsFullCommitDetails mockCommit(String hash, String message) {
|
||||
final List<Change> changes = new ArrayList<Change>();
|
||||
changes.add(new Change(null, new MockContentRevision(new FilePathImpl(new MockVirtualFile("name")), VcsRevisionNumber.NULL)));
|
||||
return ServiceManager.getService(myProject, VcsLogObjectsFactory.class).createFullDetails(
|
||||
HashImpl.build(hash), Collections.<Hash>emptyList(), 0, NullVirtualFile.INSTANCE, message, "John Smith", "john@mail.com", message,
|
||||
"John Smith", "john@mail.com", 0, new ThrowableComputable<Collection<Change>, Exception>() {
|
||||
@Override
|
||||
public Collection<Change> compute() throws Exception {
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user