From 358e3a68e16f00c7f5e3656fd73799a9759caa3d Mon Sep 17 00:00:00 2001 From: Constantine Plotnikov Date: Fri, 7 Nov 2008 21:09:29 +0300 Subject: [PATCH] git4idea: rewritten fetch dialog to support more options (also fix for IDEADEV-31460, IDEADEV-31464, IDEADEV-32078) --- plugins/git4idea/src/git4idea/GitFileRevision.java | 2 +- plugins/git4idea/src/git4idea/GitRemote.java | 51 ++ .../src/git4idea/actions/GitCurrentBranch.java | 1 + .../{GitCurrentBranch.java => GitFetch.java} | 24 +- .../src/git4idea/checkout/GitCloneDialog.java | 8 - .../src/git4idea/commands/StringScanner.java | 37 +- .../src/git4idea/config/GitConfigUtil.java | 30 ++ .../src/git4idea/i18n/GitBundle.properties | 39 +- .../git4idea/src/git4idea/merge/GitMergeUtil.java | 2 +- .../git4idea/src/git4idea/merge/GitPullDialog.java | 25 +- .../{actions => ui}/CurrentBranchDialog.form | 2 +- .../{actions => ui}/CurrentBranchDialog.java | 5 +- .../git4idea/src/git4idea/ui/GitFetchDialog.form | 80 +++ .../git4idea/src/git4idea/ui/GitFetchDialog.java | 227 ++++++++ .../src/git4idea/ui/GitRefspecAddRefsDialog.form | 77 +++ .../src/git4idea/ui/GitRefspecAddRefsDialog.java | 327 ++++++++++++ .../git4idea/src/git4idea/ui/GitRefspecPanel.form | 112 ++++ .../git4idea/src/git4idea/ui/GitRefspecPanel.java | 575 +++++++++++++++++++++ plugins/git4idea/src/git4idea/ui/GitUIUtil.java | 92 +++- 19 files changed, 1652 insertions(+), 64 deletions(-) copy plugins/git4idea/src/git4idea/actions/{GitCurrentBranch.java => GitFetch.java} (66%) rename plugins/git4idea/src/git4idea/{actions => ui}/CurrentBranchDialog.form (97%) rename plugins/git4idea/src/git4idea/{actions => ui}/CurrentBranchDialog.java (91%) create mode 100644 plugins/git4idea/src/git4idea/ui/GitFetchDialog.form create mode 100644 plugins/git4idea/src/git4idea/ui/GitFetchDialog.java create mode 100644 plugins/git4idea/src/git4idea/ui/GitRefspecAddRefsDialog.form create mode 100644 plugins/git4idea/src/git4idea/ui/GitRefspecAddRefsDialog.java create mode 100644 plugins/git4idea/src/git4idea/ui/GitRefspecPanel.form create mode 100644 plugins/git4idea/src/git4idea/ui/GitRefspecPanel.java diff --git a/plugins/git4idea/src/git4idea/GitFileRevision.java b/plugins/git4idea/src/git4idea/GitFileRevision.java index be773b97e6..67647c63cf 100644 --- a/plugins/git4idea/src/git4idea/GitFileRevision.java +++ b/plugins/git4idea/src/git4idea/GitFileRevision.java @@ -39,7 +39,7 @@ public class GitFileRevision implements VcsFileRevision, Comparable getFetchSpecs(Project project, VirtualFile root, String remoteName) throws VcsException { + ArrayList rc = new ArrayList(); + final File rootFile = GitUtil.getIOFile(root); + @NonNls final File remotesFile = new File(rootFile, ".git" + File.separator + "remotes" + File.separator + remoteName); + // TODO try branches file? + if (remotesFile.exists() && !remotesFile.isDirectory()) { + // try remotes file + try { + //noinspection IOResourceOpenedButNotSafelyClosed + String text = FileUtil.loadTextAndClose(new InputStreamReader(new FileInputStream(remotesFile), US_ASCII_ENCODING)); + @NonNls String pullPrefix = "Pull:"; + for (StringScanner s = new StringScanner(text); s.hasMoreData();) { + String line = s.line(); + if (line.startsWith(pullPrefix)) { + rc.add(line.substring(pullPrefix.length()).trim()); + } + } + } + catch (IOException e) { + throw new VcsException("Unable to read remotes file: " + remotesFile, e); + } + } + else { + // try config file + for (Pair pair : GitConfigUtil.getAllValues(project, root, "remote." + remoteName + ".fetch")) { + rc.add(pair.second); + } + } + return rc; + } + + /** * Information about git remote */ public class Info { diff --git a/plugins/git4idea/src/git4idea/actions/GitCurrentBranch.java b/plugins/git4idea/src/git4idea/actions/GitCurrentBranch.java index 4fccad60ef..7de7ced36f 100644 --- a/plugins/git4idea/src/git4idea/actions/GitCurrentBranch.java +++ b/plugins/git4idea/src/git4idea/actions/GitCurrentBranch.java @@ -19,6 +19,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import git4idea.i18n.GitBundle; +import git4idea.ui.CurrentBranchDialog; import org.jetbrains.annotations.NotNull; import java.util.List; diff --git a/plugins/git4idea/src/git4idea/actions/GitCurrentBranch.java b/plugins/git4idea/src/git4idea/actions/GitFetch.java similarity index 66% copy from plugins/git4idea/src/git4idea/actions/GitCurrentBranch.java copy to plugins/git4idea/src/git4idea/actions/GitFetch.java index 4fccad60ef..bae145ccb1 100644 --- a/plugins/git4idea/src/git4idea/actions/GitCurrentBranch.java +++ b/plugins/git4idea/src/git4idea/actions/GitFetch.java @@ -18,24 +18,26 @@ package git4idea.actions; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; +import git4idea.commands.GitHandlerUtil; +import git4idea.commands.GitLineHandler; import git4idea.i18n.GitBundle; +import git4idea.ui.GitFetchDialog; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Set; /** - * Git action for showing the current branch + * Git "fetch" action */ -public class GitCurrentBranch extends GitRepositoryAction { - +public class GitFetch extends GitRepositoryAction { /** * {@inheritDoc} */ @Override @NotNull protected String getActionName() { - return GitBundle.getString("current.branch.action.name"); + return GitBundle.getString("fetch.action.name"); } /** @@ -46,6 +48,18 @@ public class GitCurrentBranch extends GitRepositoryAction { @NotNull final VirtualFile defaultRoot, final Set affectedRoots, final List exceptions) throws VcsException { - new CurrentBranchDialog(project, gitRoots, defaultRoot).show(); + GitFetchDialog d = new GitFetchDialog(project, gitRoots, defaultRoot); + d.show(); + if (!d.isOK()) { + return; + } + final GitLineHandler h = d.fetchHandler(); + try { + GitHandlerUtil.doSynchronously(h, GitBundle.message("fetching.title", d.getRemote()), h.printableCommandLine()); + // note that fetch does not affects checked out sources + } + finally { + exceptions.addAll(h.errors()); + } } } diff --git a/plugins/git4idea/src/git4idea/checkout/GitCloneDialog.java b/plugins/git4idea/src/git4idea/checkout/GitCloneDialog.java index 278cba8419..4e56b42db8 100644 --- a/plugins/git4idea/src/git4idea/checkout/GitCloneDialog.java +++ b/plugins/git4idea/src/git4idea/checkout/GitCloneDialog.java @@ -137,14 +137,6 @@ public class GitCloneDialog extends DialogWrapper { return myOriginName.getText(); } - - /** - * Custom component creation code - */ - private void createUIComponents() { - - } - /** * Init components */ diff --git a/plugins/git4idea/src/git4idea/commands/StringScanner.java b/plugins/git4idea/src/git4idea/commands/StringScanner.java index 08c139f951..40cf866857 100644 --- a/plugins/git4idea/src/git4idea/commands/StringScanner.java +++ b/plugins/git4idea/src/git4idea/commands/StringScanner.java @@ -81,10 +81,31 @@ public class StringScanner { * @return a token */ public String spaceToken() { + return boundedToken(' '); + } + + /** + * Gets next token that is ended by tab or new line. Consumes tab but not a new line. + * Start position is the current. So if the string starts with space a empty token is returned. + * + * @return a token + */ + public String tabToken() { + return boundedToken('\t'); + } + + /** + * Gets next token that is ended by {@code bondaryChar} or new line. Consumes {@code bondaryChar} but not a new line. + * Start position is the current. So if the string starts with {@code bondaryChar} a empty token is returned. + * + * @param bondaryChar a boundary character + * @return a token + */ + public String boundedToken(final char bondaryChar) { int start = myPosition; for (; myPosition < myText.length(); myPosition++) { final char ch = myText.charAt(myPosition); - if (ch == ' ') { + if (ch == bondaryChar) { final String rc = myText.substring(start, myPosition); myPosition++; return rc; @@ -152,4 +173,18 @@ public class StringScanner { } myPosition += n; } + + /** + * Try string and consume it if it matches + * + * @param c a character to try + * @return true if the string was consumed. + */ + public boolean tryConsume(final char c) { + if (startsWith(c)) { + skipChars(1); + return true; + } + return false; + } } diff --git a/plugins/git4idea/src/git4idea/config/GitConfigUtil.java b/plugins/git4idea/src/git4idea/config/GitConfigUtil.java index 0d873ae95d..6e58ca1c6e 100644 --- a/plugins/git4idea/src/git4idea/config/GitConfigUtil.java +++ b/plugins/git4idea/src/git4idea/config/GitConfigUtil.java @@ -16,12 +16,15 @@ package git4idea.config; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import git4idea.commands.GitSimpleHandler; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -64,6 +67,33 @@ public class GitConfigUtil { } /** + * Get configuration values for the repository. Note that the method executes a git command. + * + * @param project the context project + * @param root the git root + * @param key the keys to be queried + * @return list of pairs ({@link Pair#first} is the key, {@link Pair#second} is the value) + * @throws VcsException an exception + */ + public static List> getAllValues(Project project, VirtualFile root, @NonNls String key) throws VcsException { + List> result = new ArrayList>(); + GitSimpleHandler h = new GitSimpleHandler(project, root, "config"); + h.setNoSSH(true); + h.setSilent(true); + h.addParameters("--null", "--get-all", key); + String output = h.run(); + int start = 0; + int pos; + while ((pos = output.indexOf('\u0000', start)) != -1) { + String value = output.substring(start, pos); + start = pos + 1; + result.add(new Pair(key, value)); + } + return result; + } + + + /** * Get configuration value for the repository. Note that the method executes a git command. * * @param project the context project diff --git a/plugins/git4idea/src/git4idea/i18n/GitBundle.properties b/plugins/git4idea/src/git4idea/i18n/GitBundle.properties index 80bbf413ec..d7b93830e6 100644 --- a/plugins/git4idea/src/git4idea/i18n/GitBundle.properties +++ b/plugins/git4idea/src/git4idea/i18n/GitBundle.properties @@ -1,5 +1,16 @@ action.text.show.all.submitted=Show all files changed by this commit add.action.name=Add +addrefspec.get.references.tooltip=Get remote tag and branch references (depending on checkbox state). +addrefspec.get.references=&Get References +addrefspec.getting.references.title=Getting remote references for {0} +addrefspec.include.branches.tooltip=The next update references wil retrieve remote branches. +addrefspec.include.branches=Include &branches +addrefspec.include.tags.tooltip=The next update references wil retrieve remote tags. +addrefspec.include.tags=Include &tags +addrefspec.node.branches=Branches +addrefspec.node.tags=Tags +addrefspec.reference.chooser.tooltip=Select remote branch and tag references +addrefspec.title=Add Reference(s) annotate.action.name=Annotate annotation.tool.tip=commit {0}\nAuthor: {1}\nDate: {2}\n\n{3} checking.out=Checkout {0} @@ -65,8 +76,16 @@ error.dialog.title=Error error.list.title={0} Error: error.occurred.during=Error occurred during ''{0}'' fetch.action.name=Fetch -fetch.url.message=Enter remote repository URL to fetch (empty for default): -fetch.url.title=Fetch URL +fetch.force.references.update.tooltip=Forces update of branch references for which update is not forced in refrence mapping. +fetch.force.references.update=Force references &update +fetch.remote.label=Re&mote: +fetch.remote.tooltip=Remote name or url for fetch +fetch.tags.label=Fetch &tags: +fetch.tags.policy.all=All +fetch.tags.policy.for.fetched.commits=For fetched commits +fetch.tags.policy.none=None +fetch.tags.tooltip=Select fetched commits policy
  • For fetched commits means tags associated with commits on fetched branches are fetched.
  • All means that all tags and reference commits are fetched
  • None means that no tags are fetched.
+fetch.title=Git Fetch fetching.tags.title=Updating tags from {0} fetching.title=Fetching from {0} find.git.description=Select path to git executable @@ -120,6 +139,22 @@ pull.url.title=Pull URL pulling.title=Pulling changes from {0} push.action.name=Push pushing.all.changes=Pushing all commited changes, refs & tags to remote repos +refspec.add.all.branches.tooltip=Add refspec that maps all remote branches by glob spec. +refspec.add.all.branches=Add A&ll Branches +refspec.add.all.tags.tooltip=Adds mapping entry for all tags +refspec.add.all.tags=Add All Ta&gs +refspec.add.ref.tooltip=Add branch or tag by name +refspec.add.ref=&Add... +refspec.branch.prefix.tooltip=Specify default branch prefix for the mapping. +refspec.branch.prefix=Remote &Name: +refspec.column.force=Force +refspec.column.local=Local +refspec.column.remote=Remote +refspec.remove.tooltip=Delete refspec entry +refspec.remove=Remo&ve +refspec.title=Reference mapping +refspec.validation.remote.invalid=The invalid local name for remote +refspec.validation.remote.is.blank=The local name for remote is blank repository.action.missing.roots.misconfigured=Neither of configured git roots are is under git. The configured directory or some of its ancestors must have ".git" directory in it. repository.action.missing.roots.title=No git roots repository.action.missing.roots.unconfigured.message=No git roots are configured for the project. diff --git a/plugins/git4idea/src/git4idea/merge/GitMergeUtil.java b/plugins/git4idea/src/git4idea/merge/GitMergeUtil.java index f628d5a2ab..79b6e9d221 100644 --- a/plugins/git4idea/src/git4idea/merge/GitMergeUtil.java +++ b/plugins/git4idea/src/git4idea/merge/GitMergeUtil.java @@ -55,7 +55,7 @@ public class GitMergeUtil { * @return an array of strategy names */ @NonNls - public static String[] getMergeStrategies(int branchCount) { + private static String[] getMergeStrategies(int branchCount) { if (branchCount < 0) { throw new IllegalArgumentException("Brach count must be non-negative: " + branchCount); } diff --git a/plugins/git4idea/src/git4idea/merge/GitPullDialog.java b/plugins/git4idea/src/git4idea/merge/GitPullDialog.java index d4525284b0..66a1c2277e 100644 --- a/plugins/git4idea/src/git4idea/merge/GitPullDialog.java +++ b/plugins/git4idea/src/git4idea/merge/GitPullDialog.java @@ -26,7 +26,6 @@ import git4idea.GitVcs; import git4idea.commands.GitHandlerUtil; import git4idea.commands.GitLineHandler; import git4idea.commands.GitSimpleHandler; -import git4idea.config.GitConfigUtil; import git4idea.i18n.GitBundle; import git4idea.ui.GitUIUtil; import org.jetbrains.annotations.Nullable; @@ -259,29 +258,7 @@ public class GitPullDialog extends DialogWrapper { * Update remotes for the git root */ private void updateRemotes() { - try { - List remotes = GitRemote.list(myProject, gitRoot()); - String branch = currentBranch(); - String remote = null; - if (branch != null) { - remote = GitConfigUtil.getValue(myProject, gitRoot(), "branch." + branch + ".remote"); - } - myRemote.setRenderer(GitUIUtil.getGitRemoteListCellRenderer(remote)); - GitRemote toSelect = null; - myRemote.removeAllItems(); - for (GitRemote r : remotes) { - myRemote.addItem(r); - if (r.name().equals(remote)) { - toSelect = r; - } - } - if (toSelect != null) { - myRemote.setSelectedItem(toSelect); - } - } - catch (VcsException e) { - GitVcs.getInstance(myProject).showErrors(Collections.singletonList(e), GitBundle.getString("pull.retriving.remotes")); - } + GitUIUtil.setupRemotes(myProject, gitRoot(), currentBranch(), myRemote); } /** diff --git a/plugins/git4idea/src/git4idea/actions/CurrentBranchDialog.form b/plugins/git4idea/src/git4idea/ui/CurrentBranchDialog.form similarity index 97% rename from plugins/git4idea/src/git4idea/actions/CurrentBranchDialog.form rename to plugins/git4idea/src/git4idea/ui/CurrentBranchDialog.form index 2502b0dc8f..6e1eed52d5 100644 --- a/plugins/git4idea/src/git4idea/actions/CurrentBranchDialog.form +++ b/plugins/git4idea/src/git4idea/ui/CurrentBranchDialog.form @@ -1,5 +1,5 @@ -
+ diff --git a/plugins/git4idea/src/git4idea/actions/CurrentBranchDialog.java b/plugins/git4idea/src/git4idea/ui/CurrentBranchDialog.java similarity index 91% rename from plugins/git4idea/src/git4idea/actions/CurrentBranchDialog.java rename to plugins/git4idea/src/git4idea/ui/CurrentBranchDialog.java index 79a9c09d7a..6bdd360bc6 100644 --- a/plugins/git4idea/src/git4idea/actions/CurrentBranchDialog.java +++ b/plugins/git4idea/src/git4idea/ui/CurrentBranchDialog.java @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package git4idea.actions; +package git4idea.ui; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vfs.VirtualFile; import git4idea.i18n.GitBundle; -import git4idea.ui.GitUIUtil; import javax.swing.*; import java.util.List; @@ -48,7 +47,7 @@ public class CurrentBranchDialog extends DialogWrapper { * @param roots the git roots for the project * @param defaultRoot the default root */ - protected CurrentBranchDialog(Project project, List roots, VirtualFile defaultRoot) { + public CurrentBranchDialog(Project project, List roots, VirtualFile defaultRoot) { super(project, true); setTitle(GitBundle.getString("current.branch.title")); GitUIUtil.setupRootChooser(project, roots, defaultRoot, myGitRoot, myCurrentBranch); diff --git a/plugins/git4idea/src/git4idea/ui/GitFetchDialog.form b/plugins/git4idea/src/git4idea/ui/GitFetchDialog.form new file mode 100644 index 0000000000..f8087c97bd --- /dev/null +++ b/plugins/git4idea/src/git4idea/ui/GitFetchDialog.form @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/git4idea/src/git4idea/ui/GitFetchDialog.java b/plugins/git4idea/src/git4idea/ui/GitFetchDialog.java new file mode 100644 index 0000000000..478e30bd6b --- /dev/null +++ b/plugins/git4idea/src/git4idea/ui/GitFetchDialog.java @@ -0,0 +1,227 @@ +/* + * Copyright 2000-2008 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.ui; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.DocumentAdapter; +import git4idea.GitBranch; +import git4idea.commands.GitLineHandler; +import git4idea.i18n.GitBundle; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; + +/** + * Fetch dialog. It represents most of the parameters for "git fetch" operation. + */ +public class GitFetchDialog extends DialogWrapper { + /** + * The git root + */ + private JComboBox myGitRoot; + /** + * Refrence specification panel + */ + private GitRefspecPanel myRefspecs; + /** + * Fetch tags policy + */ + private JComboBox myFetchTagsComboBox; + /** + * Force reference updates + */ + private JCheckBox myForceReferencesUpdateCheckBox; + /** + * Remote name/url + */ + private JComboBox myRemote; + /** + * The root panel + */ + private JPanel myPanel; + /** + * The project for the dialog + */ + private final Project myProject; + /** + * Fetch tags for fetched commits (default) + */ + private static final String TAGS_POLICY_FOR_FETCHED_COMMITS = GitBundle.getString("fetch.tags.policy.for.fetched.commits"); + /** + * Fetch all tags policy + */ + private static final String TAGS_POLICY_ALL = GitBundle.getString("fetch.tags.policy.all"); + /** + * Fetch no tags except explicitly listed + */ + private static final String TAGS_POLICY_NONE = GitBundle.getString("fetch.tags.policy.none"); + + /** + * A constructor + * + * @param project the project + * @param roots the list of the roots + * @param defaultRoot the default root to select + */ + public GitFetchDialog(final Project project, final List roots, final VirtualFile defaultRoot) { + super(project, true); + setTitle(GitBundle.getString("fetch.title")); + GitUIUtil.setupRootChooser(project, roots, defaultRoot, myGitRoot, null); + myProject = project; + myRefspecs.setProject(project); + myRefspecs.setReferenceSource(GitRefspecPanel.ReferenceSource.FETCH); + setupRemotes(); + setupFetchTagsPolicy(); + init(); + setupValidation(); + } + + + /** + * Setup validation for combobox + */ + private void setupValidation() { + final JTextField remoteTextField = getRemoteTextField(); + final DocumentAdapter listener = new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + if (remoteTextField.getText().length() == 0) { + setOKActionEnabled(false); + setErrorText(null); + return; + } + final String result = myRefspecs.validateFields(); + if (result != null) { + setOKActionEnabled(false); + setErrorText(result.length() == 0 ? null : result); + return; + } + setOKActionEnabled(true); + setErrorText(null); + } + }; + remoteTextField.getDocument().addDocumentListener(listener); + myRefspecs.addValidationRequiredListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + listener.changedUpdate(null); + } + }); + listener.changedUpdate(null); + } + + /** + * Setup fetch tags policy combobox + */ + private void setupFetchTagsPolicy() { + myFetchTagsComboBox.addItem(TAGS_POLICY_FOR_FETCHED_COMMITS); + myFetchTagsComboBox.addItem(TAGS_POLICY_ALL); + myFetchTagsComboBox.addItem(TAGS_POLICY_NONE); + myFetchTagsComboBox.setSelectedIndex(0); + } + + + /** + * Setup drop down with remotes + */ + private void setupRemotes() { + final ActionListener actionListener = new ActionListener() { + public void actionPerformed(final ActionEvent e) { + myRefspecs.setGitRoot(getGitRoot()); + updateRemotes(); + } + }; + myGitRoot.addActionListener(actionListener); + actionListener.actionPerformed(null); + final JTextField textField = getRemoteTextField(); + final DocumentAdapter remoteListener = new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + myRefspecs.setRemote(textField.getText()); + } + }; + textField.getDocument().addDocumentListener(remoteListener); + remoteListener.changedUpdate(null); + } + + /** + * @return text field for {@link #myRemote} + */ + private JTextField getRemoteTextField() { + return (JTextField)myRemote.getEditor().getEditorComponent(); + } + + /** + * Update remotes + */ + private void updateRemotes() { + final VirtualFile root = getGitRoot(); + GitBranch gitBranch = null; + try { + gitBranch = GitBranch.current(myProject, root); + } + catch (VcsException ex) { + // ingore error + } + final String branch = gitBranch != null ? gitBranch.getName() : null; + GitUIUtil.setupRemotes(myProject, root, branch, myRemote); + } + + /** + * @return get current git root + */ + protected VirtualFile getGitRoot() { + return (VirtualFile)myGitRoot.getSelectedItem(); + } + + /** + * {@inheritDoc} + */ + protected JComponent createCenterPanel() { + return myPanel; + } + + /** + * @return the handler for the fetch operation + */ + public GitLineHandler fetchHandler() { + GitLineHandler h = new GitLineHandler(myProject, getGitRoot(), "fetch"); + h.addParameters("-v"); + if (myForceReferencesUpdateCheckBox.isSelected()) { + h.addParameters("--force"); + } + String tagsPolicy = (String)myFetchTagsComboBox.getSelectedItem(); + if (TAGS_POLICY_ALL.equals(tagsPolicy)) { + h.addParameters("--tags"); + } + else if (TAGS_POLICY_NONE.equals(tagsPolicy)) { + h.addParameters("--no-tags"); + } + h.addParameters(getRemote()); + h.addParameters(myRefspecs.getReferences()); + return h; + } + + /** + * @return remote name or URL + */ + public String getRemote() { + return getRemoteTextField().getText(); + } +} diff --git a/plugins/git4idea/src/git4idea/ui/GitRefspecAddRefsDialog.form b/plugins/git4idea/src/git4idea/ui/GitRefspecAddRefsDialog.form new file mode 100644 index 0000000000..45e2556707 --- /dev/null +++ b/plugins/git4idea/src/git4idea/ui/GitRefspecAddRefsDialog.form @@ -0,0 +1,77 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/git4idea/src/git4idea/ui/GitRefspecAddRefsDialog.java b/plugins/git4idea/src/git4idea/ui/GitRefspecAddRefsDialog.java new file mode 100644 index 0000000000..00fa6b3cbd --- /dev/null +++ b/plugins/git4idea/src/git4idea/ui/GitRefspecAddRefsDialog.java @@ -0,0 +1,327 @@ +/* + * Copyright 2000-2008 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.ui; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.CheckboxTree; +import com.intellij.ui.CheckedTreeNode; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.Icons; +import com.intellij.util.ui.Tree; +import com.intellij.util.ui.tree.TreeUtil; +import git4idea.commands.GitHandlerUtil; +import git4idea.commands.GitSimpleHandler; +import git4idea.commands.StringScanner; +import git4idea.i18n.GitBundle; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashSet; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * This dialog allows adding selected tag and branches are references. + */ +public class GitRefspecAddRefsDialog extends DialogWrapper { + /** + * Get references button + */ + private JButton myGetRefsButton; + /** + * If selected, the branches are fetched by {@link #myGetRefsButton} + */ + private JCheckBox myIncludeBranchesCheckBox; + /** + * If selected, the tags are fetched by {@link #myGetRefsButton} + */ + private JCheckBox myIncludeTagsCheckBox; + /** + * The selector for tags and branches + */ + private CheckboxTree myReferenceChooser; + /** + * The root panel of the dialog + */ + private JPanel myPanel; + /** + * The context proejct + */ + private final Project myProject; + /** + * Root of the tree + */ + private CheckedTreeNode myTreeRoot; + /** + * The git root of the repository + */ + private final VirtualFile myRoot; + /** + * The name of the remote + */ + private final String myRemote; + /** + * The set of tags + */ + private final SortedSet myTags; + /** + * The set of branches + */ + private final SortedSet myBranches; + /** + * The logger for the class + */ + private static final Logger log = Logger.getInstance(GitRefspecAddRefsDialog.class.getName()); + + /** + * A consturctor + * + * @param project the project + * @param root the git repository root + * @param remote the remote name or url of remote repository + * @param tags the set of tags (might be modified if update button is pressed) + * @param branches the set of branches (might be modified if update button is pressed) + */ + protected GitRefspecAddRefsDialog(@NotNull Project project, + @NotNull VirtualFile root, + @NotNull String remote, + @NotNull SortedSet tags, + @NotNull SortedSet branches) { + super(project, true); + setTitle(GitBundle.getString("addrefspec.title")); + myProject = project; + myRoot = root; + myRemote = remote; + myTags = tags; + myBranches = branches; + updateTree(); + setupGetReferences(); + init(); + setOKActionEnabled(false); + } + + + /** + * Set up action listener for {@link #myGetRefsButton} + */ + private void setupGetReferences() { + // setup enabled state + final ActionListener enabledListener = new ActionListener() { + public void actionPerformed(final ActionEvent e) { + myGetRefsButton.setEnabled(myIncludeBranchesCheckBox.isSelected() || myIncludeTagsCheckBox.isSelected()); + } + }; + myIncludeBranchesCheckBox.addActionListener(enabledListener); + myIncludeTagsCheckBox.addActionListener(enabledListener); + // perform update + myGetRefsButton.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + GitSimpleHandler handler = new GitSimpleHandler(myProject, myRoot, "ls-remote"); + if (myIncludeBranchesCheckBox.isSelected()) { + handler.addParameters("--heads"); + myBranches.clear(); + } + if (myIncludeTagsCheckBox.isSelected()) { + handler.addParameters("--tags"); + myTags.clear(); + } + handler.addParameters(myRemote); + String result = GitHandlerUtil + .doSynchronously(handler, GitBundle.message("addrefspec.getting.references.title", myRemote), handler.printableCommandLine()); + if (result != null) { + StringScanner s = new StringScanner(result); + while (s.hasMoreData()) { + s.tabToken(); // skip last commit hash + String ref = s.line(); + if (ref.startsWith(GitRefspecPanel.REFS_HEADS_PREFIX)) { + myBranches.add(ref); + } + else if (ref.startsWith(GitRefspecPanel.REFS_TAGS_PREFIX)) { + myTags.add(ref); + } + else { + log.warn("Unknwon reference type from ls-remote \"" + myRemote + "\" :" + ref); + } + } + } + updateTree(); + } + }); + } + + /** + * Update checkbox tree basing on the current state of the tag and branches set. The checkbox state is preserved. New items are created + * in unselected state. + */ + private void updateTree() { + // save the previous selection + HashSet oldTags = new HashSet(); + HashSet oldBranches = new HashSet(); + for (Reference ref : myReferenceChooser.getCheckedNodes(Reference.class, null)) { + (ref.isTag ? oldTags : oldBranches).add(ref.name); + } + // clear the tree + myTreeRoot.removeAllChildren(); + // fill tags and branches + addReferences(false, oldBranches, myBranches, GitBundle.getString("addrefspec.node.branches")); + addReferences(true, oldTags, myTags, GitBundle.getString("addrefspec.node.tags")); + TreeUtil.expandAll(myReferenceChooser); + myReferenceChooser.treeDidChange(); + } + + /** + * Add references to the tree along with category node + * + * @param isTag if true tag nodes are added + * @param old the set of old elements (used to select + * @param current the current set of elements (after update) + * @param name the name of the set + */ + private void addReferences(final boolean isTag, final HashSet old, final SortedSet current, @Nls final String name) { + if (!current.isEmpty()) { + final CheckedTreeNode tagsRoot = new CheckedTreeNode(name); + for (String t : current) { + final CheckedTreeNode node = new CheckedTreeNode(new Reference(isTag, t)); + node.setChecked(old.contains(t)); + tagsRoot.add(node); + } + myTreeRoot.add(tagsRoot); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected String getDimensionServiceKey() { + return GitRefspecAddRefsDialog.class.getName(); + } + + /** + * {@inheritDoc} + */ + protected JComponent createCenterPanel() { + return myPanel; + } + + /** + * Create UI components that require custom creation: {@link #myReferenceChooser} + */ + private void createUIComponents() { + myTreeRoot = new CheckedTreeNode(""); + myReferenceChooser = new CheckboxTree(new CheckboxTree.CheckboxTreeCellRenderer() { + + public void customizeCellRenderer(final JTree tree, + final Object value, + final boolean selected, + final boolean expanded, + final boolean leaf, + final int row, + final boolean hasFocus) { + final CheckedTreeNode node = (CheckedTreeNode)value; + final Object userObject = node.getUserObject(); + String text; + SimpleTextAttributes attributes; + Icon icon; + if (userObject == null) { + // invisible root (do nothing) + //noinspection HardCodedStringLiteral + text = "INVISBLE ROOT"; + attributes = SimpleTextAttributes.ERROR_ATTRIBUTES; + icon = null; + } + else if (userObject instanceof String) { + // category node (render as bold) + text = (String)userObject; + attributes = SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES; + icon = expanded ? Icons.DIRECTORY_OPEN_ICON : Icons.DIRECTORY_CLOSED_ICON; + } + else { + // reference node + text = ((Reference)userObject).name; + attributes = node.isChecked() ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES; + icon = null; + } + final ColoredTreeCellRenderer textRenderer = getTextRenderer(); + if (icon != null) { + textRenderer.setIcon(icon); + } + if (text != null) { + textRenderer.append(text, attributes); + } + } + }, myTreeRoot) { + @Override + protected void onNodeStateChanged(final CheckedTreeNode node) { + boolean flag = node.isChecked() || myReferenceChooser.getCheckedNodes(Reference.class, null).length != 0; + setOKActionEnabled(flag); + super.onNodeStateChanged(node); + } + }; + } + + /** + * Get selected elements + * + * @param isTag if true tags are returned, heads otherwise + * @return a collection of selected reference of the specified typ + */ + public SortedSet getSelected(final boolean isTag) { + TreeSet rc = new TreeSet(); + final Reference[] checked = myReferenceChooser.getCheckedNodes(Reference.class, new Tree.NodeFilter() { + public boolean accept(final Reference node) { + return node.isTag == isTag; + } + }); + for (Reference r : checked) { + rc.add(r.name); + } + return rc; + } + + + /** + * A remote reference + */ + static final class Reference { + /** + * If true, the name represents a tag. if false, the name represents the branch name. + */ + final boolean isTag; + /** + * Name of the reference + */ + final String name; + + /** + * A constructor from fields + * + * @param tag the value for {@link #isTag} + * @param name the value for {@link #name} + */ + public Reference(final boolean tag, final String name) { + isTag = tag; + this.name = name; + } + } +} diff --git a/plugins/git4idea/src/git4idea/ui/GitRefspecPanel.form b/plugins/git4idea/src/git4idea/ui/GitRefspecPanel.form new file mode 100644 index 0000000000..6fb2803b5e --- /dev/null +++ b/plugins/git4idea/src/git4idea/ui/GitRefspecPanel.form @@ -0,0 +1,112 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/git4idea/src/git4idea/ui/GitRefspecPanel.java b/plugins/git4idea/src/git4idea/ui/GitRefspecPanel.java new file mode 100644 index 0000000000..ef1e4771c3 --- /dev/null +++ b/plugins/git4idea/src/git4idea/ui/GitRefspecPanel.java @@ -0,0 +1,575 @@ +/* + * Copyright 2000-2008 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.ui; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.DocumentAdapter; +import com.intellij.util.containers.HashMap; +import git4idea.GitRemote; +import git4idea.commands.StringScanner; +import git4idea.i18n.GitBundle; +import git4idea.validators.GitBranchNameValidator; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.AbstractTableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * The component that allows specifying a list of references + */ +public class GitRefspecPanel extends JPanel { + /** + * The logger for the class + */ + private static final Logger log = Logger.getInstance(Logger.class.getName()); + /** + * Named remotes associated with the current git root + */ + private final HashMap myRemotes = new HashMap(); + /** + * The project + */ + private Project myProject; + /** + * The git root for mapping + */ + private VirtualFile myGitRoot; + /** + * Remote heads (for Add... dialog) + */ + private final SortedSet myRemoteHeads = new TreeSet(); + /** + * Remote tags (for Add.. dialog) + */ + private final SortedSet myRemoteTags = new TreeSet(); + /** + * The button that adds all branches button + */ + private JButton myAddAllBranchesButton; + /** + * The button that adds selected references + */ + private JButton myAddButton; + /** + * The button that removes currently selected entries from the table + */ + private JButton myRemoveButton; + /** + * The text that contains remote name + */ + private JTextField myRemoteNameTextField; + /** + * The root panel of the form + */ + private JPanel myPanel; + /** + * The references table + */ + private JTable myReferences; + /** + * The button that adds entry that maps all tags + */ + private JButton myAddAllTagsButton; + /** + * The name of the remote + */ + private String myRemote; + /** + * The source of default referneces + */ + private ReferenceSource myReferenceSource; + /** + * Mapping table model + */ + private final MyMappingTableModel myReferencesModel = new MyMappingTableModel(); + /** + * Prefix for local branches + */ + @NonNls public static final String REFS_HEADS_PREFIX = "refs/heads/"; + /** + * Prefix for tags + */ + @NonNls public static final String REFS_TAGS_PREFIX = "refs/tags/"; + /** + * Prefix for remotes + */ + @NonNls public static final String REFS_REMOTES_PREFIX = "refs/remotes/"; + + /** + * A constructor + */ + public GitRefspecPanel() { + super(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + c.weightx = 1; + c.weighty = 1; + c.fill = GridBagConstraints.BOTH; + add(myPanel, c); + setupTable(); + setupButtons(); + } + + + /** + * Validates fields + * + * @return null if there is no error; empty string means that there is no error yet but OK should be disabled; otherwise error text should be used as the current error for dialog + */ + @Nullable + public String validateFields() { + final String remote = getRemoteName(); + if (remote.length() == 0) { + if (myReferencesModel.isRemoteNameUsed()) { + return GitBundle.getString("refspec.validation.remote.is.blank"); + } + } + else { + if (!GitBranchNameValidator.INSTANCE.checkInput(remote)) { + return GitBundle.getString("refspec.validation.remote.invalid"); + } + } + return null; + } + + /** + * Set project for panel + * + * @param project the context project + */ + public void setProject(Project project) { + myProject = project; + } + + /** + * Setup add/remove buttons + */ + private void setupButtons() { + // disable ok button if nothing is selected + myReferences.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + public void valueChanged(final ListSelectionEvent e) { + myRemoveButton.setEnabled(myReferences.getSelectedRowCount() != 0); + } + }); + // remove selected mappings + myRemoveButton.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + myReferencesModel.removeSelectedMapping(); + } + }); + // add all tags (mapped to tags directory) + myAddAllTagsButton.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + myReferencesModel.addMapping(false, REFS_TAGS_PREFIX + "*", REFS_TAGS_PREFIX + "*"); + } + }); + // all heads (mapped to remotes directory) + myAddAllBranchesButton.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + myReferencesModel.addMapping(false, REFS_HEADS_PREFIX + "*", "refs/remotes/" + getRemoteName() + "/*"); + } + }); + // map selected tags and heads + myAddButton.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + if (myGitRoot == null) { + throw new IllegalStateException("Git root must be already set at this point."); + } + GitRefspecAddRefsDialog d = new GitRefspecAddRefsDialog(myProject, myGitRoot, myRemote, myRemoteTags, myRemoteHeads); + d.show(); + if (!d.isOK()) { + return; + } + for (String tag : d.getSelected(true)) { + myReferencesModel.addMapping(false, tag, tag); + } + for (String head : d.getSelected(false)) { + myReferencesModel + .addMapping(true, head, REFS_REMOTES_PREFIX + getRemoteName() + "/" + head.substring(REFS_HEADS_PREFIX.length())); + } + } + }); + } + + /** + * @return the current name of the remote + */ + public String getRemoteName() { + return myRemoteNameTextField.getText(); + } + + /** + * Setup table header and table model + */ + private void setupTable() { + // setup model + myReferences.setModel(myReferencesModel); + myReferences.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + myReferences.getColumnModel().getColumn(MyMappingTableModel.FORCE_COLUMN).sizeWidthToFit(); + myRemoteNameTextField.getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + myReferencesModel.remoteUpdated(); + } + }); + } + + /** + * Set git root for reference mapping + * + * @param gitRoot a git root + */ + public void setGitRoot(final VirtualFile gitRoot) { + if (gitRoot == myGitRoot) { + return; + } + myGitRoot = gitRoot; + myRemotes.clear(); + if (myGitRoot != null) { + try { + for (GitRemote r : GitRemote.list(myProject, myGitRoot)) { + myRemotes.put(r.name(), r); + } + } + catch (VcsException e) { + GitUIUtil.showOperationError(myProject, e, "listing remotes"); + } + } + } + + /** + * Set remote or url + * + * @param name a name of remote or URL + */ + public void setRemote(String name) { + if (name != null && name.length() == 0) { + name = null; + } + if (myRemote == null && name == null || myRemote != null && myRemote.equals(name)) { + return; + } + myRemote = name; + myAddButton.setEnabled(myRemote != null && myRemote.length() != 0); + final GitRemote remote = myRemotes.get(name); + if (remote != null) { + myRemoteNameTextField.setText(name); + myRemoteNameTextField.setEditable(false); + } + else { + myRemoteNameTextField.setText(""); + myRemoteNameTextField.setEditable(true); + } + myRemoteHeads.clear(); + myRemoteTags.clear(); + if (remote != null && myReferenceSource == ReferenceSource.FETCH) { + myReferencesModel.clear(); + try { + for (String ref : GitRemote.getFetchSpecs(myProject, myGitRoot, remote.name())) { + StringScanner s = new StringScanner(ref); + boolean force = s.tryConsume('+'); + String remotePart = s.boundedToken(':'); + String localPart = s.line(); + myReferencesModel.addMapping(force, remotePart, localPart); + } + } + catch (VcsException e) { + log.error("Failed to get fetch references ", e); + } + } + } + + /** + * Add listener that is fired when validation is required + * + * @param l a listener to add + */ + public void addValidationRequiredListener(final ActionListener l) { + myRemoteNameTextField.getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + //noinspection HardCodedStringLiteral + l.actionPerformed(new ActionEvent(myRemoteNameTextField, ActionEvent.ACTION_PERFORMED, "validationRequired")); + } + }); + } + + /** + * Set default reference source for panel. + * + * @param referenceSource a reference source + */ + public void setReferenceSource(final ReferenceSource referenceSource) { + myReferenceSource = referenceSource; + } + + /** + * @return references added to the model + */ + public String[] getReferences() { + return myReferencesModel.getReferences(); + } + + + /** + * Mapping table model + */ + private class MyMappingTableModel extends AbstractTableModel { + /** + * Force column in the table + */ + private static final int FORCE_COLUMN = 0; + /** + * Remote reference column in the table + */ + private static final int REMOTE_COLUMN = 1; + /** + * Local reference column in the table + */ + private static final int LOCAL_COLUMN = 2; + /** + * Remote name used for the table update + */ + private String mySavedRemoteName = null; + /** + * The currently constructed mapping + */ + private final ArrayList myMapping = new ArrayList(); + + /** + * {@inheritDoc} + */ + public int getRowCount() { + return myMapping.size(); + } + + /** + * Remove currently selected mappings + */ + public void removeSelectedMapping() { + final int[] rows = myReferences.getSelectedRows(); + Arrays.sort(rows); + for (int i = rows.length - 1; i >= 0; i--) { + myMapping.remove(rows[i]); + } + fireTableDataChanged(); + } + + /** + * {@inheritDoc} + */ + public int getColumnCount() { + return LOCAL_COLUMN + 1; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCellEditable(final int rowIndex, final int columnIndex) { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) { + RefMapping m = myMapping.get(rowIndex); + switch (columnIndex) { + case FORCE_COLUMN: + m.force = ((Boolean)aValue).booleanValue(); + break; + case LOCAL_COLUMN: + m.local = (String)aValue; + break; + case REMOTE_COLUMN: + m.remote = (String)aValue; + break; + default: + throw new IllegalStateException("Invalid column: " + columnIndex); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getColumnName(final int column) { + switch (column) { + case FORCE_COLUMN: + return GitBundle.getString("refspec.column.force"); + case LOCAL_COLUMN: + return GitBundle.getString("refspec.column.local"); + case REMOTE_COLUMN: + return GitBundle.getString("refspec.column.remote"); + default: + throw new IllegalStateException("Invalid column: " + column); + } + } + + /** + * {@inheritDoc} + */ + public Object getValueAt(final int rowIndex, final int columnIndex) { + RefMapping m = myMapping.get(rowIndex); + switch (columnIndex) { + case FORCE_COLUMN: + return m.force; + case LOCAL_COLUMN: + return m.local; + case REMOTE_COLUMN: + return m.remote; + default: + throw new IllegalStateException("Invalid column: " + columnIndex); + } + } + + /** + * Add mapping + * + * @param force a force flag + * @param remote a remote reference + * @param local a local reference + */ + public void addMapping(final boolean force, @NonNls final String remote, @NonNls final String local) { + final RefMapping m = new RefMapping(); + m.force = force; + m.remote = remote; + m.local = local; + int row = myMapping.size(); + myMapping.add(m); + fireTableRowsInserted(row, row); + if (mySavedRemoteName == null) { + remoteUpdated(); + } + } + + /** + * This method updates all local heads in the table with remote name + */ + private void remoteUpdated() { + String newText = myRemoteNameTextField.getText(); + if (mySavedRemoteName != null && !newText.equals(mySavedRemoteName)) { + @NonNls String oldTagsPrefix = REFS_TAGS_PREFIX + mySavedRemoteName + "/"; + @NonNls String newTagsPrefix = REFS_TAGS_PREFIX + newText + "/"; + @NonNls String oldHeadsPrefix = REFS_REMOTES_PREFIX + mySavedRemoteName + "/"; + @NonNls String newHeadsPrefix = REFS_REMOTES_PREFIX + newText + "/"; + for (RefMapping m : myMapping) { + if (m.local.startsWith(oldTagsPrefix)) { + m.local = newTagsPrefix + m.local.substring(oldTagsPrefix.length()); + } + else if (m.local.startsWith(oldHeadsPrefix)) { + m.local = newHeadsPrefix + m.local.substring(oldHeadsPrefix.length()); + } + } + fireTableDataChanged(); + } + mySavedRemoteName = newText; + } + + @Override + public Class getColumnClass(final int columnIndex) { + if (columnIndex == FORCE_COLUMN) { + return Boolean.class; + } + return super.getColumnClass(columnIndex); + } + + /** + * @return true if remote name is actually used in the entries + */ + boolean isRemoteNameUsed() { + String newText = myRemoteNameTextField.getText(); + @NonNls String newTagsPrefix = REFS_TAGS_PREFIX + newText + "/"; + @NonNls String newHeadsPrefix = REFS_REMOTES_PREFIX + newText + "/"; + for (RefMapping m : myMapping) { + if (m.local.startsWith(newTagsPrefix) || m.local.startsWith(newHeadsPrefix)) { + return true; + } + } + return false; + } + + /** + * Clear the mapping + */ + public void clear() { + myMapping.clear(); + fireTableDataChanged(); + } + + /** + * @return a list of references + */ + public String[] getReferences() { + final int n = myMapping.size(); + String[] rc = new String[n]; + for (int i = 0; i < n; i++) { + rc[i] = myMapping.get(i).toString(); + } + return rc; + } + + /** + * Reference mapping object used in the table model + */ + class RefMapping { + /** + * if true update is forced + */ + boolean force; + /** + * remote reference name + */ + String remote; + /** + * local reference name + */ + String local; + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return (force ? "+" : "") + remote + ":" + local; + } + } + } + + /** + * The source of default references + */ + enum ReferenceSource { + /** + * The references are pulled from fetch specification + */ + FETCH, } +} diff --git a/plugins/git4idea/src/git4idea/ui/GitUIUtil.java b/plugins/git4idea/src/git4idea/ui/GitUIUtil.java index 3da3a67470..ec1647e0b3 100644 --- a/plugins/git4idea/src/git4idea/ui/GitUIUtil.java +++ b/plugins/git4idea/src/git4idea/ui/GitUIUtil.java @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2008 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.ui; import com.intellij.openapi.project.Project; @@ -7,9 +22,11 @@ import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitBranch; import git4idea.GitRemote; import git4idea.GitVcs; +import git4idea.config.GitConfigUtil; import git4idea.i18n.GitBundle; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -91,37 +108,40 @@ public class GitUIUtil { * @param roots git roots for the project * @param defaultRoot a default root * @param gitRootChooser git root selector - * @param currentBranchLabel current branch label + * @param currentBranchLabel current branch label (might be null) */ public static void setupRootChooser(final Project project, final List roots, final VirtualFile defaultRoot, final JComboBox gitRootChooser, - final JLabel currentBranchLabel) { + @Nullable final JLabel currentBranchLabel) { for (VirtualFile root : roots) { gitRootChooser.addItem(root); } gitRootChooser.setRenderer(getVirtualFileListCellRenderer()); gitRootChooser.setSelectedItem(defaultRoot); - final ActionListener listener = new ActionListener() { - public void actionPerformed(final ActionEvent e) { - try { - VirtualFile root = (VirtualFile)gitRootChooser.getSelectedItem(); - GitBranch current = GitBranch.current(project, root); - if (current == null) { - currentBranchLabel.setText(NO_CURRENT_BRANCH); + if (currentBranchLabel != null) { + final ActionListener listener = new ActionListener() { + public void actionPerformed(final ActionEvent e) { + try { + VirtualFile root = (VirtualFile)gitRootChooser.getSelectedItem(); + GitBranch current = GitBranch.current(project, root); + assert currentBranchLabel != null; + if (current == null) { + currentBranchLabel.setText(NO_CURRENT_BRANCH); + } + else { + currentBranchLabel.setText(current.getName()); + } } - else { - currentBranchLabel.setText(current.getName()); + catch (VcsException ex) { + GitVcs.getInstance(project).showErrors(Collections.singletonList(ex), GitBundle.getString("merge.retriving.branches")); } } - catch (VcsException ex) { - GitVcs.getInstance(project).showErrors(Collections.singletonList(ex), GitBundle.getString("merge.retriving.branches")); - } - } - }; - listener.actionPerformed(null); - gitRootChooser.addActionListener(listener); + }; + listener.actionPerformed(null); + gitRootChooser.addActionListener(listener); + } } /** @@ -145,4 +165,40 @@ public class GitUIUtil { public static void showOperationError(final Project project, final String operation, final String message) { Messages.showErrorDialog(project, message, GitBundle.message("error.occurred.during", operation)); } + + /** + * Setup remotes combobox. The default remote for the current branch is selected by default. + * + * @param project the project + * @param root the git root + * @param currentBranch the current branch + * @param remoteCombobox the combobox to update + */ + public static void setupRemotes(final Project project, + final VirtualFile root, + final String currentBranch, + final JComboBox remoteCombobox) { + try { + List remotes = GitRemote.list(project, root); + String remote = null; + if (currentBranch != null) { + remote = GitConfigUtil.getValue(project, root, "branch." + currentBranch + ".remote"); + } + remoteCombobox.setRenderer(getGitRemoteListCellRenderer(remote)); + GitRemote toSelect = null; + remoteCombobox.removeAllItems(); + for (GitRemote r : remotes) { + remoteCombobox.addItem(r); + if (r.name().equals(remote)) { + toSelect = r; + } + } + if (toSelect != null) { + remoteCombobox.setSelectedItem(toSelect); + } + } + catch (VcsException e) { + GitVcs.getInstance(project).showErrors(Collections.singletonList(e), GitBundle.getString("pull.retriving.remotes")); + } + } } -- 2.11.4.GIT