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/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(); } } diff --git a/plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java b/plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java new file mode 100644 index 000000000000..6e0ec2e52497 --- /dev/null +++ b/plugins/git4idea/src/git4idea/push/GitManualPushToBranch.java @@ -0,0 +1,226 @@ +/* + * 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) { + 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()); + 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/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/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..578065a56c0e 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 will be 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/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 c475b3e4983e..ce510ccd2bb9 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 { @@ -350,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) { 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(); + } }