From 9219055bdb0be3dfc69e12db64ee8bb1ceb83c61 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Thu, 17 Nov 2011 17:13:00 +0300 Subject: [PATCH 1/5] IDEA-64586 Support Git pushing to any specified manually entered remote branch Add a panel with optional field to specify target branch. Refresh button refreshes the commit list. If the branch doesn't exist in remote branches, last 10 commits are shown with the proper message --- plugins/git4idea/src/git4idea/GitUtil.java | 4 + .../git4idea/push/GitManualPushToBranch.java | 219 ++++++++++++++++++ .../src/git4idea/push/GitPushBranchInfo.java | 17 +- .../src/git4idea/push/GitPushDialog.java | 114 ++++++--- .../src/git4idea/push/GitPushLog.java | 24 +- .../git4idea/src/git4idea/push/GitPusher.java | 18 +- 6 files changed, 355 insertions(+), 41 deletions(-) create mode 100644 plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java diff --git a/plugins/git4idea/src/git4idea/GitUtil.java b/plugins/git4idea/src/git4idea/GitUtil.java index 2727c731e18a..a2414a2a552a 100644 --- a/plugins/git4idea/src/git4idea/GitUtil.java +++ b/plugins/git4idea/src/git4idea/GitUtil.java @@ -619,4 +619,8 @@ public class GitUtil { } return null; } + + public static boolean repoContainsRemoteBranch(@NotNull GitRepository repository, @NotNull GitBranch dest) { + return repository.getBranches().getRemoteBranches().contains(dest); + } } diff --git a/plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java b/plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java new file mode 100644 index 000000000000..d69d5df5dd1a --- /dev/null +++ b/plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java @@ -0,0 +1,219 @@ +/* + * Copyright 2000-2011 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.push; + +import com.intellij.openapi.util.IconLoader; +import com.intellij.ui.components.JBLabel; +import com.intellij.util.ui.GridBag; +import com.intellij.util.ui.UIUtil; +import git4idea.repo.GitRemote; +import git4idea.repo.GitRepository; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.*; +import java.util.List; + +/** + * @author Kirill Likhodedov + */ +class GitManualPushToBranch extends JPanel { + + private final Collection myRepositories; + + private final JCheckBox myManualPush; + private final JTextField myDestBranchTextField; + private final JBLabel myComment; + private final JButton myRefreshButton; + private final RemoteSelector myRemoteSelector; + private final JComponent myRemoteSelectorComponent; + + GitManualPushToBranch(@NotNull Collection repositories, + @NotNull final Runnable performOnRefresh) { + super(); + myRepositories = repositories; + + myManualPush = new JCheckBox("Push current branch to: ", false); + myManualPush.setMnemonic('b'); + + myDestBranchTextField = new JTextField(15); + + myComment = new JBLabel("This will apply to all selected repositories", UIUtil.ComponentStyle.SMALL); + + myRefreshButton = new JButton(IconLoader.getIcon("/actions/sync.png")); + myRefreshButton.setToolTipText("Refresh commit list"); + + myRefreshButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + performOnRefresh.run(); + } + }); + + myRemoteSelector = new RemoteSelector(getRemotesWithCommonNames(repositories)); + myRemoteSelectorComponent = myRemoteSelector.createComponent(); + + setDefaultComponentsEnabledState(myManualPush.isSelected()); + myManualPush.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + boolean isManualPushSelected = myManualPush.isSelected(); + setDefaultComponentsEnabledState(isManualPushSelected); + } + }); + + layoutComponents(); + } + + private void setDefaultComponentsEnabledState(boolean selected) { + setComponentsEnabledState(selected, myRemoteSelectorComponent, myDestBranchTextField, myRefreshButton, myComment); + } + + private void layoutComponents() { + JPanel panel = new JPanel(); + GridBagLayout layout = new GridBagLayout(); + panel.setLayout(layout); + GridBag g = new GridBag() + .setDefaultFill(GridBagConstraints.NONE) + .setDefaultAnchor(GridBagConstraints.LINE_START) + .setDefaultWeightX(1, 1); + + panel.add(myManualPush, g.nextLine().next()); + panel.add(myRemoteSelectorComponent, g.next()); + panel.add(myDestBranchTextField, g.next()); + panel.add(myRefreshButton, g.next()); + g.nextLine(); + if (myRepositories.size() > 1) { + panel.add(myComment, g.insets(0, 20, 0, 0).next()); + } + + setLayout(new BorderLayout()); + add(panel, BorderLayout.WEST); + } + + boolean canBeUsed() { + return myManualPush.isSelected() && !myDestBranchTextField.getText().isEmpty(); + } + + @NotNull + String getBranchToPush() { + return myDestBranchTextField.getText(); + } + + void setBranchToPushIfNotSet(@NotNull String text) { + if (myDestBranchTextField.getText().isEmpty()) { + myDestBranchTextField.setText(text); + } + } + + @NotNull + GitRemote getSelectedRemote() { + return myRemoteSelector.getSelectedValue(); + } + + private static void setComponentsEnabledState(boolean enabled, JComponent... components) { + for (JComponent component : components) { + component.setEnabled(enabled); + } + } + + @NotNull + private static Collection getRemotesWithCommonNames(@NotNull Collection repositories) { + if (repositories.isEmpty()) { + return Collections.emptyList(); + } + Iterator iterator = repositories.iterator(); + List commonRemotes = new ArrayList(iterator.next().getRemotes()); + while (iterator.hasNext()) { + GitRepository repository = iterator.next(); + Collection remoteNames = getRemoteNames(repository); + for (Iterator commonIter = commonRemotes.iterator(); commonIter.hasNext(); ) { + GitRemote remote = commonIter.next(); + if (!remoteNames.contains(remote.getName())) { + commonIter.remove(); + } + } + } + return commonRemotes; + } + + @NotNull + private static Collection getRemoteNames(@NotNull GitRepository repository) { + Collection names = new ArrayList(repository.getRemotes().size()); + for (GitRemote remote : repository.getRemotes()) { + names.add(remote.getName()); + } + return names; + } + + /** + * Component to select remotes. + * If there is only one remote, JLabel is used instead of JCombobox. + */ + private static class RemoteSelector { + + private final Collection myRemotes; + private JComboBox myRemoteCombobox; + + private RemoteSelector(@NotNull Collection remotes) { + myRemotes = remotes; + } + + @NotNull + JComponent createComponent() { + //if (myRemotes.size() == 1) { + // JBLabel label = new JBLabel(myRemotes.iterator().next().getName()); + // label.setToolTipText("Remote"); + // return label; + //} else { + myRemoteCombobox = new JComboBox(); + myRemoteCombobox.setRenderer(new RemoteCellRenderer()); + for (GitRemote remote : myRemotes) { + myRemoteCombobox.addItem(remote); + } + myRemoteCombobox.setToolTipText("Select remote"); + return myRemoteCombobox; + //} + } + + @NotNull + GitRemote getSelectedValue() { + //if (myRemotes.size() == 1) { + // return myRemotes.iterator().next(); + //} + return (GitRemote)myRemoteCombobox.getSelectedItem(); + } + + private static class RemoteCellRenderer implements ListCellRenderer { + + public static final DefaultListCellRenderer DEFAULT_RENDERER = new DefaultListCellRenderer(); + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + Component renderer = DEFAULT_RENDERER.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value instanceof GitRemote) { + ((JLabel) renderer).setText(((GitRemote)value).getName()); + } + return renderer; + } + } + + } + +} diff --git a/plugins/git4idea/src/git4idea/push/GitPushBranchInfo.java b/plugins/git4idea/src/git4idea/push/GitPushBranchInfo.java index 5b5d21c56eec..0b7539277b61 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushBranchInfo.java +++ b/plugins/git4idea/src/git4idea/push/GitPushBranchInfo.java @@ -29,16 +29,24 @@ import java.util.List; */ final class GitPushBranchInfo { + private final GitBranch mySourceBranch; private final GitBranch myDestBranch; + private final boolean myNewBranch; private final List myCommits; - GitPushBranchInfo(@NotNull GitBranch destBranch, @NotNull List commits) { + GitPushBranchInfo(@NotNull GitBranch sourceBranch, @NotNull GitBranch destBranch, @NotNull List commits, boolean newBranch) { + mySourceBranch = sourceBranch; myCommits = commits; myDestBranch = destBranch; + myNewBranch = newBranch; } GitPushBranchInfo(@NotNull GitPushBranchInfo pushBranchInfo) { - this(pushBranchInfo.getDestBranch(), pushBranchInfo.getCommits()); + this(pushBranchInfo.getSourceBranch(), pushBranchInfo.getDestBranch(), pushBranchInfo.getCommits(), pushBranchInfo.isNewBranchCreated()); + } + + boolean isNewBranchCreated() { + return myNewBranch; } @NotNull @@ -50,4 +58,9 @@ final class GitPushBranchInfo { List getCommits() { return new ArrayList(myCommits); } + + @NotNull + public GitBranch getSourceBranch() { + return mySourceBranch; + } } diff --git a/plugins/git4idea/src/git4idea/push/GitPushDialog.java b/plugins/git4idea/src/git4idea/push/GitPushDialog.java index 797c96c1ed92..cd18aabef110 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushDialog.java +++ b/plugins/git4idea/src/git4idea/push/GitPushDialog.java @@ -59,6 +59,7 @@ public class GitPushDialog extends DialogWrapper { private final JBLoadingPanel myLoadingPanel; private final JCheckBox myPushAllCheckbox; private final Object COMMITS_LOADING_LOCK = new Object(); + private final GitManualPushToBranch myRefspecPanel; public GitPushDialog(@NotNull Project project) { super(project); @@ -73,28 +74,16 @@ public class GitPushDialog extends DialogWrapper { myPushAllCheckbox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - loadCommitsInBackground(myPushAllCheckbox.isSelected()); + loadCommitsInBackground(); } }); /* hidden: it may confuse users, the target is not clear, hidden until really needed, not removed completely because it is default behavior for 'git push' in command line. */ myPushAllCheckbox.setVisible(false); - myListPanel = new GitPushLog(myProject, myRepositories, new Consumer() { - @Override public void consume(Boolean checked) { - if (checked) { - setOKActionEnabled(true); - } else { - Collection repositories = myListPanel.getSelectedRepositories(); - if (repositories.isEmpty()) { - setOKActionEnabled(false); - } else { - setOKActionEnabled(true); - } - } - } - }); - + myListPanel = new GitPushLog(myProject, myRepositories, new RepositoryCheckboxListener()); + myRefspecPanel = new GitManualPushToBranch(myRepositories, new RefreshButtonListener()); + init(); setOKButtonText("Push"); setTitle("Git Push"); @@ -103,31 +92,33 @@ public class GitPushDialog extends DialogWrapper { @Override protected JComponent createCenterPanel() { JPanel optionsPanel = new JPanel(new BorderLayout()); - optionsPanel.add(myPushAllCheckbox); + optionsPanel.add(myPushAllCheckbox, BorderLayout.NORTH); + optionsPanel.add(myRefspecPanel); - myRootPanel = new JPanel(new BorderLayout()); + myRootPanel = new JPanel(new BorderLayout(0, 15)); myRootPanel.add(createCommitListPanel(), BorderLayout.CENTER); myRootPanel.add(optionsPanel, BorderLayout.SOUTH); return myRootPanel; } - + + private JComponent createCommitListPanel() { myLoadingPanel.add(myListPanel, BorderLayout.CENTER); - loadCommitsInBackground(false); + loadCommitsInBackground(); JPanel commitListPanel = new JPanel(new BorderLayout()); commitListPanel.add(myLoadingPanel, BorderLayout.CENTER); return commitListPanel; } - private void loadCommitsInBackground(final boolean pushAll) { + private void loadCommitsInBackground() { myLoadingPanel.startLoading(); ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { public void run() { final AtomicReference error = new AtomicReference(); synchronized (COMMITS_LOADING_LOCK) { - error.set(collectInfoToPush(pushAll)); + error.set(collectInfoToPush()); } UIUtil.invokeLaterIfNeeded(new Runnable() { @@ -138,17 +129,50 @@ public class GitPushDialog extends DialogWrapper { } else { myListPanel.setCommits(myGitCommitsToPush); } + myRefspecPanel.setBranchToPushIfNotSet(getTrackedCurrentBranchName()); myLoadingPanel.stopLoading(); } }); } }); } - + + @NotNull + private String getTrackedCurrentBranchName() { + if (myGitCommitsToPush != null) { + Collection repositories = myGitCommitsToPush.getRepositories(); + if (!repositories.isEmpty()) { + GitRepository repository = repositories.iterator().next(); + GitBranch currentBranch = repository.getCurrentBranch(); + assert currentBranch != null; + return getNameWithoutRemote(myGitCommitsToPush.get(repository).get(currentBranch).getDestBranch()); + } + } + return ""; + } + + @NotNull + private String getNameWithoutRemote(@NotNull GitBranch remoteBranch) { + String remoteName = myRefspecPanel.getSelectedRemote().getName() + "/"; + String branchName = remoteBranch.getName(); + if (branchName.startsWith(remoteName)) { + return branchName.substring(remoteName.length()); + } + else { + // we are taking the current branch of the first repository + // it is possible (though unlikely), that this branch has other remote than the common remote selected in the refspec panel + // then we return the full branch name. + // the push won't work absolutely correct, if the remote doesn't have this branch, but it is not our problem in the case of + // several repositories with different remotes sets and different branches. + return remoteBranch.getFullName(); + } + } + @Nullable - private String collectInfoToPush(boolean pushAll) { + private String collectInfoToPush() { try { - myPushSpecs = pushAll ? pushSpecsForPushAll() : pushSpecsForCurrentBranches(); + boolean pushAll = myPushAllCheckbox.isSelected(); + myPushSpecs = pushAll ? pushSpecsForPushAll() : pushSpecsForCurrentOrEnteredBranches(); myGitCommitsToPush = myPusher.collectCommitsToPush(myPushSpecs); return null; } @@ -159,7 +183,7 @@ public class GitPushDialog extends DialogWrapper { } } - private Map pushSpecsForCurrentBranches() throws VcsException { + private Map pushSpecsForCurrentOrEnteredBranches() throws VcsException { Map defaultSpecs = new HashMap(); for (GitRepository repository : myRepositories) { GitBranch currentBranch = repository.getCurrentBranch(); @@ -178,6 +202,19 @@ public class GitPushDialog extends DialogWrapper { remote = remoteAndBranch.getFirst(); tracked = remoteAndBranch.getSecond(); } + + if (myRefspecPanel.canBeUsed()) { + String manualBranchName = myRefspecPanel.getBranchToPush(); + GitBranch manualBranch = findRemoteBranchByName(repository, remote, manualBranchName); + if (manualBranch == null) { + if (!manualBranchName.startsWith("refs/remotes/")) { + manualBranchName = myRefspecPanel.getSelectedRemote().getName() + "/" + manualBranchName; + } + manualBranch = new GitBranch(manualBranchName, false, true); + } + tracked = manualBranch; + } + GitPushSpec pushSpec = new GitPushSpec(remote, currentBranch, tracked); defaultSpecs.put(repository, pushSpec); } @@ -227,7 +264,7 @@ public class GitPushDialog extends DialogWrapper { synchronized (COMMITS_LOADING_LOCK) { GitCommitsByRepoAndBranch selectedCommits; if (myGitCommitsToPush == null) { - collectInfoToPush(myPushAllCheckbox.isSelected()); + collectInfoToPush(); selectedCommits = myGitCommitsToPush; } else { Collection selectedRepositories = myListPanel.getSelectedRepositories(); @@ -236,4 +273,27 @@ public class GitPushDialog extends DialogWrapper { return new GitPushInfo(selectedCommits, myPushSpecs); } } + + private class RepositoryCheckboxListener implements Consumer { + @Override public void consume(Boolean checked) { + if (checked) { + setOKActionEnabled(true); + } else { + Collection repositories = myListPanel.getSelectedRepositories(); + if (repositories.isEmpty()) { + setOKActionEnabled(false); + } else { + setOKActionEnabled(true); + } + } + } + } + + private class RefreshButtonListener implements Runnable { + @Override + public void run() { + loadCommitsInBackground(); + } + } + } diff --git a/plugins/git4idea/src/git4idea/push/GitPushLog.java b/plugins/git4idea/src/git4idea/push/GitPushLog.java index 7f720211cbd1..5d14cf5e59a2 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushLog.java +++ b/plugins/git4idea/src/git4idea/push/GitPushLog.java @@ -36,7 +36,6 @@ import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import git4idea.GitBranch; import git4idea.GitUtil; -import git4idea.branch.GitBranchPair; import git4idea.history.browser.GitCommit; import git4idea.repo.GitRepository; import org.jetbrains.annotations.NotNull; @@ -208,7 +207,7 @@ class GitPushLog extends JPanel implements TypeSafeDataProvider { } private static DefaultMutableTreeNode createBranchNode(@NotNull GitBranch branch, @NotNull GitPushBranchInfo branchInfo) { - DefaultMutableTreeNode branchNode = new DefaultMutableTreeNode(new GitBranchPair(branch, branchInfo.getDestBranch())); + DefaultMutableTreeNode branchNode = new DefaultMutableTreeNode(branchInfo); for (GitCommit commit : branchInfo.getCommits()) { branchNode.add(new DefaultMutableTreeNode(commit)); } @@ -290,10 +289,10 @@ class GitPushLog extends JPanel implements TypeSafeDataProvider { Font font = EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.PLAIN); // using probable monospace font to emulate table renderer.setFont(font); + SimpleTextAttributes smallGrey = new SimpleTextAttributes(SimpleTextAttributes.STYLE_SMALLER, UIUtil.getInactiveTextColor()); if (userObject instanceof GitCommit) { GitCommit commit = (GitCommit)userObject; SimpleTextAttributes small = new SimpleTextAttributes(SimpleTextAttributes.STYLE_SMALLER, renderer.getForeground()); - SimpleTextAttributes smallGrey = new SimpleTextAttributes(SimpleTextAttributes.STYLE_SMALLER, UIUtil.getInactiveTextColor()); renderer.append(commit.getShortHash().toString(), smallGrey); renderer.append(String.format(" %" + myDateMaxWidth + "s ", getDateString(commit)), smallGrey); renderer.append(commit.getSubject(), small); @@ -302,13 +301,20 @@ class GitPushLog extends JPanel implements TypeSafeDataProvider { String repositoryPath = calcRootPath((GitRepository)userObject); renderer.append(repositoryPath, SimpleTextAttributes.GRAY_ATTRIBUTES); } - else if (userObject instanceof GitBranchPair) { - GitBranchPair branchPair = (GitBranchPair) userObject; - GitBranch fromBranch = branchPair.getBranch(); - GitBranch dest = branchPair.getDest(); - assert dest != null : "Destination branch can't be null for branch " + fromBranch; + else if (userObject instanceof GitPushBranchInfo) { + GitPushBranchInfo branchInfo = (GitPushBranchInfo) userObject; + GitBranch fromBranch = branchInfo.getSourceBranch(); + GitBranch dest = branchInfo.getDestBranch(); - renderer.append(fromBranch.getName() + " -> " + dest.getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + String text = fromBranch.getName() + " -> "; + if (branchInfo.isNewBranchCreated()) { + text += "+"; + } + text += dest.getName(); + renderer.append(text, branchInfo.isNewBranchCreated() ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES); + if (branchInfo.isNewBranchCreated()) { + renderer.append(" new branch is created, showing last 10 commits on the current branch", smallGrey); + } } else if (userObject instanceof FakeCommit) { int spaces = 6 + 15 + 3 + 30; diff --git a/plugins/git4idea/src/git4idea/push/GitPusher.java b/plugins/git4idea/src/git4idea/push/GitPusher.java index c475b3e4983e..1d5f047f3854 100644 --- a/plugins/git4idea/src/git4idea/push/GitPusher.java +++ b/plugins/git4idea/src/git4idea/push/GitPusher.java @@ -168,16 +168,28 @@ public final class GitPusher { GitBranch dest = sourceDest.getDest(); assert dest != null : "Destination branch can't be null here for branch " + source; - List commits = collectCommitsToPush(repository, source.getName(), dest.getName()); + List commits; + boolean newBranch; + if (GitUtil.repoContainsRemoteBranch(repository, dest)) { + commits = collectCommitsToPush(repository, source.getName(), dest.getName()); + newBranch = false; + } + else { + commits = collectRecentCommitsOnBranch(repository, source); + newBranch = true; + } if (!commits.isEmpty()) { - commitsByBranch.put(source, new GitPushBranchInfo(dest, commits)); + commitsByBranch.put(source, new GitPushBranchInfo(source, dest, commits, newBranch)); } - } return new GitCommitsByBranch(commitsByBranch); } + private List collectRecentCommitsOnBranch(GitRepository repository, GitBranch source) throws VcsException { + return GitHistoryUtils.history(myProject, repository.getRoot(), "--max-count=10", source.getName()); + } + @NotNull private List collectCommitsToPush(@NotNull GitRepository repository, @NotNull String source, @NotNull String destination) throws VcsException { From 553037a8541cd3f83c1a4ab56777cd1c07e692a5 Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Thu, 17 Nov 2011 19:36:31 +0400 Subject: [PATCH 2/5] WI-6613 fix enter after empty tag --- .../EnterBetweenXmlTagsHandler.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/xml/impl/src/com/intellij/codeInsight/editorActions/EnterBetweenXmlTagsHandler.java b/xml/impl/src/com/intellij/codeInsight/editorActions/EnterBetweenXmlTagsHandler.java index 4f4f1d98aab1..6637befbded8 100644 --- a/xml/impl/src/com/intellij/codeInsight/editorActions/EnterBetweenXmlTagsHandler.java +++ b/xml/impl/src/com/intellij/codeInsight/editorActions/EnterBetweenXmlTagsHandler.java @@ -17,29 +17,36 @@ package com.intellij.codeInsight.editorActions; import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter; import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IElementType; import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; import com.intellij.psi.xml.XmlTokenType; import org.jetbrains.annotations.NotNull; public class EnterBetweenXmlTagsHandler extends EnterHandlerDelegateAdapter { public Result preprocessEnter(@NotNull final PsiFile file, @NotNull final Editor editor, @NotNull final Ref caretOffset, @NotNull final Ref caretAdvance, @NotNull final DataContext dataContext, final EditorActionHandler originalHandler) { - if (file instanceof XmlFile && isBetweenXmlTags(editor, caretOffset.get().intValue())) { + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + + if (file instanceof XmlFile && isBetweenXmlTags(project, editor, file, caretOffset.get().intValue())) { originalHandler.execute(editor, dataContext); return Result.DefaultForceIndent; } return Result.Continue; } - private static boolean isBetweenXmlTags(Editor editor, int offset) { + private static boolean isBetweenXmlTags(Project project, Editor editor, PsiFile file, int offset) { if (offset == 0) return false; CharSequence chars = editor.getDocument().getCharsSequence(); if (chars.charAt(offset - 1) != '>') return false; @@ -47,6 +54,11 @@ public class EnterBetweenXmlTagsHandler extends EnterHandlerDelegateAdapter { EditorHighlighter highlighter = ((EditorEx)editor).getHighlighter(); HighlighterIterator iterator = highlighter.createIterator(offset - 1); if (iterator.getTokenType() != XmlTokenType.XML_TAG_END) return false; + + if (isAtTheEndOfEmptyTag(project, editor, file, iterator)) { + return false; + } + iterator.retreat(); int retrieveCount = 1; @@ -62,4 +74,21 @@ public class EnterBetweenXmlTagsHandler extends EnterHandlerDelegateAdapter { iterator.advance(); return !iterator.atEnd() && iterator.getTokenType() == XmlTokenType.XML_END_TAG_START; } + + private static boolean isAtTheEndOfEmptyTag(Project project, Editor editor, PsiFile file, HighlighterIterator iterator) { + if (iterator.getTokenType() != XmlTokenType.XML_TAG_END) { + return false; + } + + PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); + final PsiElement element = file.findElementAt(iterator.getStart()); + + if (element == null) { + return false; + } + + final PsiElement parent = element.getParent(); + return parent instanceof XmlTag && + parent.getTextRange().getEndOffset() == iterator.getEnd(); + } } From dfb6c932a9a55596c689af32c229355eb1220fe1 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Thu, 17 Nov 2011 18:46:25 +0300 Subject: [PATCH 3/5] IDEA-64586 Push to manually entered remote branch: support in push result notification --- .../git4idea/push/GitPushBranchResult.java | 27 ++++++++++++++++--- .../src/git4idea/push/GitPushLog.java | 2 +- .../src/git4idea/push/GitPushRepoResult.java | 8 ++++-- .../src/git4idea/push/GitPushResult.java | 8 ++++-- .../git4idea/src/git4idea/push/GitPusher.java | 6 ++++- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/plugins/git4idea/src/git4idea/push/GitPushBranchResult.java b/plugins/git4idea/src/git4idea/push/GitPushBranchResult.java index a3f451b22299..4bf8891c9ab9 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushBranchResult.java +++ b/plugins/git4idea/src/git4idea/push/GitPushBranchResult.java @@ -15,6 +15,9 @@ */ package git4idea.push; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + /** * Result of pushing a single branch. * @@ -24,28 +27,35 @@ final class GitPushBranchResult { private final Type myType; private final int myNumberOfPushedCommits; + private final String myTargetBranchName; enum Type { SUCCESS, + NEW_BRANCH, REJECTED, ERROR } - private GitPushBranchResult(Type type, int numberOfPushedCommits) { + private GitPushBranchResult(Type type, int numberOfPushedCommits, @Nullable String targetBranchName) { myType = type; myNumberOfPushedCommits = numberOfPushedCommits; + myTargetBranchName = targetBranchName; } static GitPushBranchResult success(int numberOfPushedCommits) { - return new GitPushBranchResult(Type.SUCCESS, numberOfPushedCommits); + return new GitPushBranchResult(Type.SUCCESS, numberOfPushedCommits, null); + } + + static GitPushBranchResult newBranch(String targetBranchName) { + return new GitPushBranchResult(Type.NEW_BRANCH, 0, targetBranchName); } static GitPushBranchResult rejected() { - return new GitPushBranchResult(Type.REJECTED, 0); + return new GitPushBranchResult(Type.REJECTED, 0, null); } static GitPushBranchResult error() { - return new GitPushBranchResult(Type.ERROR, 0); + return new GitPushBranchResult(Type.ERROR, 0, null); } int getNumberOfPushedCommits() { @@ -64,4 +74,13 @@ final class GitPushBranchResult { return myType == Type.ERROR; } + boolean isNewBranch() { + return myType == Type.NEW_BRANCH; + } + + @NotNull + String getTargetBranchName() { + return myTargetBranchName != null ? myTargetBranchName : ""; + } + } diff --git a/plugins/git4idea/src/git4idea/push/GitPushLog.java b/plugins/git4idea/src/git4idea/push/GitPushLog.java index 5d14cf5e59a2..578065a56c0e 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushLog.java +++ b/plugins/git4idea/src/git4idea/push/GitPushLog.java @@ -313,7 +313,7 @@ class GitPushLog extends JPanel implements TypeSafeDataProvider { text += dest.getName(); renderer.append(text, branchInfo.isNewBranchCreated() ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES); if (branchInfo.isNewBranchCreated()) { - renderer.append(" new branch is created, showing last 10 commits on the current branch", smallGrey); + renderer.append(" new branch will be created, showing last 10 commits on the current branch", smallGrey); } } else if (userObject instanceof FakeCommit) { diff --git a/plugins/git4idea/src/git4idea/push/GitPushRepoResult.java b/plugins/git4idea/src/git4idea/push/GitPushRepoResult.java index 438fbefadda4..d3946febb307 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushRepoResult.java +++ b/plugins/git4idea/src/git4idea/push/GitPushRepoResult.java @@ -132,7 +132,7 @@ final class GitPushRepoResult { } @NotNull - String getPerBranchesReport() { + String getPerBranchesNonErrorReport() { StringBuilder sb = new StringBuilder(); int i = 0; for (Map.Entry entry : myBranchResults.entrySet()) { @@ -141,7 +141,11 @@ final class GitPushRepoResult { if (branchResult.isSuccess()) { sb.append(bold(branch.getName()) + ": pushed " + commits(branchResult.getNumberOfPushedCommits())); - } else { + } + else if (branchResult.isNewBranch()) { + sb.append(bold(branch.getName()) + " pushed to new branch " + bold(branchResult.getTargetBranchName())); + } + else { sb.append(code(branch.getName())).append(": rejected"); } diff --git a/plugins/git4idea/src/git4idea/push/GitPushResult.java b/plugins/git4idea/src/git4idea/push/GitPushResult.java index e1067ea3b0e1..aaeaaf3b3b69 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushResult.java +++ b/plugins/git4idea/src/git4idea/push/GitPushResult.java @@ -225,8 +225,12 @@ class GitPushResult { } notificationType = NotificationType.WARNING; } else { - title = "Pushed " + pushedCommitsNumber + " " + StringUtil.pluralize("commit", pushedCommitsNumber); notificationType = NotificationType.INFORMATION; + if (pushedCommitsNumber == 0) { // happens on new branch creation + title = "Pushed successfully"; + } else { + title = "Pushed " + pushedCommitsNumber + " " + StringUtil.pluralize("commit", pushedCommitsNumber); + } } String errorReport = reportForGroup(groupedResult.myErrorResults, GroupedResult.Type.ERROR); @@ -291,7 +295,7 @@ class GitPushResult { sb.append("" + repository.getPresentableUrl() + ":
"); } if (resultType == GroupedResult.Type.SUCCESS || resultType == GroupedResult.Type.REJECT) { - sb.append(result.getPerBranchesReport()); + sb.append(result.getPerBranchesNonErrorReport()); } else { sb.append(result.getOutput()); } diff --git a/plugins/git4idea/src/git4idea/push/GitPusher.java b/plugins/git4idea/src/git4idea/push/GitPusher.java index 1d5f047f3854..ce510ccd2bb9 100644 --- a/plugins/git4idea/src/git4idea/push/GitPusher.java +++ b/plugins/git4idea/src/git4idea/push/GitPusher.java @@ -362,7 +362,11 @@ public final class GitPusher { @NotNull private static GitPushBranchResult successfulResultForBranch(@NotNull GitCommitsByBranch commitsByBranch, @NotNull GitBranch branch) { - return GitPushBranchResult.success(commitsByBranch.get(branch).getCommits().size()); + GitPushBranchInfo branchInfo = commitsByBranch.get(branch); + if (branchInfo.isNewBranchCreated()) { + return GitPushBranchResult.newBranch(branchInfo.getDestBranch().getName()); + } + return GitPushBranchResult.success(branchInfo.getCommits().size()); } private static boolean branchInRejected(@NotNull GitBranch branch, @NotNull Collection rejectedBranches) { From c7d605262c504ff0107679c70054ecf9f776553e Mon Sep 17 00:00:00 2001 From: irengrig Date: Thu, 17 Nov 2011 19:50:00 +0400 Subject: [PATCH 4/5] git references are kept in log -> clear cache =( on refresh --- .../git4idea/src/git4idea/history/wholeTree/DetailsCache.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java b/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java index e4e2b0f8ea30..410c2b1bb13f 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java @@ -113,7 +113,8 @@ public class DetailsCache { myBranches.clear(); myStash.clear(); // will be cleared by itself; commits are not changed while they have same hash - //myCache.clear(); + // uncommented because of reference caching + myCache.clear(); } } From 863a72f9e92e86b5eb303129496add4b09aedaa8 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Thu, 17 Nov 2011 19:45:11 +0300 Subject: [PATCH 5/5] Git Push to specified branch: better alignment (quick fix) --- .../src/git4idea/push/GitManualPushToBranch.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java b/plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java index d69d5df5dd1a..6e0ec2e52497 100644 --- a/plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java +++ b/plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java @@ -100,7 +100,14 @@ class GitManualPushToBranch extends JPanel { panel.add(myRefreshButton, g.next()); g.nextLine(); if (myRepositories.size() > 1) { - panel.add(myComment, g.insets(0, 20, 0, 0).next()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.anchor = GridBagConstraints.LINE_START; + constraints.gridx = 0; + constraints.gridy = 1; + constraints.insets = new Insets(0, 28, 0, 0); + //panel.add(myComment, g.insets(0, 20, 0, 0).coverLine().next()); + panel.add(myComment, constraints); } setLayout(new BorderLayout());