mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge branch 'cherrypick'
Conflicts: platform/vcs-api/src/com/intellij/openapi/vcs/AbstractVcsHelper.java platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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.testFramework.vcs;
|
||||
|
||||
import com.intellij.openapi.vcs.changes.Change;
|
||||
import com.intellij.openapi.vcs.changes.LocalChangeList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
public class MockChangeList extends LocalChangeList {
|
||||
|
||||
Collection<Change> myChanges = new ArrayList<Change>();
|
||||
private final String myName;
|
||||
|
||||
public MockChangeList(String name) {
|
||||
myName = name;
|
||||
}
|
||||
|
||||
public void add(Change change) {
|
||||
myChanges.add(change);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Change> getChanges() {
|
||||
return myChanges;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(@NotNull String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setComment(String comment) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefault() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadOnly(boolean isReadOnly) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalChangeList copy() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
<orderEntry type="module" module-name="community-resources" scope="RUNTIME" />
|
||||
<orderEntry type="library" exported="" scope="TEST" name="Mocks" level="project" />
|
||||
<orderEntry type="module" module-name="java-runtime" exported="" />
|
||||
<orderEntry type="module" module-name="vcs-impl" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
package com.intellij.testFramework.vcs;
|
||||
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.vcs.FilePath;
|
||||
import com.intellij.openapi.vcs.FileStatus;
|
||||
import com.intellij.openapi.vcs.changes.*;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.continuation.ContinuationPause;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
public class MockChangeListManager extends ChangeListManager {
|
||||
|
||||
public static final String DEFAULT_CHANGE_LIST_NAME = "Default";
|
||||
|
||||
private final Map<String, MockChangeList> myChangeLists = new HashMap<String, MockChangeList>();
|
||||
private LocalChangeList myActiveChangeList;
|
||||
private final MockChangeList myDefaultChangeList;
|
||||
|
||||
public MockChangeListManager() {
|
||||
myDefaultChangeList = new MockChangeList(DEFAULT_CHANGE_LIST_NAME);
|
||||
myChangeLists.put(DEFAULT_CHANGE_LIST_NAME, myDefaultChangeList);
|
||||
myActiveChangeList = myDefaultChangeList;
|
||||
}
|
||||
|
||||
public void addChanges(Change... changes) {
|
||||
MockChangeList changeList = myChangeLists.get(DEFAULT_CHANGE_LIST_NAME);
|
||||
for (Change change : changes) {
|
||||
changeList.add(change);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleUpdate() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleUpdate(boolean updateUnversionedFiles) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeAfterUpdate(Runnable afterUpdate,
|
||||
InvokeAfterUpdateMode mode,
|
||||
String title,
|
||||
ModalityState state) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeAfterUpdate(Runnable afterUpdate,
|
||||
InvokeAfterUpdateMode mode,
|
||||
String title,
|
||||
Consumer<VcsDirtyScopeManager> dirtyScopeManager,
|
||||
ModalityState state) {
|
||||
afterUpdate.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ensureUpToDate(boolean canBeCanceled) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LocalChangeList> getChangeListsCopy() {
|
||||
return new ArrayList<LocalChangeList>(myChangeLists.values());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<LocalChangeList> getChangeLists() {
|
||||
return getChangeListsCopy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<File> getAffectedPaths() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<VirtualFile> getAffectedFiles() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFileAffected(VirtualFile file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<Change> getAllChanges() {
|
||||
Collection<Change> changes = new ArrayList<Change>();
|
||||
for (MockChangeList list : myChangeLists.values()) {
|
||||
changes.addAll(list.getChanges());
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalChangeList findChangeList(String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalChangeList getChangeList(String id) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalChangeList getDefaultChangeList() {
|
||||
return myActiveChangeList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefaultChangeList(ChangeList list) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalChangeList getChangeList(Change change) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChangeListNameIfOnlyOne(Change[] changes) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Runnable prepareForChangeDeletion(Collection<Change> changes) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change getChange(@NotNull VirtualFile file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalChangeList getChangeList(@NotNull VirtualFile file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change getChange(FilePath file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnversioned(VirtualFile file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public FileStatus getStatus(VirtualFile file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<Change> getChangesIn(VirtualFile dir) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<Change> getChangesIn(FilePath path) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChangeListListener(ChangeListListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChangeListListener(ChangeListListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommitExecutor(CommitExecutor executor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitChanges(LocalChangeList changeList, List<Change> changes) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitChangesSynchronously(LocalChangeList changeList, List<Change> changes) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commitChangesSynchronouslyWithResult(LocalChangeList changeList, List<Change> changes) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reopenFiles(List<FilePath> paths) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CommitExecutor> getRegisteredExecutors() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFilesToIgnore(IgnoredFileBean... ignoredFiles) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilesToIgnore(IgnoredFileBean... ignoredFiles) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IgnoredFileBean[] getFilesToIgnore() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIgnoredFile(@NotNull VirtualFile file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSwitchedBranch(VirtualFile file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultListName() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void freeze(ContinuationPause context, String reason) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void letGo() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String isFreezed() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFreezedWithNotification(@Nullable String modalTitle) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VirtualFile> getModifiedWithoutEditing() {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalChangeList addChangeList(@NotNull String name, @Nullable String comment) {
|
||||
MockChangeList changeList = new MockChangeList(name);
|
||||
myChangeLists.put(name, changeList);
|
||||
return changeList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultChangeList(@NotNull LocalChangeList list) {
|
||||
myActiveChangeList = list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChangeList(String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChangeList(LocalChangeList list) {
|
||||
myChangeLists.remove(list.getName());
|
||||
if (myActiveChangeList.equals(list)) {
|
||||
myActiveChangeList = myDefaultChangeList;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveChangesTo(LocalChangeList list, Change[] changes) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setReadOnly(String name, boolean value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean editName(@NotNull String fromName, @NotNull String toName) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String editComment(@NotNull String fromName, String newComment) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
|
||||
package com.intellij.testFramework.vcs;
|
||||
|
||||
import com.intellij.openapi.vcs.changes.ContentRevision;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.FilePath;
|
||||
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class MockContentRevision implements ContentRevision {
|
||||
private final FilePath myPath;
|
||||
private final VcsRevisionNumber myRevisionNumber;
|
||||
|
||||
public MockContentRevision(final FilePath path, final VcsRevisionNumber revisionNumber) {
|
||||
myPath = path;
|
||||
myRevisionNumber = revisionNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getContent() throws VcsException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public FilePath getFile() {
|
||||
return myPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public VcsRevisionNumber getRevisionNumber() {
|
||||
return myRevisionNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myPath.getName() + ":" + myRevisionNumber;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vcs.annotate.AnnotationProvider;
|
||||
import com.intellij.openapi.vcs.annotate.FileAnnotation;
|
||||
import com.intellij.openapi.vcs.changes.Change;
|
||||
import com.intellij.openapi.vcs.changes.CommitExecutor;
|
||||
import com.intellij.openapi.vcs.changes.LocalChangeList;
|
||||
import com.intellij.openapi.vcs.history.VcsFileRevision;
|
||||
import com.intellij.openapi.vcs.history.VcsHistoryProvider;
|
||||
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
|
||||
@@ -162,4 +164,14 @@ public abstract class AbstractVcsHelper {
|
||||
@Nullable String cancelActionName) {
|
||||
return selectFilePathsToProcess(files, title, prompt, singleFileTitle, singleFilePromptTemplate, confirmationOption);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Shows commit dialog, fills it with the given changes and given commit message, initially selects the given changelist.
|
||||
* @return true if user commits the changes, and commit succeeds; false if user presses Cancel or commit fails with errors.
|
||||
*/
|
||||
public abstract boolean commitChanges(@NotNull List<Change> changes, @NotNull LocalChangeList initialChangeList,
|
||||
@NotNull String commitMessage, @Nullable CommitExecutor executor);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -237,14 +237,8 @@ public class LocalChangeListImpl extends LocalChangeList {
|
||||
public synchronized boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
final LocalChangeListImpl list = (LocalChangeListImpl)o;
|
||||
|
||||
if (myIsDefault != list.myIsDefault) return false;
|
||||
if (!myName.equals(list.myName)) return false;
|
||||
if (myIsReadOnly != list.myIsReadOnly) return false;
|
||||
|
||||
return true;
|
||||
return myName.equals(list.myName);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
|
||||
@@ -462,20 +462,42 @@ public class CommitHelper {
|
||||
}
|
||||
|
||||
private void markCommittingDocuments() {
|
||||
for (Change change : myIncludedChanges) {
|
||||
Document doc = ChangesUtil.getFilePath(change).getDocument();
|
||||
if (doc != null) {
|
||||
doc.putUserData(DOCUMENT_BEING_COMMITTED_KEY, myProject);
|
||||
myCommittingDocuments.add(doc);
|
||||
}
|
||||
}
|
||||
myCommittingDocuments.addAll(markCommittingDocuments(myProject, myIncludedChanges));
|
||||
}
|
||||
|
||||
private void unmarkCommittingDocuments() {
|
||||
for (Document doc : myCommittingDocuments) {
|
||||
unmarkCommittingDocuments(myCommittingDocuments);
|
||||
myCommittingDocuments.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks {@link Document documents} related to the given changes as "being committed".
|
||||
* @return documents which were marked that way.
|
||||
* @see #unmarkCommittingDocuments(java.util.Collection)
|
||||
* @see VetoSavingCommittingDocumentsAdapter
|
||||
*/
|
||||
@NotNull
|
||||
public static Collection<Document> markCommittingDocuments(@NotNull Project project, @NotNull List<Change> changes) {
|
||||
Collection<Document> committingDocs = new ArrayList<Document>();
|
||||
for (Change change : changes) {
|
||||
Document doc = ChangesUtil.getFilePath(change).getDocument();
|
||||
if (doc != null) {
|
||||
doc.putUserData(DOCUMENT_BEING_COMMITTED_KEY, project);
|
||||
committingDocs.add(doc);
|
||||
}
|
||||
}
|
||||
return committingDocs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the "being committed marker" from the given {@link Document documents}.
|
||||
* @see #markCommittingDocuments(com.intellij.openapi.project.Project, java.util.List)
|
||||
* @see VetoSavingCommittingDocumentsAdapter
|
||||
*/
|
||||
public static void unmarkCommittingDocuments(@NotNull Collection<Document> committingDocs) {
|
||||
for (Document doc : committingDocs) {
|
||||
doc.putUserData(DOCUMENT_BEING_COMMITTED_KEY, null);
|
||||
}
|
||||
myCommittingDocuments.clear();
|
||||
}
|
||||
|
||||
private void commitCompleted(final List<VcsException> allExceptions, final GeneralCommitProcessor processor) {
|
||||
|
||||
@@ -49,6 +49,8 @@ import com.intellij.openapi.vcs.annotate.AnnotationProvider;
|
||||
import com.intellij.openapi.vcs.annotate.FileAnnotation;
|
||||
import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
|
||||
import com.intellij.openapi.vcs.changes.Change;
|
||||
import com.intellij.openapi.vcs.changes.CommitExecutor;
|
||||
import com.intellij.openapi.vcs.changes.LocalChangeList;
|
||||
import com.intellij.openapi.vcs.changes.committed.*;
|
||||
import com.intellij.openapi.vcs.changes.ui.*;
|
||||
import com.intellij.openapi.vcs.history.*;
|
||||
@@ -218,6 +220,12 @@ public class AbstractVcsHelperImpl extends AbstractVcsHelper {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commitChanges(@NotNull List<Change> changes, @NotNull LocalChangeList initialChangeList,
|
||||
@NotNull String commitMessage, @Nullable CommitExecutor executor) {
|
||||
return CommitChangeListDialog.commitChanges(myProject, changes, initialChangeList, executor, commitMessage);
|
||||
}
|
||||
|
||||
private void addDirectMessages(VcsErrorViewPanel vcsErrorViewPanel, List<VcsException> abstractVcsExceptions) {
|
||||
for (final VcsException exception : abstractVcsExceptions) {
|
||||
|
||||
@@ -68,11 +68,23 @@ public class Notificator {
|
||||
}
|
||||
|
||||
public void notifyError(@NotNull String title, @NotNull String message) {
|
||||
notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, title, message, NotificationType.ERROR, null);
|
||||
notifyError(title, message, null);
|
||||
}
|
||||
|
||||
public void notifyError(@NotNull String title, @NotNull String message, @Nullable NotificationListener listener) {
|
||||
notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, title, message, NotificationType.ERROR, listener);
|
||||
}
|
||||
|
||||
public void notifySuccess(@NotNull String title, @NotNull String message) {
|
||||
notify(GitVcs.NOTIFICATION_GROUP_ID, title, message, NotificationType.INFORMATION, null);
|
||||
}
|
||||
|
||||
public void notifyWeakWarning(@NotNull String title, @NotNull String message, @Nullable NotificationListener listener) {
|
||||
notify(GitVcs.MINOR_NOTIFICATION, title, message, NotificationType.WARNING, listener);
|
||||
}
|
||||
|
||||
public void notifyStrongWarning(@NotNull String title, @NotNull String content, @Nullable NotificationListener listener) {
|
||||
notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, title, content, NotificationType.WARNING, listener);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,15 +15,17 @@
|
||||
*/
|
||||
package git4idea;
|
||||
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.vcs.AbstractVcs;
|
||||
import com.intellij.openapi.vcs.AbstractVcsHelper;
|
||||
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
|
||||
import com.intellij.openapi.vcs.changes.ChangeListManager;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import git4idea.config.GitVcsSettings;
|
||||
import git4idea.repo.GitRepositoryManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -65,12 +67,17 @@ public interface PlatformFacade {
|
||||
|
||||
void runReadAction(@NotNull Runnable runnable);
|
||||
|
||||
/**
|
||||
* @return the instance of {@link git4idea.config.GitVcsSettings}
|
||||
*/
|
||||
GitVcsSettings getGitWorkspaceSettings(@NotNull Project project);
|
||||
void runWriteAction(@NotNull Runnable runnable);
|
||||
|
||||
void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState);
|
||||
|
||||
ChangeListManager getChangeListManager(@NotNull Project project);
|
||||
|
||||
LocalFileSystem getLocalFileSystem();
|
||||
|
||||
@NotNull
|
||||
AbstractVcsHelper getVcsHelper(@NotNull Project project);
|
||||
|
||||
@NotNull
|
||||
GitRepositoryManager getRepositoryManager(@NotNull Project project);
|
||||
}
|
||||
|
||||
@@ -16,15 +16,18 @@
|
||||
package git4idea;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.vcs.AbstractVcs;
|
||||
import com.intellij.openapi.vcs.AbstractVcsHelper;
|
||||
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
|
||||
import com.intellij.openapi.vcs.changes.ChangeListManager;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import git4idea.config.GitVcsSettings;
|
||||
import git4idea.repo.GitRepositoryManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -66,8 +69,13 @@ public class PlatformFacadeImpl implements PlatformFacade {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GitVcsSettings getGitWorkspaceSettings(@NotNull Project project) {
|
||||
return GitVcsSettings.getInstance(project);
|
||||
public void runWriteAction(@NotNull Runnable runnable) {
|
||||
ApplicationManager.getApplication().runWriteAction(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState) {
|
||||
ApplicationManager.getApplication().invokeAndWait(runnable, modalityState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,6 +88,18 @@ public class PlatformFacadeImpl implements PlatformFacade {
|
||||
return LocalFileSystem.getInstance();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public AbstractVcsHelper getVcsHelper(@NotNull Project project) {
|
||||
return AbstractVcsHelper.getInstance(project);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public GitRepositoryManager getRepositoryManager(@NotNull Project project) {
|
||||
return ServiceManager.getService(project, GitRepositoryManager.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public AbstractVcs getVcs(@NotNull Project project) {
|
||||
|
||||
@@ -297,7 +297,7 @@ abstract class GitBranchOperation {
|
||||
setMergeDescription("The following files have unresolved conflicts. You need to resolve them before " +
|
||||
getOperationName() + ".").
|
||||
setErrorNotificationTitle("Unresolved files remain.");
|
||||
new GitConflictResolver(myProject, GitUtil.getRoots(getRepositories()), params).merge();
|
||||
new GitConflictResolver(myProject, myGit, ServiceManager.getService(PlatformFacade.class), GitUtil.getRoots(getRepositories()), params).merge();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -333,7 +333,8 @@ abstract class GitBranchOperation {
|
||||
}
|
||||
|
||||
private void showUntrackedFilesNotification(@NotNull Collection<VirtualFile> untrackedFiles) {
|
||||
UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedFiles, getOperationName());
|
||||
UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, ServiceManager.getService(myProject, PlatformFacade.class),
|
||||
untrackedFiles, getOperationName(), null);
|
||||
}
|
||||
|
||||
private void showUntrackedFilesDialogWithRollback(@NotNull Collection<VirtualFile> untrackedFiles) {
|
||||
|
||||
@@ -201,7 +201,8 @@ class GitCheckoutOperation extends GitBranchOperation {
|
||||
private boolean smartCheckout(@NotNull final List<GitRepository> repositories, @NotNull final String reference, @Nullable final String newBranch, @NotNull ProgressIndicator indicator) {
|
||||
|
||||
final AtomicBoolean result = new AtomicBoolean();
|
||||
GitPreservingProcess preservingProcess = new GitPreservingProcess(myProject, repositories, "checkout", reference, indicator, new Runnable() {
|
||||
GitPreservingProcess preservingProcess = new GitPreservingProcess(myProject, myGit, repositories, "checkout", reference, indicator,
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
result.set(checkoutOrNotify(repositories, reference, newBranch, false));
|
||||
|
||||
@@ -209,7 +209,7 @@ class GitMergeOperation extends GitBranchOperation {
|
||||
|
||||
private boolean doSmartMerge(@NotNull final Collection<GitRepository> repositories) {
|
||||
final AtomicBoolean success = new AtomicBoolean();
|
||||
myPreservingProcess = new GitPreservingProcess(myProject, repositories, "merge", myBranchToMerge, getIndicator(),
|
||||
myPreservingProcess = new GitPreservingProcess(myProject, myGit, repositories, "merge", myBranchToMerge, getIndicator(),
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -303,7 +303,8 @@ class GitMergeOperation extends GitBranchOperation {
|
||||
private GitCompoundResult smartRollback(@NotNull final Collection<GitRepository> repositories) {
|
||||
LOG.info("Starting smart rollback...");
|
||||
final GitCompoundResult result = new GitCompoundResult(myProject);
|
||||
GitPreservingProcess preservingProcess = new GitPreservingProcess(myProject, repositories, "merge", myBranchToMerge, getIndicator(),
|
||||
GitPreservingProcess preservingProcess = new GitPreservingProcess(myProject, myGit, repositories, "merge", myBranchToMerge,
|
||||
getIndicator(),
|
||||
new Runnable() {
|
||||
@Override public void run() {
|
||||
for (GitRepository repository : repositories) {
|
||||
@@ -361,7 +362,7 @@ class GitMergeOperation extends GitBranchOperation {
|
||||
|
||||
private class MyMergeConflictResolver extends GitMergeCommittingConflictResolver {
|
||||
public MyMergeConflictResolver() {
|
||||
super(GitMergeOperation.this.myProject, new GitMerger(GitMergeOperation.this.myProject),
|
||||
super(GitMergeOperation.this.myProject, myGit, new GitMerger(GitMergeOperation.this.myProject),
|
||||
GitUtil.getRoots(GitMergeOperation.this.myConflictedRepositories.keySet()), new Params(), true);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ 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.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import git4idea.GitContentRevision;
|
||||
@@ -32,9 +31,9 @@ import git4idea.GitUtil;
|
||||
import git4idea.commands.GitCommand;
|
||||
import git4idea.commands.GitHandler;
|
||||
import git4idea.commands.GitSimpleHandler;
|
||||
import git4idea.util.StringScanner;
|
||||
import git4idea.history.browser.SHAHash;
|
||||
import git4idea.history.wholeTree.AbstractHash;
|
||||
import git4idea.util.StringScanner;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -57,49 +56,6 @@ public class GitChangeUtils {
|
||||
private GitChangeUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse changes from lines
|
||||
*
|
||||
* @param project the context project
|
||||
* @param root the git root
|
||||
* @return a set of unmerged files
|
||||
* @throws VcsException if the input format does not matches expected format
|
||||
*/
|
||||
public static List<VirtualFile> unmergedFiles(Project project, VirtualFile root) throws VcsException {
|
||||
HashSet<VirtualFile> unmerged = new HashSet<VirtualFile>();
|
||||
String rootPath = root.getPath();
|
||||
GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LS_FILES);
|
||||
h.setNoSSH(true);
|
||||
h.setSilent(true);
|
||||
h.addParameters("--unmerged");
|
||||
LocalFileSystem lfs = LocalFileSystem.getInstance();
|
||||
for (StringScanner s = new StringScanner(h.run()); s.hasMoreData();) {
|
||||
if (s.isEol()) {
|
||||
s.nextLine();
|
||||
continue;
|
||||
}
|
||||
s.boundedToken('\t');
|
||||
final String relative = s.line();
|
||||
String path = rootPath + "/" + GitUtil.unescapePath(relative);
|
||||
VirtualFile file = lfs.refreshAndFindFileByPath(path);
|
||||
if (file != null) {
|
||||
// the file name is in the delete- or rename- conflict, so it is shown in the list of unmerged files,
|
||||
// but the file itself doesn't exist. In that case we just ignore the file.
|
||||
file.refresh(false, false);
|
||||
unmerged.add(file);
|
||||
}
|
||||
}
|
||||
if (unmerged.size() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
else {
|
||||
ArrayList<VirtualFile> rc = new ArrayList<VirtualFile>(unmerged.size());
|
||||
rc.addAll(unmerged);
|
||||
Collections.sort(rc, GitUtil.VIRTUAL_FILE_COMPARATOR);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse changes from lines
|
||||
*
|
||||
|
||||
@@ -33,42 +33,38 @@ import java.util.Set;
|
||||
*/
|
||||
public interface Git {
|
||||
|
||||
@NotNull
|
||||
GitCommandResult init(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLineHandlerListener... listeners);
|
||||
|
||||
@NotNull
|
||||
Set<VirtualFile> untrackedFiles(@NotNull Project project,
|
||||
@NotNull VirtualFile root,
|
||||
Set<VirtualFile> untrackedFiles(@NotNull Project project, @NotNull VirtualFile root,
|
||||
@Nullable Collection<VirtualFile> files) throws VcsException;
|
||||
|
||||
// relativePaths are guaranteed to fit into command line length limitations.
|
||||
@NotNull
|
||||
Collection<VirtualFile> untrackedFilesNoChunk(@NotNull Project project,
|
||||
@NotNull VirtualFile root,
|
||||
@Nullable List<String> relativePaths)
|
||||
throws VcsException;
|
||||
Collection<VirtualFile> untrackedFilesNoChunk(@NotNull Project project, @NotNull VirtualFile root,
|
||||
@Nullable List<String> relativePaths) throws VcsException;
|
||||
|
||||
@NotNull
|
||||
GitCommandResult clone(@NotNull Project project, @NotNull File parentDirectory, @NotNull String url, @NotNull String clonedDirectoryName);
|
||||
|
||||
@NotNull
|
||||
GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge,
|
||||
@NotNull GitLineHandlerListener... listeners);
|
||||
GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge, @NotNull GitLineHandlerListener... listeners);
|
||||
|
||||
GitCommandResult checkout(@NotNull GitRepository repository,
|
||||
@NotNull String reference,
|
||||
@Nullable String newBranch,
|
||||
boolean force,
|
||||
@NotNull
|
||||
GitCommandResult checkout(@NotNull GitRepository repository, @NotNull String reference, @Nullable String newBranch, boolean force,
|
||||
@NotNull GitLineHandlerListener... listeners);
|
||||
|
||||
@NotNull
|
||||
GitCommandResult checkoutNewBranch(@NotNull GitRepository repository, @NotNull String branchName,
|
||||
@Nullable GitLineHandlerListener listener);
|
||||
|
||||
@NotNull
|
||||
GitCommandResult createNewTag(@NotNull GitRepository repository, @NotNull String tagName,
|
||||
@Nullable GitLineHandlerListener listener, String reference);
|
||||
@Nullable GitLineHandlerListener listener, @NotNull String reference);
|
||||
|
||||
GitCommandResult branchDelete(@NotNull GitRepository repository,
|
||||
@NotNull String branchName,
|
||||
boolean force,
|
||||
@NotNull
|
||||
GitCommandResult branchDelete(@NotNull GitRepository repository, @NotNull String branchName, boolean force,
|
||||
@NotNull GitLineHandlerListener... listeners);
|
||||
|
||||
@NotNull
|
||||
@@ -83,6 +79,7 @@ public interface Git {
|
||||
@NotNull
|
||||
GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision);
|
||||
|
||||
@NotNull
|
||||
GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName);
|
||||
|
||||
@NotNull
|
||||
@@ -92,4 +89,12 @@ public interface Git {
|
||||
@NotNull
|
||||
GitCommandResult push(@NotNull GitRepository repository, @NotNull GitPushSpec pushSpec,
|
||||
@NotNull GitLineHandlerListener... listeners);
|
||||
|
||||
@NotNull
|
||||
GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit,
|
||||
@NotNull GitLineHandlerListener... listeners);
|
||||
|
||||
@NotNull
|
||||
GitCommandResult getUnmergedFiles(@NotNull GitRepository repository);
|
||||
|
||||
}
|
||||
|
||||
@@ -73,4 +73,9 @@ public class GitCommandResult {
|
||||
public String getErrorOutputAsJoinedString() {
|
||||
return StringUtil.join(myErrorOutput, "\n");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getOutputAsJoinedString() {
|
||||
return StringUtil.join(myOutput, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ public class GitImpl implements Git {
|
||||
/**
|
||||
* Calls 'git init' on the specified directory.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public GitCommandResult init(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLineHandlerListener... listeners) {
|
||||
GitLineHandler h = new GitLineHandler(project, root, GitCommand.INIT);
|
||||
@@ -153,6 +154,7 @@ public class GitImpl implements Git {
|
||||
* {@code git checkout <reference>} <br/>
|
||||
* {@code git checkout -b <newBranch> <reference>}
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public GitCommandResult checkout(@NotNull GitRepository repository,
|
||||
@NotNull String reference,
|
||||
@@ -179,6 +181,7 @@ public class GitImpl implements Git {
|
||||
/**
|
||||
* {@code git checkout -b <branchName>}
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public GitCommandResult checkoutNewBranch(@NotNull GitRepository repository, @NotNull String branchName,
|
||||
@Nullable GitLineHandlerListener listener) {
|
||||
@@ -192,9 +195,10 @@ public class GitImpl implements Git {
|
||||
return run(h);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public GitCommandResult createNewTag(@NotNull GitRepository repository, @NotNull String tagName,
|
||||
@Nullable GitLineHandlerListener listener, String reference) {
|
||||
@Nullable GitLineHandlerListener listener, @NotNull String reference) {
|
||||
final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.TAG);
|
||||
h.setSilent(false);
|
||||
h.addParameters(tagName);
|
||||
@@ -210,6 +214,7 @@ public class GitImpl implements Git {
|
||||
/**
|
||||
* {@code git branch -d <reference>} or {@code git branch -D <reference>}
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public GitCommandResult branchDelete(@NotNull GitRepository repository,
|
||||
@NotNull String branchName,
|
||||
@@ -272,6 +277,7 @@ public class GitImpl implements Git {
|
||||
* Returns the last (tip) commit on the given branch.<br/>
|
||||
* {@code git rev-list -1 <branchName>}
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName) {
|
||||
final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REV_LIST);
|
||||
@@ -287,9 +293,7 @@ public class GitImpl implements Git {
|
||||
final GitLineHandlerPasswordRequestAware h = new GitLineHandlerPasswordRequestAware(repository.getProject(), repository.getRoot(),
|
||||
GitCommand.PUSH);
|
||||
h.setSilent(false);
|
||||
for (GitLineHandlerListener listener : listeners) {
|
||||
h.addLineListener(listener);
|
||||
}
|
||||
addListeners(h, listeners);
|
||||
h.addParameters(remote);
|
||||
h.addParameters(spec);
|
||||
return run(h, true);
|
||||
@@ -305,6 +309,36 @@ public class GitImpl implements Git {
|
||||
return push(repository, remote.getName(), pushSpec.getSource().getName() + ":" + destination, listeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit,
|
||||
@NotNull GitLineHandlerListener... listeners) {
|
||||
final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CHERRY_PICK);
|
||||
handler.addParameters("-x");
|
||||
if (!autoCommit) {
|
||||
handler.addParameters("-n");
|
||||
}
|
||||
handler.addParameters(hash);
|
||||
addListeners(handler, listeners);
|
||||
handler.setSilent(false);
|
||||
return run(handler);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public GitCommandResult getUnmergedFiles(@NotNull GitRepository repository) {
|
||||
GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.LS_FILES);
|
||||
h.addParameters("--unmerged");
|
||||
h.setSilent(true);
|
||||
return run(h);
|
||||
}
|
||||
|
||||
private static void addListeners(@NotNull GitLineHandler handler, @NotNull GitLineHandlerListener... listeners) {
|
||||
for (GitLineHandlerListener listener : listeners) {
|
||||
handler.addLineListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private GitCommandResult run(@NotNull GitLineHandler handler) {
|
||||
return run(handler, false);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ public class GitSimpleEventDetector implements GitLineHandlerListener {
|
||||
private boolean myHappened;
|
||||
|
||||
public enum Event {
|
||||
CHERRY_PICK_CONFLICT("fter resolving the conflicts"), // a is uppercase in 1.7.0.5 and lowercase in 1.7.9.2
|
||||
LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK("would be overwritten by merge"),
|
||||
UNMERGED_PREVENTING_CHECKOUT("you need to resolve your current index first"),
|
||||
UNMERGED_PREVENTING_MERGE("is not possible because you have unmerged files"),
|
||||
BRANCH_NOT_FULLY_MERGED("is not fully merged"),
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<grid id="4d15a" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="4d15a" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
@@ -133,7 +133,7 @@
|
||||
<children>
|
||||
<component id="9d661" class="javax.swing.JCheckBox" binding="myAutoUpdateIfPushRejected">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<iconTextGap value="4"/>
|
||||
@@ -142,13 +142,22 @@
|
||||
</component>
|
||||
<component id="2e80d" class="com.intellij.ui.components.JBCheckBox" binding="mySyncBranchControl">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Control &branches from different roots synchronously"/>
|
||||
<toolTipText value="If selected, you would be able to checkout, compare, delete and create new branches in all Git roots with a single action"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="5a98" class="javax.swing.JCheckBox" binding="myAutoCommitOnCherryPick">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<iconTextGap value="4"/>
|
||||
<text value="Commit automatically on cherry-pick"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
|
||||
@@ -54,6 +54,7 @@ public class GitVcsPanel {
|
||||
private JComboBox myConvertTextFilesComboBox; // The conversion policy
|
||||
private JCheckBox myAutoUpdateIfPushRejected;
|
||||
private JBCheckBox mySyncBranchControl;
|
||||
private JCheckBox myAutoCommitOnCherryPick;
|
||||
|
||||
public GitVcsPanel(@NotNull Project project) {
|
||||
myVcs = GitVcs.getInstance(project);
|
||||
@@ -124,6 +125,7 @@ public class GitVcsPanel {
|
||||
myConvertTextFilesComboBox.setSelectedItem(crlfPolicyItem(settings));
|
||||
myAutoUpdateIfPushRejected.setSelected(settings.autoUpdateIfPushRejected());
|
||||
mySyncBranchControl.setSelected(settings.getSyncSetting() == GitBranchSyncSetting.SYNC);
|
||||
myAutoCommitOnCherryPick.setSelected(settings.isAutoCommitOnCherryPick());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +163,8 @@ public class GitVcsPanel {
|
||||
(settings.isIdeaSsh() != IDEA_SSH.equals(mySSHExecutableComboBox.getSelectedItem())) ||
|
||||
!crlfPolicyItem(settings).equals(myConvertTextFilesComboBox.getSelectedItem()) ||
|
||||
!settings.autoUpdateIfPushRejected() == myAutoUpdateIfPushRejected.isSelected() ||
|
||||
((settings.getSyncSetting() == GitBranchSyncSetting.SYNC) != mySyncBranchControl.isSelected());
|
||||
((settings.getSyncSetting() == GitBranchSyncSetting.SYNC) != mySyncBranchControl.isSelected() ||
|
||||
settings.isAutoCommitOnCherryPick() == myAutoCommitOnCherryPick.isSelected());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,5 +192,6 @@ public class GitVcsPanel {
|
||||
}
|
||||
settings.setLineSeparatorsConversion(conversionPolicy);
|
||||
settings.setSyncSetting(mySyncBranchControl.isSelected() ? GitBranchSyncSetting.SYNC : GitBranchSyncSetting.DONT);
|
||||
settings.setAutoCommitOnCherryPick(myAutoCommitOnCherryPick.isSelected());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ public class GitVcsSettings implements PersistentStateComponent<GitVcsSettings.S
|
||||
public String RECENT_GIT_ROOT_PATH = null;
|
||||
public Map<String, String> RECENT_BRANCH_BY_REPOSITORY = new HashMap<String, String>();
|
||||
public String RECENT_COMMON_BRANCH = null;
|
||||
public boolean AUTO_COMMIT_ON_CHERRY_PICK = false;
|
||||
}
|
||||
|
||||
public GitVcsSettings(GitVcsApplicationSettings appSettings) {
|
||||
@@ -190,4 +191,12 @@ public class GitVcsSettings implements PersistentStateComponent<GitVcsSettings.S
|
||||
myState.RECENT_COMMON_BRANCH = branch;
|
||||
}
|
||||
|
||||
public void setAutoCommitOnCherryPick(boolean autoCommit) {
|
||||
myState.AUTO_COMMIT_ON_CHERRY_PICK = autoCommit;
|
||||
}
|
||||
|
||||
public boolean isAutoCommitOnCherryPick() {
|
||||
return myState.AUTO_COMMIT_ON_CHERRY_PICK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,74 +15,306 @@
|
||||
*/
|
||||
package git4idea.history.browser;
|
||||
|
||||
import com.intellij.lifecycle.PeriodicalTasksCloser;
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationListener;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.MessageType;
|
||||
import com.intellij.openapi.vcs.AbstractVcsHelper;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.vcs.FilePath;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.changes.*;
|
||||
import com.intellij.openapi.vcs.changes.ui.CommitHelper;
|
||||
import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
|
||||
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.Consumer;
|
||||
import git4idea.GitVcs;
|
||||
import com.intellij.util.WaitForProgressToShow;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.commands.GitCommandResult;
|
||||
import git4idea.commands.GitMessageWithFilesDetector;
|
||||
import git4idea.commands.GitSimpleEventDetector;
|
||||
import git4idea.merge.GitConflictResolver;
|
||||
import git4idea.repo.GitRepository;
|
||||
import git4idea.util.UntrackedFilesNotifier;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier.showOverChangesView;
|
||||
import static com.intellij.openapi.util.text.StringUtil.pluralize;
|
||||
import static git4idea.commands.GitMessageWithFilesDetector.Event.UNTRACKED_FILES_OVERWRITTEN_BY;
|
||||
import static git4idea.commands.GitSimpleEventDetector.Event.CHERRY_PICK_CONFLICT;
|
||||
import static git4idea.commands.GitSimpleEventDetector.Event.LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK;
|
||||
|
||||
public class CherryPicker {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(CherryPicker.class);
|
||||
|
||||
private final GitVcs myVcs;
|
||||
private final List<GitCommit> myCommits;
|
||||
@NotNull private final CheckinEnvironment myCheckinEnvironment;
|
||||
private final LowLevelAccess myAccess;
|
||||
@NotNull private final Project myProject;
|
||||
@NotNull private final Git myGit;
|
||||
@NotNull private final PlatformFacade myPlatformFacade;
|
||||
@NotNull private final ChangeListManager myChangeListManager;
|
||||
private final boolean myAutoCommit;
|
||||
|
||||
private final List<VcsException> myExceptions;
|
||||
private final List<VcsException> myWarnings;
|
||||
private boolean myConflictsExist;
|
||||
private final ChangeListManager myChangeListManager;
|
||||
|
||||
private final List<CherryPickedData> myCherryPickedData;
|
||||
|
||||
public CherryPicker(GitVcs vcs, final List<GitCommit> commits, LowLevelAccess access) {
|
||||
myVcs = vcs;
|
||||
myCommits = commits;
|
||||
myAccess = access;
|
||||
|
||||
myChangeListManager = PeriodicalTasksCloser.getInstance().safeGetComponent(myVcs.getProject(), ChangeListManager.class);
|
||||
CheckinEnvironment ce = myVcs.getCheckinEnvironment();
|
||||
LOG.assertTrue(ce != null);
|
||||
myCheckinEnvironment = ce;
|
||||
|
||||
myExceptions = new ArrayList<VcsException>();
|
||||
myWarnings = new ArrayList<VcsException>();
|
||||
myCherryPickedData = new ArrayList<CherryPickedData>();
|
||||
public CherryPicker(@NotNull Project project, @NotNull Git git, @NotNull PlatformFacade platformFacade, boolean autoCommit) {
|
||||
myProject = project;
|
||||
myGit = git;
|
||||
myPlatformFacade = platformFacade;
|
||||
myAutoCommit = autoCommit;
|
||||
myChangeListManager = myPlatformFacade.getChangeListManager(myProject);
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
for (GitCommit commit : myCommits) {
|
||||
cherryPickStep(commit);
|
||||
public void cherryPick(@NotNull Map<GitRepository, List<GitCommit>> commitsInRoots) {
|
||||
List<GitCommit> successfulCommits = new ArrayList<GitCommit>();
|
||||
for (Map.Entry<GitRepository, List<GitCommit>> entry : commitsInRoots.entrySet()) {
|
||||
if (!cherryPick(entry.getKey(), entry.getValue(), successfulCommits)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// remove those that are in newer lists
|
||||
checkListsForSamePaths();
|
||||
|
||||
refreshChangedFiles();
|
||||
findAndProcessChangedForVcs();
|
||||
|
||||
showResults();
|
||||
notifySuccess(successfulCommits);
|
||||
}
|
||||
|
||||
private void refreshChangedFiles() {
|
||||
for (FilePath file : getAllChangedFiles()) {
|
||||
VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByPath(file.getPath());
|
||||
// return true to continue with other roots, false to break execution
|
||||
private boolean cherryPick(@NotNull GitRepository repository, @NotNull List<GitCommit> commits,
|
||||
@NotNull List<GitCommit> successfulCommits) {
|
||||
for (GitCommit commit : commits) {
|
||||
GitSimpleEventDetector conflictDetector = new GitSimpleEventDetector(CHERRY_PICK_CONFLICT);
|
||||
GitSimpleEventDetector localChangesOverwrittenDetector = new GitSimpleEventDetector(LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK);
|
||||
GitMessageWithFilesDetector untrackedFilesDetector = new GitMessageWithFilesDetector(UNTRACKED_FILES_OVERWRITTEN_BY,
|
||||
repository.getRoot());
|
||||
GitCommandResult result = myGit.cherryPick(repository, commit.getHash().getValue(), myAutoCommit,
|
||||
conflictDetector, localChangesOverwrittenDetector, untrackedFilesDetector);
|
||||
if (result.success()) {
|
||||
if (myAutoCommit) {
|
||||
successfulCommits.add(commit);
|
||||
}
|
||||
else {
|
||||
boolean committed = updateChangeListManagerShowCommitDialogAndRemoveChangeListOnSuccess(repository, commit, successfulCommits);
|
||||
if (!committed) {
|
||||
notifyCommitCancelled(commit, successfulCommits);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (conflictDetector.hasHappened()) {
|
||||
boolean mergeCompleted = new CherryPickConflictResolver(myProject, myGit, myPlatformFacade, repository.getRoot(),
|
||||
commit.getShortHash().getString(), commit.getAuthor(),
|
||||
commit.getSubject()).merge();
|
||||
|
||||
if (mergeCompleted) {
|
||||
boolean committed = updateChangeListManagerShowCommitDialogAndRemoveChangeListOnSuccess(repository, commit, successfulCommits);
|
||||
if (!committed) {
|
||||
notifyCommitCancelled(commit, successfulCommits);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
updateChangeListManager(commit);
|
||||
notifyConflictWarning(repository, commit, successfulCommits);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (untrackedFilesDetector.wasMessageDetected()) {
|
||||
String description = commitDetails(commit)
|
||||
+ "<br/>Some untracked working tree files would be overwritten by cherry-pick.<br/>" +
|
||||
"Please move, remove or add them before you can cherry-pick. <a href='view'>View them</a>";
|
||||
description += getSuccessfulCommitDetailsIfAny(successfulCommits);
|
||||
|
||||
UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, myPlatformFacade, untrackedFilesDetector.getFiles(),
|
||||
"cherry-pick", description);
|
||||
return false;
|
||||
}
|
||||
else if (localChangesOverwrittenDetector.hasHappened()) {
|
||||
notifyError("Your local changes would be overwritten by cherry-pick.<br/>Commit your changes or stash them to proceed.",
|
||||
commit, successfulCommits);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
notifyError(result.getErrorOutputAsHtmlString(), commit, successfulCommits);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean updateChangeListManagerShowCommitDialogAndRemoveChangeListOnSuccess(@NotNull GitRepository repository,
|
||||
@NotNull GitCommit commit,
|
||||
@NotNull List<GitCommit> successfulCommits) {
|
||||
CherryPickData data = updateChangeListManager(commit);
|
||||
boolean committed = showCommitDialog(repository, commit, data.myChangeList, data.myCommitMessage);
|
||||
if (committed) {
|
||||
removeChangeList(data);
|
||||
successfulCommits.add(commit);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void removeChangeList(CherryPickData list) {
|
||||
myChangeListManager.setDefaultChangeList(list.myPreviouslyDefaultChangeList);
|
||||
if (!myChangeListManager.getDefaultChangeList().equals(list.myChangeList)) {
|
||||
myChangeListManager.removeChangeList(list.myChangeList);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyConflictWarning(@NotNull GitRepository repository, @NotNull GitCommit commit,
|
||||
@NotNull List<GitCommit> successfulCommits) {
|
||||
NotificationListener resolveLinkListener = new ResolveLinkListener(myProject, myGit, myPlatformFacade, repository.getRoot(),
|
||||
commit.getShortHash().getString(), commit.getAuthor(),
|
||||
commit.getSubject());
|
||||
String description = commitDetails(commit)
|
||||
+ "<br/>Unresolved conflicts remain in the working tree. <a href='resolve'>Resolve them.<a/>";
|
||||
description += getSuccessfulCommitDetailsIfAny(successfulCommits);
|
||||
myPlatformFacade.getNotificator(myProject).notifyStrongWarning("Cherry-picked with conflicts", description, resolveLinkListener);
|
||||
}
|
||||
|
||||
private void notifyCommitCancelled(@NotNull GitCommit commit, @NotNull List<GitCommit> successfulCommits) {
|
||||
String description = commitDetails(commit);
|
||||
description += getSuccessfulCommitDetailsIfAny(successfulCommits);
|
||||
myPlatformFacade.getNotificator(myProject).notifyWeakWarning("Cherry-pick cancelled", description, null);
|
||||
}
|
||||
|
||||
private CherryPickData updateChangeListManager(@NotNull final GitCommit commit) {
|
||||
final Collection<FilePath> paths = ChangesUtil.getPaths(commit.getChanges());
|
||||
refreshChangedFiles(paths);
|
||||
final String commitMessage = createCommitMessage(commit, paths);
|
||||
LocalChangeList previouslyDefaultChangeList = myChangeListManager.getDefaultChangeList();
|
||||
LocalChangeList changeList = createChangeListAfterUpdate(commit.getChanges(), paths, commitMessage);
|
||||
return new CherryPickData(changeList, commitMessage, previouslyDefaultChangeList);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private LocalChangeList createChangeListAfterUpdate(@NotNull final List<Change> changes, @NotNull final Collection<FilePath> paths,
|
||||
@NotNull final String commitMessage) {
|
||||
final AtomicReference<LocalChangeList> changeList = new AtomicReference<LocalChangeList>();
|
||||
myPlatformFacade.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myChangeListManager.invokeAfterUpdate(new Runnable() {
|
||||
public void run() {
|
||||
changeList.set(createChangeList(changes, commitMessage));
|
||||
}
|
||||
}, InvokeAfterUpdateMode.SYNCHRONOUS_NOT_CANCELLABLE, "",
|
||||
new Consumer<VcsDirtyScopeManager>() {
|
||||
public void consume(VcsDirtyScopeManager vcsDirtyScopeManager) {
|
||||
vcsDirtyScopeManager.filePathsDirty(paths, null);
|
||||
}
|
||||
}, ModalityState.NON_MODAL
|
||||
);
|
||||
}
|
||||
}, ModalityState.NON_MODAL);
|
||||
|
||||
|
||||
return changeList.get();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String createCommitMessage(@NotNull GitCommit commit, @NotNull Collection<FilePath> paths) {
|
||||
CheckinEnvironment ce = myPlatformFacade.getVcs(myProject).getCheckinEnvironment();
|
||||
String message = ce == null ? null : ce.getDefaultMessageFor(ArrayUtil.toObjectArray(paths, FilePath.class));
|
||||
message = message == null ? commit.getDescription() + "\n(cherry-picked from " + commit.getShortHash().getString() + ")" : message;
|
||||
return message;
|
||||
}
|
||||
|
||||
private boolean showCommitDialog(@NotNull final GitRepository repository, @NotNull final GitCommit commit,
|
||||
@NotNull final LocalChangeList changeList, @NotNull final String commitMessage) {
|
||||
final AtomicBoolean commitSucceeded = new AtomicBoolean();
|
||||
myPlatformFacade.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cancelCherryPick(repository);
|
||||
List<Change> changes = commit.getChanges();
|
||||
CherryPickCommitExecutor executor = new CherryPickCommitExecutor(myProject, myPlatformFacade, changes, commitMessage);
|
||||
boolean commitNotCancelled = myPlatformFacade.getVcsHelper(myProject).commitChanges(changes, changeList, commitMessage, executor);
|
||||
commitSucceeded.set(commitNotCancelled && !executor.hasCommitFailed());
|
||||
}
|
||||
}, ModalityState.NON_MODAL);
|
||||
return commitSucceeded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* We control the cherry-pick workflow ourselves + we want to use partial commits ('git commit --only'), which is prohibited during
|
||||
* cherry-pick, i.e. until the CHERRY_PICK_HEAD exists.
|
||||
*/
|
||||
private void cancelCherryPick(@NotNull GitRepository repository) {
|
||||
if (myAutoCommit) {
|
||||
removeCherryPickHead(repository);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeCherryPickHead(@NotNull GitRepository repository) {
|
||||
File cherryPickHeadFile = new File(repository.getGitDir().getPath(), "CHERRY_PICK_HEAD");
|
||||
final VirtualFile cherryPickHead = myPlatformFacade.getLocalFileSystem().refreshAndFindFileByIoFile(cherryPickHeadFile);
|
||||
|
||||
if (cherryPickHead != null && cherryPickHead.exists()) {
|
||||
myPlatformFacade.runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
cherryPickHead.delete(this);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// if CHERRY_PICK_HEAD is not deleted, the partial commit will fail, and the user will be notified anyway.
|
||||
// So here we just log the fact. It is happens relatively often, maybe some additional solution will follow.
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
LOG.info("Cancel cherry-pick in " + repository.getPresentableUrl() + ": no CHERRY_PICK_HEAD found");
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyError(@NotNull String content, @NotNull GitCommit failedCommit, @NotNull List<GitCommit> successfulCommits) {
|
||||
String description = commitDetails(failedCommit) + "<br/>" + content;
|
||||
description += getSuccessfulCommitDetailsIfAny(successfulCommits);
|
||||
myPlatformFacade.getNotificator(myProject).notifyError("Cherry-pick failed", description);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getSuccessfulCommitDetailsIfAny(@NotNull List<GitCommit> successfulCommits) {
|
||||
String description = "";
|
||||
if (!successfulCommits.isEmpty()) {
|
||||
description += "<hr/>However cherry-pick succeeded for the following " + pluralize("commit", successfulCommits.size()) + ":<br/>";
|
||||
description += getCommitsDetails(successfulCommits);
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
private void notifySuccess(@NotNull List<GitCommit> successfulCommits) {
|
||||
String description = getCommitsDetails(successfulCommits);
|
||||
myPlatformFacade.getNotificator(myProject).notifySuccess("Cherry-pick successful", description);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getCommitsDetails(@NotNull List<GitCommit> successfulCommits) {
|
||||
String description = "";
|
||||
for (GitCommit commit : successfulCommits) {
|
||||
description += commitDetails(commit) + "<br/>";
|
||||
}
|
||||
return description.substring(0, description.length() - "<br/>".length());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String commitDetails(@NotNull GitCommit commit) {
|
||||
return commit.getShortHash().toString() + " \"" + commit.getSubject() + "\"";
|
||||
}
|
||||
|
||||
private void refreshChangedFiles(@NotNull Collection<FilePath> filePaths) {
|
||||
for (FilePath file : filePaths) {
|
||||
VirtualFile vf = myPlatformFacade.getLocalFileSystem().refreshAndFindFileByPath(file.getPath());
|
||||
if (vf != null) {
|
||||
vf.refresh(false, false);
|
||||
}
|
||||
@@ -90,190 +322,230 @@ public class CherryPicker {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Collection<FilePath> getAllChangedFiles() {
|
||||
Collection<FilePath> files = new ArrayList<FilePath>();
|
||||
for (CherryPickedData data : myCherryPickedData) {
|
||||
files.addAll(data.getFiles());
|
||||
private LocalChangeList createChangeList(@NotNull List<Change> changes, @NotNull String commitMessage) {
|
||||
if (!changes.isEmpty()) {
|
||||
final LocalChangeList changeList = myChangeListManager.addChangeList(commitMessage, commitMessage);
|
||||
myChangeListManager.moveChangesTo(changeList, changes.toArray(new Change[changes.size()]));
|
||||
myChangeListManager.setDefaultChangeList(changeList);
|
||||
return changeList;
|
||||
}
|
||||
return files;
|
||||
return myChangeListManager.getDefaultChangeList();
|
||||
}
|
||||
|
||||
private void findAndProcessChangedForVcs() {
|
||||
myChangeListManager.invokeAfterUpdate(new Runnable() {
|
||||
public void run() {
|
||||
moveToCorrectLists();
|
||||
}
|
||||
}, InvokeAfterUpdateMode.SILENT, "", new Consumer<VcsDirtyScopeManager>() {
|
||||
public void consume(VcsDirtyScopeManager vcsDirtyScopeManager) {
|
||||
vcsDirtyScopeManager.filePathsDirty(getAllChangedFiles(), null);
|
||||
}
|
||||
}, ModalityState.NON_MODAL);
|
||||
}
|
||||
private static class CherryPickData {
|
||||
private final LocalChangeList myChangeList;
|
||||
private final String myCommitMessage;
|
||||
private final LocalChangeList myPreviouslyDefaultChangeList;
|
||||
|
||||
private void showResults() {
|
||||
final Project project = myVcs.getProject();
|
||||
if (myExceptions.isEmpty() && !myConflictsExist) {
|
||||
showOverChangesView(project, "Successful cherry-pick into working tree, please commit changes", MessageType.INFO);
|
||||
} else {
|
||||
if (myExceptions.isEmpty()) {
|
||||
showOverChangesView(project, "Unresolved conflicts while cherry-picking. Resolve conflicts, then commit changes",
|
||||
MessageType.WARNING);
|
||||
} else {
|
||||
showOverChangesView(project, "Errors in cherry-pick", MessageType.ERROR);
|
||||
}
|
||||
}
|
||||
if ((! myExceptions.isEmpty()) || (! myWarnings.isEmpty())) {
|
||||
myExceptions.addAll(myWarnings);
|
||||
AbstractVcsHelper.getInstance(project).showErrors(myExceptions, "Cherry-pick problems");
|
||||
private CherryPickData(LocalChangeList list, String message, LocalChangeList previouslyDefaultChangeList) {
|
||||
myChangeList = list;
|
||||
myCommitMessage = message;
|
||||
myPreviouslyDefaultChangeList = previouslyDefaultChangeList;
|
||||
}
|
||||
}
|
||||
|
||||
private void moveToCorrectLists() {
|
||||
for (CherryPickedData pickedData : myCherryPickedData) {
|
||||
final Collection<FilePath> filePaths = pickedData.getFiles();
|
||||
final String message = pickedData.getCommitMessage();
|
||||
private static class CherryPickConflictResolver extends GitConflictResolver {
|
||||
|
||||
if (filePaths.isEmpty()) continue;
|
||||
|
||||
final List<Change> changes = pathsToChanges(filePaths);
|
||||
pickedData.setChanges(changes);
|
||||
if (!changes.isEmpty()) {
|
||||
final LocalChangeList cl = myChangeListManager.addChangeList(message, null);
|
||||
pickedData.setChangeList(cl);
|
||||
myChangeListManager.moveChangesTo(cl, changes.toArray(new Change[changes.size()]));
|
||||
}
|
||||
public CherryPickConflictResolver(@NotNull Project project, @NotNull Git git, @NotNull PlatformFacade facade, @NotNull VirtualFile root,
|
||||
@NotNull String commitHash, @NotNull String commitAuthor, @NotNull String commitMessage) {
|
||||
super(project, git, facade, Collections.singleton(root), makeParams(commitHash, commitAuthor, commitMessage));
|
||||
}
|
||||
|
||||
private static Params makeParams(String commitHash, String commitAuthor, String commitMessage) {
|
||||
Params params = new Params();
|
||||
params.setErrorNotificationTitle("Cherry-picked with conflicts");
|
||||
params.setMergeDialogCustomizer(new CherryPickMergeDialogCustomizer(commitHash, commitAuthor, commitMessage));
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyUnresolvedRemain() {
|
||||
// we show a [possibly] compound notification after cherry-picking all commits.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<Change> pathsToChanges(@NotNull Collection<FilePath> filePaths) {
|
||||
final List<Change> changes = new ArrayList<Change>(filePaths.size());
|
||||
for (FilePath filePath : filePaths) {
|
||||
changes.add(myChangeListManager.getChange(filePath));
|
||||
private static class ResolveLinkListener implements NotificationListener {
|
||||
@NotNull private final Project myProject;
|
||||
@NotNull private final Git myGit;
|
||||
@NotNull private final PlatformFacade myFacade;
|
||||
@NotNull private final VirtualFile myRoot;
|
||||
@NotNull private final String myHash;
|
||||
@NotNull private final String myAuthor;
|
||||
@NotNull private final String myMessage;
|
||||
|
||||
public ResolveLinkListener(@NotNull Project project, @NotNull Git git, @NotNull PlatformFacade facade, @NotNull VirtualFile root,
|
||||
@NotNull String commitHash, @NotNull String commitAuthor, @NotNull String commitMessage) {
|
||||
|
||||
myProject = project;
|
||||
myGit = git;
|
||||
myFacade = facade;
|
||||
myRoot = root;
|
||||
myHash = commitHash;
|
||||
myAuthor = commitAuthor;
|
||||
myMessage = commitMessage;
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
private void checkListsForSamePaths() {
|
||||
List<String> myMessagesInOrder = new ArrayList<String>(myCherryPickedData.size());
|
||||
Map<String, Collection<FilePath>> myFilesToMove = new HashMap<String, Collection<FilePath>>(myCherryPickedData.size());
|
||||
for (CherryPickedData data : myCherryPickedData) {
|
||||
myMessagesInOrder.add(data.getCommitMessage());
|
||||
myFilesToMove.put(data.getCommitMessage(), data.getFiles());
|
||||
}
|
||||
final GroupOfListsProcessor listsProcessor = new GroupOfListsProcessor();
|
||||
listsProcessor.process(myMessagesInOrder, myFilesToMove);
|
||||
final Set<String> lostSet = listsProcessor.getHaveLostSomething();
|
||||
markFilesMovesToNewerLists(myWarnings, lostSet, myFilesToMove);
|
||||
}
|
||||
|
||||
private void cherryPickStep(@NotNull GitCommit commit) {
|
||||
try {
|
||||
if (!myAccess.cherryPick(commit)) {
|
||||
myConflictsExist = true;
|
||||
}
|
||||
}
|
||||
catch (VcsException e) {
|
||||
myExceptions.add(e);
|
||||
}
|
||||
final List<Change> changes = commit.getChanges();
|
||||
|
||||
final Collection<FilePath> paths = ChangesUtil.getPaths(changes);
|
||||
String message = myCheckinEnvironment.getDefaultMessageFor(paths.toArray(new FilePath[paths.size()]));
|
||||
message = (message == null) ? commit.getDescription() + " (cherry picked from commit " + commit.getShortHash() + ")" : message;
|
||||
|
||||
myCherryPickedData.add(new CherryPickedData(message, paths));
|
||||
}
|
||||
|
||||
private static void markFilesMovesToNewerLists(List<VcsException> exceptions, Set<String> lostSet,
|
||||
Map<String, Collection<FilePath>> filesToMove) {
|
||||
if (! lostSet.isEmpty()) {
|
||||
final StringBuilder sb = new StringBuilder("Some changes are moved from following list(s) to other:");
|
||||
boolean first = true;
|
||||
for (String s : lostSet) {
|
||||
if (filesToMove.get(s).isEmpty()) {
|
||||
final VcsException exc =
|
||||
new VcsException("Changelist not created since all files moved to other cherry-pick(s): '" + s + "'");
|
||||
exc.setIsWarning(true);
|
||||
exceptions.add(exc);
|
||||
continue;
|
||||
@Override
|
||||
public void hyperlinkUpdate(@NotNull Notification notification,
|
||||
@NotNull HyperlinkEvent event) {
|
||||
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
|
||||
if (event.getDescription().equals("resolve")) {
|
||||
new CherryPickConflictResolver(myProject, myGit, myFacade, myRoot, myHash, myAuthor, myMessage).mergeNoProceed();
|
||||
}
|
||||
sb.append(s);
|
||||
if (! first) {
|
||||
sb.append(", ");
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if (! first) {
|
||||
final VcsException exc = new VcsException(sb.toString());
|
||||
exc.setIsWarning(true);
|
||||
exceptions.add(exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class GroupOfListsProcessor {
|
||||
private final Set<String> myHaveLostSomething;
|
||||
private static class CherryPickMergeDialogCustomizer extends MergeDialogCustomizer {
|
||||
|
||||
private GroupOfListsProcessor() {
|
||||
myHaveLostSomething = new HashSet<String>();
|
||||
private String myCommitHash;
|
||||
private String myCommitAuthor;
|
||||
private String myCommitMessage;
|
||||
|
||||
public CherryPickMergeDialogCustomizer(String commitHash, String commitAuthor, String commitMessage) {
|
||||
myCommitHash = commitHash;
|
||||
myCommitAuthor = commitAuthor;
|
||||
myCommitMessage = commitMessage;
|
||||
}
|
||||
|
||||
public void process(final List<String> messagesInOrder, final Map<String, Collection<FilePath>> filesToMove) {
|
||||
// remove those that are in newer lists
|
||||
for (int i = 1; i < messagesInOrder.size(); i++) {
|
||||
final String message = messagesInOrder.get(i);
|
||||
final Collection<FilePath> currentFiles = filesToMove.get(message);
|
||||
@Override
|
||||
public String getMultipleFileMergeDescription(Collection<VirtualFile> files) {
|
||||
return "<html>Conflicts during cherry-picking commit <code>" + myCommitHash + "</code> made by " + myCommitAuthor + "<br/>" +
|
||||
"<code>\"" + myCommitMessage + "\"</code></html>";
|
||||
}
|
||||
|
||||
for (int j = 0; j < i; j++) {
|
||||
final String previous = messagesInOrder.get(j);
|
||||
final boolean somethingChanged = filesToMove.get(previous).removeAll(currentFiles);
|
||||
if (somethingChanged) {
|
||||
myHaveLostSomething.add(previous);
|
||||
@Override
|
||||
public String getLeftPanelTitle(VirtualFile file) {
|
||||
return "Local changes";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) {
|
||||
return "<html>Changes from cherry-pick <code>" + myCommitHash + "</code>";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Commit procedure is overridden by the executor with its own CommitSession.
|
||||
The reason of that is the asynchronous nature of the CommitHelper: it returns, we continue cherry-picking and occasionally pick
|
||||
the next commit in the queue, and only then Git is called for commit. Thus it commits two cherry-picks at once, which is wrong.
|
||||
|
||||
Here we call GitCheckinEnvironment manually
|
||||
*/
|
||||
private static class CherryPickCommitExecutor implements CommitExecutor {
|
||||
|
||||
@NotNull private final Project myProject;
|
||||
@NotNull private final PlatformFacade myPlatformFacade;
|
||||
@NotNull private final List<Change> myChanges;
|
||||
@NotNull private final String myCommitMessage;
|
||||
private boolean myCommitFailed;
|
||||
|
||||
CherryPickCommitExecutor(@NotNull Project project, @NotNull PlatformFacade platformFacade,
|
||||
@NotNull List<Change> changes, @NotNull String commitMessage) {
|
||||
myProject = project;
|
||||
myPlatformFacade = platformFacade;
|
||||
myChanges = changes;
|
||||
myCommitMessage = commitMessage;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@Override
|
||||
public String getActionText() {
|
||||
return "Commit";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CommitSession createCommitSession() {
|
||||
return new CherryPickCommitSession();
|
||||
}
|
||||
|
||||
public boolean hasCommitFailed() {
|
||||
return myCommitFailed;
|
||||
}
|
||||
|
||||
private class CherryPickCommitSession implements CommitSession {
|
||||
@Override
|
||||
public JComponent getAdditionalConfigurationUI() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getAdditionalConfigurationUI(Collection<Change> changes, String commitMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canExecute(Collection<Change> changes, String commitMessage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Collection<Change> changes, String commitMessage) {
|
||||
final Collection<Document> committingDocs = markCommittingDocs();
|
||||
try {
|
||||
CheckinEnvironment ce = myPlatformFacade.getVcs(myProject).getCheckinEnvironment();
|
||||
if (ce != null) {
|
||||
try {
|
||||
List<VcsException> exceptions = ce.commit(myChanges, myCommitMessage);
|
||||
VcsDirtyScopeManager.getInstance(myProject).filePathsDirty(ChangesUtil.getPaths(myChanges), null);
|
||||
if (exceptions != null && !exceptions.isEmpty()) {
|
||||
VcsException exception = exceptions.get(0);
|
||||
handleError(exception);
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
LOG.error(e);
|
||||
handleError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
unmarkCommittingDocs(committingDocs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getHaveLostSomething() {
|
||||
return myHaveLostSomething;
|
||||
}
|
||||
}
|
||||
private void handleError(Throwable exception) {
|
||||
myCommitFailed = true;
|
||||
final String errorMessage = exception.getMessage();
|
||||
WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() {
|
||||
public void run() {
|
||||
Messages.showErrorDialog(myProject, errorMessage, "Commit Failed");
|
||||
}
|
||||
}, null, myProject);
|
||||
}
|
||||
|
||||
private static class CherryPickedData {
|
||||
@Override
|
||||
public void executionCanceled() {
|
||||
}
|
||||
|
||||
private final String myCommitMessage;
|
||||
private final Collection<FilePath> myFiles;
|
||||
private LocalChangeList myChangeList;
|
||||
private Collection<Change> myChanges;
|
||||
@Override
|
||||
public String getHelpId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private CherryPickedData(@NotNull String message, @NotNull Collection<FilePath> files) {
|
||||
myCommitMessage = message;
|
||||
myFiles = files;
|
||||
}
|
||||
private void unmarkCommittingDocs(final Collection<Document> committingDocs) {
|
||||
myPlatformFacade.runReadAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CommitHelper.unmarkCommittingDocuments(committingDocs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Collection<Change> getChanges() {
|
||||
return myChanges;
|
||||
}
|
||||
@NotNull
|
||||
private Collection<Document> markCommittingDocs() {
|
||||
final Collection<Document> committingDocs = new ArrayList<Document>();
|
||||
myPlatformFacade.runReadAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
committingDocs.addAll(CommitHelper.markCommittingDocuments(myProject, myChanges));
|
||||
}
|
||||
});
|
||||
return committingDocs;
|
||||
}
|
||||
|
||||
public LocalChangeList getChangeList() {
|
||||
return myChangeList;
|
||||
}
|
||||
|
||||
public String getCommitMessage() {
|
||||
return myCommitMessage;
|
||||
}
|
||||
|
||||
public Collection<FilePath> getFiles() {
|
||||
return myFiles;
|
||||
}
|
||||
|
||||
public void setChanges(List<Change> changes) {
|
||||
myChanges = changes;
|
||||
}
|
||||
|
||||
public void setChangeList(LocalChangeList changeList) {
|
||||
myChangeList = changeList;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,14 +56,6 @@ public interface LowLevelAccess {
|
||||
|
||||
void loadAllTags(final Collection<String> sink) throws VcsException;
|
||||
|
||||
/**
|
||||
* Cherry-picks the specified commit.
|
||||
* Doesn't autocommit - instead puts the changes into a separate changelist.
|
||||
* In the case of merge conflict provides the Conflict Resolver dialog.
|
||||
* @return true if all conflicts were resolved or there were no merge conflicts; false if unresolved files remain.
|
||||
* @throws VcsException
|
||||
*/
|
||||
boolean cherryPick(GitCommit hash) throws VcsException;
|
||||
void loadHashesWithParents(final @NotNull Collection<String> startingPoints, @NotNull final Collection<ChangesFilter.Filter> filters,
|
||||
final AsynchConsumer<CommitHashPlusParents> consumer, Getter<Boolean> isCanceled, int useMaxCnt,
|
||||
final boolean topoOrder) throws VcsException;
|
||||
|
||||
@@ -18,6 +18,7 @@ package git4idea.history.browser;
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationListener;
|
||||
import com.intellij.notification.NotificationType;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Getter;
|
||||
@@ -29,11 +30,9 @@ import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.AsynchConsumer;
|
||||
import git4idea.GitBranch;
|
||||
import git4idea.GitTag;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.*;
|
||||
import git4idea.branch.GitBranchesCollection;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.commands.GitCommand;
|
||||
import git4idea.commands.GitLineHandler;
|
||||
import git4idea.commands.GitLineHandlerAdapter;
|
||||
@@ -43,6 +42,7 @@ import git4idea.history.wholeTree.AbstractHash;
|
||||
import git4idea.history.wholeTree.CommitHashPlusParents;
|
||||
import git4idea.merge.GitConflictResolver;
|
||||
import git4idea.repo.GitRepository;
|
||||
import git4idea.repo.GitRepositoryImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -121,7 +121,7 @@ public class LowLevelAccessImpl implements LowLevelAccess {
|
||||
if (! child.exists()) {
|
||||
throw new VcsException("No git repository in " + myRoot.getPath());
|
||||
}
|
||||
repository = GitRepository.getLightInstance(myRoot, myProject, myProject);
|
||||
repository = GitRepositoryImpl.getLightInstance(myRoot, myProject, myProject);
|
||||
repository.getBranches();
|
||||
}
|
||||
GitBranchesCollection branches = repository.getBranches();
|
||||
@@ -212,102 +212,4 @@ public class LowLevelAccessImpl implements LowLevelAccess {
|
||||
GitTag.listAsStrings(myProject, myRoot, sink, null);
|
||||
}
|
||||
|
||||
public boolean cherryPick(GitCommit commit) throws VcsException {
|
||||
final GitLineHandler handler = new GitLineHandler(myProject, myRoot, GitCommand.CHERRY_PICK);
|
||||
handler.addParameters("-x", "-n", commit.getHash().getValue());
|
||||
handler.endOptions();
|
||||
handler.setNoSSH(true);
|
||||
|
||||
final AtomicBoolean conflict = new AtomicBoolean();
|
||||
|
||||
handler.addLineListener(new GitLineHandlerAdapter() {
|
||||
public void onLineAvailable(String line, Key outputType) {
|
||||
if (line.toLowerCase().contains("after resolving the conflicts")) {
|
||||
conflict.set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
handler.runInCurrentThread(null);
|
||||
|
||||
if (conflict.get()) {
|
||||
return new CherryPickConflictResolver(myProject, myRoot, commit.getShortHash().getString(), commit.getAuthor(), commit.getSubject()).merge();
|
||||
} else {
|
||||
final List<VcsException> errors = handler.errors();
|
||||
if (!errors.isEmpty()) {
|
||||
throw errors.get(0);
|
||||
} else { // no conflicts, no errors
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class CherryPickConflictResolver extends GitConflictResolver {
|
||||
|
||||
private VirtualFile myRoot;
|
||||
private String myCommitHash;
|
||||
private String myCommitAuthor;
|
||||
private String myCommitMessage;
|
||||
|
||||
public CherryPickConflictResolver(Project project, VirtualFile root, String commitHash, String commitAuthor, String commitMessage) {
|
||||
super(project, Collections.singleton(root), makeParams(commitHash, commitAuthor, commitMessage));
|
||||
myRoot = root;
|
||||
myCommitHash = commitHash;
|
||||
myCommitAuthor = commitAuthor;
|
||||
myCommitMessage = commitMessage;
|
||||
}
|
||||
|
||||
private static Params makeParams(String commitHash, String commitAuthor, String commitMessage) {
|
||||
Params params = new Params();
|
||||
params.setErrorNotificationTitle("Cherry-picked with conflicts");
|
||||
params.setMergeDialogCustomizer(new CherryPickMergeDialogCustomizer(commitHash, commitAuthor, commitMessage));
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyUnresolvedRemain() {
|
||||
GitVcs.IMPORTANT_ERROR_NOTIFICATION.createNotification("Conflicts were not resolved during cherry-pick",
|
||||
"Cherry-pick is not complete, you have unresolved merges in your working tree<br/>" +
|
||||
"<a href='resolve'>Resolve</a> conflicts.",
|
||||
NotificationType.WARNING, new NotificationListener() {
|
||||
@Override
|
||||
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
|
||||
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
|
||||
if (event.getDescription().equals("resolve")) {
|
||||
new CherryPickConflictResolver(myProject, myRoot, myCommitHash, myCommitAuthor, myCommitMessage).mergeNoProceed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).notify(myProject);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CherryPickMergeDialogCustomizer extends MergeDialogCustomizer {
|
||||
|
||||
private String myCommitHash;
|
||||
private String myCommitAuthor;
|
||||
private String myCommitMessage;
|
||||
|
||||
public CherryPickMergeDialogCustomizer(String commitHash, String commitAuthor, String commitMessage) {
|
||||
myCommitHash = commitHash;
|
||||
myCommitAuthor = commitAuthor;
|
||||
myCommitMessage = commitMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMultipleFileMergeDescription(Collection<VirtualFile> files) {
|
||||
return "<html>Conflicts during cherry-picking commit <code>" + myCommitHash + "</code> made by " + myCommitAuthor + "<br/>" +
|
||||
"<code>\"" + myCommitMessage + "\"</code></html>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLeftPanelTitle(VirtualFile file) {
|
||||
return "Local changes";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) {
|
||||
return "<html>Changes from cherry-pick <code>" + myCommitHash + "</code>";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,12 +17,14 @@ import com.intellij.ide.actions.ContextHelpAction;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.diff.impl.CaptionIcon;
|
||||
import com.intellij.openapi.diff.impl.patch.formove.FilePathComparator;
|
||||
import com.intellij.openapi.ide.CopyPasteManager;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.DumbAware;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -58,10 +60,14 @@ import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.vcsUtil.VcsUtil;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.branch.GitBranchOperationsProcessor;
|
||||
import git4idea.changes.GitChangeUtils;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.config.GitVcsSettings;
|
||||
import git4idea.history.browser.*;
|
||||
import git4idea.repo.GitRepository;
|
||||
import git4idea.repo.GitRepositoryManager;
|
||||
import git4idea.ui.branch.GitBranchUiUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -1608,17 +1614,14 @@ public class GitLogUI implements Disposable {
|
||||
myIdsInProgress.add(commit.getShortHash());
|
||||
}
|
||||
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
application.executeOnPooledThread(new Runnable() {
|
||||
public void run() {
|
||||
for (VirtualFile file : commits.keySet()) {
|
||||
final List<GitCommit> part = (List<GitCommit>)commits.get(file);
|
||||
// earliest first!!!
|
||||
Collections.reverse(part);
|
||||
new CherryPicker(GitVcs.getInstance(myProject), part, new LowLevelAccessImpl(myProject, file)).execute();
|
||||
}
|
||||
new Task.Backgroundable(myProject, "Cherry-picking", false) {
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
boolean autoCommit = GitVcsSettings.getInstance(myProject).isAutoCommitOnCherryPick();
|
||||
Map<GitRepository, List<GitCommit>> commitsInRoots = prepareCommitsForCherryPick(commits);
|
||||
new CherryPicker(myProject, ServiceManager.getService(Git.class), ServiceManager.getService(PlatformFacade.class), autoCommit)
|
||||
.cherryPick(commitsInRoots);
|
||||
|
||||
application.invokeLater(new Runnable() {
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
for (GitCommit commit : commits.values()) {
|
||||
myIdsInProgress.remove(commit.getShortHash());
|
||||
@@ -1626,7 +1629,23 @@ public class GitLogUI implements Disposable {
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}.queue();
|
||||
}
|
||||
|
||||
private Map<GitRepository, List<GitCommit>> prepareCommitsForCherryPick(MultiMap<VirtualFile, GitCommit> commits) {
|
||||
Map<GitRepository, List<GitCommit>> commitsInRoots = new HashMap<GitRepository, List<GitCommit>>();
|
||||
GitRepositoryManager repositoryManager = ServiceManager.getService(myProject, GitRepositoryManager.class);
|
||||
for (Map.Entry<VirtualFile, Collection<GitCommit>> entry : commits.entrySet()) {
|
||||
List<GitCommit> sortedCommits = new ArrayList<GitCommit>(entry.getValue());
|
||||
// earliest first!!!
|
||||
Collections.reverse(sortedCommits);
|
||||
GitRepository repository = repositoryManager.getRepositoryForRoot(entry.getKey());
|
||||
if (repository == null) {
|
||||
continue;
|
||||
}
|
||||
commitsInRoots.put(repository, sortedCommits);
|
||||
}
|
||||
return commitsInRoots;
|
||||
}
|
||||
|
||||
// newest first
|
||||
|
||||
@@ -21,18 +21,26 @@ import com.intellij.notification.NotificationType;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vcs.AbstractVcsHelper;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
|
||||
import com.intellij.openapi.vcs.merge.MergeProvider;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.commands.GitCommandResult;
|
||||
import git4idea.repo.GitRepository;
|
||||
import git4idea.repo.GitRepositoryManager;
|
||||
import git4idea.util.StringScanner;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -43,12 +51,14 @@ public class GitConflictResolver {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(GitConflictResolver.class);
|
||||
|
||||
protected final Project myProject;
|
||||
@NotNull protected final Project myProject;
|
||||
@NotNull private final Git myGit;
|
||||
@NotNull private final PlatformFacade myPlatformFacade;
|
||||
private final Collection<VirtualFile> myRoots;
|
||||
private final Params myParams;
|
||||
|
||||
@NotNull private final GitRepositoryManager myRepositoryManager;
|
||||
private final AbstractVcsHelper myVcsHelper;
|
||||
private final GitVcs myVcs;
|
||||
|
||||
/**
|
||||
* Customizing parameters - mostly String notification texts, etc.
|
||||
@@ -94,13 +104,15 @@ public class GitConflictResolver {
|
||||
|
||||
}
|
||||
|
||||
public GitConflictResolver(@NotNull Project project, @NotNull Collection<VirtualFile> roots, @NotNull Params params) {
|
||||
public GitConflictResolver(@NotNull Project project, @NotNull Git git, @NotNull PlatformFacade platformFacade,
|
||||
@NotNull Collection<VirtualFile> roots, @NotNull Params params) {
|
||||
myProject = project;
|
||||
myGit = git;
|
||||
myPlatformFacade = platformFacade;
|
||||
myRoots = roots;
|
||||
myParams = params;
|
||||
|
||||
myVcsHelper = AbstractVcsHelper.getInstance(project);
|
||||
myVcs = GitVcs.getInstance(project);
|
||||
myRepositoryManager = myPlatformFacade.getRepositoryManager(myProject);
|
||||
myVcsHelper = myPlatformFacade.getVcsHelper(project);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,8 +153,12 @@ public class GitConflictResolver {
|
||||
protected boolean proceedAfterAllMerged() throws VcsException {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected final boolean mergeNoProceed() {
|
||||
|
||||
/**
|
||||
* Invoke the merge dialog, but execute nothing after merge is completed.
|
||||
* @return true if all changes were merged, false if unresolved merges remain.
|
||||
*/
|
||||
public final boolean mergeNoProceed() {
|
||||
return merge(true);
|
||||
}
|
||||
|
||||
@@ -165,12 +181,12 @@ public class GitConflictResolver {
|
||||
}
|
||||
|
||||
private void notifyWarning(String title, String content) {
|
||||
GitVcs.IMPORTANT_ERROR_NOTIFICATION.createNotification(title, content, NotificationType.WARNING, new ResolveNotificationListener()).notify(myProject);
|
||||
myPlatformFacade.getNotificator(myProject).notifyStrongWarning(title, content, new ResolveNotificationListener());
|
||||
}
|
||||
|
||||
private boolean merge(boolean mergeDialogInvokedFromNotification) {
|
||||
try {
|
||||
final Collection<VirtualFile> initiallyUnmergedFiles = GitMergeUtil.getUnmergedFiles(myProject, myRoots);
|
||||
final Collection<VirtualFile> initiallyUnmergedFiles = getUnmergedFiles(myRoots);
|
||||
if (initiallyUnmergedFiles.isEmpty()) {
|
||||
LOG.info("merge: no unmerged files");
|
||||
return mergeDialogInvokedFromNotification ? true : proceedIfNothingToMerge();
|
||||
@@ -178,7 +194,7 @@ public class GitConflictResolver {
|
||||
else {
|
||||
showMergeDialog(initiallyUnmergedFiles);
|
||||
|
||||
final Collection<VirtualFile> unmergedFilesAfterResolve = GitMergeUtil.getUnmergedFiles(myProject, myRoots);
|
||||
final Collection<VirtualFile> unmergedFilesAfterResolve = getUnmergedFiles(myRoots);
|
||||
if (unmergedFilesAfterResolve.isEmpty()) {
|
||||
LOG.info("merge no more unmerged files");
|
||||
return mergeDialogInvokedFromNotification ? true : proceedAfterAllMerged();
|
||||
@@ -201,7 +217,8 @@ public class GitConflictResolver {
|
||||
private void showMergeDialog(final Collection<VirtualFile> initiallyUnmergedFiles) {
|
||||
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
|
||||
@Override public void run() {
|
||||
final MergeProvider mergeProvider = myParams.reverse ? myVcs.getReverseMergeProvider() : myVcs.getMergeProvider();
|
||||
final MergeProvider mergeProvider = myParams.reverse ?
|
||||
new GitMergeProvider(myProject, true) : new GitMergeProvider(myProject, false);
|
||||
myVcsHelper.showMergeDialog(new ArrayList<VirtualFile>(initiallyUnmergedFiles), mergeProvider, myParams.myMergeDialogCustomizer);
|
||||
}
|
||||
});
|
||||
@@ -238,4 +255,77 @@ public class GitConflictResolver {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return unmerged files in the given Git roots, all in a single collection.
|
||||
* @see #getUnmergedFiles(com.intellij.openapi.vfs.VirtualFile)
|
||||
*/
|
||||
private Collection<VirtualFile> getUnmergedFiles(@NotNull Collection<VirtualFile> roots) throws VcsException {
|
||||
final Collection<VirtualFile> unmergedFiles = new HashSet<VirtualFile>();
|
||||
for (VirtualFile root : roots) {
|
||||
unmergedFiles.addAll(getUnmergedFiles(root));
|
||||
}
|
||||
return unmergedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return unmerged files in the given Git root.
|
||||
* @see #getUnmergedFiles(java.util.Collection
|
||||
*/
|
||||
private Collection<VirtualFile> getUnmergedFiles(@NotNull VirtualFile root) throws VcsException {
|
||||
return unmergedFiles(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse changes from lines
|
||||
*
|
||||
*
|
||||
* @param root the git root
|
||||
* @return a set of unmerged files
|
||||
* @throws com.intellij.openapi.vcs.VcsException if the input format does not matches expected format
|
||||
*/
|
||||
private List<VirtualFile> unmergedFiles(VirtualFile root) throws VcsException {
|
||||
HashSet<VirtualFile> unmerged = new HashSet<VirtualFile>();
|
||||
String rootPath = root.getPath();
|
||||
|
||||
GitRepository repository = myRepositoryManager.getRepositoryForRoot(root);
|
||||
if (repository == null) {
|
||||
LOG.error("Repository not found for root " + root);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
GitCommandResult result = myGit.getUnmergedFiles(repository);
|
||||
if (!result.success()) {
|
||||
throw new VcsException(result.getErrorOutputAsJoinedString());
|
||||
}
|
||||
|
||||
String output = StringUtil.join(result.getOutput(), "\n");
|
||||
|
||||
LocalFileSystem lfs = myPlatformFacade.getLocalFileSystem();
|
||||
for (StringScanner s = new StringScanner(output); s.hasMoreData();) {
|
||||
if (s.isEol()) {
|
||||
s.nextLine();
|
||||
continue;
|
||||
}
|
||||
s.boundedToken('\t');
|
||||
final String relative = s.line();
|
||||
String path = rootPath + "/" + GitUtil.unescapePath(relative);
|
||||
VirtualFile file = lfs.refreshAndFindFileByPath(path);
|
||||
if (file != null) {
|
||||
// the file name is in the delete- or rename- conflict, so it is shown in the list of unmerged files,
|
||||
// but the file itself doesn't exist. In that case we just ignore the file.
|
||||
file.refresh(false, false);
|
||||
unmerged.add(file);
|
||||
}
|
||||
}
|
||||
if (unmerged.size() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
else {
|
||||
ArrayList<VirtualFile> rc = new ArrayList<VirtualFile>(unmerged.size());
|
||||
rc.addAll(unmerged);
|
||||
Collections.sort(rc, GitUtil.VIRTUAL_FILE_COMPARATOR);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
*/
|
||||
package git4idea.merge;
|
||||
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.commands.Git;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -31,9 +35,9 @@ public class GitMergeCommittingConflictResolver extends GitConflictResolver {
|
||||
private final boolean myRefreshAfterCommit;
|
||||
private final GitMerger myMerger;
|
||||
|
||||
public GitMergeCommittingConflictResolver(Project project, GitMerger merger, Collection<VirtualFile> mergingRoots, Params params,
|
||||
boolean refreshAfterCommit) {
|
||||
super(project, mergingRoots, params);
|
||||
public GitMergeCommittingConflictResolver(Project project, @NotNull Git git, GitMerger merger, Collection<VirtualFile> mergingRoots,
|
||||
Params params, boolean refreshAfterCommit) {
|
||||
super(project, git, ServiceManager.getService(PlatformFacade.class), mergingRoots, params);
|
||||
myMerger = merger;
|
||||
myMergingRoots = mergingRoots;
|
||||
myRefreshAfterCommit = refreshAfterCommit;
|
||||
|
||||
@@ -34,7 +34,6 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import git4idea.GitRevisionNumber;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.actions.GitRepositoryAction;
|
||||
import git4idea.changes.GitChangeUtils;
|
||||
import git4idea.i18n.GitBundle;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -44,7 +43,6 @@ import javax.swing.*;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -189,25 +187,4 @@ public class GitMergeUtil {
|
||||
public static boolean isMergeInProgress(@NotNull VirtualFile root) {
|
||||
return getMergeHead(root) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return unmerged files in the given Git roots, all in a single collection.
|
||||
* @see #getUnmergedFiles(com.intellij.openapi.project.Project, com.intellij.openapi.vfs.VirtualFile)
|
||||
*/
|
||||
public static Collection<VirtualFile> getUnmergedFiles(@NotNull Project project, @NotNull Collection<VirtualFile> roots) throws VcsException {
|
||||
final Collection<VirtualFile> unmergedFiles = new HashSet<VirtualFile>();
|
||||
for (VirtualFile root : roots) {
|
||||
unmergedFiles.addAll(getUnmergedFiles(project, root));
|
||||
}
|
||||
return unmergedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return unmerged files in the given Git root.
|
||||
* @see #getUnmergedFiles(com.intellij.openapi.project.Project, java.util.Collection)
|
||||
*/
|
||||
public static Collection<VirtualFile> getUnmergedFiles(@NotNull Project project, @NotNull VirtualFile root) throws VcsException {
|
||||
return GitChangeUtils.unmergedFiles(project, root);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package git4idea.rebase;
|
||||
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -24,6 +25,7 @@ import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.commands.*;
|
||||
import git4idea.merge.GitConflictResolver;
|
||||
import git4idea.util.GitUIUtil;
|
||||
@@ -47,10 +49,12 @@ public class GitRebaser {
|
||||
private GitVcs myVcs;
|
||||
private List<GitRebaseUtils.CommitInfo> mySkippedCommits;
|
||||
private static final Logger LOG = Logger.getInstance(GitRebaser.class);
|
||||
@NotNull private final Git myGit;
|
||||
private final @Nullable ProgressIndicator myProgressIndicator;
|
||||
|
||||
public GitRebaser(Project project, ProgressIndicator progressIndicator) {
|
||||
public GitRebaser(Project project, @NotNull Git git, ProgressIndicator progressIndicator) {
|
||||
myProject = project;
|
||||
myGit = git;
|
||||
myProgressIndicator = progressIndicator;
|
||||
myVcs = GitVcs.getInstance(project);
|
||||
mySkippedCommits = new ArrayList<GitRebaseUtils.CommitInfo>();
|
||||
@@ -172,7 +176,7 @@ public class GitRebaser {
|
||||
private boolean handleRebaseFailure(final VirtualFile root, final GitLineHandler h, GitRebaseProblemDetector rebaseConflictDetector) {
|
||||
if (rebaseConflictDetector.isMergeConflict()) {
|
||||
LOG.info("handleRebaseFailure merge conflict");
|
||||
return new GitConflictResolver(myProject, Collections.singleton(root), makeParamsForRebaseConflict()) {
|
||||
return new GitConflictResolver(myProject, myGit, ServiceManager.getService(PlatformFacade.class), Collections.singleton(root), makeParamsForRebaseConflict()) {
|
||||
@Override protected boolean proceedIfNothingToMerge() {
|
||||
return continueRebase(root, "--continue");
|
||||
}
|
||||
|
||||
@@ -15,23 +15,14 @@
|
||||
*/
|
||||
package git4idea.repo;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.concurrency.QueueProcessor;
|
||||
import com.intellij.util.messages.MessageBus;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import com.intellij.util.messages.Topic;
|
||||
import git4idea.GitBranch;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.branch.GitBranchesCollection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
@@ -67,30 +58,14 @@ import java.util.Collection;
|
||||
*
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
public final class GitRepository implements Disposable {
|
||||
public interface GitRepository {
|
||||
|
||||
public static final Topic<GitRepositoryChangeListener> GIT_REPO_CHANGE = Topic.create("GitRepository change", GitRepositoryChangeListener.class);
|
||||
|
||||
private static final Object STUB_OBJECT = new Object();
|
||||
|
||||
private final Project myProject;
|
||||
private final VirtualFile myRootDir;
|
||||
private final GitRepositoryReader myReader;
|
||||
private final VirtualFile myGitDir;
|
||||
private final MessageBus myMessageBus;
|
||||
private final GitUntrackedFilesHolder myUntrackedFilesHolder;
|
||||
private final QueueProcessor<Object> myNotifier;
|
||||
|
||||
private volatile State myState;
|
||||
private volatile String myCurrentRevision;
|
||||
private volatile GitBranch myCurrentBranch;
|
||||
private volatile GitBranchesCollection myBranches = GitBranchesCollection.EMPTY;
|
||||
private volatile GitConfig myConfig;
|
||||
Topic<GitRepositoryChangeListener> GIT_REPO_CHANGE = Topic.create("GitRepository change", GitRepositoryChangeListener.class);
|
||||
|
||||
/**
|
||||
* Current state of the repository.
|
||||
*/
|
||||
public enum State {
|
||||
enum State {
|
||||
/**
|
||||
* HEAD is on branch, no merge process is in progress (and no rebase as well).
|
||||
*/
|
||||
@@ -121,141 +96,40 @@ public final class GitRepository implements Disposable {
|
||||
* GitRepository tracks the updates of some information about Git repository, caches this information and provides methods to access to
|
||||
* it. The pieces of this information are called Topics. They can be used to update the repository.
|
||||
*/
|
||||
public enum TrackedTopic {
|
||||
STATE {
|
||||
@Override void update(GitRepository repository) {
|
||||
repository.updateState();
|
||||
}
|
||||
},
|
||||
CURRENT_REVISION {
|
||||
@Override void update(GitRepository repository) {
|
||||
repository.updateCurrentRevision();
|
||||
}
|
||||
},
|
||||
CURRENT_BRANCH {
|
||||
@Override void update(GitRepository repository) {
|
||||
repository.updateCurrentBranch();
|
||||
}
|
||||
},
|
||||
BRANCHES {
|
||||
@Override void update(GitRepository repository) {
|
||||
repository.updateBranchList();
|
||||
}
|
||||
},
|
||||
CONFIG {
|
||||
@Override void update(GitRepository repository) {
|
||||
repository.updateConfig();
|
||||
}
|
||||
},
|
||||
ALL_CURRENT {
|
||||
@Override void update(GitRepository repository) {
|
||||
STATE.update(repository);
|
||||
CURRENT_REVISION.update(repository);
|
||||
CURRENT_BRANCH.update(repository);
|
||||
}
|
||||
},
|
||||
ALL {
|
||||
@Override void update(GitRepository repository) {
|
||||
ALL_CURRENT.update(repository);
|
||||
BRANCHES.update(repository);
|
||||
CONFIG.update(repository);
|
||||
}
|
||||
};
|
||||
|
||||
abstract void update(GitRepository repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GitRepository instance from the {@link GitRepositoryManager}.
|
||||
* If you need to have an instance of GitRepository for a repository outside the project, use
|
||||
* {@link #getLightInstance(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.project.Project, com.intellij.openapi.Disposable)}.
|
||||
*/
|
||||
private GitRepository(@NotNull VirtualFile rootDir, @NotNull Project project, @NotNull Disposable parentDisposable) {
|
||||
myRootDir = rootDir;
|
||||
myProject = project;
|
||||
Disposer.register(parentDisposable, this);
|
||||
|
||||
myGitDir = GitUtil.findGitDir(myRootDir);
|
||||
assert myGitDir != null : ".git directory wasn't found under " + rootDir.getPresentableUrl();
|
||||
|
||||
myReader = new GitRepositoryReader(VfsUtil.virtualToIoFile(myGitDir));
|
||||
|
||||
myUntrackedFilesHolder = new GitUntrackedFilesHolder(this);
|
||||
Disposer.register(this, myUntrackedFilesHolder);
|
||||
|
||||
myMessageBus = project.getMessageBus();
|
||||
myNotifier = new QueueProcessor<Object>(new NotificationConsumer(myProject, myMessageBus), myProject.getDisposed());
|
||||
update(TrackedTopic.ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the temporary light instance of GitRepository.
|
||||
* It lacks functionality of auto-updating GitRepository on Git internal files change, and also stored a stub instance of
|
||||
* {@link GitUntrackedFilesHolder}.
|
||||
*/
|
||||
@NotNull
|
||||
public static GitRepository getLightInstance(@NotNull VirtualFile root, @NotNull Project project, @NotNull Disposable parentDisposable) {
|
||||
return new GitRepository(root, project, parentDisposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full-functional instance of GitRepository - with UntrackedFilesHolder and GitRepositoryUpdater.
|
||||
* This is used for repositories registered in project, and should be optained via {@link GitRepositoryManager}.
|
||||
*/
|
||||
public static GitRepository getFullInstance(@NotNull VirtualFile root, @NotNull Project project, @NotNull Disposable parentDisposable) {
|
||||
GitRepository repository = new GitRepository(root, project, parentDisposable);
|
||||
repository.myUntrackedFilesHolder.setupVfsListener(project);
|
||||
repository.setupUpdater();
|
||||
return repository;
|
||||
}
|
||||
|
||||
private void setupUpdater() {
|
||||
GitRepositoryUpdater updater = new GitRepositoryUpdater(this);
|
||||
Disposer.register(this, updater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
enum TrackedTopic {
|
||||
STATE,
|
||||
CURRENT_REVISION,
|
||||
CURRENT_BRANCH,
|
||||
BRANCHES,
|
||||
CONFIG,
|
||||
ALL_CURRENT,
|
||||
ALL
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public VirtualFile getRoot() {
|
||||
return myRootDir;
|
||||
}
|
||||
VirtualFile getRoot();
|
||||
|
||||
@NotNull
|
||||
public VirtualFile getGitDir() {
|
||||
return myGitDir;
|
||||
}
|
||||
VirtualFile getGitDir();
|
||||
|
||||
@NotNull
|
||||
public String getPresentableUrl() {
|
||||
return getRoot().getPresentableUrl();
|
||||
}
|
||||
String getPresentableUrl();
|
||||
|
||||
@NotNull
|
||||
public Project getProject() {
|
||||
return myProject;
|
||||
}
|
||||
Project getProject();
|
||||
|
||||
@NotNull
|
||||
public GitUntrackedFilesHolder getUntrackedFilesHolder() {
|
||||
return myUntrackedFilesHolder;
|
||||
}
|
||||
GitUntrackedFilesHolder getUntrackedFilesHolder();
|
||||
|
||||
@NotNull
|
||||
public State getState() {
|
||||
return myState;
|
||||
}
|
||||
State getState();
|
||||
|
||||
/**
|
||||
* Returns the hash of the revision, which HEAD currently points to.
|
||||
* Returns null only in the case of a fresh repository, when no commit have been made.
|
||||
*/
|
||||
@Nullable
|
||||
public String getCurrentRevision() {
|
||||
return myCurrentRevision;
|
||||
}
|
||||
String getCurrentRevision();
|
||||
|
||||
/**
|
||||
* Returns the current branch of this Git repository.
|
||||
@@ -264,22 +138,13 @@ public final class GitRepository implements Disposable {
|
||||
* Returns null, if the repository is not on a branch and not in the REBASING state.
|
||||
*/
|
||||
@Nullable
|
||||
public GitBranch getCurrentBranch() {
|
||||
return myCurrentBranch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return local and remote branches in this repository.
|
||||
*/
|
||||
@NotNull
|
||||
public GitBranchesCollection getBranches() {
|
||||
return new GitBranchesCollection(myBranches);
|
||||
}
|
||||
GitBranch getCurrentBranch();
|
||||
|
||||
@NotNull
|
||||
public GitConfig getConfig() {
|
||||
return myConfig;
|
||||
}
|
||||
GitBranchesCollection getBranches();
|
||||
|
||||
@NotNull
|
||||
GitConfig getConfig();
|
||||
|
||||
/**
|
||||
* Returns remotes defined in this Git repository.
|
||||
@@ -291,125 +156,26 @@ public final class GitRepository implements Disposable {
|
||||
* @return GitRemotes defined for this repository.
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<GitRemote> getRemotes() {
|
||||
return myConfig.getRemotes();
|
||||
}
|
||||
Collection<GitRemote> getRemotes();
|
||||
|
||||
public boolean isMergeInProgress() {
|
||||
return getState() == State.MERGING;
|
||||
}
|
||||
boolean isMergeInProgress();
|
||||
|
||||
public boolean isRebaseInProgress() {
|
||||
return getState() == State.REBASING;
|
||||
}
|
||||
boolean isRebaseInProgress();
|
||||
|
||||
public boolean isOnBranch() {
|
||||
return getState() != State.DETACHED && getState() != State.REBASING;
|
||||
}
|
||||
boolean isOnBranch();
|
||||
|
||||
/**
|
||||
* @return true if current repository is "fresh", i.e. if no commits have been made yet.
|
||||
*/
|
||||
public boolean isFresh() {
|
||||
return getCurrentRevision() == null;
|
||||
}
|
||||
boolean isFresh();
|
||||
|
||||
public void addListener(GitRepositoryChangeListener listener) {
|
||||
MessageBusConnection connection = myMessageBus.connect();
|
||||
Disposer.register(this, connection);
|
||||
connection.subscribe(GIT_REPO_CHANGE, listener);
|
||||
}
|
||||
void addListener(@NotNull GitRepositoryChangeListener listener);
|
||||
|
||||
/**
|
||||
* Synchronously updates the GitRepository by reading information from the specified topics.
|
||||
*/
|
||||
public void update(TrackedTopic... topics) {
|
||||
for (TrackedTopic topic : topics) {
|
||||
topic.update(this);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
private void updateConfig() {
|
||||
File configFile = new File(VfsUtil.virtualToIoFile(myGitDir), "config");
|
||||
myConfig = GitConfig.read(configFile);
|
||||
}
|
||||
void update(TrackedTopic... topics);
|
||||
|
||||
/**
|
||||
* Reads current state and notifies listeners about the change.
|
||||
*/
|
||||
private void updateState() {
|
||||
myState = myReader.readState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads current revision and notifies listeners about the change.
|
||||
*/
|
||||
private void updateCurrentRevision() {
|
||||
myCurrentRevision = myReader.readCurrentRevision();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads current branch and notifies listeners about the change.
|
||||
*/
|
||||
private void updateCurrentBranch() {
|
||||
myCurrentBranch = myReader.readCurrentBranch();
|
||||
}
|
||||
|
||||
private void updateBranchList() {
|
||||
myBranches = myReader.readBranches();
|
||||
}
|
||||
|
||||
private void notifyListeners() {
|
||||
myNotifier.add(STUB_OBJECT); // we don't have parameters for listeners
|
||||
}
|
||||
|
||||
private static class NotificationConsumer implements Consumer<Object> {
|
||||
|
||||
private final Project myProject;
|
||||
private final MessageBus myMessageBus;
|
||||
|
||||
NotificationConsumer(Project project, MessageBus messageBus) {
|
||||
myProject = project;
|
||||
myMessageBus = messageBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(Object o) {
|
||||
if (!Disposer.isDisposed(myProject)) {
|
||||
myMessageBus.syncPublisher(GIT_REPO_CHANGE).repositoryChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toLogString() {
|
||||
return String.format("GitRepository{myCurrentBranch=%s, myCurrentRevision='%s', myState=%s, myRootDir=%s}",
|
||||
myCurrentBranch, myCurrentRevision, myState, myRootDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getPresentableUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
GitRepository that = (GitRepository)o;
|
||||
|
||||
if (myProject != null ? !myProject.equals(that.myProject) : that.myProject != null) return false;
|
||||
if (myRootDir != null ? !myRootDir.equals(that.myRootDir) : that.myRootDir != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myProject != null ? myProject.hashCode() : 0;
|
||||
result = 31 * result + (myRootDir != null ? myRootDir.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
String toLogString();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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 git4idea.repo;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.concurrency.QueueProcessor;
|
||||
import com.intellij.util.messages.MessageBus;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import git4idea.GitBranch;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.branch.GitBranchesCollection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
public class GitRepositoryImpl implements GitRepository, Disposable {
|
||||
|
||||
private static final Object STUB_OBJECT = new Object();
|
||||
|
||||
private final Project myProject;
|
||||
private final VirtualFile myRootDir;
|
||||
private final GitRepositoryReader myReader;
|
||||
private final VirtualFile myGitDir;
|
||||
private final MessageBus myMessageBus;
|
||||
private final GitUntrackedFilesHolder myUntrackedFilesHolder;
|
||||
private final QueueProcessor<Object> myNotifier;
|
||||
|
||||
private volatile State myState;
|
||||
private volatile String myCurrentRevision;
|
||||
private volatile GitBranch myCurrentBranch;
|
||||
private volatile GitBranchesCollection myBranches = GitBranchesCollection.EMPTY;
|
||||
private volatile GitConfig myConfig;
|
||||
|
||||
/**
|
||||
* Get the GitRepository instance from the {@link GitRepositoryManager}.
|
||||
* If you need to have an instance of GitRepository for a repository outside the project, use
|
||||
* {@link #getLightInstance(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.project.Project, com.intellij.openapi.Disposable)}.
|
||||
*/
|
||||
private GitRepositoryImpl(@NotNull VirtualFile rootDir, @NotNull Project project, @NotNull Disposable parentDisposable) {
|
||||
myRootDir = rootDir;
|
||||
myProject = project;
|
||||
Disposer.register(parentDisposable, this);
|
||||
|
||||
myGitDir = GitUtil.findGitDir(myRootDir);
|
||||
assert myGitDir != null : ".git directory wasn't found under " + rootDir.getPresentableUrl();
|
||||
|
||||
myReader = new GitRepositoryReader(VfsUtil.virtualToIoFile(myGitDir));
|
||||
|
||||
myUntrackedFilesHolder = new GitUntrackedFilesHolder(this);
|
||||
Disposer.register(this, myUntrackedFilesHolder);
|
||||
|
||||
myMessageBus = project.getMessageBus();
|
||||
myNotifier = new QueueProcessor<Object>(new NotificationConsumer(myProject, myMessageBus), myProject.getDisposed());
|
||||
update(TrackedTopic.ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the temporary light instance of GitRepository.
|
||||
* It lacks functionality of auto-updating GitRepository on Git internal files change, and also stored a stub instance of
|
||||
* {@link GitUntrackedFilesHolder}.
|
||||
*/
|
||||
@NotNull
|
||||
public static GitRepository getLightInstance(@NotNull VirtualFile root, @NotNull Project project, @NotNull Disposable parentDisposable) {
|
||||
return new GitRepositoryImpl(root, project, parentDisposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full-functional instance of GitRepository - with UntrackedFilesHolder and GitRepositoryUpdater.
|
||||
* This is used for repositories registered in project, and should be optained via {@link GitRepositoryManager}.
|
||||
*/
|
||||
public static GitRepository getFullInstance(@NotNull VirtualFile root, @NotNull Project project, @NotNull Disposable parentDisposable) {
|
||||
GitRepositoryImpl repository = new GitRepositoryImpl(root, project, parentDisposable);
|
||||
repository.myUntrackedFilesHolder.setupVfsListener(project);
|
||||
repository.setupUpdater();
|
||||
return repository;
|
||||
}
|
||||
|
||||
private void setupUpdater() {
|
||||
GitRepositoryUpdater updater = new GitRepositoryUpdater(this);
|
||||
Disposer.register(this, updater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public VirtualFile getRoot() {
|
||||
return myRootDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public VirtualFile getGitDir() {
|
||||
return myGitDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getPresentableUrl() {
|
||||
return getRoot().getPresentableUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Project getProject() {
|
||||
return myProject;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public GitUntrackedFilesHolder getUntrackedFilesHolder() {
|
||||
return myUntrackedFilesHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public State getState() {
|
||||
return myState;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getCurrentRevision() {
|
||||
return myCurrentRevision;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public GitBranch getCurrentBranch() {
|
||||
return myCurrentBranch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return local and remote branches in this repository.
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public GitBranchesCollection getBranches() {
|
||||
return new GitBranchesCollection(myBranches);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public GitConfig getConfig() {
|
||||
return myConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Collection<GitRemote> getRemotes() {
|
||||
return myConfig.getRemotes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMergeInProgress() {
|
||||
return getState() == State.MERGING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRebaseInProgress() {
|
||||
return getState() == State.REBASING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnBranch() {
|
||||
return getState() != State.DETACHED && getState() != State.REBASING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFresh() {
|
||||
return getCurrentRevision() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(@NotNull GitRepositoryChangeListener listener) {
|
||||
MessageBusConnection connection = myMessageBus.connect();
|
||||
Disposer.register(this, connection);
|
||||
connection.subscribe(GIT_REPO_CHANGE, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(TrackedTopic... topics) {
|
||||
for (TrackedTopic topic : topics) {
|
||||
switch (topic) {
|
||||
case STATE: updateState(); break;
|
||||
case CURRENT_REVISION: updateCurrentRevision(); break;
|
||||
case CURRENT_BRANCH: updateCurrentBranch(); break;
|
||||
case BRANCHES: updateBranchList(); break;
|
||||
case CONFIG: updateConfig(); break;
|
||||
case ALL_CURRENT:
|
||||
updateState();
|
||||
updateCurrentRevision();
|
||||
updateCurrentBranch();
|
||||
break;
|
||||
case ALL:
|
||||
updateState();
|
||||
updateCurrentRevision();
|
||||
updateCurrentBranch();
|
||||
updateBranchList();
|
||||
updateConfig();
|
||||
break;
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
private void updateConfig() {
|
||||
File configFile = new File(VfsUtil.virtualToIoFile(myGitDir), "config");
|
||||
myConfig = GitConfig.read(configFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads current state and notifies listeners about the change.
|
||||
*/
|
||||
private void updateState() {
|
||||
myState = myReader.readState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads current revision and notifies listeners about the change.
|
||||
*/
|
||||
private void updateCurrentRevision() {
|
||||
myCurrentRevision = myReader.readCurrentRevision();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads current branch and notifies listeners about the change.
|
||||
*/
|
||||
private void updateCurrentBranch() {
|
||||
myCurrentBranch = myReader.readCurrentBranch();
|
||||
}
|
||||
|
||||
private void updateBranchList() {
|
||||
myBranches = myReader.readBranches();
|
||||
}
|
||||
|
||||
private void notifyListeners() {
|
||||
myNotifier.add(STUB_OBJECT); // we don't have parameters for listeners
|
||||
}
|
||||
|
||||
private static class NotificationConsumer implements Consumer<Object> {
|
||||
|
||||
private final Project myProject;
|
||||
private final MessageBus myMessageBus;
|
||||
|
||||
NotificationConsumer(Project project, MessageBus messageBus) {
|
||||
myProject = project;
|
||||
myMessageBus = messageBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(Object o) {
|
||||
if (!Disposer.isDisposed(myProject)) {
|
||||
myMessageBus.syncPublisher(GIT_REPO_CHANGE).repositoryChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toLogString() {
|
||||
return String.format("GitRepository{myCurrentBranch=%s, myCurrentRevision='%s', myState=%s, myRootDir=%s}",
|
||||
myCurrentBranch, myCurrentRevision, myState, myRootDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getPresentableUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
GitRepositoryImpl that = (GitRepositoryImpl)o;
|
||||
|
||||
if (myProject != null ? !myProject.equals(that.myProject) : that.myProject != null) return false;
|
||||
if (myRootDir != null ? !myRootDir.equals(that.myRootDir) : that.myRootDir != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myProject != null ? myProject.hashCode() : 0;
|
||||
result = 31 * result + (myRootDir != null ? myRootDir.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ public class GitRepositoryManagerImpl extends AbstractProjectComponent implement
|
||||
}
|
||||
|
||||
private GitRepository createGitRepository(VirtualFile root) {
|
||||
GitRepository repository = GitRepository.getFullInstance(root, myProject, this);
|
||||
GitRepository repository = GitRepositoryImpl.getFullInstance(root, myProject, this);
|
||||
for (GitRepositoryChangeListener listener : myListeners) {
|
||||
repository.addListener(listener);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class GitIntegrationEnabler {
|
||||
GitCommandResult result = myGit.init(myProject, projectDir);
|
||||
if (result.success()) {
|
||||
refreshGitDir(projectDir);
|
||||
notificator.notifySuccess("", "Created Git repository in \n" + projectDir.getPresentableUrl());
|
||||
notificator.notifySuccess("", "Created Git repository in " + projectDir.getPresentableUrl());
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.intellij.openapi.vcs.changes.ChangeListManagerEx;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.continuation.ContinuationContext;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.config.GitVcsSettings;
|
||||
import git4idea.merge.GitConflictResolver;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -46,6 +47,7 @@ public abstract class GitChangesSaver {
|
||||
|
||||
protected final Project myProject;
|
||||
protected final ChangeListManagerEx myChangeManager;
|
||||
@NotNull protected final Git myGit;
|
||||
protected final ProgressIndicator myProgressIndicator;
|
||||
protected final String myStashMessage;
|
||||
|
||||
@@ -60,25 +62,27 @@ public abstract class GitChangesSaver {
|
||||
* Returns an instance of the proper GitChangesSaver depending on the chosen save changes policy.
|
||||
* @return {@link GitStashChangesSaver}, {@link GitShelveChangesSaver} or {@link GitDumbChangesSaver}
|
||||
*/
|
||||
public static GitChangesSaver getSaver(Project project, ProgressIndicator progressIndicator, String stashMessage) {
|
||||
public static GitChangesSaver getSaver(Project project, @NotNull Git git, ProgressIndicator progressIndicator, String stashMessage) {
|
||||
final GitVcsSettings settings = GitVcsSettings.getInstance(project);
|
||||
if (settings == null) {
|
||||
return getDefaultSaver(project, progressIndicator, stashMessage);
|
||||
return getDefaultSaver(project, git, progressIndicator, stashMessage);
|
||||
}
|
||||
switch (settings.updateChangesPolicy()) {
|
||||
case STASH: return new GitStashChangesSaver(project, progressIndicator, stashMessage);
|
||||
case SHELVE: return new GitShelveChangesSaver(project, progressIndicator, stashMessage);
|
||||
case STASH: return new GitStashChangesSaver(project, git, progressIndicator, stashMessage);
|
||||
case SHELVE: return new GitShelveChangesSaver(project, git, progressIndicator, stashMessage);
|
||||
}
|
||||
return getDefaultSaver(project, progressIndicator, stashMessage);
|
||||
return getDefaultSaver(project, git, progressIndicator, stashMessage);
|
||||
}
|
||||
|
||||
// In the case of illegal value in the settings or impossibility to get the settings.
|
||||
private static GitChangesSaver getDefaultSaver(Project project, ProgressIndicator progressIndicator, String stashMessage) {
|
||||
return new GitStashChangesSaver(project, progressIndicator, stashMessage);
|
||||
private static GitChangesSaver getDefaultSaver(Project project, @NotNull Git git, ProgressIndicator progressIndicator,
|
||||
String stashMessage) {
|
||||
return new GitStashChangesSaver(project, git, progressIndicator, stashMessage);
|
||||
}
|
||||
|
||||
protected GitChangesSaver(Project project, ProgressIndicator indicator, String stashMessage) {
|
||||
protected GitChangesSaver(Project project, @NotNull Git git, ProgressIndicator indicator, String stashMessage) {
|
||||
myProject = project;
|
||||
myGit = git;
|
||||
myProgressIndicator = indicator;
|
||||
myStashMessage = stashMessage;
|
||||
myChangeManager = (ChangeListManagerEx)ChangeListManagerEx.getInstance(myProject);
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.intellij.openapi.vcs.changes.shelf.ShelvedChangesViewManager;
|
||||
import com.intellij.openapi.vcs.impl.LocalChangesUnderRoots;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.continuation.ContinuationContext;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.i18n.GitBundle;
|
||||
import git4idea.rollback.GitRollbackEnvironment;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -41,8 +42,8 @@ public class GitShelveChangesSaver extends GitChangesSaver {
|
||||
private final ShelvedChangesViewManager myShelveViewManager;
|
||||
private ShelvedChangeList myShelvedChangeList;
|
||||
|
||||
public GitShelveChangesSaver(Project project, ProgressIndicator indicator, String stashMessage) {
|
||||
super(project, indicator, stashMessage);
|
||||
public GitShelveChangesSaver(Project project, Git git, ProgressIndicator indicator, String stashMessage) {
|
||||
super(project, git, indicator, stashMessage);
|
||||
myShelveManager = ShelveChangesManager.getInstance(myProject);
|
||||
myShelveViewManager = ShelvedChangesViewManager.getInstance(myProject);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package git4idea.stash;
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationListener;
|
||||
import com.intellij.notification.NotificationType;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -31,6 +32,7 @@ import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.continuation.ContinuationContext;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.commands.*;
|
||||
import git4idea.config.GitVcsSettings;
|
||||
import git4idea.convert.GitFileSeparatorConverter;
|
||||
@@ -54,8 +56,8 @@ public class GitStashChangesSaver extends GitChangesSaver {
|
||||
private static final Logger LOG = Logger.getInstance(GitStashChangesSaver.class);
|
||||
private final Set<VirtualFile> myStashedRoots = new HashSet<VirtualFile>(); // save stashed roots to unstash only them
|
||||
|
||||
public GitStashChangesSaver(Project project, ProgressIndicator progressIndicator, String stashMessage) {
|
||||
super(project, progressIndicator, stashMessage);
|
||||
public GitStashChangesSaver(Project project, @NotNull Git git, ProgressIndicator progressIndicator, String stashMessage) {
|
||||
super(project, git, progressIndicator, stashMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -85,7 +87,7 @@ public class GitStashChangesSaver extends GitChangesSaver {
|
||||
}
|
||||
}
|
||||
|
||||
boolean conflictsResolved = new UnstashConflictResolver(myProject, myStashedRoots, myParams).merge();
|
||||
boolean conflictsResolved = new UnstashConflictResolver(myProject, myGit, myStashedRoots, myParams).merge();
|
||||
if (conflictsResolved) {
|
||||
LOG.info("load: all conflicts resolved, dropping stash in " + myStashedRoots);
|
||||
for (VirtualFile root : conflictedRoots) {
|
||||
@@ -194,8 +196,8 @@ public class GitStashChangesSaver extends GitChangesSaver {
|
||||
|
||||
private final Set<VirtualFile> myStashedRoots;
|
||||
|
||||
public UnstashConflictResolver(@NotNull Project project, @NotNull Set<VirtualFile> stashedRoots, @Nullable Params params) {
|
||||
super(project, stashedRoots, makeParamsOrUse(params));
|
||||
public UnstashConflictResolver(@NotNull Project project, @NotNull Git git, @NotNull Set<VirtualFile> stashedRoots, @Nullable Params params) {
|
||||
super(project, git, ServiceManager.getService(PlatformFacade.class), stashedRoots, makeParamsOrUse(params));
|
||||
myStashedRoots = stashedRoots;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package git4idea.ui;
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationListener;
|
||||
import com.intellij.notification.NotificationType;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
@@ -35,6 +36,7 @@ import com.intellij.util.Consumer;
|
||||
import git4idea.GitBranch;
|
||||
import git4idea.GitRevisionNumber;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.actions.GitShowAllSubmittedFilesAction;
|
||||
import git4idea.commands.*;
|
||||
import git4idea.config.GitVersionSpecialty;
|
||||
@@ -449,7 +451,8 @@ public class GitUnstashDialog extends DialogWrapper {
|
||||
private final StashInfo myStashInfo;
|
||||
|
||||
public UnstashConflictResolver(Project project, VirtualFile root, StashInfo stashInfo) {
|
||||
super(project, Collections.singleton(root), makeParams(stashInfo));
|
||||
super(project, ServiceManager.getService(Git.class), ServiceManager.getService(PlatformFacade.class),
|
||||
Collections.singleton(root), makeParams(stashInfo));
|
||||
myRoot = root;
|
||||
myStashInfo = stashInfo;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package git4idea.update;
|
||||
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -29,11 +30,11 @@ import com.intellij.openapi.vcs.update.UpdatedFiles;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.branch.GitBranchPair;
|
||||
import git4idea.commands.*;
|
||||
import git4idea.merge.GitConflictResolver;
|
||||
import git4idea.merge.GitMerger;
|
||||
import git4idea.commands.GitMessageWithFilesDetector;
|
||||
import git4idea.util.GitUIUtil;
|
||||
import git4idea.util.UntrackedFilesNotifier;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -51,12 +52,12 @@ public class GitMergeUpdater extends GitUpdater {
|
||||
|
||||
private final ChangeListManager myChangeListManager;
|
||||
|
||||
public GitMergeUpdater(Project project,
|
||||
public GitMergeUpdater(Project project, @NotNull Git git,
|
||||
VirtualFile root,
|
||||
final Map<VirtualFile, GitBranchPair> trackedBranches,
|
||||
ProgressIndicator progressIndicator,
|
||||
UpdatedFiles updatedFiles) {
|
||||
super(project, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
super(project, git, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
myChangeListManager = ChangeListManager.getInstance(myProject);
|
||||
}
|
||||
|
||||
@@ -111,7 +112,7 @@ public class GitMergeUpdater extends GitUpdater {
|
||||
if (error == MergeError.CONFLICT) {
|
||||
LOG.info("Conflict detected");
|
||||
final boolean allMerged =
|
||||
new MyConflictResolver(myProject, merger, myRoot).merge();
|
||||
new MyConflictResolver(myProject, myGit, merger, myRoot).merge();
|
||||
return allMerged ? GitUpdateResult.SUCCESS : GitUpdateResult.INCOMPLETE;
|
||||
}
|
||||
else if (error == MergeError.LOCAL_CHANGES) {
|
||||
@@ -134,7 +135,8 @@ public class GitMergeUpdater extends GitUpdater {
|
||||
}
|
||||
else if (untrackedFilesWouldBeOverwrittenByMergeDetector.wasMessageDetected()) {
|
||||
LOG.info("handleMergeFailure: untracked files would be overwritten by merge");
|
||||
UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedFilesWouldBeOverwrittenByMergeDetector.getFiles(), "merge");
|
||||
UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, ServiceManager.getService(myProject, PlatformFacade.class),
|
||||
untrackedFilesWouldBeOverwrittenByMergeDetector.getFiles(), "merge", null);
|
||||
return GitUpdateResult.ERROR;
|
||||
}
|
||||
else {
|
||||
@@ -278,8 +280,8 @@ public class GitMergeUpdater extends GitUpdater {
|
||||
private final GitMerger myMerger;
|
||||
private final VirtualFile myRoot;
|
||||
|
||||
public MyConflictResolver(Project project, GitMerger merger, VirtualFile root) {
|
||||
super(project, Collections.singleton(root), makeParams());
|
||||
public MyConflictResolver(Project project, @NotNull Git git, GitMerger merger, VirtualFile root) {
|
||||
super(project, git, ServiceManager.getService(git4idea.PlatformFacade.class), Collections.singleton(root), makeParams());
|
||||
myMerger = merger;
|
||||
myRoot = root;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package git4idea.update;
|
||||
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -23,14 +24,15 @@ import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.update.UpdatedFiles;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.branch.GitBranchPair;
|
||||
import git4idea.commands.*;
|
||||
import git4idea.merge.GitConflictResolver;
|
||||
import git4idea.commands.GitMessageWithFilesDetector;
|
||||
import git4idea.rebase.GitRebaseProblemDetector;
|
||||
import git4idea.rebase.GitRebaser;
|
||||
import git4idea.util.GitUIUtil;
|
||||
import git4idea.util.UntrackedFilesNotifier;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
@@ -44,13 +46,12 @@ public class GitRebaseUpdater extends GitUpdater {
|
||||
private static final Logger LOG = Logger.getInstance(GitRebaseUpdater.class.getName());
|
||||
private final GitRebaser myRebaser;
|
||||
|
||||
public GitRebaseUpdater(Project project,
|
||||
VirtualFile root,
|
||||
final Map<VirtualFile, GitBranchPair> trackedBranches,
|
||||
public GitRebaseUpdater(@NotNull Project project, @NotNull Git git, @NotNull VirtualFile root,
|
||||
@NotNull final Map<VirtualFile, GitBranchPair> trackedBranches,
|
||||
ProgressIndicator progressIndicator,
|
||||
UpdatedFiles updatedFiles) {
|
||||
super(project, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
myRebaser = new GitRebaser(myProject, myProgressIndicator);
|
||||
super(project, git, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
myRebaser = new GitRebaser(myProject, git, myProgressIndicator);
|
||||
}
|
||||
|
||||
@Override public boolean isSaveNeeded() {
|
||||
@@ -103,11 +104,12 @@ public class GitRebaseUpdater extends GitUpdater {
|
||||
final GitMessageWithFilesDetector untrackedWouldBeOverwrittenDetector) {
|
||||
if (rebaseConflictDetector.isMergeConflict()) {
|
||||
LOG.info("handleRebaseFailure merge conflict");
|
||||
final boolean allMerged = new MyConflictResolver(myProject, myRoot, myRebaser).merge();
|
||||
final boolean allMerged = new MyConflictResolver(myProject, myGit, myRoot, myRebaser).merge();
|
||||
return allMerged ? GitUpdateResult.SUCCESS : GitUpdateResult.INCOMPLETE;
|
||||
} else if (untrackedWouldBeOverwrittenDetector.wasMessageDetected()) {
|
||||
LOG.info("handleRebaseFailure: untracked files would be overwritten by checkout");
|
||||
UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedWouldBeOverwrittenDetector.getFiles(), "rebase");
|
||||
UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, ServiceManager.getService(myProject, PlatformFacade.class),
|
||||
untrackedWouldBeOverwrittenDetector.getFiles(), "rebase", null);
|
||||
return GitUpdateResult.ERROR;
|
||||
} else {
|
||||
LOG.info("handleRebaseFailure error " + pullHandler.errors());
|
||||
@@ -156,8 +158,8 @@ public class GitRebaseUpdater extends GitUpdater {
|
||||
private final GitRebaser myRebaser;
|
||||
private final VirtualFile myRoot;
|
||||
|
||||
public MyConflictResolver(Project project, VirtualFile root, GitRebaser rebaser) {
|
||||
super(project, Collections.singleton(root), makeParams());
|
||||
public MyConflictResolver(Project project, @NotNull Git git, VirtualFile root, GitRebaser rebaser) {
|
||||
super(project, git, ServiceManager.getService(PlatformFacade.class), Collections.singleton(root), makeParams());
|
||||
myRebaser = rebaser;
|
||||
myRoot = root;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package git4idea.update;
|
||||
|
||||
import com.intellij.notification.NotificationType;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -30,6 +31,7 @@ import com.intellij.util.text.DateFormatUtil;
|
||||
import git4idea.GitBranch;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.branch.GitBranchPair;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.merge.GitConflictResolver;
|
||||
import git4idea.merge.GitMergeCommittingConflictResolver;
|
||||
import git4idea.merge.GitMerger;
|
||||
@@ -52,6 +54,7 @@ public class GitUpdateProcess {
|
||||
|
||||
private final Project myProject;
|
||||
private final GitVcs myVcs;
|
||||
@NotNull private final Git myGit;
|
||||
private final Set<VirtualFile> myRoots;
|
||||
private final UpdatedFiles myUpdatedFiles;
|
||||
private final ProgressIndicator myProgressIndicator;
|
||||
@@ -62,7 +65,7 @@ public class GitUpdateProcess {
|
||||
private boolean myResult;
|
||||
private final Map<VirtualFile, GitUpdater> myUpdaters;
|
||||
private final Collection<VirtualFile> myRootsToSave;
|
||||
|
||||
|
||||
public enum UpdateMethod {
|
||||
MERGE,
|
||||
REBASE,
|
||||
@@ -75,10 +78,11 @@ public class GitUpdateProcess {
|
||||
myProject = project;
|
||||
myRoots = roots;
|
||||
myVcs = GitVcs.getInstance(project);
|
||||
myGit = ServiceManager.getService(Git.class);
|
||||
myUpdatedFiles = updatedFiles;
|
||||
myProgressIndicator = progressIndicator;
|
||||
myMerger = new GitMerger(myProject);
|
||||
mySaver = GitChangesSaver.getSaver(myProject, myProgressIndicator,
|
||||
mySaver = GitChangesSaver.getSaver(myProject, myGit, myProgressIndicator,
|
||||
"Uncommitted changes before update operation at " + DateFormatUtil.formatDateTime(Clock.getTime()));
|
||||
myUpdaters = new HashMap<VirtualFile, GitUpdater>();
|
||||
myRootsToSave = new HashSet<VirtualFile>(1);
|
||||
@@ -134,11 +138,11 @@ public class GitUpdateProcess {
|
||||
for (VirtualFile root : myRoots) {
|
||||
final GitUpdater updater;
|
||||
if (updateMethod == UpdateMethod.MERGE) {
|
||||
updater = new GitMergeUpdater(myProject, root, myTrackedBranches, myProgressIndicator, myUpdatedFiles);
|
||||
updater = new GitMergeUpdater(myProject, myGit, root, myTrackedBranches, myProgressIndicator, myUpdatedFiles);
|
||||
} else if (updateMethod == UpdateMethod.REBASE) {
|
||||
updater = new GitRebaseUpdater(myProject, root, myTrackedBranches, myProgressIndicator, myUpdatedFiles);
|
||||
updater = new GitRebaseUpdater(myProject, myGit, root, myTrackedBranches, myProgressIndicator, myUpdatedFiles);
|
||||
} else {
|
||||
updater = GitUpdater.getUpdater(myProject, myTrackedBranches, root, myProgressIndicator, myUpdatedFiles);
|
||||
updater = GitUpdater.getUpdater(myProject, myGit, myTrackedBranches, root, myProgressIndicator, myUpdatedFiles);
|
||||
}
|
||||
|
||||
if (updater.isUpdateNeeded()) {
|
||||
@@ -301,7 +305,7 @@ public class GitUpdateProcess {
|
||||
GitConflictResolver.Params params = new GitConflictResolver.Params();
|
||||
params.setErrorNotificationTitle("Can't update");
|
||||
params.setMergeDescription("You have unfinished merge. These conflicts must be resolved before update.");
|
||||
return !new GitMergeCommittingConflictResolver(myProject, myMerger, mergingRoots, params, false).merge();
|
||||
return !new GitMergeCommittingConflictResolver(myProject, myGit, myMerger, mergingRoots, params, false).merge();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,7 +316,7 @@ public class GitUpdateProcess {
|
||||
GitConflictResolver.Params params = new GitConflictResolver.Params();
|
||||
params.setErrorNotificationTitle("Can't update");
|
||||
params.setMergeDescription("Unmerged files detected. These conflicts must be resolved before update.");
|
||||
return !new GitMergeCommittingConflictResolver(myProject, myMerger, myRoots, params, false).merge();
|
||||
return !new GitMergeCommittingConflictResolver(myProject, myGit, myMerger, myRoots, params, false).merge();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,7 +324,7 @@ public class GitUpdateProcess {
|
||||
* @return true if rebase is in progress, which means that update can't continue.
|
||||
*/
|
||||
private boolean checkRebaseInProgress() {
|
||||
final GitRebaser rebaser = new GitRebaser(myProject, myProgressIndicator);
|
||||
final GitRebaser rebaser = new GitRebaser(myProject, myGit, myProgressIndicator);
|
||||
final Collection<VirtualFile> rebasingRoots = rebaser.getRebasingRoots();
|
||||
if (rebasingRoots.isEmpty()) {
|
||||
return false;
|
||||
@@ -332,7 +336,7 @@ public class GitUpdateProcess {
|
||||
params.setMergeDescription("You have unfinished rebase process. These conflicts must be resolved before update.");
|
||||
params.setErrorNotificationAdditionalDescription("Then you may <b>continue rebase</b>. <br/> You also may <b>abort rebase</b> to restore the original branch and stop rebasing.");
|
||||
params.setReverse(true);
|
||||
return !new GitConflictResolver(myProject, rebasingRoots, params) {
|
||||
return !new GitConflictResolver(myProject, myGit, ServiceManager.getService(git4idea.PlatformFacade.class), rebasingRoots, params) {
|
||||
@Override protected boolean proceedIfNothingToMerge() {
|
||||
return rebaser.continueRebase(rebasingRoots);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import git4idea.GitBranch;
|
||||
import git4idea.GitRevisionNumber;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.branch.GitBranchPair;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.commands.GitCommand;
|
||||
import git4idea.commands.GitSimpleHandler;
|
||||
import git4idea.config.GitConfigUtil;
|
||||
@@ -44,7 +45,8 @@ import java.util.Map;
|
||||
public abstract class GitUpdater {
|
||||
private static final Logger LOG = Logger.getInstance(GitUpdater.class);
|
||||
|
||||
protected final @NotNull Project myProject;
|
||||
@NotNull protected final Project myProject;
|
||||
@NotNull protected final Git myGit;
|
||||
protected final @NotNull VirtualFile myRoot;
|
||||
protected final @NotNull Map<VirtualFile, GitBranchPair> myTrackedBranches;
|
||||
protected final @NotNull ProgressIndicator myProgressIndicator;
|
||||
@@ -54,9 +56,11 @@ public abstract class GitUpdater {
|
||||
|
||||
protected GitRevisionNumber myBefore; // The revision that was before update
|
||||
|
||||
protected GitUpdater(@NotNull Project project, @NotNull VirtualFile root, @NotNull Map<VirtualFile, GitBranchPair> trackedBranches,
|
||||
@NotNull ProgressIndicator progressIndicator, @NotNull UpdatedFiles updatedFiles) {
|
||||
protected GitUpdater(@NotNull Project project, @NotNull Git git, @NotNull VirtualFile root,
|
||||
@NotNull Map<VirtualFile, GitBranchPair> trackedBranches, @NotNull ProgressIndicator progressIndicator,
|
||||
@NotNull UpdatedFiles updatedFiles) {
|
||||
myProject = project;
|
||||
myGit = git;
|
||||
myRoot = root;
|
||||
myTrackedBranches = trackedBranches;
|
||||
myProgressIndicator = progressIndicator;
|
||||
@@ -70,39 +74,39 @@ public abstract class GitUpdater {
|
||||
* @return {@link GitMergeUpdater} or {@link GitRebaseUpdater}.
|
||||
*/
|
||||
@NotNull
|
||||
public static GitUpdater getUpdater(@NotNull Project project, @NotNull Map<VirtualFile, GitBranchPair> trackedBranches,
|
||||
public static GitUpdater getUpdater(@NotNull Project project, @NotNull Git git, @NotNull Map<VirtualFile, GitBranchPair> trackedBranches,
|
||||
@NotNull VirtualFile root, @NotNull ProgressIndicator progressIndicator,
|
||||
@NotNull UpdatedFiles updatedFiles) {
|
||||
final GitVcsSettings settings = GitVcsSettings.getInstance(project);
|
||||
if (settings == null) {
|
||||
return getDefaultUpdaterForBranch(project, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
return getDefaultUpdaterForBranch(project, git, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
}
|
||||
switch (settings.getUpdateType()) {
|
||||
case REBASE:
|
||||
return new GitRebaseUpdater(project, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
return new GitRebaseUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
case MERGE:
|
||||
return new GitMergeUpdater(project, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
return new GitMergeUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
case BRANCH_DEFAULT:
|
||||
// use default for the branch
|
||||
return getDefaultUpdaterForBranch(project, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
return getDefaultUpdaterForBranch(project, git, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
}
|
||||
return getDefaultUpdaterForBranch(project, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
return getDefaultUpdaterForBranch(project, git, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static GitUpdater getDefaultUpdaterForBranch(@NotNull Project project, @NotNull VirtualFile root,
|
||||
private static GitUpdater getDefaultUpdaterForBranch(@NotNull Project project, @NotNull Git git, @NotNull VirtualFile root,
|
||||
@NotNull Map<VirtualFile, GitBranchPair> trackedBranches,
|
||||
@NotNull ProgressIndicator progressIndicator, @NotNull UpdatedFiles updatedFiles) {
|
||||
try {
|
||||
final GitBranch branchName = GitBranch.current(project, root);
|
||||
final String rebase = GitConfigUtil.getValue(project, root, "branch." + branchName + ".rebase");
|
||||
if (rebase != null && rebase.equalsIgnoreCase("true")) {
|
||||
return new GitRebaseUpdater(project, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
return new GitRebaseUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
}
|
||||
} catch (VcsException e) {
|
||||
LOG.info("getDefaultUpdaterForBranch branch", e);
|
||||
}
|
||||
return new GitMergeUpdater(project, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
return new GitMergeUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.text.DateFormatUtil;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.Notificator;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.merge.GitConflictResolver;
|
||||
import git4idea.repo.GitRepository;
|
||||
import git4idea.stash.GitStashChangesSaver;
|
||||
@@ -49,6 +50,7 @@ public class GitPreservingProcess {
|
||||
private static final Logger LOG = Logger.getInstance(GitPreservingProcess.class);
|
||||
|
||||
@NotNull private final Project myProject;
|
||||
@NotNull private final Git myGit;
|
||||
@NotNull private final Collection<GitRepository> myRepositories;
|
||||
@NotNull private final String myOperationTitle;
|
||||
@NotNull private final String myDestinationName;
|
||||
@@ -61,10 +63,11 @@ public class GitPreservingProcess {
|
||||
private boolean myLoaded;
|
||||
private final Object LOAD_LOCK = new Object();
|
||||
|
||||
public GitPreservingProcess(@NotNull Project project, @NotNull Collection<GitRepository> repositories,
|
||||
public GitPreservingProcess(@NotNull Project project, @NotNull Git git, @NotNull Collection<GitRepository> repositories,
|
||||
@NotNull String operationTitle, @NotNull String destinationName,
|
||||
@NotNull ProgressIndicator indicator, @NotNull Runnable operation) {
|
||||
myProject = project;
|
||||
myGit = git;
|
||||
myRepositories = repositories;
|
||||
myOperationTitle = operationTitle;
|
||||
myDestinationName = destinationName;
|
||||
@@ -110,7 +113,7 @@ public class GitPreservingProcess {
|
||||
* Configures the saver, actually notifications and texts in the GitConflictResolver used inside.
|
||||
*/
|
||||
private GitStashChangesSaver configureSaver() {
|
||||
GitStashChangesSaver saver = new GitStashChangesSaver(myProject, myProgressIndicator, myStashMessage);
|
||||
GitStashChangesSaver saver = new GitStashChangesSaver(myProject, myGit, myProgressIndicator, myStashMessage);
|
||||
MergeDialogCustomizer mergeDialogCustomizer = new MergeDialogCustomizer() {
|
||||
@Override
|
||||
public String getMultipleFileMergeDescription(Collection<VirtualFile> files) {
|
||||
|
||||
@@ -17,14 +17,13 @@ package git4idea.util;
|
||||
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationListener;
|
||||
import com.intellij.notification.NotificationType;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vcs.changes.ui.SelectFilesDialog;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.Notificator;
|
||||
import git4idea.PlatformFacade;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
@@ -42,23 +41,31 @@ public class UntrackedFilesNotifier {
|
||||
/**
|
||||
* Displays notification about {@code untracked files would be overwritten by checkout} error.
|
||||
* Clicking on the link in the notification opens a simple dialog with the list of these files.
|
||||
* @param operation the name of the Git operation that caused the error: {@code rebase, merge, checkout}.
|
||||
* @param operation the name of the Git operation that caused the error: {@code rebase, merge, checkout}.
|
||||
* @param description the content of the notification or null if the deafult content is to be used.
|
||||
*/
|
||||
public static void notifyUntrackedFilesOverwrittenBy(final @NotNull Project project, final @NotNull Collection<VirtualFile> untrackedFiles, @NotNull final String operation) {
|
||||
public static void notifyUntrackedFilesOverwrittenBy(@NotNull final Project project, @NotNull PlatformFacade platformFacade,
|
||||
@NotNull final Collection<VirtualFile> untrackedFiles,
|
||||
@NotNull final String operation, @Nullable String description) {
|
||||
final String notificationTitle = StringUtil.capitalize(operation) + " error";
|
||||
final String notificationDesc = createUntrackedFilesOverwrittenDescription(operation, false);
|
||||
final String notificationDesc = description == null ? createUntrackedFilesOverwrittenDescription(operation, false) : description;
|
||||
final String dialogDesc = createUntrackedFilesOverwrittenDescription(operation, true);
|
||||
|
||||
Notificator.getInstance(project).notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, notificationTitle, notificationDesc, NotificationType.ERROR, new NotificationListener() {
|
||||
@Override public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
|
||||
SelectFilesDialog dlg = new SelectFilesDialog(project, new ArrayList<VirtualFile>(untrackedFiles),
|
||||
StringUtil.stripHtml(dialogDesc, true), null, false, false) {
|
||||
@Override protected Action[] createActions() {
|
||||
return new Action[]{getOKAction()};
|
||||
}
|
||||
};
|
||||
dlg.setTitle("Untracked Files Preventing " + StringUtil.capitalize(operation));
|
||||
dlg.show();
|
||||
platformFacade.getNotificator(project).notifyError(notificationTitle, notificationDesc,
|
||||
new NotificationListener() {
|
||||
@Override
|
||||
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
|
||||
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
|
||||
SelectFilesDialog dlg = new SelectFilesDialog(project, new ArrayList<VirtualFile>(untrackedFiles),
|
||||
StringUtil.stripHtml(dialogDesc, true), null, false, false) {
|
||||
@Override
|
||||
protected Action[] createActions() {
|
||||
return new Action[]{getOKAction()};
|
||||
}
|
||||
};
|
||||
dlg.setTitle("Untracked Files Preventing " + StringUtil.capitalize(operation));
|
||||
dlg.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -71,7 +78,7 @@ public class UntrackedFilesNotifier {
|
||||
notificationDesc = "These" + description1 + "<br/>" + description2;
|
||||
}
|
||||
else {
|
||||
notificationDesc = "Some" + description1 + "<br/>" + description2 + " <a href=''>View them</a>";
|
||||
notificationDesc = "Some" + description1 + "<br/>" + description2 + " <a href='view'>View them</a>";
|
||||
}
|
||||
return notificationDesc;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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 git4idea.cherrypick
|
||||
|
||||
import com.intellij.notification.NotificationType
|
||||
import git4idea.history.browser.CherryPicker
|
||||
import git4idea.history.browser.GitCommit
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
import static git4idea.test.MockGit.*
|
||||
import static git4idea.test.MockGit.OperationName.CHERRY_PICK
|
||||
import static git4idea.test.MockGit.OperationName.GET_UNMERGED_FILES
|
||||
import static junit.framework.Assert.assertTrue
|
||||
|
||||
/**
|
||||
* Cherry-pick of one or multiple commits with "commit at once" option enabled.
|
||||
*
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
class GitAutoCommittingCherryPickTest extends GitCherryPickTest {
|
||||
|
||||
public static final String EMPTY_CHERRY_PICK = """
|
||||
# On branch master
|
||||
# Your branch is ahead of 'origin/master' by 11 commits.
|
||||
#
|
||||
# Untracked files:
|
||||
# (use "git add <file>..." to include in what will be committed)
|
||||
#
|
||||
#\t.idea/
|
||||
#\tlocal_staged.patch
|
||||
#\tout/
|
||||
nothing added to commit but untracked files present (use "git add" to track)
|
||||
The previous cherry-pick is now empty, possibly due to conflict resolution.
|
||||
If you wish to commit it anyway, use:
|
||||
|
||||
git commit --allow-empty
|
||||
|
||||
Otherwise, please use 'git reset'
|
||||
"""
|
||||
|
||||
@Before
|
||||
void setUp() {
|
||||
super.setUp()
|
||||
myCherryPicker = new CherryPicker(myProject, myGit, myPlatformFacade, true)
|
||||
}
|
||||
|
||||
@Test
|
||||
void "clean tree, no conflicts, then commit & notify, no new changelists"() {
|
||||
GitCommit commit = commit()
|
||||
|
||||
myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit))
|
||||
invokeCherryPick(commit)
|
||||
|
||||
assertHeadCommit(commit)
|
||||
assertOnlyDefaultChangelist()
|
||||
assertNotificationShown("Cherry-pick successful", commitDetails(commit), NotificationType.INFORMATION)
|
||||
}
|
||||
|
||||
@Test
|
||||
void "dirty tree, conflicting with commit, then show error"() {
|
||||
myGit.registerOperationExecutors(new SimpleErrorOperationExecutor(CHERRY_PICK, LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK))
|
||||
|
||||
def commit = commit()
|
||||
invokeCherryPick(commit)
|
||||
|
||||
assertNotCherryPicked()
|
||||
assertOnlyDefaultChangelist()
|
||||
assertNotificationShown("Cherry-pick failed",
|
||||
"""
|
||||
${commitDetails(commit)}<br/>
|
||||
Your local changes would be overwritten by cherry-pick.<br/>
|
||||
Commit your changes or stash them to proceed.
|
||||
""",
|
||||
NotificationType.ERROR)
|
||||
}
|
||||
|
||||
@Test
|
||||
void "untracked files, conflicting with commit, then show error"() {
|
||||
myGit.registerOperationExecutors(new SimpleErrorOperationExecutor(CHERRY_PICK, UNTRACKED_FILES_OVERWRITTEN_BY_CHERRY_PICK))
|
||||
|
||||
def commit = commit()
|
||||
invokeCherryPick(commit)
|
||||
|
||||
assertNotCherryPicked()
|
||||
assertOnlyDefaultChangelist()
|
||||
assertNotificationShown("Cherry-pick error",
|
||||
"""
|
||||
${commitDetails(commit)}<br/>
|
||||
Some untracked working tree files would be overwritten by cherry-pick.<br/>
|
||||
Please move, remove or add them before you can cherry-pick. <a href='view'>View them</a>
|
||||
""",
|
||||
NotificationType.ERROR)
|
||||
}
|
||||
|
||||
@Test
|
||||
void "conflict, merge dialog, not all merged, then new & active changelist, notification"() {
|
||||
prepareConflict()
|
||||
myGit.registerOperationExecutors(new SimpleSuccessOperationExecutor(GET_UNMERGED_FILES, UNMERGED_FILE))
|
||||
GitCommit commit = commit()
|
||||
invokeCherryPick(commit)
|
||||
assertMergeDialogShown()
|
||||
assertChangeLists([DEFAULT, newCommitMessage(commit)], newCommitMessage(commit))
|
||||
assertNotificationShown "Cherry-picked with conflicts",
|
||||
"""
|
||||
${commitDetails(commit)}<br/>
|
||||
Unresolved conflicts remain in the working tree. <a href='resolve'>Resolve them.<a/>
|
||||
""",
|
||||
NotificationType.WARNING
|
||||
}
|
||||
|
||||
@Test
|
||||
void "conflict, merge completed, then commit dialog"() {
|
||||
prepareConflict()
|
||||
GitCommit commit = commit()
|
||||
|
||||
OKCommitDialogHandler handler = new OKCommitDialogHandler(myRepository)
|
||||
myVcsHelper.registerHandler(handler)
|
||||
|
||||
invokeCherryPick(commit)
|
||||
assertMergeDialogShown()
|
||||
assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown()
|
||||
}
|
||||
|
||||
@Test
|
||||
void "conflict, merge finished, commit succeeded, no new changelists"() {
|
||||
prepareConflict()
|
||||
|
||||
OKCommitDialogHandler handler = new OKCommitDialogHandler(myRepository)
|
||||
myVcsHelper.registerHandler(handler)
|
||||
|
||||
GitCommit commit = commit()
|
||||
invokeCherryPick(commit)
|
||||
assertMergeDialogShown()
|
||||
assertHeadCommit(commit)
|
||||
assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown()
|
||||
assertOnlyDefaultChangelist()
|
||||
}
|
||||
|
||||
@Test
|
||||
void "conflict, merge ok, commit cancelled, then new & active changelist"() {
|
||||
prepareConflict()
|
||||
|
||||
CancelCommitDialogHandler handler = new CancelCommitDialogHandler()
|
||||
myVcsHelper.registerHandler(handler)
|
||||
|
||||
GitCommit commit = commit()
|
||||
invokeCherryPick(commit)
|
||||
assertMergeDialogShown()
|
||||
assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown()
|
||||
assertChangeLists([DEFAULT, newCommitMessage(commit)], newCommitMessage(commit))
|
||||
}
|
||||
|
||||
@Test
|
||||
void "2 commits, no problems, then commit all & notify"() {
|
||||
GitCommit commit1 = commit("First commit to cherry-pick")
|
||||
GitCommit commit2 = commit("Second commit to cherry-pick")
|
||||
myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit1),
|
||||
new SuccessfulCherryPickExecutor(myRepository, commit2))
|
||||
|
||||
invokeCherryPick([commit1, commit2])
|
||||
assertLastCommits commit2, commit1
|
||||
assertNotificationShown("Cherry-pick successful", notificationContent(commit1, commit2), NotificationType.INFORMATION)
|
||||
}
|
||||
|
||||
@Test
|
||||
void "3 commits, 2nd cherry-pick conflicts with local, then 1st success, 2nd stop & error"() {
|
||||
GitCommit commit1 = commit("First")
|
||||
GitCommit commit2 = commit("Second")
|
||||
GitCommit commit3 = commit("Third")
|
||||
|
||||
myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit1),
|
||||
new SimpleErrorOperationExecutor(CHERRY_PICK, LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK),
|
||||
new SuccessfulCherryPickExecutor(myRepository, commit3))
|
||||
|
||||
invokeCherryPick([commit1, commit2, commit3])
|
||||
|
||||
assertHeadCommit(commit1)
|
||||
assertNotificationShown("Cherry-pick failed",
|
||||
"""
|
||||
${commitDetails(commit2)}<br/>
|
||||
Your local changes would be overwritten by cherry-pick.<br/>
|
||||
Commit your changes or stash them to proceed.
|
||||
<hr/>
|
||||
However cherry-pick succeeded for the following commit:<br/>
|
||||
${commitDetails(commit1)}
|
||||
""", NotificationType.ERROR)
|
||||
}
|
||||
|
||||
@Test
|
||||
void "3 commits, 2nd conflicts with committed, then 1st success, on 2nd show merge dialog"() {
|
||||
GitCommit commit1 = commit("First")
|
||||
GitCommit commit2 = commit("Second")
|
||||
GitCommit commit3 = commit("Third")
|
||||
|
||||
myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit1))
|
||||
prepareConflict()
|
||||
myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit3))
|
||||
|
||||
OKCommitDialogHandler handler = new OKCommitDialogHandler(myRepository)
|
||||
myVcsHelper.registerHandler(handler)
|
||||
invokeCherryPick([commit1, commit2, commit3])
|
||||
|
||||
assertMergeDialogShown()
|
||||
assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown()
|
||||
assertLastCommits commit3, commit2, commit1
|
||||
}
|
||||
|
||||
// @Test
|
||||
void "Notify if changes have already been applied"() {
|
||||
// Inspired by IDEA-73548
|
||||
myGit.registerOperationExecutors(new SimpleErrorOperationExecutor(CHERRY_PICK, EMPTY_CHERRY_PICK))
|
||||
|
||||
GitCommit commit = commit()
|
||||
invokeCherryPick(commit)
|
||||
|
||||
assertNotCherryPicked()
|
||||
assertNotificationShown("Nothing to cherry-pick", "All changes from ${commitDetails(commit)} have already been applied",
|
||||
NotificationType.WARNING)
|
||||
}
|
||||
|
||||
// @Test
|
||||
void "1st successful, 2nd empty (all applied), then compound notification"() {
|
||||
// Inspired by IDEA-73548
|
||||
GitCommit commit1 = commit()
|
||||
GitCommit commit2 = commit()
|
||||
myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit1),
|
||||
new SimpleErrorOperationExecutor(CHERRY_PICK, EMPTY_CHERRY_PICK))
|
||||
|
||||
invokeCherryPick([ commit1, commit2 ])
|
||||
|
||||
assertHeadCommit(commit1)
|
||||
assertNotificationShown("Cherry-picked with problems",
|
||||
"""Successfully cherry-picked ${commitDetails(commit1)}<br/>
|
||||
Not cherry-picked ${commitDetails(commit2)} - all changes have already been applied""", NotificationType.WARNING)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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 git4idea.cherrypick
|
||||
|
||||
import com.intellij.mock.MockVirtualFile
|
||||
import com.intellij.openapi.vcs.FilePathImpl
|
||||
import com.intellij.openapi.vcs.changes.Change
|
||||
import com.intellij.openapi.vcs.changes.ChangeListManager
|
||||
import com.intellij.openapi.vcs.changes.LocalChangeList
|
||||
import com.intellij.openapi.vcs.history.VcsRevisionNumber
|
||||
import com.intellij.testFramework.vcs.MockChangeListManager
|
||||
import com.intellij.testFramework.vcs.MockContentRevision
|
||||
import git4idea.history.browser.CherryPicker
|
||||
import git4idea.history.browser.GitCommit
|
||||
import git4idea.history.browser.SHAHash
|
||||
import git4idea.history.wholeTree.AbstractHash
|
||||
import git4idea.repo.GitRepository
|
||||
import git4idea.test.GitFastTest
|
||||
import git4idea.test.GitLightRepository
|
||||
import git4idea.test.MockGit
|
||||
import sun.security.provider.SHA
|
||||
|
||||
import static git4idea.test.MockGit.OperationName.CHERRY_PICK
|
||||
import static junit.framework.Assert.assertEquals
|
||||
import static junit.framework.Assert.assertTrue
|
||||
import static git4idea.test.MockGit.commitMessageForCherryPick
|
||||
import git4idea.test.MockVcsHelper
|
||||
|
||||
/**
|
||||
* Common parent for all tests on cherry-pick
|
||||
*
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
class GitCherryPickTest extends GitFastTest {
|
||||
|
||||
public static final String DEFAULT = MockChangeListManager.DEFAULT_CHANGE_LIST_NAME;
|
||||
public static final String UNMERGED_FILE = """
|
||||
100644 d87b28d6fd6e97620603e64ce70fc2f24535ec28 1\ttest.txt
|
||||
100644 7b50450f5deb7cce3b5ce92ba866f1af6e58c3c6 2\ttest.txt
|
||||
100644 a784477cdd0437a84751c52f72b971503deb48cb 3\ttest.txt
|
||||
"""
|
||||
public static final String CHERRY_PICK_CONFLICT = """
|
||||
error: could not apply ec15d8e... message
|
||||
hint: after resolving the conflicts, mark the corrected paths
|
||||
hint: with 'git add <paths>' or 'git rm <paths>'
|
||||
hint: and commit the result with 'git commit'
|
||||
"""
|
||||
CherryPicker myCherryPicker
|
||||
GitLightRepository myRepository
|
||||
GitLightRepository.Commit myInitialCommit
|
||||
|
||||
static final LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK =
|
||||
"""
|
||||
error: Your local changes to the following files would be overwritten by merge:
|
||||
\ttest.txt
|
||||
Please, commit your changes or stash them before you can merge.
|
||||
Aborting
|
||||
""";
|
||||
|
||||
static final UNTRACKED_FILES_OVERWRITTEN_BY_CHERRY_PICK =
|
||||
"""
|
||||
error: The following untracked working tree files would be overwritten by merge:
|
||||
\tcp.txt
|
||||
Please move or remove them before you can merge.
|
||||
Aborting
|
||||
"""
|
||||
|
||||
void setUp() {
|
||||
super.setUp()
|
||||
|
||||
myRepository = new GitLightRepository()
|
||||
myRepositoryManager.add(myRepository)
|
||||
myInitialCommit = myRepository.commit("initial")
|
||||
}
|
||||
|
||||
GitCommit commit(String commitMessage = "plain commit") {
|
||||
AbstractHash hash = AbstractHash.create(Integer.toHexString(new SHA().hashCode()))
|
||||
List<Change> changes = new ArrayList<Change>();
|
||||
changes.add(new Change(null, new MockContentRevision(new FilePathImpl(new MockVirtualFile("name")), VcsRevisionNumber.NULL)));
|
||||
new GitCommit(hash, SHAHash.emulate(hash), "John Smith", null, null, commitMessage, commitMessage, null, null, null, null, null, null,
|
||||
null, changes, 0)
|
||||
}
|
||||
|
||||
void assertOnlyDefaultChangelist() {
|
||||
assertChangeLists( [DEFAULT], DEFAULT)
|
||||
}
|
||||
|
||||
void invokeCherryPick(GitCommit commit) {
|
||||
invokeCherryPick([commit])
|
||||
}
|
||||
|
||||
void invokeCherryPick(List<GitCommit> commits) {
|
||||
myCherryPicker.cherryPick(Collections.<GitRepository, List<GitCommit>> singletonMap(myRepository, commits))
|
||||
}
|
||||
|
||||
void assertHeadCommit(GitCommit commit) {
|
||||
assertEquals "Wrong commit at the HEAD", commitMessageForCherryPick(commit), myRepository.head.commitMessage
|
||||
}
|
||||
|
||||
void assertLastCommits(GitCommit... commits) {
|
||||
GitLightRepository.Commit current = myRepository.head
|
||||
int level = 0;
|
||||
for (GitCommit commit : commits) {
|
||||
assertEquals "Wrong commit at level $level", commitMessageForCherryPick(commit), current.commitMessage
|
||||
current = current.parent
|
||||
level++;
|
||||
}
|
||||
}
|
||||
|
||||
void assertChangeLists(Collection<String> changeLists, String activeChangelist) {
|
||||
ChangeListManager changeListManager = myPlatformFacade.getChangeListManager(myProject)
|
||||
List<LocalChangeList> lists = changeListManager.changeLists
|
||||
Collection<String> listNames = lists.collect { it.name }
|
||||
assertEquals "Change lists are different", changeLists.toSet(), listNames.toSet()
|
||||
assertEquals "Wrong active changelist", activeChangelist, changeListManager.defaultChangeList.name
|
||||
}
|
||||
|
||||
String commitDetails(GitCommit commit) {
|
||||
"${commit.shortHash.toString()} \"${commit.subject}\""
|
||||
}
|
||||
|
||||
String notificationContent(GitCommit... commits) {
|
||||
commits.collect { commitDetails(it) }.join("<br/>")
|
||||
}
|
||||
|
||||
void assertNotCherryPicked() {
|
||||
// 1. assert not committed (i.e. git cherry-pick was not performed)
|
||||
assertNothingCommitted()
|
||||
// 2. assert working tree not changed (i.e. git cherry-pick -n was not performed either)
|
||||
assertTrue myPlatformFacade.getChangeListManager(myProject).getAllChanges().isEmpty()
|
||||
}
|
||||
|
||||
void assertNothingCommitted() {
|
||||
assertEquals(myInitialCommit, myRepository.head)
|
||||
}
|
||||
|
||||
void prepareConflict() {
|
||||
myGit.registerOperationExecutors(new MockGit.SimpleErrorOperationExecutor(CHERRY_PICK, CHERRY_PICK_CONFLICT),
|
||||
new MockGit.SimpleSuccessOperationExecutor(MockGit.OperationName.GET_UNMERGED_FILES, UNMERGED_FILE))
|
||||
}
|
||||
|
||||
void assertMergeDialogShown() {
|
||||
assertTrue "Merge dialog was not shown", myVcsHelper.mergeDialogWasShown()
|
||||
}
|
||||
|
||||
String newCommitMessage(GitCommit commit) {
|
||||
"${commit.description}\n(cherry-picked from ${commit.hash.value})"
|
||||
}
|
||||
|
||||
protected static class OKCommitDialogHandler implements MockVcsHelper.CommitHandler {
|
||||
|
||||
private final GitLightRepository myRepository;
|
||||
boolean myCommitDialogShown
|
||||
|
||||
OKCommitDialogHandler(GitLightRepository repository) {
|
||||
myRepository = repository
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean commit(String commitMessage) {
|
||||
myCommitDialogShown = true;
|
||||
myRepository.commit(commitMessage) // answering OK in the dialog => committing
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean wasCommitDialogShown() {
|
||||
myCommitDialogShown
|
||||
}
|
||||
}
|
||||
|
||||
protected static class CancelCommitDialogHandler implements MockVcsHelper.CommitHandler {
|
||||
|
||||
boolean myCommitDialogShown
|
||||
|
||||
@Override
|
||||
boolean commit(String commitMessage) {
|
||||
myCommitDialogShown = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean wasCommitDialogShown() {
|
||||
myCommitDialogShown
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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 git4idea.cherrypick
|
||||
|
||||
import com.intellij.notification.NotificationType
|
||||
import git4idea.history.browser.CherryPicker
|
||||
import git4idea.history.browser.GitCommit
|
||||
import git4idea.test.MockGit
|
||||
import git4idea.test.MockVcsHelper
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
import static git4idea.test.MockGit.OperationName.CHERRY_PICK
|
||||
import static junit.framework.Assert.*
|
||||
import git4idea.test.GitLightRepository
|
||||
|
||||
/**
|
||||
* Tests for {@link CherryPicker}, when the "auto-commit on cherry-pick" option is deselected.
|
||||
* Most situations are equal or similar, so the majority of cherry pick tests are located in {@link GitAutoCommittingCherryPickTest}.
|
||||
*
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
class GitNotCommittingCherryPickTest extends GitCherryPickTest {
|
||||
|
||||
@Before
|
||||
void setUp() {
|
||||
super.setUp()
|
||||
myCherryPicker = new CherryPicker(myProject, myGit, myPlatformFacade, false)
|
||||
}
|
||||
|
||||
@Test
|
||||
void "clean tree, no conflicts, then show commit dialog, commit on ok"() {
|
||||
GitCommit commit = commit()
|
||||
|
||||
myGit.registerOperationExecutors(new MockGit.SuccessfulCherryPickExecutor(myRepository, commit))
|
||||
OKCommitDialogHandler handler = new OKCommitDialogHandler(myRepository)
|
||||
myVcsHelper.registerHandler(handler)
|
||||
|
||||
invokeCherryPick(commit)
|
||||
|
||||
assertHeadCommit(commit)
|
||||
assertOnlyDefaultChangelist()
|
||||
assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown()
|
||||
// notification is shown from the successful commit, can't check from here
|
||||
}
|
||||
|
||||
@Test
|
||||
void "dirty tree, conflicting with commit, then show error"() {
|
||||
myGit.registerOperationExecutors(new MockGit.SimpleErrorOperationExecutor(CHERRY_PICK, LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK))
|
||||
|
||||
def commit = commit()
|
||||
OKCommitDialogHandler handler = new OKCommitDialogHandler(myRepository)
|
||||
myVcsHelper.registerHandler(handler)
|
||||
|
||||
invokeCherryPick(commit)
|
||||
|
||||
assertNotCherryPicked()
|
||||
assertFalse "Commit dialog was shown, but it shouldn't", handler.wasCommitDialogShown()
|
||||
assertOnlyDefaultChangelist()
|
||||
assertNotificationShown("Cherry-pick failed",
|
||||
"""
|
||||
${commitDetails(commit)}<br/>
|
||||
Your local changes would be overwritten by cherry-pick.<br/>
|
||||
Commit your changes or stash them to proceed.
|
||||
""",
|
||||
NotificationType.ERROR)
|
||||
}
|
||||
|
||||
@Test
|
||||
void "conflict, merge ok, commit cancelled, then new & active changelist"() {
|
||||
prepareConflict()
|
||||
|
||||
CancelCommitDialogHandler handler = new CancelCommitDialogHandler()
|
||||
myVcsHelper.registerHandler(handler)
|
||||
|
||||
GitCommit commit = commit()
|
||||
invokeCherryPick(commit)
|
||||
assertMergeDialogShown()
|
||||
assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown()
|
||||
assertChangeLists([DEFAULT, newCommitMessage(commit)], newCommitMessage(commit))
|
||||
}
|
||||
|
||||
@Test
|
||||
void "2 simple commits in a row, then 2 commit dialogs in a row"() {
|
||||
GitCommit commit1 = commit()
|
||||
GitCommit commit2 = commit()
|
||||
|
||||
myGit.registerOperationExecutors(new MockGit.SimpleSuccessOperationExecutor(CHERRY_PICK, ""),
|
||||
new MockGit.SimpleSuccessOperationExecutor(CHERRY_PICK, ""))
|
||||
|
||||
CountingOKCommitHandler handler = new CountingOKCommitHandler(myRepository)
|
||||
myVcsHelper.registerHandler(handler)
|
||||
|
||||
invokeCherryPick([commit1, commit2])
|
||||
|
||||
assertOnlyDefaultChangelist()
|
||||
assertEquals "Commit dialog shown wrong number of times", 2, handler.myCommitDialogs
|
||||
assertLastCommits(commit2, commit1)
|
||||
}
|
||||
|
||||
@Test
|
||||
void "3 commits, 2nd conflicts with committed, then 1st success, on 2nd show merge dialog"() {
|
||||
GitCommit commit1 = commit("First")
|
||||
GitCommit commit2 = commit("Second")
|
||||
GitCommit commit3 = commit("Third")
|
||||
|
||||
myGit.registerOperationExecutors(new MockGit.SimpleSuccessOperationExecutor(CHERRY_PICK, ""))
|
||||
prepareConflict()
|
||||
myGit.registerOperationExecutors(new MockGit.SimpleSuccessOperationExecutor(CHERRY_PICK, ""))
|
||||
|
||||
CountingOKCommitHandler handler = new CountingOKCommitHandler(myRepository)
|
||||
myVcsHelper.registerHandler(handler)
|
||||
|
||||
invokeCherryPick([commit1, commit2, commit3])
|
||||
|
||||
assertMergeDialogShown()
|
||||
assertEquals "Commit dialog shown wrong number of times", 3, handler.myCommitDialogs
|
||||
assertLastCommits commit3, commit2, commit1
|
||||
}
|
||||
|
||||
private static class CountingOKCommitHandler implements MockVcsHelper.CommitHandler {
|
||||
|
||||
GitLightRepository myRepository
|
||||
int myCommitDialogs;
|
||||
|
||||
CountingOKCommitHandler(GitLightRepository repository) {
|
||||
myRepository = repository
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean commit(String commitMessage) {
|
||||
myCommitDialogs++
|
||||
myRepository.commit(commitMessage)
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public class GitRepositoryTest extends GitTest {
|
||||
@BeforeMethod
|
||||
public void setUp(Method testMethod) throws Exception {
|
||||
super.setUp(testMethod);
|
||||
myRepository = GitRepository.getFullInstance(myRepo.getVFRootDir(), myProject, myProject);
|
||||
myRepository = GitRepositoryImpl.getFullInstance(myRepo.getVFRootDir(), myProject, myProject);
|
||||
myReader = new GitRepositoryReader(new File(VfsUtil.virtualToIoFile(myRepository.getRoot()), ".git"));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.intellij.testFramework.VfsTestUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import git4idea.repo.GitRepository;
|
||||
import git4idea.repo.GitRepositoryImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -41,7 +42,7 @@ public class GitExec {
|
||||
public static GitRepository init(@NotNull Project project, @NotNull VirtualFile root) throws IOException {
|
||||
new GitTestRunEnv(new File(root.getPath())).run("init");
|
||||
root.refresh(false, true);
|
||||
return GitRepository.getLightInstance(root, project, project);
|
||||
return GitRepositoryImpl.getLightInstance(root, project, project);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +58,7 @@ public class GitExec {
|
||||
VirtualFile root = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(destinationPath));
|
||||
assert root != null;
|
||||
root.refresh(false, true);
|
||||
return bare ? null : GitRepository.getLightInstance(root, project, project);
|
||||
return bare ? null : GitRepositoryImpl.getLightInstance(root, project, project);
|
||||
}
|
||||
|
||||
public static String push(@NotNull GitRepository repository, String... args) throws IOException {
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
package git4idea.test
|
||||
|
||||
import com.intellij.notification.Notification
|
||||
|
||||
import static git4idea.test.GitGTestUtil.stripLineBreaksAndHtml
|
||||
import static junit.framework.Assert.assertEquals
|
||||
import static junit.framework.Assert.assertNotNull
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import git4idea.tests.TestDialogManager
|
||||
import org.junit.Before
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
|
||||
import static junit.framework.Assert.assertEquals
|
||||
import static junit.framework.Assert.assertNotNull
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -31,11 +31,15 @@ import com.intellij.openapi.util.io.FileUtil
|
||||
*/
|
||||
class GitFastTest {
|
||||
|
||||
public static final String TEST_NOTIFICATION_GROUP = "Test"
|
||||
|
||||
Project myProject
|
||||
GitTestPlatformFacade myPlatformFacade
|
||||
GitTestRepositoryManager myRepositoryManager
|
||||
MockGit myGit
|
||||
String myProjectDir
|
||||
TestDialogManager myDialogManager
|
||||
MockVcsHelper myVcsHelper
|
||||
|
||||
@Before
|
||||
void setUp() {
|
||||
@@ -48,14 +52,30 @@ class GitFastTest {
|
||||
myPlatformFacade = new GitTestPlatformFacade()
|
||||
myGit = new MockGit()
|
||||
myDialogManager = myPlatformFacade.getDialogManager()
|
||||
myRepositoryManager = (GitTestRepositoryManager) myPlatformFacade.getRepositoryManager(myProject)
|
||||
myVcsHelper = (MockVcsHelper) myPlatformFacade.getVcsHelper(myProject)
|
||||
}
|
||||
|
||||
void assertNotificationShown(Notification expected) {
|
||||
if (expected) {
|
||||
Notification actualNotification = (myPlatformFacade.getNotificator(myProject) as TestNotificator).lastNotification
|
||||
assertNotNull "No notification was shown", actualNotification
|
||||
assertEquals(stripLineBreaksAndHtml(expected.content), stripLineBreaksAndHtml(actualNotification.content))
|
||||
assertEquals "Notification has wrong title", expected.title, actualNotification.title
|
||||
assertEquals "Notification has wrong type", expected.type, actualNotification.type
|
||||
assertEquals "Notification has wrong content", adjustTestContent(expected.content), actualNotification.content
|
||||
}
|
||||
}
|
||||
|
||||
// we allow more spaces and line breaks in tests to make them more readable.
|
||||
// After all, notifications display html, so all line breaks and extra spaces are ignored.
|
||||
String adjustTestContent(String s) {
|
||||
StringBuilder res = new StringBuilder()
|
||||
s.split("\n").each { res.append it.trim() }
|
||||
res.toString()
|
||||
}
|
||||
|
||||
void assertNotificationShown(String title, String message, NotificationType type) {
|
||||
assertNotificationShown(new Notification(TEST_NOTIFICATION_GROUP, title, message, type))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,4 +37,8 @@ class GitGTestUtil {
|
||||
StringUtil.stripHtml(s, true).replace('\n', '');
|
||||
}
|
||||
|
||||
static String stripLineBreaksAndMultiSpaces(String s) {
|
||||
s.replace('\n', '').replaceAll(" {3,}", " ")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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 git4idea.test;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import git4idea.GitBranch;
|
||||
import git4idea.branch.GitBranchesCollection;
|
||||
import git4idea.repo.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import sun.security.provider.SHA;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Simulates Git repository.
|
||||
* Made for unit tests, not to spawn a Git process.
|
||||
* Stores information about commits and branches in memory.
|
||||
*
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
public class GitLightRepository implements GitRepository {
|
||||
|
||||
public static class Commit {
|
||||
@Nullable private Commit myParent;
|
||||
@NotNull private String myHash;
|
||||
@NotNull private String myCommitMessage;
|
||||
|
||||
public Commit(String hash, String message, Commit parent) {
|
||||
myHash = hash;
|
||||
myCommitMessage = message;
|
||||
myParent = parent;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getCommitMessage() {
|
||||
return myCommitMessage;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Commit getParent() {
|
||||
return myParent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Commit commit = (Commit)o;
|
||||
|
||||
if (!myCommitMessage.equals(commit.myCommitMessage)) return false;
|
||||
if (!myHash.equals(commit.myHash)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myHash.hashCode();
|
||||
result = 31 * result + myCommitMessage.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Branch {
|
||||
@NotNull private String myName;
|
||||
@Nullable private Commit myCommit;
|
||||
|
||||
public Branch(String name, Commit commit) {
|
||||
myName = name;
|
||||
myCommit = commit;
|
||||
}
|
||||
}
|
||||
|
||||
private Branch myHead;
|
||||
|
||||
public GitLightRepository() {
|
||||
myHead = new Branch("master", null);
|
||||
}
|
||||
|
||||
public Commit commit(String commitMessage) {
|
||||
Commit parent = myHead.myCommit;
|
||||
String hash = new SHA().toString();
|
||||
Commit commit = new Commit(hash, commitMessage, parent);
|
||||
myHead.myCommit = commit;
|
||||
return commit;
|
||||
}
|
||||
|
||||
public Commit cherryPick(String commitMessage) {
|
||||
return commit(commitMessage);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Commit getHead() {
|
||||
return myHead.myCommit;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public VirtualFile getRoot() {
|
||||
return new GitMockVirtualFile(FileUtil.getTempDirectory());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public VirtualFile getGitDir() {
|
||||
return new GitMockVirtualFile(getRoot().getPath() + "/.git");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getPresentableUrl() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Project getProject() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public GitUntrackedFilesHolder getUntrackedFilesHolder() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public State getState() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentRevision() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GitBranch getCurrentBranch() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public GitBranchesCollection getBranches() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public GitConfig getConfig() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<GitRemote> getRemotes() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMergeInProgress() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRebaseInProgress() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnBranch() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFresh() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(@NotNull GitRepositoryChangeListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(TrackedTopic... topics) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toLogString() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,20 +15,23 @@
|
||||
*/
|
||||
package git4idea.test;
|
||||
|
||||
import com.intellij.mock.MockLocalFileSystem;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.vcs.AbstractVcs;
|
||||
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
|
||||
import git4idea.Notificator;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.tests.TestDialogManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import git4idea.config.GitVcsSettings;
|
||||
import com.intellij.openapi.vcs.AbstractVcs;
|
||||
import com.intellij.openapi.vcs.AbstractVcsHelper;
|
||||
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
|
||||
import com.intellij.openapi.vcs.changes.ChangeListManager;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.mock.MockLocalFileSystem;
|
||||
import com.intellij.testFramework.vcs.MockChangeListManager;
|
||||
import git4idea.Notificator;
|
||||
import git4idea.PlatformFacade;
|
||||
import git4idea.repo.GitRepositoryManager;
|
||||
import git4idea.tests.TestDialogManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -41,10 +44,15 @@ public class GitTestPlatformFacade implements PlatformFacade {
|
||||
private TestNotificator myNotificator;
|
||||
private TestDialogManager myTestDialogManager;
|
||||
private GitMockProjectRootManager myProjectRootManager;
|
||||
private ChangeListManager myChangeListManager;
|
||||
private GitTestRepositoryManager myRepositoryManager;
|
||||
private MockVcsHelper myVcsHelper;
|
||||
|
||||
public GitTestPlatformFacade() {
|
||||
myTestDialogManager = new TestDialogManager();
|
||||
myProjectRootManager = new GitMockProjectRootManager();
|
||||
myChangeListManager = new MockChangeListManager();
|
||||
myRepositoryManager = new GitTestRepositoryManager();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -95,13 +103,18 @@ public class GitTestPlatformFacade implements PlatformFacade {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GitVcsSettings getGitWorkspaceSettings(@NotNull Project project) {
|
||||
throw new UnsupportedOperationException();
|
||||
public void runWriteAction(@NotNull Runnable runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState) {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeListManager getChangeListManager(@NotNull Project project) {
|
||||
throw new UnsupportedOperationException();
|
||||
return myChangeListManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,6 +122,21 @@ public class GitTestPlatformFacade implements PlatformFacade {
|
||||
return new MockLocalFileSystem();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public AbstractVcsHelper getVcsHelper(@NotNull Project project) {
|
||||
if (myVcsHelper == null) {
|
||||
myVcsHelper = new MockVcsHelper();
|
||||
}
|
||||
return myVcsHelper;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public GitRepositoryManager getRepositoryManager(@NotNull Project project) {
|
||||
return myRepositoryManager;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public AbstractVcs getVcs(@NotNull Project project) {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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 git4idea.test;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import git4idea.repo.GitRepository;
|
||||
import git4idea.repo.GitRepositoryChangeListener;
|
||||
import git4idea.repo.GitRepositoryManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
class GitTestRepositoryManager implements GitRepositoryManager {
|
||||
|
||||
private final Collection<GitRepository> myRepositories = new ArrayList<GitRepository>();
|
||||
|
||||
public void add(GitRepository repository) {
|
||||
myRepositories.add(repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GitRepository getRepositoryForRoot(@Nullable VirtualFile root) {
|
||||
return myRepositories.iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GitRepository getRepositoryForFile(@NotNull VirtualFile file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<GitRepository> getRepositories() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moreThanOneRoot() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListenerToAllRepositories(@NotNull GitRepositoryChangeListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepository(VirtualFile root, GitRepository.TrackedTopic... topics) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAllRepositories(GitRepository.TrackedTopic... topics) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,21 @@
|
||||
*/
|
||||
package git4idea.test
|
||||
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.containers.hash.HashMap
|
||||
import git4idea.commands.Git
|
||||
import git4idea.commands.GitCommandResult
|
||||
import git4idea.commands.GitLineHandlerListener
|
||||
import git4idea.history.browser.GitCommit
|
||||
import git4idea.push.GitPushSpec
|
||||
import git4idea.repo.GitRepository
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.jetbrains.annotations.Nullable
|
||||
|
||||
import static git4idea.test.MockGit.OperationName.CHERRY_PICK
|
||||
import static git4idea.test.MockGit.OperationName.GET_UNMERGED_FILES
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -30,84 +37,240 @@ import org.jetbrains.annotations.NotNull
|
||||
*/
|
||||
class MockGit implements Git {
|
||||
|
||||
public static final GitCommandResult FAKE_SUCCESS_RESULT = new GitCommandResult(true, 0, Collections.emptyList(), Collections.emptyList())
|
||||
private final Map<OperationName, Queue<OperationExecutor>> myExecutors = new HashMap<OperationName, Queue<OperationExecutor>>()
|
||||
|
||||
public static interface OperationExecutor {
|
||||
GitCommandResult execute();
|
||||
OperationName getName();
|
||||
}
|
||||
|
||||
public enum OperationName {
|
||||
CHERRY_PICK,
|
||||
GET_UNMERGED_FILES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register executors for specific operations. These are put into queues, i.e. once operation is called, the executor is popped out of the
|
||||
* queue. If the queue is empty or certain operation, then it is executed as by default.
|
||||
*/
|
||||
void registerOperationExecutors(OperationExecutor... executors) {
|
||||
for (OperationExecutor executor : executors) {
|
||||
OperationName name = executor.getName()
|
||||
Queue<OperationExecutor> exs = myExecutors.get(name)
|
||||
if (exs == null) {
|
||||
exs = new ArrayDeque<OperationExecutor>()
|
||||
myExecutors.put(name, exs)
|
||||
}
|
||||
exs.add(executor)
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult init(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLineHandlerListener... listeners) {
|
||||
new File(root.path, ".git").mkdir()
|
||||
new GitCommandResult(true, 0, Collections.emptyList(), Collections.emptyList())
|
||||
FAKE_SUCCESS_RESULT
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
Set<VirtualFile> untrackedFiles(Project project, VirtualFile root, Collection<VirtualFile> files) {
|
||||
Set<VirtualFile> untrackedFiles(@NotNull Project project, @NotNull VirtualFile root, Collection<VirtualFile> files) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
Collection<VirtualFile> untrackedFilesNoChunk(Project project, VirtualFile root, List<String> relativePaths) {
|
||||
Collection<VirtualFile> untrackedFilesNoChunk(@NotNull Project project, @NotNull VirtualFile root, List<String> relativePaths) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult clone(Project project, File parentDirectory, String url, String clonedDirectoryName) {
|
||||
GitCommandResult clone(@NotNull Project project, @NotNull File parentDirectory, @NotNull String url, @NotNull String clonedDirectoryName) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult merge(GitRepository repository, String branchToMerge, GitLineHandlerListener... listeners) {
|
||||
GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge, @NotNull GitLineHandlerListener... listeners) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult checkout(GitRepository repository, String reference, String newBranch, boolean force, GitLineHandlerListener... listeners) {
|
||||
GitCommandResult checkout(@NotNull GitRepository repository, @NotNull String reference, String newBranch, boolean force, @NotNull GitLineHandlerListener... listeners) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult checkoutNewBranch(GitRepository repository, String branchName, GitLineHandlerListener listener) {
|
||||
GitCommandResult checkoutNewBranch(@NotNull GitRepository repository, @NotNull String branchName, GitLineHandlerListener listener) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult createNewTag(GitRepository repository, String tagName, GitLineHandlerListener listener, String reference) {
|
||||
GitCommandResult createNewTag(@NotNull GitRepository repository, @NotNull String tagName, GitLineHandlerListener listener, @NotNull String reference) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult branchDelete(GitRepository repository, String branchName, boolean force, GitLineHandlerListener... listeners) {
|
||||
GitCommandResult branchDelete(@NotNull GitRepository repository, @NotNull String branchName, boolean force, @NotNull GitLineHandlerListener... listeners) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult branchContains(GitRepository repository, String commit) {
|
||||
GitCommandResult branchContains(@NotNull GitRepository repository, @NotNull String commit) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult branchCreate(GitRepository repository, String branchName) {
|
||||
GitCommandResult branchCreate(@NotNull GitRepository repository, @NotNull String branchName) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult resetHard(GitRepository repository, String revision) {
|
||||
GitCommandResult resetHard(@NotNull GitRepository repository, @NotNull String revision) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult resetMerge(GitRepository repository, String revision) {
|
||||
GitCommandResult resetMerge(@NotNull GitRepository repository, String revision) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult tip(GitRepository repository, String branchName) {
|
||||
GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult push(GitRepository repository, String remote, String spec, GitLineHandlerListener... listeners) {
|
||||
GitCommandResult push(@NotNull GitRepository repository, @NotNull String remote, @NotNull String spec, @NotNull GitLineHandlerListener... listeners) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult push(GitRepository repository, GitPushSpec pushSpec, GitLineHandlerListener... listeners) {
|
||||
GitCommandResult push(@NotNull GitRepository repository, @NotNull GitPushSpec pushSpec, @NotNull GitLineHandlerListener... listeners) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit, @NotNull GitLineHandlerListener... listeners) {
|
||||
GitCommandResult result = callExecutor(CHERRY_PICK)
|
||||
if (result != null) {
|
||||
produceOutput(result.getOutputAsJoinedString(), listeners)
|
||||
return result;
|
||||
}
|
||||
((GitLightRepository)repository).cherryPick("cherry-pick from $hash")
|
||||
return FAKE_SUCCESS_RESULT
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult getUnmergedFiles(@NotNull GitRepository repository) {
|
||||
GitCommandResult result = callExecutor(GET_UNMERGED_FILES)
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
return FAKE_SUCCESS_RESULT
|
||||
}
|
||||
|
||||
private void produceOutput(String output, GitLineHandlerListener... listeners) {
|
||||
for (String line : output.split("\n")) { // for simplicity all output goes to OUTPUT, no ERROR
|
||||
listeners.each { it.onLineAvailable(line, ProcessOutputTypes.STDOUT) }
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private GitCommandResult callExecutor(OperationName operationName) {
|
||||
Queue<OperationExecutor> cherryPickExecutors = myExecutors.get(operationName)
|
||||
if (cherryPickExecutors != null && !cherryPickExecutors.isEmpty()) {
|
||||
OperationExecutor executor = cherryPickExecutors.poll()
|
||||
return executor.execute()
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class SimpleErrorOperationExecutor implements MockGit.OperationExecutor {
|
||||
|
||||
String myOutput
|
||||
MockGit.OperationName myOperationName
|
||||
|
||||
SimpleErrorOperationExecutor(MockGit.OperationName operationName, String output) {
|
||||
myOutput = output;
|
||||
myOperationName = operationName
|
||||
}
|
||||
|
||||
@Override
|
||||
GitCommandResult execute() {
|
||||
return result(myOutput, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
MockGit.OperationName getName() {
|
||||
return myOperationName
|
||||
}
|
||||
}
|
||||
|
||||
public static class SimpleSuccessOperationExecutor implements MockGit.OperationExecutor {
|
||||
|
||||
String myOutput
|
||||
MockGit.OperationName myOperationName
|
||||
|
||||
SimpleSuccessOperationExecutor(MockGit.OperationName operationName, String output) {
|
||||
myOutput = output;
|
||||
myOperationName = operationName
|
||||
}
|
||||
|
||||
@Override
|
||||
GitCommandResult execute() {
|
||||
return result(myOutput, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
MockGit.OperationName getName() {
|
||||
return myOperationName
|
||||
}
|
||||
}
|
||||
|
||||
private static GitCommandResult result(String output, boolean success) {
|
||||
new GitCommandResult(success, success ? 0 : 127, Collections.emptyList(), Collections.singletonList(output))
|
||||
}
|
||||
|
||||
public static class SuccessfulCherryPickExecutor implements OperationExecutor {
|
||||
|
||||
GitRepository myRepository
|
||||
GitCommit myOriginalCommit
|
||||
|
||||
SuccessfulCherryPickExecutor(GitRepository repository, GitCommit originalCommit) {
|
||||
myRepository = repository;
|
||||
myOriginalCommit = originalCommit
|
||||
}
|
||||
|
||||
@Override
|
||||
GitCommandResult execute() {
|
||||
((GitLightRepository)myRepository).cherryPick(commitMessageForCherryPick(myOriginalCommit))
|
||||
return FAKE_SUCCESS_RESULT
|
||||
}
|
||||
|
||||
@Override
|
||||
OperationName getName() {
|
||||
return CHERRY_PICK
|
||||
}
|
||||
}
|
||||
|
||||
static String commitMessageForCherryPick(GitCommit commit) {
|
||||
"$commit.subject\n(cherry-picked from ${commit.shortHash.getString()})"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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 git4idea.test;
|
||||
|
||||
import com.intellij.ide.errorTreeView.HotfixData;
|
||||
import com.intellij.openapi.vcs.*;
|
||||
import com.intellij.openapi.vcs.annotate.AnnotationProvider;
|
||||
import com.intellij.openapi.vcs.annotate.FileAnnotation;
|
||||
import com.intellij.openapi.vcs.changes.Change;
|
||||
import com.intellij.openapi.vcs.changes.CommitExecutor;
|
||||
import com.intellij.openapi.vcs.changes.LocalChangeList;
|
||||
import com.intellij.openapi.vcs.history.VcsFileRevision;
|
||||
import com.intellij.openapi.vcs.history.VcsHistoryProvider;
|
||||
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
|
||||
import com.intellij.openapi.vcs.merge.MergeProvider;
|
||||
import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
|
||||
import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
public class MockVcsHelper extends AbstractVcsHelper {
|
||||
private boolean myMergeDialogShown;
|
||||
private CommitHandler myCommitHandler;
|
||||
|
||||
@Override
|
||||
public void showErrors(List<VcsException> abstractVcsExceptions, @NotNull String tabDisplayName) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showErrors(Map<HotfixData, List<VcsException>> exceptionGroups, @NotNull String tabDisplayName) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VcsException> runTransactionRunnable(AbstractVcs vcs, TransactionRunnable runnable, Object vcsParameters) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showAnnotation(FileAnnotation annotation, VirtualFile file, AbstractVcs vcs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDifferences(VcsFileRevision cvsVersionOn, VcsFileRevision cvsVersionOn1, File file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showChangesListBrowser(CommittedChangeList changelist, @Nls String title) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showChangesBrowser(List<CommittedChangeList> changelists) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showChangesBrowser(List<CommittedChangeList> changelists, @Nls String title) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showChangesBrowser(CommittedChangesProvider provider,
|
||||
RepositoryLocation location,
|
||||
@Nls String title,
|
||||
@Nullable Component parent) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showWhatDiffersBrowser(@Nullable Component parent, Collection<Change> changes, @Nls String title) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends CommittedChangeList, U extends ChangeBrowserSettings> T chooseCommittedChangeList(CommittedChangesProvider<T, U> provider,
|
||||
RepositoryLocation location) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openCommittedChangesTab(AbstractVcs vcs, VirtualFile root, ChangeBrowserSettings settings, int maxCount, String title) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openCommittedChangesTab(CommittedChangesProvider provider,
|
||||
RepositoryLocation location,
|
||||
ChangeBrowserSettings settings,
|
||||
int maxCount,
|
||||
String title) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<VirtualFile> showMergeDialog(List<VirtualFile> files,
|
||||
MergeProvider provider,
|
||||
@NotNull MergeDialogCustomizer mergeDialogCustomizer) {
|
||||
myMergeDialogShown = true;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public boolean mergeDialogWasShown() {
|
||||
return myMergeDialogShown;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<VirtualFile> showMergeDialog(List<VirtualFile> files, MergeProvider provider) {
|
||||
return showMergeDialog(files, provider, new MergeDialogCustomizer());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<VirtualFile> showMergeDialog(List<VirtualFile> files) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFileHistory(VcsHistoryProvider vcsHistoryProvider, FilePath path, AbstractVcs vcs, String repositoryPath) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFileHistory(VcsHistoryProvider vcsHistoryProvider,
|
||||
AnnotationProvider annotationProvider,
|
||||
FilePath path,
|
||||
String repositoryPath,
|
||||
AbstractVcs vcs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showRollbackChangesDialog(List<Change> changes) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<VirtualFile> selectFilesToProcess(List<VirtualFile> files,
|
||||
String title,
|
||||
@Nullable String prompt,
|
||||
String singleFileTitle,
|
||||
String singleFilePromptTemplate,
|
||||
VcsShowConfirmationOption confirmationOption) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<FilePath> selectFilePathsToProcess(List<FilePath> files,
|
||||
String title,
|
||||
@Nullable String prompt,
|
||||
String singleFileTitle,
|
||||
String singleFilePromptTemplate,
|
||||
VcsShowConfirmationOption confirmationOption) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commitChanges(@NotNull List<Change> changes, @NotNull LocalChangeList initialChangeList,
|
||||
@NotNull String commitMessage, @Nullable CommitExecutor executor) {
|
||||
if (myCommitHandler != null) {
|
||||
return myCommitHandler.commit(commitMessage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void registerHandler(CommitHandler handler) {
|
||||
myCommitHandler = handler;
|
||||
}
|
||||
|
||||
public interface CommitHandler {
|
||||
boolean commit(String commitMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,11 +44,26 @@ public class TestNotificator extends Notificator {
|
||||
notify(notificationGroup, title, message, type, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyError(@NotNull String title, @NotNull String message, @Nullable NotificationListener listener) {
|
||||
myLastNotification = createNotification(title, message, NotificationType.ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifySuccess(@NotNull String title, @NotNull String message) {
|
||||
myLastNotification = createNotification(title, message, NotificationType.INFORMATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyWeakWarning(@NotNull String title, @NotNull String message, @Nullable NotificationListener listener) {
|
||||
myLastNotification = createNotification(title, message, NotificationType.WARNING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStrongWarning(@NotNull String title, @NotNull String content, @Nullable NotificationListener listener) {
|
||||
myLastNotification = createNotification(title, content, NotificationType.WARNING);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Notification createNotification(@NotNull String title, @NotNull String message, NotificationType type) {
|
||||
return new Notification(TEST_NOTIFICATION_GROUP, title, message, type);
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
package git4idea.tests.rebase;
|
||||
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.rebase.GitRebaser;
|
||||
import git4idea.tests.GitTest;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
@@ -41,7 +43,7 @@ public class GitRebaserReorderCommitsTest extends GitTest {
|
||||
|
||||
@BeforeMethod @Override protected void setUp(Method testMethod) throws Exception {
|
||||
super.setUp(testMethod);
|
||||
myRebaser = new GitRebaser(myProject, null);
|
||||
myRebaser = new GitRebaser(myProject, ServiceManager.getService(Git.class), null);
|
||||
myRoot = myRepo.getVFRootDir();
|
||||
myFirstCommit = makeCommit();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import com.intellij.openapi.vcs.*;
|
||||
import com.intellij.openapi.vcs.annotate.AnnotationProvider;
|
||||
import com.intellij.openapi.vcs.annotate.FileAnnotation;
|
||||
import com.intellij.openapi.vcs.changes.Change;
|
||||
import com.intellij.openapi.vcs.changes.CommitExecutor;
|
||||
import com.intellij.openapi.vcs.changes.LocalChangeList;
|
||||
import com.intellij.openapi.vcs.history.VcsFileRevision;
|
||||
import com.intellij.openapi.vcs.history.VcsHistoryProvider;
|
||||
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
|
||||
@@ -168,6 +170,12 @@ public class HgMockVcsHelper extends AbstractVcsHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commitChanges(@NotNull List<Change> changes, @NotNull LocalChangeList initialChangeList,
|
||||
@NotNull String commitMessage, @Nullable CommitExecutor executor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void addListener(VcsHelperListener listener) {
|
||||
myListeners.add(listener);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user