mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
git4idea: IDEA-55724: initial support for checking out multiple branches added
This commit is contained in:
@@ -104,6 +104,9 @@
|
||||
<projectService
|
||||
serviceInterface="git4idea.history.browser.GitProjectLogManager"
|
||||
serviceImplementation="git4idea.history.browser.GitProjectLogManager"/>
|
||||
<projectService
|
||||
serviceInterface="git4idea.checkout.branches.GitBranchConfigurations"
|
||||
serviceImplementation="git4idea.checkout.branches.GitBranchConfigurations"/>
|
||||
<applicationService
|
||||
serviceInterface="git4idea.config.GitVcsApplicationSettings"
|
||||
serviceImplementation="git4idea.config.GitVcsApplicationSettings"/>
|
||||
|
||||
@@ -491,6 +491,21 @@ public class GitUtil {
|
||||
return relativePath(VfsUtil.virtualToIoFile(root), VfsUtil.virtualToIoFile(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relative path
|
||||
*
|
||||
* @param root a root file
|
||||
* @param file a virtual file
|
||||
* @return a relative path
|
||||
* @throws IllegalArgumentException if path is not under root.
|
||||
*/
|
||||
public static String relativeOrFullPath(final VirtualFile root, VirtualFile file) {
|
||||
if (root == null) {
|
||||
file.getPath();
|
||||
}
|
||||
return relativePath(VfsUtil.virtualToIoFile(root), VfsUtil.virtualToIoFile(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relative path
|
||||
*
|
||||
|
||||
@@ -51,6 +51,7 @@ import git4idea.changes.GitCommittedChangeListProvider;
|
||||
import git4idea.changes.GitOutgoingChangesProvider;
|
||||
import git4idea.checkin.GitCheckinEnvironment;
|
||||
import git4idea.checkin.GitCommitAndPushExecutor;
|
||||
import git4idea.checkout.branches.GitBranchConfigurations;
|
||||
import git4idea.commands.GitCommand;
|
||||
import git4idea.commands.GitSimpleHandler;
|
||||
import git4idea.config.GitVcsConfigurable;
|
||||
@@ -510,6 +511,7 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
|
||||
myReferenceTracker.activate();
|
||||
GitUsersComponent.getInstance(myProject).activate();
|
||||
GitProjectLogManager.getInstance(myProject).activate();
|
||||
GitBranchConfigurations.getInstance(myProject).activate();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -517,6 +519,7 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
|
||||
*/
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
GitBranchConfigurations.getInstance(myProject).deactivate();
|
||||
if (myRootTracker != null) {
|
||||
myRootTracker.dispose();
|
||||
myRootTracker = null;
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.checkout.branches;
|
||||
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The branch configuration wrapper
|
||||
*/
|
||||
public class GitBranchConfiguration {
|
||||
/**
|
||||
* The configuration
|
||||
*/
|
||||
final GitBranchConfigurations myConfig;
|
||||
/**
|
||||
* The name of configuration
|
||||
*/
|
||||
private String myName;
|
||||
/**
|
||||
* The auto-detected flag
|
||||
*/
|
||||
private boolean myAutoDetected;
|
||||
/**
|
||||
* The root to reference mapping
|
||||
*/
|
||||
private HashMap<String, String> myReferences = new HashMap<String, String>();
|
||||
/**
|
||||
* The
|
||||
*/
|
||||
@Nullable private GitBranchConfigurations.BranchChanges myChanges;
|
||||
|
||||
/**
|
||||
* The configuration with the specified name
|
||||
*
|
||||
* @param config
|
||||
* @param name
|
||||
*/
|
||||
GitBranchConfiguration(GitBranchConfigurations config, String name) {
|
||||
myConfig = config;
|
||||
myName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of configuration
|
||||
*/
|
||||
public String getName() {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
return myName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of configuration
|
||||
*/
|
||||
public void setName(String name) {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
assert name != null;
|
||||
if (name.equals(myName)) {
|
||||
return;
|
||||
}
|
||||
assert myConfig.configurationRenamed(this, myName, name) : "Configuration should have existed";
|
||||
myName = name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the copy of list of branches for configuration
|
||||
*/
|
||||
public Map<String, String> getReferences() {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
return new HashMap<String, String>(myReferences);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBranch(String root, String reference) {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
myReferences.put(root, reference);
|
||||
}
|
||||
}
|
||||
|
||||
public String getReference(String root) {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
return myReferences.get(root);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearReferences() {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
myReferences.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set changes to the configuration. Note that changes are assumed not to change
|
||||
*
|
||||
* @param changes
|
||||
*/
|
||||
void setChanges(@Nullable GitBranchConfigurations.BranchChanges changes) {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
myChanges = changes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return get copy of stored changes descriptor
|
||||
*/
|
||||
@Nullable
|
||||
GitBranchConfigurations.BranchChanges getChanges() {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
if (myChanges == null) {
|
||||
return null;
|
||||
}
|
||||
GitBranchConfigurations.BranchChanges rc = new GitBranchConfigurations.BranchChanges();
|
||||
XmlSerializerUtil.copyBean(myChanges, rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set mapping for roots
|
||||
*
|
||||
* @param references the new mapping
|
||||
*/
|
||||
public void setReferences(Map<String, String> references) {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
myReferences.clear();
|
||||
myReferences.putAll(references);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the configuration was auto-detected
|
||||
*/
|
||||
public boolean isAutoDetected() {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
return myAutoDetected;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if configuration was auto-detected
|
||||
*
|
||||
* @param autoDetected new value
|
||||
*/
|
||||
public void setAutoDetected(boolean autoDetected) {
|
||||
synchronized (myConfig.getStateLock()) {
|
||||
myAutoDetected = autoDetected;
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="git4idea.checkout.branches.GitBranchConfigurationChangedDialog">
|
||||
<grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="738" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="9c9f2" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="<html>The current branch configuration has been changed.<br/>Do you want to update the current branch configuration, rename it, or save it as a new branch configuration?</hml>"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="d959d" class="javax.swing.JLabel">
|
||||
<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"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="b591c"/>
|
||||
<text value="&Name"/>
|
||||
</properties>
|
||||
</component>
|
||||
<scrollpane id="14dad" class="com.intellij.ui.components.JBScrollPane">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="b7cc6" class="com.intellij.ui.table.JBTable" binding="myTable">
|
||||
<constraints/>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</scrollpane>
|
||||
<component id="b591c" class="javax.swing.JTextField" binding="myNameTextField" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.checkout.branches;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.DocumentAdapter;
|
||||
import com.intellij.ui.table.JBTable;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* The dialog shown to inform of changes in the current configuration
|
||||
*/
|
||||
public class GitBranchConfigurationChangedDialog extends DialogWrapper {
|
||||
/**
|
||||
* The new configuration exit code
|
||||
*/
|
||||
private static final int NEW_CONFIGURATION = NEXT_USER_EXIT_CODE;
|
||||
/**
|
||||
* The name text field
|
||||
*/
|
||||
private JTextField myNameTextField;
|
||||
/**
|
||||
* The table that describes configuration changes
|
||||
*/
|
||||
private JBTable myTable;
|
||||
/**
|
||||
* The root panel
|
||||
*/
|
||||
private JPanel myRootPanel;
|
||||
/**
|
||||
* The base directory for the project
|
||||
*/
|
||||
private final File myBaseFile;
|
||||
/**
|
||||
* The branch descriptors to show in the table
|
||||
*/
|
||||
private final List<BranchDescriptor> myBranches;
|
||||
/**
|
||||
* The new configuration action
|
||||
*/
|
||||
private DialogWrapperExitAction myNewAction;
|
||||
|
||||
|
||||
/**
|
||||
* The constructor from project
|
||||
*
|
||||
* @param project the project to use to display window
|
||||
* @param config
|
||||
* @param branches
|
||||
* @param names
|
||||
*/
|
||||
protected GitBranchConfigurationChangedDialog(Project project,
|
||||
final GitBranchConfiguration config,
|
||||
List<BranchDescriptor> branches, final Set<String> names) {
|
||||
super(project, true);
|
||||
setTitle("Git Branch Configuration Changed");
|
||||
myBranches = branches;
|
||||
VirtualFile baseDir = project.getBaseDir();
|
||||
myBaseFile = baseDir == null ? null : new File(baseDir.getPath());
|
||||
myTable.setModel(new DescriptorTableModel());
|
||||
myNameTextField.setText(config.getName());
|
||||
myNewAction = new DialogWrapperExitAction("New Configuration", NEW_CONFIGURATION);
|
||||
myNameTextField.getDocument().addDocumentListener(new DocumentAdapter() {
|
||||
@Override
|
||||
protected void textChanged(DocumentEvent e) {
|
||||
String text = myNameTextField.getText().trim();
|
||||
if (text.length() == 0) {
|
||||
setError("Empty configuration name is not allowed.");
|
||||
}
|
||||
else if (text.equals(config.getName())) {
|
||||
setError(null);
|
||||
myNewAction.setEnabled(false);
|
||||
}
|
||||
else if (names.contains(text)) {
|
||||
setError("There is another configuration with the same name");
|
||||
}
|
||||
else {
|
||||
setError(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setError(String s) {
|
||||
setErrorText(s);
|
||||
setOKActionEnabled(s == null);
|
||||
myNewAction.setEnabled(s == null);
|
||||
}
|
||||
});
|
||||
|
||||
setOKButtonText("Update");
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myRootPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the dialog in AWT thread and wait for its completion
|
||||
*
|
||||
* @param settings the settings to use
|
||||
* @param config the configuration to check
|
||||
* @param roots the roots collection
|
||||
* @return null if project is cancelled, or configuration that user has selected for storing the current state (created or updated)
|
||||
*/
|
||||
@Nullable
|
||||
static GitBranchConfiguration showDialog(final GitBranchConfigurations settings,
|
||||
final GitBranchConfiguration config,
|
||||
final List<VirtualFile> roots)
|
||||
throws VcsException {
|
||||
final Set<String> names = settings.getConfigurationNames();
|
||||
final Ref<String> name = new Ref<String>();
|
||||
final Ref<Integer> code = new Ref<Integer>();
|
||||
final List<BranchDescriptor> list = prepareBranchDescriptors(settings, config, roots);
|
||||
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GitBranchConfigurationChangedDialog d = new GitBranchConfigurationChangedDialog(settings.getProject(), config, list, names);
|
||||
d.show();
|
||||
code.set(d.getExitCode());
|
||||
name.set(d.myNameTextField.getText());
|
||||
}
|
||||
});
|
||||
if (code.get() == CANCEL_EXIT_CODE) {
|
||||
return null;
|
||||
}
|
||||
GitBranchConfiguration updateConfig;
|
||||
synchronized (settings.getStateLock()) {
|
||||
if (code.get() == OK_EXIT_CODE) {
|
||||
updateConfig = config;
|
||||
updateConfig.setName(name.get());
|
||||
}
|
||||
else if (code.get() == NEW_CONFIGURATION) {
|
||||
updateConfig = settings.createConfiguration(name.get());
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("Unexpected exit code: " + code.get());
|
||||
}
|
||||
updateConfig.clearReferences();
|
||||
for (BranchDescriptor d : list) {
|
||||
if (d.root != null) {
|
||||
updateConfig.setBranch(d.root.getPath(), d.actualRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
return updateConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected Action[] createActions() {
|
||||
return new Action[]{getOKAction(), myNewAction, getCancelAction()};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected String getDimensionServiceKey() {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
static List<BranchDescriptor> prepareBranchDescriptors(GitBranchConfigurations settings,
|
||||
GitBranchConfiguration config,
|
||||
List<VirtualFile> roots)
|
||||
throws VcsException {
|
||||
List<BranchDescriptor> rc = new ArrayList<BranchDescriptor>();
|
||||
Map<String, String> map = config.getReferences();
|
||||
for (VirtualFile root : roots) {
|
||||
BranchDescriptor d = new BranchDescriptor();
|
||||
d.root = root;
|
||||
d.actualRef = settings.describeRoot(root);
|
||||
d.storedRef = map.remove(root.getPath());
|
||||
rc.add(d);
|
||||
}
|
||||
for (Map.Entry<String, String> m : map.entrySet()) {
|
||||
BranchDescriptor d = new BranchDescriptor();
|
||||
d.storedRoot = m.getKey();
|
||||
d.storedRef = m.getValue();
|
||||
rc.add(d);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* The table model that describes roots
|
||||
*/
|
||||
private class DescriptorTableModel extends AbstractTableModel {
|
||||
/**
|
||||
* The relative path for the root
|
||||
*/
|
||||
private static final int ROOT_COLUMN = 0;
|
||||
/**
|
||||
* The actual reference
|
||||
*/
|
||||
private static final int ACTUAL_REF_COLUMN = 1;
|
||||
/**
|
||||
* The reference stored in the configuration
|
||||
*/
|
||||
private static final int STORED_REF_COLUMN = 2;
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return myBranches.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return STORED_REF_COLUMN + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
BranchDescriptor d = myBranches.get(rowIndex);
|
||||
switch (columnIndex) {
|
||||
case ROOT_COLUMN:
|
||||
return d.getRoot(myBaseFile);
|
||||
case ACTUAL_REF_COLUMN:
|
||||
return d.actualRef == null ? "" : d.actualRef;
|
||||
case STORED_REF_COLUMN:
|
||||
return d.storedRef == null ? "" : d.storedRef;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected column");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
switch (column) {
|
||||
case ROOT_COLUMN:
|
||||
return "Vcs Root";
|
||||
case ACTUAL_REF_COLUMN:
|
||||
return "Actual";
|
||||
case STORED_REF_COLUMN:
|
||||
return "Configured";
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected column");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The descriptor for the branch
|
||||
*/
|
||||
private static class BranchDescriptor {
|
||||
/**
|
||||
* The vcs root
|
||||
*/
|
||||
VirtualFile root;
|
||||
/**
|
||||
* The branch information stored in the configuration
|
||||
*/
|
||||
String storedRoot;
|
||||
/**
|
||||
* The stored reference
|
||||
*/
|
||||
String storedRef;
|
||||
/**
|
||||
* The actual reference
|
||||
*/
|
||||
String actualRef;
|
||||
|
||||
public String getRoot(final File baseFile) {
|
||||
String path = root == null ? storedRoot : root.getPath();
|
||||
String relative = baseFile == null ? path : FileUtil.getRelativePath(baseFile, new File(path));
|
||||
return relative == null ? path : relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,898 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.checkout.branches;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ex.ProjectManagerEx;
|
||||
import com.intellij.openapi.ui.MessageType;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.vcs.FileStatus;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.changes.*;
|
||||
import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager;
|
||||
import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.wm.ToolWindowManager;
|
||||
import com.intellij.util.EventDispatcher;
|
||||
import git4idea.GitBranch;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.commands.GitCommand;
|
||||
import git4idea.commands.GitSimpleHandler;
|
||||
import git4idea.merge.GitMergeUtil;
|
||||
import git4idea.rebase.GitRebaseUtils;
|
||||
import git4idea.ui.GitUIUtil;
|
||||
import git4idea.vfs.GitReferenceListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Git branch configurations settings and project level state
|
||||
*/
|
||||
@State(
|
||||
name = "Git.Branch.Configurations",
|
||||
storages = {@Storage(
|
||||
id = "ws",
|
||||
file = "$WORKSPACE_FILE$")})
|
||||
public class GitBranchConfigurations implements PersistentStateComponent<GitBranchConfigurations.State>, Disposable {
|
||||
/**
|
||||
* The logger
|
||||
*/
|
||||
private static final Logger LOG = Logger.getInstance(GitBranchConfigurations.class.getName());
|
||||
/**
|
||||
* The comparator for branch configuration by name
|
||||
*/
|
||||
private static final Comparator<BranchConfiguration> CONFIGURATION_COMPARATOR = new Comparator<BranchConfiguration>() {
|
||||
@Override
|
||||
public int compare(BranchConfiguration o1, BranchConfiguration o2) {
|
||||
return o1.NAME.compareTo(o2.NAME);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The comparator for branch information by root
|
||||
*/
|
||||
private static final Comparator<BranchInfo> BRANCH_INFO_COMPARATOR = new Comparator<BranchInfo>() {
|
||||
@Override
|
||||
public int compare(BranchInfo o1, BranchInfo o2) {
|
||||
return o1.ROOT.compareTo(o2.ROOT);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The project
|
||||
*/
|
||||
private final Project myProject;
|
||||
/**
|
||||
* The git vcs
|
||||
*/
|
||||
private final GitVcs myVcs;
|
||||
/**
|
||||
* The shelve manager instance
|
||||
*/
|
||||
private final ShelveChangesManager myShelveManager;
|
||||
/**
|
||||
* The dirty scope manager
|
||||
*/
|
||||
private final VcsDirtyScopeManager myDirtyScopeManager;
|
||||
/**
|
||||
* Change manager
|
||||
*/
|
||||
private final ChangeListManagerEx myChangeManager;
|
||||
/**
|
||||
* Project manager
|
||||
*/
|
||||
private final ProjectManagerEx myProjectManager;
|
||||
/**
|
||||
* The state lock
|
||||
*/
|
||||
private final Object myStateLock = new Object();
|
||||
/**
|
||||
* The set of configurations
|
||||
*/
|
||||
private final HashMap<String, GitBranchConfiguration> myConfigurations = new HashMap<String, GitBranchConfiguration>();
|
||||
/**
|
||||
* Create event dispatcher for configuration events
|
||||
*/
|
||||
private final EventDispatcher<GitBranchConfigurationsListener> myListeners =
|
||||
EventDispatcher.create(GitBranchConfigurationsListener.class);
|
||||
/**
|
||||
* The current configuration
|
||||
*/
|
||||
private GitBranchConfiguration myCurrentConfiguration;
|
||||
/**
|
||||
* The reference listener
|
||||
*/
|
||||
private final GitReferenceListener myReferenceListener;
|
||||
/**
|
||||
* The current status (cached, invalidated when references change)
|
||||
*/
|
||||
private SpecialStatus myCurrentStatus;
|
||||
/**
|
||||
* The collection of git roots
|
||||
*/
|
||||
private List<VirtualFile> myGitRoots = Collections.emptyList();
|
||||
/**
|
||||
* If true, checkout background process is in progress
|
||||
*/
|
||||
private boolean myCheckoutIsInProgress = false;
|
||||
/**
|
||||
* The widget uninstall action (on deactivate)
|
||||
*/
|
||||
private Runnable myWidgetUninstall;
|
||||
/**
|
||||
* Listener for changes
|
||||
*/
|
||||
private final ChangeListAdapter myChangesListener;
|
||||
|
||||
/**
|
||||
* The constructor used to dependency injection
|
||||
*
|
||||
* @param project the project
|
||||
* @param shelveManager the shelve manager
|
||||
* @param dirtyScopeManager the dirty scope manager
|
||||
* @param changeManager the change manager
|
||||
* @param projectManager the project manager
|
||||
*/
|
||||
public GitBranchConfigurations(Project project, ShelveChangesManager shelveManager,
|
||||
VcsDirtyScopeManager dirtyScopeManager,
|
||||
ChangeListManagerEx changeManager,
|
||||
ProjectManagerEx projectManager) {
|
||||
myProject = project;
|
||||
myVcs = GitVcs.getInstance(project);
|
||||
myShelveManager = shelveManager;
|
||||
myDirtyScopeManager = dirtyScopeManager;
|
||||
myChangeManager = changeManager;
|
||||
myProjectManager = projectManager;
|
||||
myReferenceListener = new GitReferenceListener() {
|
||||
@Override
|
||||
public void referencesChanged(VirtualFile root) {
|
||||
GitBranchConfigurations.this.referencesChanged();
|
||||
}
|
||||
};
|
||||
Disposer.register(myProject, this);
|
||||
myChangesListener = new ChangeListAdapter() {
|
||||
@Override
|
||||
public void changesRemoved(Collection<Change> changes, ChangeList fromList) {
|
||||
GitBranchConfigurations.this.referencesChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changesAdded(Collection<Change> changes, ChangeList toList) {
|
||||
GitBranchConfigurations.this.referencesChanged();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener
|
||||
*
|
||||
* @param l the listener
|
||||
*/
|
||||
public void addConfigurationListener(GitBranchConfigurationsListener l) {
|
||||
myListeners.addListener(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listener
|
||||
*
|
||||
* @param l the listener
|
||||
*/
|
||||
public void removeConfigurationListener(GitBranchConfigurationsListener l) {
|
||||
myListeners.removeListener(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle reference change, also notified when roots changed.
|
||||
*/
|
||||
public void referencesChanged() {
|
||||
synchronized (myStateLock) {
|
||||
updateRootCollection();
|
||||
updateSpecialStatus();
|
||||
fireReferencesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire that references changed
|
||||
*/
|
||||
private void fireReferencesChanged() {
|
||||
myListeners.getMulticaster().referencesChanged();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update collections of roots
|
||||
*/
|
||||
private void updateRootCollection() {
|
||||
try {
|
||||
myGitRoots = GitUtil.getGitRoots(myProject, myVcs);
|
||||
}
|
||||
catch (VcsException e) {
|
||||
LOG.warn("Empty list of roots is detected", e);
|
||||
myGitRoots = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update special status
|
||||
*/
|
||||
private void updateSpecialStatus() {
|
||||
SpecialStatus p = myCurrentStatus;
|
||||
myCurrentStatus = calculateSpecialStatus();
|
||||
if (p != myCurrentStatus) {
|
||||
myListeners.getMulticaster().specialStatusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate component
|
||||
*/
|
||||
public void activate() {
|
||||
myVcs.addGitReferenceListener(myReferenceListener);
|
||||
myChangeManager.addChangeListListener(myChangesListener);
|
||||
synchronized (myStateLock) {
|
||||
updateRootCollection();
|
||||
if (myCurrentConfiguration == null) {
|
||||
if (calculateSpecialStatus() == SpecialStatus.NORMAL) {
|
||||
try {
|
||||
detectLocals();
|
||||
}
|
||||
catch (VcsException e) {
|
||||
LOG.error("Exception during detecting local configurations", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
referencesChanged();
|
||||
}
|
||||
if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
|
||||
myWidgetUninstall = GitBranchesWidget.install(myProject, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate component
|
||||
*/
|
||||
public void deactivate() {
|
||||
myVcs.removeGitReferenceListener(myReferenceListener);
|
||||
myChangeManager.removeChangeListListener(myChangesListener);
|
||||
if (myWidgetUninstall != null) {
|
||||
myWidgetUninstall.run();
|
||||
myWidgetUninstall = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get component instance
|
||||
*
|
||||
* @param project a context project
|
||||
* @return the git settings
|
||||
*/
|
||||
public static GitBranchConfigurations getInstance(Project project) {
|
||||
return ServiceManager.getService(project, GitBranchConfigurations.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
deactivate();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext"})
|
||||
@Override
|
||||
public State getState() {
|
||||
synchronized (myStateLock) {
|
||||
State rc = new State();
|
||||
rc.CURRENT = myCurrentConfiguration == null ? null : myCurrentConfiguration.getName();
|
||||
ArrayList<BranchConfiguration> cs = new ArrayList<BranchConfiguration>(myConfigurations.size());
|
||||
for (GitBranchConfiguration ci : myConfigurations.values()) {
|
||||
BranchConfiguration c = new BranchConfiguration();
|
||||
c.NAME = ci.getName();
|
||||
Map<String, String> map = ci.getReferences();
|
||||
ArrayList<BranchInfo> bs = new ArrayList<BranchInfo>(map.size());
|
||||
for (Map.Entry<String, String> m : map.entrySet()) {
|
||||
BranchInfo b = new BranchInfo();
|
||||
b.ROOT = m.getKey();
|
||||
b.REFERENCE = m.getValue();
|
||||
bs.add(b);
|
||||
}
|
||||
c.BRANCHES = bs.toArray(new BranchInfo[bs.size()]);
|
||||
Arrays.sort(c.BRANCHES, BRANCH_INFO_COMPARATOR);
|
||||
c.CHANGES = ci.getChanges();
|
||||
c.IS_AUTO_DETECTED = ci.isAutoDetected();
|
||||
cs.add(c);
|
||||
}
|
||||
rc.CONFIGURATIONS = cs.toArray(new BranchConfiguration[cs.size()]);
|
||||
Arrays.sort(rc.CONFIGURATIONS, CONFIGURATION_COMPARATOR);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext"})
|
||||
@Override
|
||||
public void loadState(State state) {
|
||||
synchronized (myStateLock) {
|
||||
myConfigurations.clear();
|
||||
for (BranchConfiguration bc : state.CONFIGURATIONS) {
|
||||
GitBranchConfiguration n = new GitBranchConfiguration(this, bc.NAME);
|
||||
myConfigurations.put(n.getName(), n);
|
||||
for (BranchInfo bi : bc.BRANCHES) {
|
||||
n.setBranch(bi.ROOT, bi.REFERENCE);
|
||||
}
|
||||
myConfigurations.put(bc.NAME, n);
|
||||
n.setAutoDetected(bc.IS_AUTO_DETECTED);
|
||||
}
|
||||
if (myCurrentConfiguration == null) {
|
||||
myCurrentConfiguration = myConfigurations.get(state.CURRENT);
|
||||
}
|
||||
else {
|
||||
myCurrentConfiguration = myConfigurations.get(myCurrentConfiguration.getName());
|
||||
if (myCurrentConfiguration == null) {
|
||||
myCurrentConfiguration = myConfigurations.get(state.CURRENT);
|
||||
}
|
||||
}
|
||||
fireCurrentConfigurationChanged();
|
||||
fireConfigurationsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the candidate remote configurations
|
||||
*/
|
||||
List<String> getRemotesCandidates() {
|
||||
try {
|
||||
final List<VirtualFile> roots;
|
||||
synchronized (myStateLock) {
|
||||
roots = myGitRoots;
|
||||
}
|
||||
return detectConfigurations(false, roots);
|
||||
}
|
||||
catch (VcsException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the overall special status
|
||||
*/
|
||||
public SpecialStatus getSpecialStatus() {
|
||||
synchronized (myStateLock) {
|
||||
return myCurrentStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the calculated overall special status
|
||||
*/
|
||||
private SpecialStatus calculateSpecialStatus() {
|
||||
synchronized (myStateLock) {
|
||||
if (myCheckoutIsInProgress) {
|
||||
return SpecialStatus.CHECKOUT_IN_PROGRESS;
|
||||
}
|
||||
for (VirtualFile root : myGitRoots) {
|
||||
if (GitRebaseUtils.isRebaseInTheProgress(root)) {
|
||||
return SpecialStatus.REBASING;
|
||||
}
|
||||
if (GitMergeUtil.isMergeInTheProgress(root)) {
|
||||
return SpecialStatus.MERGING;
|
||||
}
|
||||
if (root.findChild(".gitmodules") != null) {
|
||||
return SpecialStatus.SUBMODULES;
|
||||
}
|
||||
}
|
||||
for (LocalChangeList changeList : myChangeManager.getChangeListsCopy()) {
|
||||
for (Change change : changeList.getChanges()) {
|
||||
if (change.getFileStatus() == FileStatus.MERGED_WITH_CONFLICTS) {
|
||||
return SpecialStatus.MERGING;
|
||||
}
|
||||
}
|
||||
}
|
||||
return myGitRoots.size() == 0 ? SpecialStatus.NON_GIT : SpecialStatus.NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect local branch configurations
|
||||
*
|
||||
* @throws VcsException if there is a problem with detecting
|
||||
*/
|
||||
private void detectLocals() throws VcsException {
|
||||
synchronized (myStateLock) {
|
||||
HashMap<VirtualFile, String> currents = new HashMap<VirtualFile, String>();
|
||||
for (VirtualFile root : myGitRoots) {
|
||||
GitBranch current = GitBranch.current(myProject, root);
|
||||
currents.put(root, current == null ? "" : current.getName());
|
||||
}
|
||||
if (myConfigurations.isEmpty()) {
|
||||
List<String> locals = detectConfigurations(true, myGitRoots);
|
||||
if (locals.isEmpty()) {
|
||||
// no commits
|
||||
locals.add("master");
|
||||
}
|
||||
for (String localName : locals) {
|
||||
GitBranchConfiguration c = createConfiguration(localName);
|
||||
c.setAutoDetected(true);
|
||||
boolean currentsMatched = true;
|
||||
for (VirtualFile root : myGitRoots) {
|
||||
c.setBranch(root.getPath(), localName);
|
||||
currentsMatched &= currents.get(root).equals(localName);
|
||||
}
|
||||
if (currentsMatched) {
|
||||
myCurrentConfiguration = c;
|
||||
}
|
||||
}
|
||||
if (myCurrentConfiguration == null) {
|
||||
// the configuration does not matches any standard, there could be no configurations with spaces at this point
|
||||
// since it is not allowed branch name.
|
||||
String name = "Unknown 1";
|
||||
GitBranchConfiguration c = createConfiguration(name);
|
||||
for (VirtualFile root : myGitRoots) {
|
||||
c.setBranch(root.getPath(), describeRoot(root));
|
||||
}
|
||||
}
|
||||
}
|
||||
fireCurrentConfigurationChanged();
|
||||
fireConfigurationsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The configurations changed
|
||||
*/
|
||||
private void fireConfigurationsChanged() {
|
||||
myListeners.getMulticaster().configurationsChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* The current configuration changed
|
||||
*/
|
||||
private void fireCurrentConfigurationChanged() {
|
||||
myListeners.getMulticaster().currentConfigurationChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe vcs root
|
||||
*
|
||||
* @param root the root to describe
|
||||
* @return the current reference
|
||||
* @throws VcsException if there is a problem with describing the root
|
||||
*/
|
||||
String describeRoot(VirtualFile root) throws VcsException {
|
||||
GitBranch current = GitBranch.current(myProject, root);
|
||||
if (current == null) {
|
||||
// It is on the tag or specific commit. In future, support for submodules should be added.
|
||||
return detectTag(root);
|
||||
}
|
||||
else {
|
||||
return current.getName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tag name for the head
|
||||
*
|
||||
* @param root the root to describe
|
||||
* @return the commit expression that describes root state
|
||||
*/
|
||||
private String detectTag(VirtualFile root) {
|
||||
try {
|
||||
GitSimpleHandler h = new GitSimpleHandler(myProject, root, GitCommand.DESCRIBE);
|
||||
h.addParameters("--tags", "--exact", "HEAD");
|
||||
h.setNoSSH(true);
|
||||
return h.run().trim();
|
||||
}
|
||||
catch (VcsException e) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("describe HEAD failed for root: " + root.getPath());
|
||||
}
|
||||
GitSimpleHandler h = new GitSimpleHandler(myProject, root, GitCommand.SHOW);
|
||||
h.setNoSSH(true);
|
||||
h.addParameters("--pretty=format:%H", "HEAD");
|
||||
try {
|
||||
return h.run().trim();
|
||||
}
|
||||
catch (VcsException e1) {
|
||||
throw new RuntimeException("Unexpected exception at this time, the failure should have been detected at current(): ", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect possible configurations
|
||||
*
|
||||
* @param roots
|
||||
* @return a sorted list of branches
|
||||
* @throws VcsException if there is a problem with running git
|
||||
*/
|
||||
private List<String> detectConfigurations(boolean local, final List<VirtualFile> roots) throws VcsException {
|
||||
HashSet<String> all = new HashSet<String>();
|
||||
HashSet<String> forRoot = new HashSet<String>();
|
||||
boolean isFirst = true;
|
||||
for (VirtualFile root : roots) {
|
||||
forRoot.clear();
|
||||
GitBranch.listAsStrings(myProject, root, !local, local, forRoot, null);
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
all.addAll(forRoot);
|
||||
}
|
||||
else {
|
||||
all.retainAll(forRoot);
|
||||
}
|
||||
}
|
||||
ArrayList<String> rc = new ArrayList<String>(all);
|
||||
Collections.sort(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the used vcs
|
||||
*/
|
||||
GitVcs getVcs() {
|
||||
return myVcs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the context project
|
||||
*/
|
||||
Project getProject() {
|
||||
return myProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current configuration
|
||||
* @throws VcsException if there is a problem with configurations
|
||||
*/
|
||||
GitBranchConfiguration getCurrentConfiguration() throws VcsException {
|
||||
synchronized (myStateLock) {
|
||||
if (myCurrentConfiguration == null) {
|
||||
throw new VcsException("The current configuration is not yet detected");
|
||||
}
|
||||
return myCurrentConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the configuration names
|
||||
*/
|
||||
Set<String> getConfigurationNames() {
|
||||
synchronized (myStateLock) {
|
||||
return new HashSet<String>(myConfigurations.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new branch configuration
|
||||
*
|
||||
* @param name the configuration name
|
||||
* @return the created configuration
|
||||
*/
|
||||
@NotNull
|
||||
GitBranchConfiguration createConfiguration(String name) {
|
||||
synchronized (myStateLock) {
|
||||
if (myConfigurations.containsKey(name)) {
|
||||
throw new IllegalStateException("The name " + name + " is already used");
|
||||
}
|
||||
GitBranchConfiguration c = new GitBranchConfiguration(this, name);
|
||||
myConfigurations.put(name, c);
|
||||
fireConfigurationsChanged();
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find configuration by name
|
||||
*
|
||||
* @param name the name to use
|
||||
* @return the configuration by name
|
||||
* @throws VcsException if there is an error in the state
|
||||
*/
|
||||
@Nullable
|
||||
GitBranchConfiguration getConfiguration(String name) throws VcsException {
|
||||
synchronized (myStateLock) {
|
||||
return myConfigurations.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the state lock for branch configurations
|
||||
*/
|
||||
Object getStateLock() {
|
||||
return myStateLock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if configuration with specified name already exists
|
||||
*
|
||||
* @param name the name to check
|
||||
* @return true if the configuration exists
|
||||
*/
|
||||
boolean hasConfigurationName(String name) {
|
||||
synchronized (myStateLock) {
|
||||
return myConfigurations.containsKey(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current configuration
|
||||
*
|
||||
* @param newConfiguration the new current configuration
|
||||
*/
|
||||
void setCurrentConfiguration(GitBranchConfiguration newConfiguration) {
|
||||
synchronized (myStateLock) {
|
||||
assert myConfigurations.get(newConfiguration.getName()) == newConfiguration;
|
||||
myCurrentConfiguration = newConfiguration;
|
||||
fireCurrentConfigurationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the shelve manager from project
|
||||
*/
|
||||
ShelveChangesManager getShelveManager() {
|
||||
return myShelveManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove configuration
|
||||
*
|
||||
* @param toRemove the removed configuration
|
||||
*/
|
||||
public void removeConfiguration(@NotNull GitBranchConfiguration toRemove) {
|
||||
synchronized (myStateLock) {
|
||||
if (toRemove == myCurrentConfiguration) {
|
||||
throw new IllegalArgumentException("Unable to remove the current configuration");
|
||||
}
|
||||
myConfigurations.remove(toRemove.getName());
|
||||
fireConfigurationsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start checkout process for selected configuration in the background
|
||||
*
|
||||
* @param configuration the selected configuration or null if new configuration is needed.
|
||||
* @param remote the remote pseudo configuration name
|
||||
* @param quick the quick checkout
|
||||
*/
|
||||
public void startCheckout(final GitBranchConfiguration configuration, final String remote, final boolean quick) {
|
||||
if (remote != null && configuration != null) {
|
||||
throw new IllegalArgumentException("Either remote or configuration to checkout must be null");
|
||||
}
|
||||
synchronized (myStateLock) {
|
||||
final SpecialStatus status = calculateSpecialStatus();
|
||||
if (status != SpecialStatus.NORMAL) {
|
||||
throw new IllegalStateException("Checkout cannot be started due to special status (it must have been checked in UI): " + status);
|
||||
}
|
||||
myCheckoutIsInProgress = true;
|
||||
updateSpecialStatus();
|
||||
}
|
||||
final String name = configuration != null ? configuration.getName() : remote == null ? "new configuration" : remote;
|
||||
final String title = "Checking out " + name;
|
||||
ProgressManager.getInstance().run(new Task.Backgroundable(myProject, title, false) {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
try {
|
||||
final GitCheckoutProcess process =
|
||||
new GitCheckoutProcess(GitBranchConfigurations.this, myProject, myShelveManager, myDirtyScopeManager, myChangeManager,
|
||||
myProjectManager, indicator, configuration, remote, quick);
|
||||
process.run();
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final List<VcsException> exceptions = process.getExceptions();
|
||||
String op = (process.isModify() ? "Modification" : "Checkout") + " of " + name;
|
||||
if (!exceptions.isEmpty()) {
|
||||
GitUIUtil.showTabErrors(myProject, title, exceptions);
|
||||
ToolWindowManager.getInstance(myProject).notifyByBalloon(
|
||||
ChangesViewContentManager.TOOLWINDOW_ID, MessageType.ERROR, op + " failed.");
|
||||
}
|
||||
else if (process.isCancelled()) {
|
||||
ToolWindowManager.getInstance(myProject).notifyByBalloon(
|
||||
ChangesViewContentManager.TOOLWINDOW_ID, MessageType.WARNING, op + " was cancelled by user.");
|
||||
|
||||
}
|
||||
else {
|
||||
ToolWindowManager.getInstance(myProject).notifyByBalloon(
|
||||
ChangesViewContentManager.TOOLWINDOW_ID, MessageType.INFO, op + " complete.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Throwable t) {
|
||||
LOG.error("Unexpected exception from checkout: ", t);
|
||||
}
|
||||
finally {
|
||||
synchronized (myStateLock) {
|
||||
myCheckoutIsInProgress = false;
|
||||
updateSpecialStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal notification about renamed configuration
|
||||
*
|
||||
* @param toRename configuration to rename
|
||||
* @param oldName the old name
|
||||
* @param newName the new name @return true if configuration actually renamed.
|
||||
*/
|
||||
boolean configurationRenamed(GitBranchConfiguration toRename, String oldName, String newName) {
|
||||
synchronized (myStateLock) {
|
||||
final GitBranchConfiguration c = myConfigurations.get(oldName);
|
||||
if (c == toRename) {
|
||||
myConfigurations.remove(oldName);
|
||||
myConfigurations.put(newName, c);
|
||||
}
|
||||
return c == toRename;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration state
|
||||
*/
|
||||
public static class State {
|
||||
/**
|
||||
* The current configuration
|
||||
*/
|
||||
public String CURRENT;
|
||||
/**
|
||||
* The branch configuration
|
||||
*/
|
||||
public BranchConfiguration[] CONFIGURATIONS = new BranchConfiguration[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* The branch configuration
|
||||
*/
|
||||
public static class BranchConfiguration {
|
||||
/**
|
||||
* If true, the configuration was auto-detected
|
||||
*/
|
||||
public boolean IS_AUTO_DETECTED;
|
||||
/**
|
||||
* The configuration name
|
||||
*/
|
||||
public String NAME;
|
||||
/**
|
||||
* The branch information
|
||||
*/
|
||||
public BranchInfo[] BRANCHES = new BranchInfo[0];
|
||||
/**
|
||||
* The branch changes
|
||||
*/
|
||||
public BranchChanges CHANGES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch mapping information
|
||||
*/
|
||||
public static class BranchInfo {
|
||||
/**
|
||||
* The vcs root for which information is stored
|
||||
*/
|
||||
public String ROOT;
|
||||
/**
|
||||
* The local branch or specific commit
|
||||
*/
|
||||
public String REFERENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The changes associated with the branch state
|
||||
*/
|
||||
public static class BranchChanges {
|
||||
/**
|
||||
* The path to shelve that keeps changes
|
||||
*/
|
||||
public String SHELVE_PATH;
|
||||
/**
|
||||
* Change list information
|
||||
*/
|
||||
public ChangeListInfo[] CHANGE_LISTS = new ChangeListInfo[0];
|
||||
/**
|
||||
* Information about distribution of changes among change lists
|
||||
*/
|
||||
public ChangeInfo[] CHANGES = new ChangeInfo[0];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The change list information
|
||||
*/
|
||||
public static class ChangeListInfo {
|
||||
/**
|
||||
* If true, the change list was a default change list
|
||||
*/
|
||||
public boolean IS_DEFAULT = false;
|
||||
/**
|
||||
* The change list name
|
||||
*/
|
||||
public String NAME;
|
||||
/**
|
||||
* The change list comment
|
||||
*/
|
||||
public String COMMENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change information. The change is identified by before path and after path (for deleted)
|
||||
*/
|
||||
public static class ChangeInfo {
|
||||
/**
|
||||
* The before path
|
||||
*/
|
||||
public String BEFORE_PATH;
|
||||
/**
|
||||
* The after path
|
||||
*/
|
||||
public String AFTER_PATH;
|
||||
/**
|
||||
* The name of change list to which change belong
|
||||
*/
|
||||
public String CHANGE_LIST_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* The special status for the roots
|
||||
*/
|
||||
public enum SpecialStatus {
|
||||
/**
|
||||
* Normal work tree, checkout is possible
|
||||
*/
|
||||
NORMAL,
|
||||
/**
|
||||
* Rebasing
|
||||
*/
|
||||
REBASING,
|
||||
/**
|
||||
* Merging
|
||||
*/
|
||||
MERGING,
|
||||
/**
|
||||
* Non git project
|
||||
*/
|
||||
NON_GIT,
|
||||
/**
|
||||
* The submodules are detected in the project
|
||||
*/
|
||||
SUBMODULES,
|
||||
/**
|
||||
* The background checkout process is in progress
|
||||
*/
|
||||
CHECKOUT_IN_PROGRESS
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.checkout.branches;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* The listener for changes in the branch configurations. The events listed here are needed for the current UI.
|
||||
*/
|
||||
public interface GitBranchConfigurationsListener extends EventListener {
|
||||
void configurationsChanged();
|
||||
|
||||
void specialStatusChanged();
|
||||
|
||||
void currentConfigurationChanged();
|
||||
|
||||
void referencesChanged();
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.checkout.branches;
|
||||
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.MessageType;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.ui.popup.ListPopup;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.IconLoader;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.wm.CustomStatusBarWidget;
|
||||
import com.intellij.openapi.wm.StatusBar;
|
||||
import com.intellij.openapi.wm.ToolWindowManager;
|
||||
import com.intellij.openapi.wm.WindowManager;
|
||||
import com.intellij.ui.awt.RelativePoint;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.GitVcs;
|
||||
import git4idea.commands.GitCommand;
|
||||
import git4idea.commands.GitHandlerUtil;
|
||||
import git4idea.commands.GitLineHandler;
|
||||
import git4idea.ui.GitUIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* The git branches widget
|
||||
*/
|
||||
public class GitBranchesWidget extends JLabel implements CustomStatusBarWidget {
|
||||
/**
|
||||
* The logger
|
||||
*/
|
||||
private static final Logger LOG = Logger.getInstance(GitBranchesWidget.class.getName());
|
||||
|
||||
/**
|
||||
* The ID of the widget
|
||||
*/
|
||||
public static final String ID = "git4idea.BranchConfigurations";
|
||||
/**
|
||||
* The listener
|
||||
*/
|
||||
final GitBranchConfigurationsListener myConfigurationsListener;
|
||||
/**
|
||||
* The project
|
||||
*/
|
||||
final Project myProject;
|
||||
/**
|
||||
* The configurations instance
|
||||
*/
|
||||
private final GitBranchConfigurations myConfigurations;
|
||||
/**
|
||||
* The selectable configurations. Null if invalidated or non-initialized
|
||||
*/
|
||||
private AnAction[] mySelectableConfigurations;
|
||||
/**
|
||||
* The candidate remote configurations. Null if invalidated or non-initialized
|
||||
*/
|
||||
private AnAction[] myRemoveConfigurations;
|
||||
/**
|
||||
* The status bar
|
||||
*/
|
||||
private StatusBar myStatusBar;
|
||||
/**
|
||||
* If true, the popup is enabled
|
||||
*/
|
||||
private boolean myPopupEnabled = false;
|
||||
/**
|
||||
* The action group for pop up
|
||||
*/
|
||||
private DefaultActionGroup myPopupActionGroup;
|
||||
/**
|
||||
* The current popup
|
||||
*/
|
||||
private ListPopup myPopup;
|
||||
/**
|
||||
* The default foreground color
|
||||
*/
|
||||
private final Color myDefaultForeground;
|
||||
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param project the project instance
|
||||
*/
|
||||
public GitBranchesWidget(Project project, GitBranchConfigurations configurations) {
|
||||
myProject = project;
|
||||
setBorder(WidgetBorder.INSTANCE);
|
||||
//setBorder(BorderFactory.createEtchedBorder());
|
||||
myConfigurations = configurations;
|
||||
myDefaultForeground = getForeground();
|
||||
myConfigurationsListener = new MyGitBranchConfigurationsListener();
|
||||
myConfigurations.addConfigurationListener(myConfigurationsListener);
|
||||
setIcon(IconLoader.findIcon("/icons/branch.png", getClass()));
|
||||
Disposer.register(myConfigurations, this);
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
showPopup();
|
||||
}
|
||||
});
|
||||
updateLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and install widget
|
||||
*
|
||||
* @param project the context project
|
||||
* @param configurations the configurations to use
|
||||
* @return the action that uninstalls widget
|
||||
*/
|
||||
static Runnable install(final Project project, final GitBranchConfigurations configurations) {
|
||||
final Ref<GitBranchesWidget> widget = new Ref<GitBranchesWidget>();
|
||||
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
StatusBar statusBar = WindowManager.getInstance().getStatusBar(project);
|
||||
if (statusBar != null) {
|
||||
final GitBranchesWidget w = new GitBranchesWidget(project, configurations);
|
||||
statusBar.addWidget(w, "after InsertOverwrite", project);
|
||||
widget.set(w);
|
||||
}
|
||||
}
|
||||
});
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (widget.get() != null) {
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Disposer.dispose(widget.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public String ID() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public WidgetPresentation getPresentation(@NotNull Type type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void install(@NotNull StatusBar statusBar) {
|
||||
myStatusBar = statusBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
myConfigurations.removeConfigurationListener(myConfigurationsListener);
|
||||
myStatusBar.removeWidget(ID());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return get or create remotes group
|
||||
*/
|
||||
private AnAction[] getRemotes() {
|
||||
assert myPopupEnabled : "pop should be enabled";
|
||||
if (myRemoveConfigurations == null) {
|
||||
ArrayList<AnAction> rc = new ArrayList<AnAction>();
|
||||
for (final String c : myConfigurations.getRemotesCandidates()) {
|
||||
rc.add(new AnAction(c) {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
myConfigurations.startCheckout(null, c, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
rc.add(new RefreshRemotesAction());
|
||||
myRemoveConfigurations = rc.toArray(new AnAction[rc.size()]);
|
||||
}
|
||||
return myRemoveConfigurations;
|
||||
}
|
||||
|
||||
void showPopup() {
|
||||
if (!myPopupEnabled) {
|
||||
return;
|
||||
}
|
||||
final DataContext parent = DataManager.getInstance().getDataContext((Component)myStatusBar);
|
||||
final DataContext dataContext = SimpleDataContext.getSimpleContext(PlatformDataKeys.PROJECT.getName(), myProject, parent);
|
||||
myPopup = JBPopupFactory.getInstance()
|
||||
.createActionGroupPopup("Checkout", getPopupActionGroup(), dataContext, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true,
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myPopup = null;
|
||||
|
||||
}
|
||||
}, 20);
|
||||
final Dimension dimension = myPopup.getContent().getPreferredSize();
|
||||
final Point at = new Point(0, -dimension.height);
|
||||
myPopup.show(new RelativePoint(getComponent(), at));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return get or create selectable configuration group
|
||||
*/
|
||||
private AnAction[] getSelectable() {
|
||||
assert myPopupEnabled : "pop should be enabled";
|
||||
if (mySelectableConfigurations == null) {
|
||||
ArrayList<AnAction> rc = new ArrayList<AnAction>();
|
||||
|
||||
GitBranchConfiguration current;
|
||||
try {
|
||||
current = myConfigurations.getCurrentConfiguration();
|
||||
}
|
||||
catch (VcsException e) {
|
||||
LOG.error("Unexpected error at this point", e);
|
||||
mySelectableConfigurations = new AnAction[0];
|
||||
return mySelectableConfigurations;
|
||||
}
|
||||
String name = current == null ? "" : current.getName();
|
||||
for (final String c : myConfigurations.getConfigurationNames()) {
|
||||
if (name.equals(c)) {
|
||||
// skip current config
|
||||
continue;
|
||||
}
|
||||
rc.add(new AnAction(c) {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
try {
|
||||
final GitBranchConfiguration toCheckout = myConfigurations.getConfiguration(c);
|
||||
if (toCheckout == null) {
|
||||
throw new VcsException("The configuration " + c + " cannot be found.");
|
||||
}
|
||||
myConfigurations.startCheckout(toCheckout, null, true);
|
||||
}
|
||||
catch (VcsException e1) {
|
||||
GitUIUtil.showOperationError(myProject, e1, "Unable to load: " + c);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
mySelectableConfigurations = rc.toArray(new AnAction[rc.size()]);
|
||||
}
|
||||
return mySelectableConfigurations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the action group for popup
|
||||
*/
|
||||
ActionGroup getPopupActionGroup() {
|
||||
if (myPopupActionGroup == null) {
|
||||
myPopupActionGroup = new DefaultActionGroup(null, false);
|
||||
myPopupActionGroup.addAction(new AnAction("Manage Configurations ...") {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
GitManageConfigurationsDialog.showDialog(myProject, myConfigurations);
|
||||
}
|
||||
});
|
||||
myPopupActionGroup.addAction(new AnAction("Modify Current Configuration ...") {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
try {
|
||||
final GitBranchConfiguration current = myConfigurations.getCurrentConfiguration();
|
||||
myConfigurations.startCheckout(current, null, false);
|
||||
}
|
||||
catch (VcsException e1) {
|
||||
LOG.error("The current configuration must exists at this point", e1);
|
||||
}
|
||||
}
|
||||
});
|
||||
myPopupActionGroup.addAction(new AnAction("New Configuration ...") {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
myConfigurations.startCheckout(null, null, false);
|
||||
}
|
||||
});
|
||||
myPopupActionGroup.add(new MyRemotesActionGroup());
|
||||
myPopupActionGroup.addSeparator("Branch Configurations");
|
||||
myPopupActionGroup.add(new MySelectableActionGroup());
|
||||
}
|
||||
return myPopupActionGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update label on the widget
|
||||
*/
|
||||
private void updateLabel() {
|
||||
cancelPopup();
|
||||
final GitBranchConfigurations.SpecialStatus status = myConfigurations.getSpecialStatus();
|
||||
String t;
|
||||
myPopupEnabled = false;
|
||||
switch (status) {
|
||||
case CHECKOUT_IN_PROGRESS:
|
||||
t = "Checkout in progress...";
|
||||
break;
|
||||
case MERGING:
|
||||
t = "Merging...";
|
||||
break;
|
||||
case REBASING:
|
||||
t = "Rebasing...";
|
||||
break;
|
||||
case NON_GIT:
|
||||
t = "Non-Git project";
|
||||
break;
|
||||
case SUBMODULES:
|
||||
t = "Submodules unsupported";
|
||||
break;
|
||||
case NORMAL:
|
||||
GitBranchConfiguration current;
|
||||
try {
|
||||
current = myConfigurations.getCurrentConfiguration();
|
||||
}
|
||||
catch (VcsException e) {
|
||||
current = null;
|
||||
}
|
||||
if (current == null) {
|
||||
t = "Detection in progress";
|
||||
}
|
||||
else {
|
||||
myPopupEnabled = true;
|
||||
t = current.getName();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
t = "Unknown status: " + status;
|
||||
}
|
||||
setForeground(myPopupEnabled ? myDefaultForeground : Color.RED);
|
||||
setText(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel popup if it is shown
|
||||
*/
|
||||
private void cancelPopup() {
|
||||
if (myPopup != null) {
|
||||
myPopup.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remotes action group
|
||||
*/
|
||||
class MySelectableActionGroup extends ActionGroup {
|
||||
/**
|
||||
* The constructor
|
||||
*/
|
||||
public MySelectableActionGroup() {
|
||||
super(null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public AnAction[] getChildren(@Nullable AnActionEvent e) {
|
||||
return getSelectable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remotes action group
|
||||
*/
|
||||
class MyRemotesActionGroup extends ActionGroup {
|
||||
/**
|
||||
* The constructor
|
||||
*/
|
||||
public MyRemotesActionGroup() {
|
||||
super("Remotes", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public AnAction[] getChildren(@Nullable AnActionEvent e) {
|
||||
return getRemotes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration listener
|
||||
*/
|
||||
class MyGitBranchConfigurationsListener implements GitBranchConfigurationsListener {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void configurationsChanged() {
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mySelectableConfigurations = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void specialStatusChanged() {
|
||||
refreshLabel();
|
||||
}
|
||||
|
||||
private void refreshLabel() {
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateLabel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void currentConfigurationChanged() {
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mySelectableConfigurations = null;
|
||||
updateLabel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void referencesChanged() {
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myRemoveConfigurations = null;
|
||||
updateLabel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh git remotes
|
||||
*/
|
||||
class RefreshRemotesAction extends AnAction {
|
||||
/**
|
||||
* The constructor
|
||||
*/
|
||||
public RefreshRemotesAction() {
|
||||
super("Refresh Remotes...", "Fetch all references for git vcs roots", IconLoader.findIcon("/vcs/refresh.png"));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
|
||||
final String title = "Refreshing remotes";
|
||||
ProgressManager.getInstance().run(new Task.Backgroundable(myProject, title, false) {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
final ArrayList<VcsException> exceptions = new ArrayList<VcsException>();
|
||||
try {
|
||||
for (VirtualFile root : GitUtil.getGitRoots(myProject, GitVcs.getInstance(myProject))) {
|
||||
GitLineHandler h = new GitLineHandler(myProject, root, GitCommand.FETCH);
|
||||
h.addParameters("--all", "-v");
|
||||
final Collection<VcsException> e = GitHandlerUtil
|
||||
.doSynchronouslyWithExceptions(h, indicator, "Fetching all for " + GitUtil.relativePath(myProject.getBaseDir(), root));
|
||||
exceptions.addAll(e);
|
||||
}
|
||||
}
|
||||
catch (VcsException e1) {
|
||||
exceptions.add(e1);
|
||||
}
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!exceptions.isEmpty()) {
|
||||
GitUIUtil.showTabErrors(myProject, title, exceptions);
|
||||
ToolWindowManager.getInstance(myProject).notifyByBalloon(
|
||||
ChangesViewContentManager.TOOLWINDOW_ID, MessageType.ERROR, "Refreshing remotes failed.");
|
||||
}
|
||||
else {
|
||||
ToolWindowManager.getInstance(myProject).notifyByBalloon(
|
||||
ChangesViewContentManager.TOOLWINDOW_ID, MessageType.INFO, "Refreshing remotes complete.");
|
||||
}
|
||||
myRemoveConfigurations = null;
|
||||
cancelPopup();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,756 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.checkout.branches;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ex.ProjectManagerEx;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.changes.*;
|
||||
import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager;
|
||||
import com.intellij.openapi.vcs.changes.shelf.ShelvedChangeList;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.vcsUtil.VcsUtil;
|
||||
import git4idea.GitRevisionNumber;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.checkout.branches.GitBranchConfigurations.BranchChanges;
|
||||
import git4idea.checkout.branches.GitBranchConfigurations.ChangeInfo;
|
||||
import git4idea.checkout.branches.GitBranchConfigurations.ChangeListInfo;
|
||||
import git4idea.commands.*;
|
||||
import git4idea.update.GitStashUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
/**
|
||||
* The checkout branch process. It used to organize the entire checkout process
|
||||
*/
|
||||
public class GitCheckoutProcess {
|
||||
/**
|
||||
* The logger
|
||||
*/
|
||||
private static final Logger LOG = Logger.getInstance(GitCheckoutProcess.class.getName());
|
||||
/**
|
||||
* The configuration process
|
||||
*/
|
||||
final GitBranchConfigurations myConfig;
|
||||
/**
|
||||
* The vcs roots
|
||||
*/
|
||||
private List<VirtualFile> myRoots;
|
||||
/**
|
||||
* The vcs exceptions
|
||||
*/
|
||||
private List<VcsException> myExceptions;
|
||||
/**
|
||||
* The project
|
||||
*/
|
||||
private final Project myProject;
|
||||
/**
|
||||
* The shelve manager
|
||||
*/
|
||||
private final ShelveChangesManager myShelveManager;
|
||||
/**
|
||||
* The dirty scope manager
|
||||
*/
|
||||
private final VcsDirtyScopeManager myDirtyScopeManager;
|
||||
/**
|
||||
* The changes manager
|
||||
*/
|
||||
private final ChangeListManagerEx myChangeManager;
|
||||
/**
|
||||
* The project manager
|
||||
*/
|
||||
private final ProjectManagerEx myProjectManager;
|
||||
/**
|
||||
* The progress indicator
|
||||
*/
|
||||
private final ProgressIndicator myProgress;
|
||||
/**
|
||||
* The name of remote pseudo-configuration
|
||||
*/
|
||||
@Nullable private final String myRemoteConfiguration;
|
||||
/**
|
||||
* If true, the quick process is being used, and user is not offered to select changes
|
||||
*/
|
||||
private boolean myQuick;
|
||||
/**
|
||||
* The checkout process is run in the modify mode. Branch name or roots are changed.
|
||||
*/
|
||||
private final boolean myIsModify;
|
||||
/**
|
||||
* The new configuration
|
||||
*/
|
||||
private GitBranchConfiguration myNewConfiguration;
|
||||
/**
|
||||
* The new branch mapping
|
||||
*/
|
||||
private Map<VirtualFile, String> myNewBranchMapping = Collections.emptyMap();
|
||||
/**
|
||||
* The described roots
|
||||
*/
|
||||
private final Map<VirtualFile, String> myDescribedRoots = new HashMap<VirtualFile, String>();
|
||||
/**
|
||||
* If true, the checkout process was cancelled
|
||||
*/
|
||||
private boolean myCancelled;
|
||||
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param config the configuration object
|
||||
* @param project the context project
|
||||
* @param shelveManager the shelve manager
|
||||
* @param dirtyScopeManager the dirty scope manager
|
||||
* @param changeManager the change manager
|
||||
* @param projectManager the project manager
|
||||
* @param progress the progress indicator for the process
|
||||
* @param newConfiguration the new configuration, the current if modify
|
||||
* @param quick if true, the no changes are assumed to be selected, so dialog need not be shown
|
||||
*/
|
||||
public GitCheckoutProcess(GitBranchConfigurations config,
|
||||
Project project,
|
||||
ShelveChangesManager shelveManager,
|
||||
VcsDirtyScopeManager dirtyScopeManager,
|
||||
ChangeListManagerEx changeManager,
|
||||
ProjectManagerEx projectManager,
|
||||
ProgressIndicator progress,
|
||||
@Nullable GitBranchConfiguration newConfiguration,
|
||||
@Nullable String remoteConfiguration,
|
||||
boolean quick) {
|
||||
myExceptions = Collections.synchronizedList(new ArrayList<VcsException>());
|
||||
myConfig = config;
|
||||
myProject = project;
|
||||
myShelveManager = shelveManager;
|
||||
myDirtyScopeManager = dirtyScopeManager;
|
||||
myChangeManager = changeManager;
|
||||
myProjectManager = projectManager;
|
||||
myProgress = progress;
|
||||
myNewConfiguration = newConfiguration;
|
||||
myRemoteConfiguration = remoteConfiguration;
|
||||
boolean f = false;
|
||||
try {
|
||||
f = myNewConfiguration == config.getCurrentConfiguration();
|
||||
}
|
||||
catch (VcsException e) {
|
||||
myExceptions.add(e);
|
||||
}
|
||||
myIsModify = f;
|
||||
myQuick = quick & !myIsModify;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start the checkout process. The all activity is done in this thread. When needed, the activity is scheduled in awt or other threads.
|
||||
*/
|
||||
public void run() {
|
||||
if (!myExceptions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
myProgress.setText("Staring checkout...");
|
||||
try {
|
||||
myRoots = GitUtil.getGitRoots(myConfig.getProject(), myConfig.getVcs());
|
||||
saveAll();
|
||||
waitForChanges();
|
||||
myProjectManager.blockReloadingProjectOnExternalChanges();
|
||||
try {
|
||||
GitBranchConfiguration oldConfiguration = checkCurrentConfiguration();
|
||||
if (oldConfiguration == null) {
|
||||
myCancelled = true;
|
||||
return;
|
||||
}
|
||||
for (VirtualFile root : myRoots) {
|
||||
myDescribedRoots.put(root, myConfig.describeRoot(root));
|
||||
}
|
||||
ensureNoChangesInCurrent(oldConfiguration);
|
||||
if (!myExceptions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Change> changes = collectChanges();
|
||||
Collection<Change> selected;
|
||||
myProgress.setText("Verifying the current configuration...");
|
||||
if (myQuick && checkRoots()) {
|
||||
// show switch dialog with new configuration that allows selecting changes (if not quick switch)
|
||||
selected = Collections.emptyList();
|
||||
}
|
||||
else {
|
||||
myProgress.setText("Selecting changes to transfer...");
|
||||
selected = selectChangesToTransfer(changes);
|
||||
if (selected == null) {
|
||||
// the process was cancelled, or no changes that require checkout were made to the current configuration
|
||||
myCancelled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// preparation phase finished. do actual checkout.
|
||||
assert myNewConfiguration != null;
|
||||
List<VirtualFile> checkoutRoots = rootsToCheckout();
|
||||
if (checkoutRoots.size() > 0) {
|
||||
// TODO disable saving
|
||||
myProgress.setText("Shelving changes...");
|
||||
Pair<BranchChanges, BranchChanges> changesPair = shelveChanges(myProgress, oldConfiguration.getName(), selected);
|
||||
try {
|
||||
// save changes in old root, it also may be aliased with new root
|
||||
oldConfiguration.setChanges(changesPair.first);
|
||||
try {
|
||||
HashSet<VirtualFile> startedRoots = new HashSet<VirtualFile>();
|
||||
boolean failed = !checkoutAndRefreshRoots(checkoutRoots, startedRoots);
|
||||
myProgress.setText2("");
|
||||
if (!failed) {
|
||||
myConfig.setCurrentConfiguration(myNewConfiguration);
|
||||
}
|
||||
else {
|
||||
myNewConfiguration = oldConfiguration;
|
||||
rollbackRootCheckout(startedRoots);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
final BranchChanges branchChanges = myNewConfiguration.getChanges();
|
||||
myNewConfiguration.setChanges(null);
|
||||
restoreChanges(myProgress, branchChanges);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// restore transient shelve
|
||||
restoreChanges(myProgress, changesPair.second);
|
||||
}
|
||||
// TODO enable saving
|
||||
}
|
||||
else {
|
||||
myConfig.setCurrentConfiguration(myNewConfiguration);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
myProjectManager.unblockReloadingProjectOnExternalChanges();
|
||||
}
|
||||
// launch project update?
|
||||
}
|
||||
catch (VcsException e) {
|
||||
myExceptions.add(e);
|
||||
}
|
||||
finally {
|
||||
saveAll(); // saves configuration changes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that current configuration contains no changes. This is a bug condition.
|
||||
*
|
||||
* @param oldConfiguration the old configuration
|
||||
*/
|
||||
private void ensureNoChangesInCurrent(GitBranchConfiguration oldConfiguration) {
|
||||
final BranchChanges oldChanges = oldConfiguration.getChanges();
|
||||
if (oldChanges != null) {
|
||||
String name = oldChanges.SHELVE_PATH;
|
||||
for (ShelvedChangeList changeList : myShelveManager.getShelvedChangeLists()) {
|
||||
if (changeList.PATH.equals(oldChanges.SHELVE_PATH)) {
|
||||
name = changeList.DESCRIPTION;
|
||||
break;
|
||||
}
|
||||
}
|
||||
final VcsException ex = new VcsException("The current configuration contains shelve: " + name);
|
||||
LOG.error(ex);
|
||||
myExceptions.add(ex);
|
||||
oldConfiguration.setChanges(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the root checkout
|
||||
*
|
||||
* @param startedRoots that roots that has been started and need to be rolled back
|
||||
*/
|
||||
private void rollbackRootCheckout(HashSet<VirtualFile> startedRoots) {
|
||||
myProgress.setText("Rolling back...");
|
||||
for (VirtualFile root : startedRoots) {
|
||||
myProgress.setText2(root.getPath());
|
||||
startedRoots.add(root);
|
||||
GitLineHandler h = new GitLineHandler(myProject, root, GitCommand.CHECKOUT);
|
||||
h.setNoSSH(true);
|
||||
h.addParameters("-f", myDescribedRoots.get(root));
|
||||
Collection<VcsException> exceptions = GitHandlerUtil.doSynchronouslyWithExceptions(h, myProgress, h.printableCommandLine());
|
||||
myExceptions.addAll(exceptions);
|
||||
}
|
||||
// filter out implicitly included roots
|
||||
ArrayList<VirtualFile> newRoots = new ArrayList<VirtualFile>();
|
||||
loop:
|
||||
for (VirtualFile root : startedRoots) {
|
||||
for (VirtualFile other : startedRoots) {
|
||||
if (other != root && VfsUtil.isAncestor(other, root, true)) {
|
||||
continue loop;
|
||||
}
|
||||
}
|
||||
newRoots.add(root);
|
||||
}
|
||||
for (VirtualFile root : newRoots) {
|
||||
root.refresh(false, true);
|
||||
}
|
||||
myProgress.setText2("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkout needed vcs roots and do vcs refresh on diff files
|
||||
*
|
||||
* @param checkoutRoots the roots to checkout
|
||||
* @param startedRoots the roots for which checkout actually started (Modified by this method)
|
||||
* @return true if checkout is successful
|
||||
* @throws VcsException
|
||||
*/
|
||||
private boolean checkoutAndRefreshRoots(List<VirtualFile> checkoutRoots, HashSet<VirtualFile> startedRoots) throws VcsException {
|
||||
boolean failed = false;
|
||||
myProgress.setText("Checking out...");
|
||||
HashSet<File> filesToRefresh = new HashSet<File>();
|
||||
for (VirtualFile root : checkoutRoots) {
|
||||
myProgress.setText2(root.getPath());
|
||||
startedRoots.add(root);
|
||||
GitRevisionNumber prev = GitRevisionNumber.resolve(myProject, root, "HEAD");
|
||||
GitLineHandler h = new GitLineHandler(myProject, root, GitCommand.CHECKOUT);
|
||||
h.addParameters("-f");
|
||||
String branchedRef = myNewBranchMapping.get(root);
|
||||
String ref = myNewConfiguration.getReference(root.getPath());
|
||||
if (branchedRef != null) {
|
||||
h.addParameters("-l", "-t", "-b", ref, branchedRef);
|
||||
}
|
||||
else {
|
||||
h.addParameters(ref);
|
||||
}
|
||||
Collection<VcsException> exceptions = GitHandlerUtil.doSynchronouslyWithExceptions(h, myProgress, h.printableCommandLine());
|
||||
if (!exceptions.isEmpty()) {
|
||||
myExceptions.addAll(exceptions);
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
GitSimpleHandler d = new GitSimpleHandler(myProject, root, GitCommand.DIFF);
|
||||
d.addParameters("--name-only", prev.asString() + "..HEAD");
|
||||
d.setNoSSH(true);
|
||||
d.setSilent(true);
|
||||
d.endOptions();
|
||||
try {
|
||||
File base = new File(root.getPath());
|
||||
for (StringScanner s = new StringScanner(d.run()); s.hasMoreData();) {
|
||||
String l = s.line();
|
||||
if (l.length() > 0) {
|
||||
filesToRefresh.add(new File(base, GitUtil.unescapePath(l)));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (VcsException e) {
|
||||
LOG.error("Unexpected diff failure", e);
|
||||
myExceptions.add(e);
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!failed) {
|
||||
LocalFileSystem.getInstance().refreshIoFiles(filesToRefresh);
|
||||
}
|
||||
return !failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return set of roots to checkout
|
||||
*/
|
||||
private List<VirtualFile> rootsToCheckout() {
|
||||
ArrayList<VirtualFile> rc = new ArrayList<VirtualFile>();
|
||||
for (VirtualFile root : myRoots) {
|
||||
String current = myDescribedRoots.get(root);
|
||||
String newRef = myNewConfiguration.getReference(root.getPath());
|
||||
if (!current.equals(newRef)) {
|
||||
rc.add(root);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param changes all changes
|
||||
* @return this method allows renaming and updating, the branch configuration and to select changes to transfer to new configuration.
|
||||
* null if process is cancelled, or only name changed in the current configuration and no checkout is required.
|
||||
*/
|
||||
@Nullable
|
||||
private Collection<Change> selectChangesToTransfer(final List<Change> changes) {
|
||||
final Ref<GitSwitchBranchesDialog.Result> t = new Ref<GitSwitchBranchesDialog.Result>();
|
||||
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
t.set(GitSwitchBranchesDialog
|
||||
.showDialog(myProject, myNewConfiguration, changes, myRoots, myRemoteConfiguration, myConfig, myIsModify));
|
||||
}
|
||||
catch (VcsException e) {
|
||||
myExceptions.add(e);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
LOG.error("Unexpected error", e);
|
||||
myExceptions.add(new VcsException("Selecting changes failed", e));
|
||||
}
|
||||
}
|
||||
});
|
||||
if (t.get() == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
myNewBranchMapping = t.get().referencesToUse;
|
||||
myNewConfiguration = t.get().target;
|
||||
return t.get().changes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if roots in the new configuration are configured correctly
|
||||
*/
|
||||
private boolean checkRoots() {
|
||||
if (myNewConfiguration == null) {
|
||||
return false;
|
||||
}
|
||||
LocalFileSystem lfs = LocalFileSystem.getInstance();
|
||||
HashSet<VirtualFile> roots = new HashSet<VirtualFile>(myRoots);
|
||||
Map<String, String> branches = myNewConfiguration.getReferences();
|
||||
for (Map.Entry<String, String> m : branches.entrySet()) {
|
||||
VirtualFile root = lfs.findFileByPath(m.getKey());
|
||||
if (root == null || root.findChild(".git") == null || !roots.contains(root)) {
|
||||
return false;
|
||||
}
|
||||
roots.remove(root);
|
||||
try {
|
||||
GitRevisionNumber.resolve(myProject, root, m.getValue());
|
||||
}
|
||||
catch (VcsException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return roots.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return collect changes from project
|
||||
*/
|
||||
private List<Change> collectChanges() {
|
||||
List<LocalChangeList> changeLists = myChangeManager.getChangeLists();
|
||||
ArrayList<Change> changes = new ArrayList<Change>();
|
||||
for (LocalChangeList l : changeLists) {
|
||||
changes.addAll(l.getChanges());
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the current configuration, or null if process was cancelled
|
||||
* @throws VcsException if there is a problem with checking configuration
|
||||
*/
|
||||
@Nullable
|
||||
private GitBranchConfiguration checkCurrentConfiguration() throws VcsException {
|
||||
GitBranchConfiguration c = myConfig.getCurrentConfiguration();
|
||||
if (!myIsModify && !rootsMatchConfiguration(c)) {
|
||||
// when the current configuration is modified, there is no need to show duplicate dialogs
|
||||
c = GitBranchConfigurationChangedDialog.showDialog(myConfig, c, myRoots);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check of the root mapping has been changed for the current vcs root
|
||||
*
|
||||
* @param c the configuration to check
|
||||
* @return true if nothing shoudlbe checked out
|
||||
* @throws VcsException
|
||||
*/
|
||||
private boolean rootsMatchConfiguration(GitBranchConfiguration c) throws VcsException {
|
||||
LocalFileSystem lfs = LocalFileSystem.getInstance();
|
||||
Map<String, String> branches = c.getReferences();
|
||||
if (branches.size() != myRoots.size()) {
|
||||
return false;
|
||||
}
|
||||
HashSet<String> realSet = new HashSet<String>();
|
||||
for (VirtualFile root : myRoots) {
|
||||
realSet.add(root.getPath());
|
||||
}
|
||||
HashSet<String> storedSet = new HashSet<String>();
|
||||
for (Map.Entry<String, String> m : branches.entrySet()) {
|
||||
String rootPath = m.getKey();
|
||||
storedSet.add(rootPath);
|
||||
VirtualFile root = lfs.findFileByPath(rootPath);
|
||||
String ref = myConfig.describeRoot(root);
|
||||
if (!ref.equals(m.getValue())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return storedSet.equals(realSet);
|
||||
}
|
||||
|
||||
private void waitForChanges() throws VcsException {
|
||||
final Semaphore s = new Semaphore(0);
|
||||
waitForChangesRefresh("Preparing for the checkout: ", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
s.release();
|
||||
}
|
||||
});
|
||||
try {
|
||||
s.acquire();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new VcsException("Waiting for changes was interrupted: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save all changes before start of update process
|
||||
*/
|
||||
private static void saveAll() {
|
||||
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
public void run() {
|
||||
FileDocumentManager.getInstance().saveAllDocuments();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore changes.
|
||||
*
|
||||
* @param progress the progress indicator
|
||||
* @param changes the changes to restore, null means no changes to restore
|
||||
* @return true if changes has been restored successfully
|
||||
*/
|
||||
private boolean restoreChanges(ProgressIndicator progress,
|
||||
final BranchChanges changes) {
|
||||
if (changes == null) {
|
||||
return true;
|
||||
}
|
||||
ShelvedChangeList shelve = null;
|
||||
for (ShelvedChangeList changeList : myShelveManager.getShelvedChangeLists()) {
|
||||
if (changeList.PATH.equals(changes.SHELVE_PATH)) {
|
||||
shelve = changeList;
|
||||
}
|
||||
}
|
||||
if (shelve == null) {
|
||||
//noinspection ThrowableInstanceNeverThrown
|
||||
myExceptions.add(new VcsException("Failed to find shelve with path" + changes.SHELVE_PATH));
|
||||
return false;
|
||||
}
|
||||
progress.setText("Refreshing files before restoring shelve: " + shelve.DESCRIPTION);
|
||||
GitStashUtils.doSystemUnshelve(myProject, shelve, myShelveManager, myChangeManager, myExceptions);
|
||||
// dirty files and parse changes
|
||||
final HashMap<Pair<String, String>, String> parsedChanges = new HashMap<Pair<String, String>, String>();
|
||||
for (ChangeInfo changeInfo : changes.CHANGES) {
|
||||
String before = changeInfo.BEFORE_PATH;
|
||||
String after = changeInfo.AFTER_PATH;
|
||||
parsedChanges.put(Pair.create(before, after), changeInfo.CHANGE_LIST_NAME);
|
||||
if (after != null) {
|
||||
myDirtyScopeManager.fileDirty(VcsUtil.getFilePath(after));
|
||||
}
|
||||
if (before != null) {
|
||||
myDirtyScopeManager.fileDirty(VcsUtil.getFilePath(before));
|
||||
}
|
||||
}
|
||||
final ShelvedChangeList finalShelve = shelve;
|
||||
try {
|
||||
waitForChanges();
|
||||
HashMap<String, LocalChangeList> lists = new HashMap<String, LocalChangeList>();
|
||||
for (LocalChangeList localChangeList : myChangeManager.getChangeLists()) {
|
||||
lists.put(localChangeList.getName(), localChangeList);
|
||||
}
|
||||
LocalChangeList defaultList = myChangeManager.getDefaultChangeList();
|
||||
for (ChangeListInfo changeListInfo : changes.CHANGE_LISTS) {
|
||||
LocalChangeList changeList = lists.get(changeListInfo.NAME);
|
||||
if (changeList != null) {
|
||||
myChangeManager.setReadOnly(changeList.getName(), false);
|
||||
}
|
||||
else {
|
||||
changeList = myChangeManager.addChangeList(changeListInfo.NAME, changeListInfo.COMMENT);
|
||||
lists.put(changeListInfo.NAME, changeList);
|
||||
}
|
||||
if (changeListInfo.IS_DEFAULT) {
|
||||
myChangeManager.setDefaultChangeList(changeList);
|
||||
}
|
||||
}
|
||||
for (Change change : defaultList.getChanges()) {
|
||||
ContentRevision beforeRevision = change.getBeforeRevision();
|
||||
String before = beforeRevision == null ? null : beforeRevision.getFile().getPath();
|
||||
ContentRevision afterRevision = change.getAfterRevision();
|
||||
String after = afterRevision == null ? null : afterRevision.getFile().getPath();
|
||||
Pair<String, String> key = Pair.create(before, after);
|
||||
String listName = parsedChanges.get(key);
|
||||
assert listName != null : "List name should be found: " + key;
|
||||
if (!listName.equals(defaultList.getName())) {
|
||||
LocalChangeList changeList = lists.get(listName);
|
||||
assert changeList != null : "Change List should be found: " + listName;
|
||||
myChangeManager.moveChangesTo(changeList, new Change[]{change});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
//noinspection ThrowableInstanceNeverThrown
|
||||
myExceptions.add(
|
||||
new VcsException("Failed to process restore shelved change list: " + finalShelve.DESCRIPTION + ". Please restore it manually.", t));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until changes are refreshed
|
||||
*
|
||||
* @param title the title of the operation
|
||||
* @param runnable the process that awaits changes
|
||||
*/
|
||||
void waitForChangesRefresh(final String title, final Runnable runnable) {
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
public void run() {
|
||||
myChangeManager.invokeAfterUpdate(runnable, InvokeAfterUpdateMode.BACKGROUND_NOT_CANCELLABLE, title,
|
||||
ModalityState.NON_MODAL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shelve selected and transient changes. If creation of one of shelves fails, the other shelve is rolled back
|
||||
*
|
||||
* @param configurationName the current configuration name
|
||||
* @param selectedChanges the selected changes
|
||||
* @return null if operation fails or pair if operation completed successfully. The first is changes to be stored in old configuration, the second is trainsient changes.
|
||||
*/
|
||||
@Nullable
|
||||
private Pair<BranchChanges, BranchChanges> shelveChanges(ProgressIndicator progress,
|
||||
String configurationName,
|
||||
Collection<Change> selectedChanges) {
|
||||
assert myExceptions.isEmpty() : "The method should not be called if there is already problems detected";
|
||||
List<LocalChangeList> changeLists = myChangeManager.getChangeListsCopy();
|
||||
HashMap<Change, LocalChangeList> changes = new HashMap<Change, LocalChangeList>();
|
||||
for (LocalChangeList l : changeLists) {
|
||||
for (Change change : l.getChanges()) {
|
||||
changes.put(change, l);
|
||||
}
|
||||
}
|
||||
HashSet<Change> selected = new HashSet<Change>(selectedChanges);
|
||||
HashSet<Change> other = new HashSet<Change>(changes.keySet());
|
||||
other.removeAll(selected);
|
||||
|
||||
Date now = new Date();
|
||||
BranchChanges storedChanges = shelveChanges(progress, changes, other,
|
||||
"Shelved changes for configuration " + configurationName + " (created on " + now + ")");
|
||||
if (!myExceptions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
BranchChanges transientChanges = shelveChanges(progress, changes, selected,
|
||||
"Transferred changes from configuration " +
|
||||
configurationName +
|
||||
" (created on " +
|
||||
now +
|
||||
")");
|
||||
if (!myExceptions.isEmpty()) {
|
||||
restoreChanges(progress, storedChanges);
|
||||
return null;
|
||||
}
|
||||
return Pair.create(storedChanges, transientChanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shelve changes remembering change configuration
|
||||
*
|
||||
* @param progress the progress
|
||||
* @param changes the all changes to process
|
||||
* @param toShelve the shelved change subset
|
||||
* @param description the description of the shelve
|
||||
* @return branch change set or null if shelve is not created (there will be exceptions in list in case of errors)
|
||||
*/
|
||||
@Nullable
|
||||
private BranchChanges shelveChanges(ProgressIndicator progress,
|
||||
HashMap<Change, LocalChangeList> changes,
|
||||
HashSet<Change> toShelve, String description
|
||||
) {
|
||||
if (toShelve.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
HashSet<LocalChangeList> lists = new HashSet<LocalChangeList>();
|
||||
ArrayList<ChangeInfo> ci = new ArrayList<ChangeInfo>(toShelve.size());
|
||||
for (Change c : toShelve) {
|
||||
LocalChangeList l = changes.get(c);
|
||||
lists.add(l);
|
||||
ChangeInfo i = new ChangeInfo();
|
||||
ContentRevision after = c.getAfterRevision();
|
||||
if (after != null) {
|
||||
i.AFTER_PATH = after.getFile().getPath();
|
||||
}
|
||||
ContentRevision before = c.getBeforeRevision();
|
||||
if (before != null) {
|
||||
i.BEFORE_PATH = before.getFile().getPath();
|
||||
}
|
||||
i.CHANGE_LIST_NAME = l.getName();
|
||||
ci.add(i);
|
||||
}
|
||||
ArrayList<ChangeListInfo> li = new ArrayList<ChangeListInfo>(lists.size());
|
||||
for (LocalChangeList l : lists) {
|
||||
ChangeListInfo i = new ChangeListInfo();
|
||||
i.IS_DEFAULT = l.isDefault();
|
||||
i.NAME = l.getName();
|
||||
i.COMMENT = l.getComment();
|
||||
li.add(i);
|
||||
}
|
||||
if (progress != null) {
|
||||
progress.setText("Creating shelve: " + description);
|
||||
}
|
||||
ShelvedChangeList shelved = GitStashUtils.shelveChanges(myProject, myShelveManager, toShelve, description, myExceptions);
|
||||
if (shelved == null) {
|
||||
return null;
|
||||
}
|
||||
BranchChanges b = new BranchChanges();
|
||||
b.SHELVE_PATH = shelved.PATH;
|
||||
b.CHANGE_LISTS = li.toArray(new ChangeListInfo[li.size()]);
|
||||
b.CHANGES = ci.toArray(new ChangeInfo[ci.size()]);
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of problems
|
||||
*/
|
||||
public List<VcsException> getExceptions() {
|
||||
return myExceptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the process was cancelled
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return myCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the process was modification process
|
||||
*/
|
||||
public boolean isModify() {
|
||||
return myIsModify;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="git4idea.checkout.branches.GitManageConfigurationsDialog">
|
||||
<grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="488" height="476"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<scrollpane id="1ca7c" class="com.intellij.ui.components.JBScrollPane">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="2" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="5ef88" class="com.intellij.ui.components.JBList" binding="myNamesList">
|
||||
<constraints/>
|
||||
<properties>
|
||||
<selectionMode value="0"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</scrollpane>
|
||||
<component id="39006" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" 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>
|
||||
<labelFor value="1ca7c"/>
|
||||
<text value="&Branches"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="eb1ed" layout-manager="GridLayoutManager" row-count="1" 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="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="87360" class="javax.swing.JButton" binding="myDeleteButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="&Delete"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="f0a1f" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="etched" title="Branch Information"/>
|
||||
<children>
|
||||
<scrollpane id="a4091" class="com.intellij.ui.components.JBScrollPane">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="618c9" class="com.intellij.ui.table.JBTable" binding="myBranchesTable">
|
||||
<constraints/>
|
||||
<properties>
|
||||
<autoCreateRowSorter value="true"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</scrollpane>
|
||||
<component id="79958" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<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>
|
||||
<labelFor value="a4091"/>
|
||||
<text value="Branch &Mapping:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="24ff5" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" 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="Name:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="269d" class="javax.swing.JLabel" binding="myNameLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="1" 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=""/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="72d60" class="javax.swing.JLabel">
|
||||
<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"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Shelve:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="18235" class="javax.swing.JLabel" binding="myShelveNameLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="4" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value=""/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.checkout.branches;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.changes.shelf.ShelvedChangeList;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.components.JBList;
|
||||
import com.intellij.ui.table.JBTable;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The manage configurations dialog. Currently only delete and limited view works.
|
||||
* <p/>
|
||||
* TODO auto-detection of configurations
|
||||
*/
|
||||
public class GitManageConfigurationsDialog extends DialogWrapper {
|
||||
/**
|
||||
* The new configuration exit code
|
||||
*/
|
||||
private static final int NEW_CONFIGURATION_EXIT_CODE = NEXT_USER_EXIT_CODE;
|
||||
/**
|
||||
* The delete configuration button
|
||||
*/
|
||||
private JButton myDeleteButton;
|
||||
/**
|
||||
* The configuration name list
|
||||
*/
|
||||
private JBList myNamesList;
|
||||
/**
|
||||
* The table with branches
|
||||
*/
|
||||
private JBTable myBranchesTable;
|
||||
/**
|
||||
* The root panel
|
||||
*/
|
||||
private JPanel myRootPanel;
|
||||
/**
|
||||
* The configuration name label
|
||||
*/
|
||||
private JLabel myNameLabel;
|
||||
/**
|
||||
* The name label
|
||||
*/
|
||||
private JLabel myShelveNameLabel;
|
||||
/**
|
||||
* The project to use
|
||||
*/
|
||||
private final Project myProject;
|
||||
/**
|
||||
* The git configurations to use
|
||||
*/
|
||||
private final GitBranchConfigurations myConfigurations;
|
||||
/**
|
||||
* Map from shelve path to shelve name
|
||||
*/
|
||||
private final HashMap<String, String> myShelveNames = new HashMap<String, String>();
|
||||
/**
|
||||
* The table model
|
||||
*/
|
||||
private final MyBranchMappingModel myBranchMappingModel;
|
||||
/**
|
||||
* The list model with names
|
||||
*/
|
||||
private final DefaultListModel myNamesModel;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param project the project
|
||||
* @param configurations the configuration service
|
||||
*/
|
||||
protected GitManageConfigurationsDialog(Project project, GitBranchConfigurations configurations) {
|
||||
super(project, true);
|
||||
setTitle("Manage Branch Configurations");
|
||||
setOKButtonText("Checkout");
|
||||
myProject = project;
|
||||
myConfigurations = configurations;
|
||||
myBranchMappingModel = new MyBranchMappingModel();
|
||||
myBranchesTable.setModel(myBranchMappingModel);
|
||||
for (ShelvedChangeList shelvedChangeList : configurations.getShelveManager().getShelvedChangeLists()) {
|
||||
myShelveNames.put(shelvedChangeList.PATH, shelvedChangeList.DESCRIPTION);
|
||||
}
|
||||
myNamesList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
updateOnSelection();
|
||||
}
|
||||
});
|
||||
myNamesModel = new DefaultListModel();
|
||||
myNamesList.setModel(myNamesModel);
|
||||
for (String n : myConfigurations.getConfigurationNames()) {
|
||||
myNamesModel.addElement(n);
|
||||
}
|
||||
myDeleteButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
GitBranchConfiguration current = getCurrentConfiguration();
|
||||
GitBranchConfiguration selected = getSelectedConfiguration();
|
||||
assert selected != null : "The configuration must be selected (the button should be disabled)";
|
||||
assert current != selected : "The current must not be the same as selected (the button should be disabled)";
|
||||
int i = myNamesList.getSelectedIndex();
|
||||
assert i != -1 && myNamesModel.elementAt(i).equals(selected.getName());
|
||||
myConfigurations.removeConfiguration(selected);
|
||||
myNamesModel.removeElementAt(i);
|
||||
if (i >= myNamesModel.size()) {
|
||||
i = myNamesModel.size() - 1;
|
||||
}
|
||||
myNamesList.setSelectedIndex(i);
|
||||
}
|
||||
});
|
||||
init();
|
||||
if (myNamesModel.size() > 0) {
|
||||
myNamesList.setSelectedIndex(0);
|
||||
}
|
||||
updateOnSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dialog on selection
|
||||
*/
|
||||
private void updateOnSelection() {
|
||||
GitBranchConfiguration current = getCurrentConfiguration();
|
||||
GitBranchConfiguration selected = getSelectedConfiguration();
|
||||
myBranchMappingModel.set(selected);
|
||||
if (selected == null) {
|
||||
myNameLabel.setText("");
|
||||
myShelveNameLabel.setText("");
|
||||
}
|
||||
else {
|
||||
myNameLabel.setText(selected.getName());
|
||||
GitBranchConfigurations.BranchChanges ch = selected.getChanges();
|
||||
if (ch == null) {
|
||||
myShelveNameLabel.setText("<html><i>No associated shelve</i></html>");
|
||||
myShelveNameLabel.setToolTipText("<html><i>This configuration has no associated shelve</i></html>");
|
||||
}
|
||||
else {
|
||||
String d = myShelveNames.get(ch.SHELVE_PATH);
|
||||
myShelveNameLabel.setText(d);
|
||||
myShelveNameLabel.setToolTipText("<html><table><tr><td>Shelve Path:</td><td>" +
|
||||
StringUtil.escapeXml(ch.SHELVE_PATH) +
|
||||
"</td></tr><tr><td>Shelve Path:</td><td>" +
|
||||
StringUtil.escapeXml(d) +
|
||||
"</td></tr></html>");
|
||||
}
|
||||
}
|
||||
boolean isNonCurrent = selected != null || selected != current;
|
||||
myDeleteButton.setEnabled(isNonCurrent);
|
||||
setOKActionEnabled(isNonCurrent && myConfigurations.getSpecialStatus() == GitBranchConfigurations.SpecialStatus.NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the selected configuration
|
||||
*/
|
||||
@Nullable
|
||||
private GitBranchConfiguration getSelectedConfiguration() {
|
||||
String value = (String)myNamesList.getSelectedValue();
|
||||
GitBranchConfiguration selected;
|
||||
try {
|
||||
selected = value == null ? null : myConfigurations.getConfiguration(value);
|
||||
}
|
||||
catch (VcsException e1) {
|
||||
selected = null;
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current configuration
|
||||
*/
|
||||
@Nullable
|
||||
private GitBranchConfiguration getCurrentConfiguration() {
|
||||
GitBranchConfiguration current;
|
||||
try {
|
||||
current = myConfigurations.getCurrentConfiguration();
|
||||
}
|
||||
catch (VcsException e1) {
|
||||
current = null;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myRootPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected String getDimensionServiceKey() {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected Action[] createActions() {
|
||||
return new Action[]{getOKAction(), new DialogWrapperExitAction("New Configuration", NEW_CONFIGURATION_EXIT_CODE), getCancelAction()};
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog and if ok (checkout) pressed, checkout the respective model.
|
||||
*
|
||||
* @param project the context project
|
||||
* @param configurations the configuration service
|
||||
*/
|
||||
public static void showDialog(Project project, GitBranchConfigurations configurations) {
|
||||
GitManageConfigurationsDialog d = new GitManageConfigurationsDialog(project, configurations);
|
||||
d.show();
|
||||
if (d.getExitCode() == OK_EXIT_CODE) {
|
||||
configurations.startCheckout(d.getSelectedConfiguration(), null, false);
|
||||
}
|
||||
else if (d.getExitCode() == NEW_CONFIGURATION_EXIT_CODE) {
|
||||
configurations.startCheckout(null, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The branch mapping model
|
||||
*/
|
||||
private class MyBranchMappingModel extends AbstractTableModel {
|
||||
/**
|
||||
* The root column
|
||||
*/
|
||||
private static final int ROOT = 0;
|
||||
/**
|
||||
* The reference column (commit, tag, or branch)
|
||||
*/
|
||||
private static final int REFERENCE = 1;
|
||||
/**
|
||||
* Total count of columns
|
||||
*/
|
||||
private static final int COLUMNS = REFERENCE + 1;
|
||||
/**
|
||||
* The mapping in branches first is root the second is reference
|
||||
*/
|
||||
final ArrayList<Pair<String, String>> myMapping = new ArrayList<Pair<String, String>>();
|
||||
/**
|
||||
* Base project file
|
||||
*/
|
||||
private final File myBaseFile;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*/
|
||||
MyBranchMappingModel() {
|
||||
VirtualFile base = myProject.getBaseDir();
|
||||
myBaseFile = base == null ? null : new File(base.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update table model
|
||||
*
|
||||
* @param c the configuration to use to update table model (null means nothing to update)
|
||||
*/
|
||||
void set(GitBranchConfiguration c) {
|
||||
myMapping.clear();
|
||||
if (c != null) {
|
||||
for (Map.Entry<String, String> e : c.getReferences().entrySet()) {
|
||||
String root = e.getKey();
|
||||
String relative = myBaseFile == null ? null : FileUtil.getRelativePath(myBaseFile, new File(root));
|
||||
myMapping.add(Pair.create(relative == null ? root : relative, e.getValue()));
|
||||
}
|
||||
}
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return myMapping.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return COLUMNS;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
Pair<String, String> d = myMapping.get(rowIndex);
|
||||
switch (columnIndex) {
|
||||
case ROOT:
|
||||
return d.first;
|
||||
case REFERENCE:
|
||||
return d.second;
|
||||
default:
|
||||
throw new IllegalStateException("The invalid column number: " + columnIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
switch (column) {
|
||||
case ROOT:
|
||||
return "Vcs Root";
|
||||
case REFERENCE:
|
||||
return "Reference";
|
||||
default:
|
||||
throw new IllegalStateException("The invalid column number: " + column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="git4idea.checkout.branches.GitSwitchBranchesDialog">
|
||||
<grid id="27dc6" binding="myRoot" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="604" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="df0f4" binding="myChangesPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="3" col-span="1" vsize-policy="3" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</grid>
|
||||
<grid id="9e5c7" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="4" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="59a7b" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" 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>
|
||||
<labelFor value="d3bf1"/>
|
||||
<text value="Branch Configuration &Name"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="d3bf1" class="javax.swing.JTextField" binding="myNameTextField">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties>
|
||||
<toolTipText value="The branch configuration name"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="54c58" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="5d3d5"/>
|
||||
<text value="&Branches"/>
|
||||
</properties>
|
||||
</component>
|
||||
<scrollpane id="5d3d5" class="com.intellij.ui.components.JBScrollPane">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="84a18" class="com.intellij.ui.table.JBTable" binding="myBranchesTable">
|
||||
<constraints/>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</scrollpane>
|
||||
</children>
|
||||
</grid>
|
||||
<component id="9416e" class="javax.swing.JLabel" binding="myChangesLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="1" 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="&Changes to transfer to new configuration"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
@@ -0,0 +1,879 @@
|
||||
/*
|
||||
* Copyright 2000-2010 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.checkout.branches;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.changes.Change;
|
||||
import com.intellij.openapi.vcs.changes.RemoteRevisionsCache;
|
||||
import com.intellij.openapi.vcs.changes.ui.ChangeNodeDecorator;
|
||||
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode;
|
||||
import com.intellij.openapi.vcs.changes.ui.ChangesTreeList;
|
||||
import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.ColoredTableCellRenderer;
|
||||
import com.intellij.ui.DocumentAdapter;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import com.intellij.ui.table.JBTable;
|
||||
import com.intellij.util.ui.AbstractTableCellEditor;
|
||||
import git4idea.GitBranch;
|
||||
import git4idea.GitRevisionNumber;
|
||||
import git4idea.validators.GitBranchNameValidator;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.event.TableModelListener;
|
||||
import javax.swing.plaf.basic.BasicComboBoxRenderer;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The switch branches dialog
|
||||
*/
|
||||
public class GitSwitchBranchesDialog extends DialogWrapper {
|
||||
/**
|
||||
* The branch configuration name text field
|
||||
*/
|
||||
private JTextField myNameTextField;
|
||||
/**
|
||||
* The changes panel
|
||||
*/
|
||||
private JPanel myChangesPanel;
|
||||
/**
|
||||
* The root panel
|
||||
*/
|
||||
private JPanel myRoot;
|
||||
/**
|
||||
* The branches table
|
||||
*/
|
||||
private JBTable myBranchesTable;
|
||||
/**
|
||||
* Changes to transfer to new configurations label
|
||||
*/
|
||||
private JLabel myChangesLabel;
|
||||
/**
|
||||
* The changes tree
|
||||
*/
|
||||
private final ChangesTreeList<Change> myChangesTree;
|
||||
/**
|
||||
* The project to use
|
||||
*/
|
||||
private final Project myProject;
|
||||
/**
|
||||
* The target branch configuration
|
||||
*/
|
||||
private final GitBranchConfiguration myTarget;
|
||||
/**
|
||||
* The configuration settings object
|
||||
*/
|
||||
private final GitBranchConfigurations myConfig;
|
||||
/**
|
||||
* If true, the dialog was invoked to modify the current configuration
|
||||
*/
|
||||
private final boolean myModify;
|
||||
/**
|
||||
* The list of branches to use
|
||||
*/
|
||||
private final List<BranchDescriptor> myBranches;
|
||||
/**
|
||||
* The existing configuration names (used to simplify validation)
|
||||
*/
|
||||
private Set<String> myExistingConfigNames;
|
||||
/**
|
||||
* Base project directory
|
||||
*/
|
||||
private File myBaseFile;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param project the project
|
||||
* @param target the target configuration
|
||||
* @param allChanges the all changes
|
||||
* @param roots the collection of roots
|
||||
* @param remoteBranch the remote branch
|
||||
* @param config the configuration
|
||||
* @param isModify the modify flag
|
||||
* @throws VcsException if there is a problem with detecting the current state
|
||||
*/
|
||||
protected GitSwitchBranchesDialog(Project project,
|
||||
final GitBranchConfiguration target,
|
||||
final List<Change> allChanges,
|
||||
List<VirtualFile> roots,
|
||||
String remoteBranch, final GitBranchConfigurations config, boolean isModify) throws VcsException {
|
||||
super(project, true);
|
||||
setTitle(isModify ? "Modify Branch Configuration" : "Checkout Branch Configuration");
|
||||
assert (remoteBranch == null) || (target == null) : "There should be no target for remote branch";
|
||||
myTarget = target;
|
||||
myConfig = config;
|
||||
myModify = isModify;
|
||||
myProject = project;
|
||||
VirtualFile baseDir = project.getBaseDir();
|
||||
myBaseFile = baseDir == null ? null : new File(baseDir.getPath());
|
||||
myExistingConfigNames = myConfig.getConfigurationNames();
|
||||
myChangesTree = new ChangesTreeList<Change>(myProject,
|
||||
Collections.<Change>emptyList(),
|
||||
!myModify,
|
||||
true,
|
||||
null,
|
||||
RemoteRevisionsCache.getInstance(project).getChangesNodeDecorator()) {
|
||||
protected DefaultTreeModel buildTreeModel(final List<Change> changes, ChangeNodeDecorator changeNodeDecorator) {
|
||||
TreeModelBuilder builder = new TreeModelBuilder(myProject, false);
|
||||
return builder.buildModel(changes, changeNodeDecorator);
|
||||
}
|
||||
|
||||
protected List<Change> getSelectedObjects(final ChangesBrowserNode<Change> node) {
|
||||
return node.getAllChangesUnder();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Change getLeadSelectedObject(final ChangesBrowserNode node) {
|
||||
final Object o = node.getUserObject();
|
||||
if (o instanceof Change) {
|
||||
return (Change)o;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
if (remoteBranch != null) {
|
||||
myBranches = prepareBranchesForRemote(remoteBranch, roots);
|
||||
}
|
||||
else {
|
||||
myBranches = prepareBranchDescriptors(target, roots);
|
||||
}
|
||||
if (target == null) {
|
||||
myNameTextField.setText(generateNewConfigurationName());
|
||||
}
|
||||
else {
|
||||
myNameTextField.setText(target.getName());
|
||||
}
|
||||
myChangesTree.setChangesToDisplay(allChanges);
|
||||
myChangesTree.setIncludedChanges(Collections.<Change>emptyList());
|
||||
myChangesPanel.add(myChangesTree, BorderLayout.CENTER);
|
||||
myChangesLabel.setLabelFor(myChangesTree);
|
||||
if (myModify) {
|
||||
myChangesLabel.setText("Changes in the current configuration");
|
||||
}
|
||||
RootTableModel tableModel = new RootTableModel();
|
||||
myBranchesTable.setModel(tableModel);
|
||||
myBranchesTable.setDefaultRenderer(Pair.class, new PairTableRenderer());
|
||||
final TableColumnModel columns = myBranchesTable.getColumnModel();
|
||||
final PairTableRenderer renderer = new PairTableRenderer();
|
||||
for (Enumeration<TableColumn> cs = columns.getColumns(); cs.hasMoreElements();) {
|
||||
cs.nextElement().setCellRenderer(renderer);
|
||||
}
|
||||
TableColumn revisionColumn = columns.getColumn(RootTableModel.REVISION_COLUMN);
|
||||
revisionColumn.setCellEditor(new ReferenceEditor());
|
||||
TableColumn branchColumn = columns.getColumn(RootTableModel.NEW_BRANCH_COLUMN);
|
||||
branchColumn.setCellEditor(new BranchNameEditor());
|
||||
myNameTextField.getDocument().addDocumentListener(new DocumentAdapter() {
|
||||
@Override
|
||||
protected void textChanged(DocumentEvent e) {
|
||||
verify();
|
||||
}
|
||||
});
|
||||
tableModel.addTableModelListener(new TableModelListener() {
|
||||
@Override
|
||||
public void tableChanged(TableModelEvent e) {
|
||||
verify();
|
||||
}
|
||||
});
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* The show configuration dialog
|
||||
*
|
||||
* @param project the project to use
|
||||
* @param target the target configuration
|
||||
* @param allChanges the collection of changes
|
||||
* @param roots the vcs roots
|
||||
* @param remoteBranch the remote branch
|
||||
* @param config the configuration
|
||||
* @param isModify the modify mode flag
|
||||
* @return the pair of selected changes and
|
||||
* @throws VcsException if there is a problem with accessing git
|
||||
*/
|
||||
@Nullable
|
||||
public static Result showDialog(Project project,
|
||||
@Nullable GitBranchConfiguration target,
|
||||
final List<Change> allChanges,
|
||||
List<VirtualFile> roots,
|
||||
@Nullable String remoteBranch,
|
||||
final GitBranchConfigurations config,
|
||||
boolean isModify) throws VcsException {
|
||||
GitSwitchBranchesDialog d = new GitSwitchBranchesDialog(project, target, allChanges, roots, remoteBranch, config, isModify);
|
||||
d.show();
|
||||
if (d.isOK()) {
|
||||
return d.createResult();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return create dialog result object basing on the dialog state
|
||||
*/
|
||||
private Result createResult() {
|
||||
Result rc = new Result();
|
||||
String name = myNameTextField.getText().trim();
|
||||
if (myTarget == null) {
|
||||
rc.target = myConfig.createConfiguration(name);
|
||||
}
|
||||
else {
|
||||
rc.target = myTarget;
|
||||
rc.target.setName(name);
|
||||
}
|
||||
rc.changes = new ArrayList<Change>(myChangesTree.getIncludedChanges());
|
||||
for (BranchDescriptor d : myBranches) {
|
||||
if (d.root != null) {
|
||||
if (!StringUtil.isEmpty(d.newBranchName)) {
|
||||
rc.referencesToUse.put(d.root, d.referenceToCheckout.trim());
|
||||
rc.target.setBranch(d.root.getPath(), d.newBranchName.trim());
|
||||
rc.checkoutNeeded.add(d.root);
|
||||
}
|
||||
else {
|
||||
rc.target.setBranch(d.root.getPath(), d.referenceToCheckout.trim());
|
||||
if (!d.referenceToCheckout.equals(d.currentReference)) {
|
||||
rc.checkoutNeeded.add(d.root);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify dialog state
|
||||
*/
|
||||
private void verify() {
|
||||
String text = myNameTextField.getText().trim();
|
||||
if (text.length() == 0) {
|
||||
setError("Empty configuration name is not allowed.");
|
||||
return;
|
||||
}
|
||||
else if (myTarget != null && text.equals(myTarget.getName())) {
|
||||
}
|
||||
else if (myExistingConfigNames.contains(text)) {
|
||||
setError("There is another configuration with the same name");
|
||||
return;
|
||||
}
|
||||
for (BranchDescriptor d : myBranches) {
|
||||
switch (d.status) {
|
||||
case BRANCH_NAME_EXISTS:
|
||||
setError("Duplicate branch name for root " + d.getRoot());
|
||||
return;
|
||||
case INVALID_BRANCH_NAME:
|
||||
setError("Invalid branch name for root " + d.getRoot());
|
||||
return;
|
||||
case BAD_REVISION:
|
||||
setError("Invalid revision for root " + d.getRoot());
|
||||
return;
|
||||
case MISSING_REVISION:
|
||||
setError("The revision must be specified for root " + d.getRoot());
|
||||
return;
|
||||
case CHECKOUT_NEEDED:
|
||||
case NO_ACTION:
|
||||
case REMOVED_ROOT:
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unexpected status: " + d.status);
|
||||
}
|
||||
}
|
||||
setError(null);
|
||||
}
|
||||
|
||||
private void setError(String s) {
|
||||
setErrorText(s);
|
||||
setOKActionEnabled(s == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate new configuration name basing on descriptor
|
||||
*
|
||||
* @return the generated configuration name
|
||||
*/
|
||||
private String generateNewConfigurationName() {
|
||||
String name = null;
|
||||
for (BranchDescriptor d : myBranches) {
|
||||
if (d.newBranchName != null) {
|
||||
name = d.newBranchName;
|
||||
break;
|
||||
}
|
||||
if (d.existingBranches.contains(d.currentReference)) {
|
||||
name = d.currentReference;
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
name = "Unnamed";
|
||||
}
|
||||
if (myExistingConfigNames.contains(name)) {
|
||||
for (int i = 2; i < Integer.MAX_VALUE; i++) {
|
||||
String t = name + " " + i;
|
||||
if (!myExistingConfigNames.contains(t)) {
|
||||
name = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare branches for the case of remote checkout
|
||||
*
|
||||
* @param remoteBranch the remote branch to checkout
|
||||
* @param roots the collection of vcs roots
|
||||
* @return the list of descriptors for the remote
|
||||
* @throws VcsException if git failed
|
||||
*/
|
||||
private List<BranchDescriptor> prepareBranchesForRemote(String remoteBranch, List<VirtualFile> roots)
|
||||
throws VcsException {
|
||||
assert roots.size() > 0;
|
||||
List<BranchDescriptor> rc = new ArrayList<BranchDescriptor>();
|
||||
HashSet<String> allBranches = new HashSet<String>();
|
||||
for (VirtualFile root : roots) {
|
||||
BranchDescriptor d = new BranchDescriptor();
|
||||
d.root = root;
|
||||
d.currentReference = myConfig.describeRoot(root);
|
||||
d.referenceToCheckout = "remotes/" + remoteBranch;
|
||||
GitBranch.listAsStrings(myProject, root, false, true, d.existingBranches, null);
|
||||
GitBranch.listAsStrings(myProject, root, true, true, d.referencesToSelect, null);
|
||||
allBranches.addAll(d.existingBranches);
|
||||
rc.add(d);
|
||||
}
|
||||
int p = remoteBranch.indexOf('/');
|
||||
assert p > 0 && p < remoteBranch.length() - 1 : "Unexpected format for remote branch: " + remoteBranch;
|
||||
String candidate = remoteBranch.substring(p + 1);
|
||||
String actual = null;
|
||||
if (!allBranches.contains(candidate)) {
|
||||
actual = candidate;
|
||||
}
|
||||
else {
|
||||
for (int i = 2; i < Integer.MAX_VALUE; i++) {
|
||||
String t = candidate + i;
|
||||
if (!allBranches.contains(t)) {
|
||||
actual = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert actual != null : "Unexpected number of branches: " + remoteBranch;
|
||||
}
|
||||
for (BranchDescriptor d : rc) {
|
||||
d.newBranchName = actual;
|
||||
d.updateStatus();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare branch descriptors for existing configuration
|
||||
*
|
||||
* @param target the target
|
||||
* @param roots the vcs root
|
||||
* @return the list of branch descriptors
|
||||
* @throws VcsException in case of git error
|
||||
*/
|
||||
private List<BranchDescriptor> prepareBranchDescriptors(
|
||||
GitBranchConfiguration target,
|
||||
List<VirtualFile> roots)
|
||||
throws VcsException {
|
||||
Map<String, String> map = target == null ? Collections.<String, String>emptyMap() : target.getReferences();
|
||||
List<BranchDescriptor> rc = new ArrayList<BranchDescriptor>();
|
||||
for (VirtualFile root : roots) {
|
||||
BranchDescriptor d = new BranchDescriptor();
|
||||
d.root = root;
|
||||
d.storedReference = map.remove(root.getPath());
|
||||
if (d.storedReference != null) {
|
||||
d.storedRoot = d.root.getPath();
|
||||
}
|
||||
d.currentReference = myConfig.describeRoot(root);
|
||||
if (d.storedReference != null && !myModify) {
|
||||
d.referenceToCheckout = d.storedReference;
|
||||
}
|
||||
else {
|
||||
d.referenceToCheckout = d.currentReference;
|
||||
}
|
||||
GitBranch.listAsStrings(myProject, root, false, true, d.existingBranches, null);
|
||||
GitBranch.listAsStrings(myProject, root, true, true, d.referencesToSelect, null);
|
||||
d.updateStatus();
|
||||
rc.add(d);
|
||||
}
|
||||
for (Map.Entry<String, String> m : map.entrySet()) {
|
||||
String root = m.getKey();
|
||||
String ref = m.getValue();
|
||||
BranchDescriptor d = new BranchDescriptor();
|
||||
d.storedReference = ref;
|
||||
d.storedRoot = root;
|
||||
d.referenceToCheckout = ref;
|
||||
d.updateStatus();
|
||||
rc.add(d);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* The table model that displays mapping for the vcs roots
|
||||
*/
|
||||
class RootTableModel extends AbstractTableModel {
|
||||
/**
|
||||
* The vcs root
|
||||
*/
|
||||
static final int ROOT_COLUMN = 0;
|
||||
/**
|
||||
* The revision
|
||||
*/
|
||||
static final int REVISION_COLUMN = 1;
|
||||
/**
|
||||
* The name of branch to checkout
|
||||
*/
|
||||
static final int NEW_BRANCH_COLUMN = 2;
|
||||
/**
|
||||
* The status
|
||||
*/
|
||||
static final int STATUS_COLUMN = 3;
|
||||
/**
|
||||
* The total number of columns
|
||||
*/
|
||||
static final int COLUMNS = STATUS_COLUMN + 1;
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return myBranches.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return COLUMNS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||
BranchDescriptor d = myBranches.get(rowIndex);
|
||||
if (d.root == null) {
|
||||
return false;
|
||||
}
|
||||
return columnIndex == REVISION_COLUMN || columnIndex == NEW_BRANCH_COLUMN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||
String t = (String)aValue;
|
||||
BranchDescriptor d = myBranches.get(rowIndex);
|
||||
if (d.root == null) {
|
||||
return;
|
||||
}
|
||||
if (columnIndex == REVISION_COLUMN) {
|
||||
d.referenceToCheckout = t;
|
||||
String remotesPrefix = "remotes/";
|
||||
if (StringUtil.isEmpty(d.newBranchName) && t.startsWith(remotesPrefix) && d.referencesToSelect.contains(t)) {
|
||||
int p = t.indexOf(t.indexOf('/'), remotesPrefix.length() + 1);
|
||||
if (p != -1) {
|
||||
String c = t.substring(p);
|
||||
if (!d.existingBranches.contains(c)) {
|
||||
d.newBranchName = c;
|
||||
}
|
||||
else {
|
||||
for (int i = 2; i < Integer.MAX_VALUE; i++) {
|
||||
String candidate = c + i;
|
||||
if (!d.existingBranches.contains(c)) {
|
||||
d.newBranchName = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (columnIndex == NEW_BRANCH_COLUMN) {
|
||||
d.newBranchName = t;
|
||||
}
|
||||
d.updateStatus();
|
||||
fireTableRowsUpdated(rowIndex, rowIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
BranchDescriptor d = myBranches.get(rowIndex);
|
||||
switch (columnIndex) {
|
||||
case ROOT_COLUMN:
|
||||
return Pair.create(d.getRoot(), d.root != null);
|
||||
case REVISION_COLUMN:
|
||||
return Pair.create(d.referenceToCheckout, d.isReferenceValid);
|
||||
case NEW_BRANCH_COLUMN:
|
||||
return Pair.create(d.newBranchName == null ? "" : d.newBranchName, d.isNewBranchValid);
|
||||
case STATUS_COLUMN:
|
||||
switch (d.status) {
|
||||
case INVALID_BRANCH_NAME:
|
||||
return Pair.create("Invalid new branch name", false);
|
||||
case BAD_REVISION:
|
||||
return Pair.create("Invalid revision", false);
|
||||
case MISSING_REVISION:
|
||||
return Pair.create("Missing revision", false);
|
||||
case CHECKOUT_NEEDED:
|
||||
return Pair.create("Checkout", true);
|
||||
case REMOVED_ROOT:
|
||||
return Pair.create("Removed root", true);
|
||||
case NO_ACTION:
|
||||
return Pair.create("", true);
|
||||
case BRANCH_NAME_EXISTS:
|
||||
return Pair.create("Branch name exists", false);
|
||||
default:
|
||||
throw new IllegalStateException("Unknown status:" + d.status);
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("Unknown column: " + columnIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
switch (column) {
|
||||
case ROOT_COLUMN:
|
||||
return "Vcs Root";
|
||||
case REVISION_COLUMN:
|
||||
return "Checkout";
|
||||
case NEW_BRANCH_COLUMN:
|
||||
return "As New Branch";
|
||||
case STATUS_COLUMN:
|
||||
return "Status";
|
||||
default:
|
||||
throw new IllegalStateException("Unknown column: " + column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The object representing row entry
|
||||
*/
|
||||
class BranchDescriptor {
|
||||
/**
|
||||
* The root to checkout, if null means that the old root is missing.
|
||||
*/
|
||||
VirtualFile root;
|
||||
/**
|
||||
* Stored root path
|
||||
*/
|
||||
String storedRoot;
|
||||
/**
|
||||
* Stored reference
|
||||
*/
|
||||
String storedReference;
|
||||
/**
|
||||
* The commit expression to checkout
|
||||
*/
|
||||
String referenceToCheckout;
|
||||
/**
|
||||
* if true, branch name is valid
|
||||
*/
|
||||
boolean isReferenceValid;
|
||||
/**
|
||||
* True if commit expression is valid
|
||||
*/
|
||||
RootStatus status;
|
||||
/**
|
||||
* The name of of branch
|
||||
*/
|
||||
String newBranchName;
|
||||
/**
|
||||
* if true, branch name is valid
|
||||
*/
|
||||
boolean isNewBranchValid;
|
||||
/**
|
||||
* The current type
|
||||
*/
|
||||
String currentReference;
|
||||
/**
|
||||
* The existing branches
|
||||
*/
|
||||
HashSet<String> existingBranches = new HashSet<String>();
|
||||
/**
|
||||
* The existing branches
|
||||
*/
|
||||
TreeSet<String> referencesToSelect = new TreeSet<String>();
|
||||
|
||||
/**
|
||||
* Update status of the entry
|
||||
*/
|
||||
void updateStatus() {
|
||||
if (root == null) {
|
||||
status = RootStatus.REMOVED_ROOT;
|
||||
return;
|
||||
}
|
||||
status = branchNameStatus(newBranchName);
|
||||
isNewBranchValid = status == null;
|
||||
isReferenceValid = true;
|
||||
if (referenceToCheckout == null) {
|
||||
status = RootStatus.MISSING_REVISION;
|
||||
isReferenceValid = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
GitRevisionNumber.resolve(myProject, root, referenceToCheckout);
|
||||
if (status == null) {
|
||||
status = StringUtil.isEmpty(newBranchName) && currentReference.equals(storedReference)
|
||||
? RootStatus.NO_ACTION
|
||||
: RootStatus.CHECKOUT_NEEDED;
|
||||
}
|
||||
}
|
||||
catch (VcsException e) {
|
||||
isReferenceValid = false;
|
||||
status = RootStatus.BAD_REVISION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get branch name status
|
||||
*
|
||||
* @param name the name to check
|
||||
* @return null if branch name is ok, or status describing the problem
|
||||
*/
|
||||
@Nullable
|
||||
private RootStatus branchNameStatus(final String name) {
|
||||
RootStatus b = null;
|
||||
if (!StringUtil.isEmpty(name)) {
|
||||
if (!GitBranchNameValidator.INSTANCE.checkInput(name)) {
|
||||
b = RootStatus.INVALID_BRANCH_NAME;
|
||||
}
|
||||
if (existingBranches.contains(name)) {
|
||||
b = RootStatus.BRANCH_NAME_EXISTS;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
public String getRoot() {
|
||||
String path = root == null ? storedRoot : root.getPath();
|
||||
String relative = myBaseFile == null ? path : FileUtil.getRelativePath(myBaseFile, new File(path));
|
||||
return relative == null ? path : relative;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The root status
|
||||
*/
|
||||
enum RootStatus {
|
||||
/**
|
||||
* The checkout is needed for the vcs root. Non-error status.
|
||||
*/
|
||||
CHECKOUT_NEEDED,
|
||||
/**
|
||||
* No action needed for vcs root. Non-error status.
|
||||
*/
|
||||
NO_ACTION,
|
||||
/**
|
||||
* The branch information does not represents a vcs root anymore. Non-error status.
|
||||
*/
|
||||
REMOVED_ROOT,
|
||||
/**
|
||||
* The bad revision expression
|
||||
*/
|
||||
MISSING_REVISION,
|
||||
/**
|
||||
* The revision expression is invalid or could not be evaluated
|
||||
*/
|
||||
BAD_REVISION,
|
||||
/**
|
||||
* The bad name for the branch
|
||||
*/
|
||||
INVALID_BRANCH_NAME,
|
||||
/**
|
||||
* The bad name for the branch
|
||||
*/
|
||||
BRANCH_NAME_EXISTS
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pair text renderer
|
||||
*/
|
||||
static class PairTableRenderer extends ColoredTableCellRenderer {
|
||||
@Override
|
||||
protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
|
||||
@SuppressWarnings({"unchecked"}) Pair<String, Boolean> p = (Pair<String, Boolean>)value;
|
||||
String t = p.first == null ? "" : p.first;
|
||||
if (p.second) {
|
||||
append(t);
|
||||
}
|
||||
else {
|
||||
append(t, SimpleTextAttributes.ERROR_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The editor for references
|
||||
*/
|
||||
class ReferenceEditor extends AbstractTableCellEditor {
|
||||
/**
|
||||
* The root panel
|
||||
*/
|
||||
private final JPanel myPanel = new JPanel(new GridBagLayout());
|
||||
/**
|
||||
* Combobox for the panel
|
||||
*/
|
||||
private final JComboBox myComboBox = new JComboBox();
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*/
|
||||
private ReferenceEditor() {
|
||||
myComboBox.setEditable(true);
|
||||
myComboBox.setRenderer(new BasicComboBoxRenderer());
|
||||
myComboBox.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
stopCellEditing();
|
||||
}
|
||||
});
|
||||
myPanel.add(myComboBox,
|
||||
new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0),
|
||||
0,
|
||||
0));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
|
||||
BranchDescriptor d = myBranches.get(row);
|
||||
myComboBox.removeAllItems();
|
||||
for (String s : d.referencesToSelect) {
|
||||
myComboBox.addItem(s);
|
||||
}
|
||||
myComboBox.setSelectedItem(d.referenceToCheckout);
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Object getCellEditorValue() {
|
||||
return myComboBox.getSelectedItem();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The editor for branch names
|
||||
*/
|
||||
class BranchNameEditor extends AbstractTableCellEditor {
|
||||
/**
|
||||
* The root panel
|
||||
*/
|
||||
private final JPanel myPanel = new JPanel(new GridBagLayout());
|
||||
/**
|
||||
* Combobox for the panel
|
||||
*/
|
||||
private final JTextField myTextField = new JTextField();
|
||||
/**
|
||||
* The values that considered invalid
|
||||
*/
|
||||
private Set<String> myInvalidValues;
|
||||
/**
|
||||
* Default foregorund color (likely black one)
|
||||
*/
|
||||
private Color myDefaultForeground;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*/
|
||||
private BranchNameEditor() {
|
||||
myDefaultForeground = myTextField.getForeground();
|
||||
myTextField.getDocument().addDocumentListener(new DocumentAdapter() {
|
||||
@Override
|
||||
protected void textChanged(DocumentEvent e) {
|
||||
String s = myTextField.getText();
|
||||
if (s.length() == 0 &&
|
||||
(myInvalidValues == null || !myInvalidValues.contains(s)) &&
|
||||
(s.length() == 0 || GitBranchNameValidator.INSTANCE.checkInput(s))) {
|
||||
myTextField.setForeground(myDefaultForeground);
|
||||
}
|
||||
else {
|
||||
myTextField.setForeground(Color.RED);
|
||||
}
|
||||
}
|
||||
});
|
||||
myPanel.add(myTextField,
|
||||
new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0),
|
||||
0,
|
||||
0));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
|
||||
BranchDescriptor d = myBranches.get(row);
|
||||
myInvalidValues = d.existingBranches;
|
||||
myTextField.setText(d.newBranchName == null ? "" : d.newBranchName);
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
public Object getCellEditorValue() {
|
||||
String s = myTextField.getText().trim();
|
||||
return s.length() == 0 ? null : s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of the dialog
|
||||
*/
|
||||
public static class Result {
|
||||
/**
|
||||
* The roots for which checkout is needed
|
||||
*/
|
||||
Collection<VirtualFile> checkoutNeeded = new ArrayList<VirtualFile>();
|
||||
/**
|
||||
* The set of selected changes to transfer to new configuration
|
||||
*/
|
||||
List<Change> changes;
|
||||
/**
|
||||
* References to use for new branches
|
||||
*/
|
||||
HashMap<VirtualFile, String> referencesToUse = new HashMap<VirtualFile, String>();
|
||||
/**
|
||||
* The target configuration
|
||||
*/
|
||||
GitBranchConfiguration target;
|
||||
}
|
||||
}
|
||||
@@ -137,6 +137,10 @@ public class GitCommand {
|
||||
* Check attributes command
|
||||
*/
|
||||
public static final GitCommand CHECK_ATTR = read("check-attr");
|
||||
/**
|
||||
* The constant for git command
|
||||
*/
|
||||
public static final GitCommand DESCRIBE = meta("describe");
|
||||
/**
|
||||
* Name of environment variable that specifies editor for the git
|
||||
*/
|
||||
@@ -251,7 +255,8 @@ public class GitCommand {
|
||||
/**
|
||||
* Metadata read/write command
|
||||
*/
|
||||
META, }
|
||||
META,
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread policy for command
|
||||
|
||||
@@ -17,6 +17,7 @@ package git4idea.ui;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.vcs.AbstractVcsHelper;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import git4idea.GitBranch;
|
||||
@@ -218,6 +219,17 @@ public class GitUIUtil {
|
||||
Messages.showErrorDialog(project, message, GitBundle.message("error.occurred.during", operation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show errors on the tab
|
||||
*
|
||||
* @param project the context project
|
||||
* @param title the operation title
|
||||
* @param errors the errors to display
|
||||
*/
|
||||
public static void showTabErrors(Project project, String title, List<VcsException> errors) {
|
||||
AbstractVcsHelper.getInstance(project).showErrors(errors, title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup remotes combobox. The default remote for the current branch is selected by default.
|
||||
* This method gets current branch for the project.
|
||||
|
||||
Reference in New Issue
Block a user