/* * Copyright 2000-2009 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.rebase; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.DocumentAdapter; import git4idea.*; import git4idea.branch.GitBranchUtil; import git4idea.branch.GitRebaseParams; import git4idea.config.GitConfigUtil; import git4idea.config.GitRebaseSettings; import git4idea.i18n.GitBundle; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; import git4idea.ui.GitReferenceValidator; import git4idea.util.GitUIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.DocumentEvent; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import static com.intellij.openapi.util.text.StringUtil.isEmptyOrSpaces; import static com.intellij.util.ObjectUtils.assertNotNull; /** * The dialog that allows initiating git rebase activity */ public class GitRebaseDialog extends DialogWrapper { private static final Logger LOG = Logger.getInstance(GitRebaseDialog.class); @NotNull private final GitRepositoryManager myRepositoryManager; /** * Git root selector */ protected ComboBox myGitRootComboBox; /** * The selector for branch to rebase */ protected ComboBox myBranchComboBox; /** * The from branch combo box. This is used as base branch if different from onto branch */ protected ComboBox myFromComboBox; /** * The validation button for from branch */ private JButton myFromValidateButton; /** * The onto branch combobox. */ protected ComboBox myOntoComboBox; /** * The validate button for onto branch */ private JButton myOntoValidateButton; /** * Show tags in drop down */ private JCheckBox myShowTagsCheckBox; /** * If selected, rebase is interactive */ protected JCheckBox myInteractiveCheckBox; /** * The root panel of the dialog */ private JPanel myPanel; /** * If selected, remote branches are shown as well */ protected JCheckBox myShowRemoteBranchesCheckBox; /** * Preserve merges checkbox */ private JCheckBox myPreserveMergesCheckBox; /** * The current project */ protected final Project myProject; /** * The list of local branches */ protected final List<GitBranch> myLocalBranches = new ArrayList<>(); /** * The list of remote branches */ protected final List<GitBranch> myRemoteBranches = new ArrayList<>(); /** * The current branch */ @Nullable protected GitBranch myCurrentBranch; /** * The tags */ protected final List<GitTag> myTags = new ArrayList<>(); /** * The validator for onto field */ private final GitReferenceValidator myOntoValidator; /** * The validator for from field */ private final GitReferenceValidator myFromValidator; @NotNull private final GitRebaseSettings mySettings; @Nullable private final String myOriginalOntoBranch; /** * A constructor * * @param project a project to select * @param roots a git repository roots for the project * @param defaultRoot a guessed default root */ public GitRebaseDialog(Project project, List<VirtualFile> roots, VirtualFile defaultRoot) { super(project, true); setTitle(GitBundle.getString("rebase.title")); setOKButtonText(GitBundle.getString("rebase.button")); init(); myProject = project; mySettings = ServiceManager.getService(myProject, GitRebaseSettings.class); myRepositoryManager = GitUtil.getRepositoryManager(myProject); final Runnable validateRunnable = () -> validateFields(); myOntoValidator = new GitReferenceValidator(myProject, myGitRootComboBox, GitUIUtil.getTextField(myOntoComboBox), myOntoValidateButton, validateRunnable); myFromValidator = new GitReferenceValidator(myProject, myGitRootComboBox, GitUIUtil.getTextField(myFromComboBox), myFromValidateButton, validateRunnable); GitUIUtil.setupRootChooser(myProject, roots, defaultRoot, myGitRootComboBox, null); myGitRootComboBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { validateFields(); } }); setupBranches(); myInteractiveCheckBox.setSelected(mySettings.isInteractive()); myPreserveMergesCheckBox.setSelected(mySettings.isPreserveMerges()); myShowTagsCheckBox.setSelected(mySettings.showTags()); myShowRemoteBranchesCheckBox.setSelected(mySettings.showRemoteBranches()); overwriteOntoForCurrentBranch(mySettings); myOriginalOntoBranch = GitUIUtil.getTextField(myOntoComboBox).getText(); validateFields(); } @Nullable @Override public JComponent getPreferredFocusedComponent() { return myOntoComboBox; } private void overwriteOntoForCurrentBranch(@NotNull GitRebaseSettings settings) { String onto = settings.getOnto(); if (onto != null && !onto.equals(myBranchComboBox.getSelectedItem())) { if (!isValidRevision(onto)) { mySettings.setOnto(null); } else { myOntoComboBox.setSelectedItem(onto); } } } private boolean isValidRevision(@NotNull String revisionExpression) { try { GitRevisionNumber.resolve(myProject, gitRoot(), revisionExpression); return true; } catch (VcsException e) { LOG.debug(e); return false; } } @Override protected void doOKAction() { try { rememberFields(); } finally { super.doOKAction(); } } private void rememberFields() { mySettings.setInteractive(myInteractiveCheckBox.isSelected()); mySettings.setPreserveMerges(myPreserveMergesCheckBox.isSelected()); mySettings.setShowTags(myShowTagsCheckBox.isSelected()); mySettings.setShowRemoteBranches(myShowRemoteBranchesCheckBox.isSelected()); String onto = StringUtil.nullize(GitUIUtil.getTextField(myOntoComboBox).getText(), true); if (onto != null && !onto.equals(myOriginalOntoBranch)) { mySettings.setOnto(onto); } } /** * Validate fields */ private void validateFields() { if (GitUIUtil.getTextField(myOntoComboBox).getText().length() == 0) { setErrorText(null); setOKActionEnabled(false); return; } else if (myOntoValidator.isInvalid()) { setErrorText(GitBundle.getString("rebase.invalid.onto"), myOntoComboBox); setOKActionEnabled(false); return; } if (GitUIUtil.getTextField(myFromComboBox).getText().length() != 0 && myFromValidator.isInvalid()) { setErrorText(GitBundle.getString("rebase.invalid.from"), myFromComboBox); setOKActionEnabled(false); return; } if (GitRebaseUtils.isRebaseInTheProgress(myProject, gitRoot())) { setErrorText(GitBundle.getString("rebase.in.progress")); setOKActionEnabled(false); return; } setErrorText(null); setOKActionEnabled(true); } /** * Setup branch drop down. */ private void setupBranches() { GitUIUtil.getTextField(myOntoComboBox).getDocument().addDocumentListener(new DocumentAdapter() { protected void textChanged(final DocumentEvent e) { validateFields(); } }); final ActionListener rootListener = new ActionListener() { public void actionPerformed(final ActionEvent e) { loadRefs(); updateBranches(); } }; final ActionListener showListener = new ActionListener() { public void actionPerformed(final ActionEvent e) { updateOntoFrom(); } }; myShowRemoteBranchesCheckBox.addActionListener(showListener); myShowTagsCheckBox.addActionListener(showListener); rootListener.actionPerformed(null); myGitRootComboBox.addActionListener(rootListener); myBranchComboBox.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { updateTrackedBranch(); } }); } /** * Update branches when git root changed */ private void updateBranches() { myBranchComboBox.removeAllItems(); for (GitBranch b : myLocalBranches) { myBranchComboBox.addItem(b.getName()); } if (myCurrentBranch != null) { myBranchComboBox.setSelectedItem(myCurrentBranch.getName()); } else { myBranchComboBox.setSelectedItem(0); } updateOntoFrom(); updateTrackedBranch(); } /** * Update onto and from comboboxes. */ protected void updateOntoFrom() { String onto = GitUIUtil.getTextField(myOntoComboBox).getText(); String from = GitUIUtil.getTextField(myFromComboBox).getText(); myFromComboBox.removeAllItems(); myOntoComboBox.removeAllItems(); for (GitBranch b : myLocalBranches) { myFromComboBox.addItem(b); myOntoComboBox.addItem(b); } if (myShowRemoteBranchesCheckBox.isSelected()) { for (GitBranch b : myRemoteBranches) { myFromComboBox.addItem(b); myOntoComboBox.addItem(b); } } if (myShowTagsCheckBox.isSelected()) { for (GitTag t : myTags) { myFromComboBox.addItem(t); myOntoComboBox.addItem(t); } } GitUIUtil.getTextField(myOntoComboBox).setText(onto); GitUIUtil.getTextField(myFromComboBox).setText(from); } /** * Load tags and branches */ protected void loadRefs() { try { myLocalBranches.clear(); myRemoteBranches.clear(); myTags.clear(); final VirtualFile root = gitRoot(); GitRepository repository = GitUtil.getRepositoryManager(myProject).getRepositoryForRoot(root); if (repository != null) { myLocalBranches.addAll(repository.getBranches().getLocalBranches()); myRemoteBranches.addAll(repository.getBranches().getRemoteBranches()); myCurrentBranch = repository.getCurrentBranch(); } else { LOG.error("Repository is null for root " + root); } GitTag.list(myProject, root, myTags); } catch (VcsException e) { GitUIUtil.showOperationError(myProject, e, "git branch -a"); } } /** * Update tracked branch basing on the currently selected branch */ private void updateTrackedBranch() { try { final VirtualFile root = gitRoot(); String currentBranch = (String)myBranchComboBox.getSelectedItem(); GitBranch trackedBranch = null; if (currentBranch != null) { String remote = GitConfigUtil.getValue(myProject, root, "branch." + currentBranch + ".remote"); String mergeBranch = GitConfigUtil.getValue(myProject, root, "branch." + currentBranch + ".merge"); if (remote == null || mergeBranch == null) { trackedBranch = null; } else { mergeBranch = GitBranchUtil.stripRefsPrefix(mergeBranch); if (remote.equals(".")) { trackedBranch = new GitSvnRemoteBranch(mergeBranch); } else { GitRemote r = GitBranchUtil.findRemoteByNameOrLogError(myProject, root, remote); if (r != null) { trackedBranch = new GitStandardRemoteBranch(r, mergeBranch); } } } } if (trackedBranch != null) { myOntoComboBox.setSelectedItem(trackedBranch); } else { GitUIUtil.getTextField(myOntoComboBox).setText(""); } GitUIUtil.getTextField(myFromComboBox).setText(""); } catch (VcsException e) { GitUIUtil.showOperationError(myProject, e, "git config"); } } /** * @return the currently selected git root */ public VirtualFile gitRoot() { return (VirtualFile)myGitRootComboBox.getSelectedItem(); } @NotNull public GitRepository getSelectedRepository() { return assertNotNull(myRepositoryManager.getRepositoryForRoot(gitRoot())); } @NotNull public GitRebaseParams getSelectedParams() { String selectedBranch = (String)myBranchComboBox.getSelectedItem(); String branch = myCurrentBranch != null && !myCurrentBranch.getName().equals(selectedBranch) ? selectedBranch : null; String from = GitUIUtil.getTextField(myFromComboBox).getText(); String onto = GitUIUtil.getTextField(myOntoComboBox).getText(); String upstream; String newBase; if (isEmptyOrSpaces(from)) { upstream = onto; newBase = null; } else { upstream = from; newBase = onto; } return new GitRebaseParams(branch, newBase, upstream, myInteractiveCheckBox.isSelected(), myPreserveMergesCheckBox.isSelected()); } /** * {@inheritDoc} */ @Override protected String getDimensionServiceKey() { return getClass().getName(); } /** * {@inheritDoc} */ protected JComponent createCenterPanel() { return myPanel; } /** * {@inheritDoc} */ @Override protected String getHelpId() { return "reference.VersionControl.Git.Rebase"; } }