From 3980e6d0f2d465b7e63c166892ef88ac53d70032 Mon Sep 17 00:00:00 2001 From: Christian Lenz Date: Mon, 28 Jul 2025 21:44:02 +0200 Subject: [PATCH 1/4] Add "Add remote" action & panel. Extend "Remove remote" functionality --- .../modules/git/ui/menu/RemoteMenu.java | 21 +++++ .../git/ui/repository/Bundle.properties | 3 +- .../ui/repository/RepositoryBrowserPanel.java | 31 ++++++- .../ui/repository/remote/AddRemoteAction.java | 38 ++++++++ .../ui/repository/remote/AddRemoteConfig.java | 76 +++++++++++++++ .../ui/repository/remote/AddRemotePanel.form | 89 ++++++++++++++++++ .../ui/repository/remote/AddRemotePanel.java | 93 +++++++++++++++++++ .../ui/repository/remote/Bundle.properties | 13 +++ .../repository/remote/RemoveRemoteAction.java | 62 +++++++++++++ 9 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteAction.java create mode 100644 ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteConfig.java create mode 100644 ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.form create mode 100644 ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.java create mode 100644 ide/git/src/org/netbeans/modules/git/ui/repository/remote/RemoveRemoteAction.java diff --git a/ide/git/src/org/netbeans/modules/git/ui/menu/RemoteMenu.java b/ide/git/src/org/netbeans/modules/git/ui/menu/RemoteMenu.java index df5c3e8e03d1..0274dddb3875 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/menu/RemoteMenu.java +++ b/ide/git/src/org/netbeans/modules/git/ui/menu/RemoteMenu.java @@ -36,6 +36,8 @@ import org.netbeans.modules.git.ui.history.SearchOutgoingWithContextAction; import org.netbeans.modules.git.ui.push.PushAction; import org.netbeans.modules.git.ui.push.PushToUpstreamAction; +import org.netbeans.modules.git.ui.repository.remote.AddRemoteAction; +import org.netbeans.modules.git.ui.repository.remote.RemoveRemoteAction; import org.netbeans.modules.versioning.spi.VCSAnnotator.ActionDestination; import org.netbeans.modules.versioning.spi.VCSContext; import org.netbeans.modules.versioning.util.SystemActionBridge; @@ -152,6 +154,19 @@ protected JMenu createMenu () { Utils.setAcceleratorBindings(Annotator.ACTIONS_PATH_PREFIX, action); Actions.connect(item, action, false); menu.add(item); + + menu.addSeparator(); + item = new JMenuItem(); + action = (Action) SystemAction.get(AddRemoteAction.class); + Utils.setAcceleratorBindings(Annotator.ACTIONS_PATH_PREFIX, action); + Actions.connect(item, action, false); + menu.add(item); + + item = new JMenuItem(); + action = (Action) SystemAction.get(RemoveRemoteAction.class); + Utils.setAcceleratorBindings(Annotator.ACTIONS_PATH_PREFIX, action); + Actions.connect(item, action, false); + menu.add(item); } else { // or use Actions.forID Action action = (Action) FileUtil.getConfigObject(ACTIONS_PATH_PREFIX + CLONE_ACTION, Action.class); @@ -187,6 +202,12 @@ protected JMenu createMenu () { org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); item = menu.add(SystemActionBridge.createAction(SystemAction.get(SearchOutgoingWithContextAction.class), NbBundle.getMessage(SearchOutgoingWithContextAction.class, "LBL_SearchOutgoingWithContextAction_PopupName"), lkp)); //NOI18N org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); + + menu.addSeparator(); + item = menu.add(SystemActionBridge.createAction(SystemAction.get(AddRemoteAction.class), NbBundle.getMessage(AddRemoteAction.class, "LBL_AddRemoteAction_PopupName"), lkp)); //NOI18N + org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); + item = menu.add(SystemActionBridge.createAction(SystemAction.get(RemoveRemoteAction.class), NbBundle.getMessage(RemoveRemoteAction.class, "LBL_RemoveRemoteAction_PopupName"), lkp)); //NOI18N + org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); } return menu; } diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/Bundle.properties b/ide/git/src/org/netbeans/modules/git/ui/repository/Bundle.properties index 63d4e2bb2dd5..69669d123c81 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/repository/Bundle.properties +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/Bundle.properties @@ -37,7 +37,8 @@ MSG_BranchNode.tracking.mergeNeeded = Merge with {0} needed. LBL_RepositoryPanel.RemotesNode.name = Remotes MSG_RepositoryPanel.refreshingRemotes = Refreshing Remotes LBL_RepositoryPanel.RemotesNode.refresh = Refresh Remotes -LBL_RepositoryPanel.RemoteNode.remove = Remove +LBL_RepositoryPanel.RemotesNode.add = Add Remote +LBL_RepositoryPanel.RemotesNode.remove = Remove Remote #RevisionInfoPanel RevisionInfoPanel.jLabel1.text=Commit &ID: RevisionInfoPanel.jLabel2.text=&Author: diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java b/ide/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java index 5d61bdfe3e7f..7f0815e59ee2 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java @@ -90,6 +90,8 @@ import org.netbeans.modules.git.ui.push.PushAction; import org.netbeans.modules.git.ui.push.PushMapping; import org.netbeans.modules.git.ui.push.PushToUpstreamAction; +import org.netbeans.modules.git.ui.repository.remote.AddRemoteConfig; +import org.netbeans.modules.git.ui.repository.remote.RemoveRemoteAction; import org.netbeans.modules.git.ui.repository.remote.RemoveRemoteConfig; import org.netbeans.modules.git.ui.stash.ApplyStashAction; import org.netbeans.modules.git.ui.stash.SaveStashAction; @@ -2092,6 +2094,33 @@ public String getDisplayName () { public String getName () { return NbBundle.getMessage(RepositoryBrowserPanel.class, "LBL_RepositoryPanel.RemotesNode.name"); //NOI18N } + + @Override + protected Action[] getPopupActions(boolean context) { + if (options.contains(Option.ENABLE_POPUP)) { + final File repo = lookupRepository(this); + List actions = new LinkedList<>(); + actions.add(new AbstractAction(NbBundle.getMessage(RepositoryBrowserPanel.class, "LBL_RepositoryPanel.RemotesNode.add")) { //NOI18N + @Override + public void actionPerformed (ActionEvent e) { + EventQueue.invokeLater(() -> { + new AddRemoteConfig().addRemote(repo); + }); + } + }); + actions.add(new AbstractAction(NbBundle.getMessage(RepositoryBrowserPanel.class, "LBL_RepositoryPanel.RemotesNode.remove")) { //NOI18N + @Override + public void actionPerformed (ActionEvent e) { + EventQueue.invokeLater(() -> { + new RemoveRemoteAction().performAction(repo, null, VCSContext.EMPTY); + }); + } + }); + return actions.toArray(Action[]::new); + } else { + return new Action[0]; + } + } } private class AllRemotesChildren extends Children.Keys implements PropertyChangeListener { @@ -2220,7 +2249,7 @@ public void actionPerformed (ActionEvent e) { } }); actions.add(null); - actions.add(new AbstractAction(NbBundle.getMessage(RepositoryBrowserPanel.class, "LBL_RepositoryPanel.RemoteNode.remove")) { //NOI18N + actions.add(new AbstractAction(NbBundle.getMessage(RepositoryBrowserPanel.class, "LBL_RepositoryPanel.RemotesNode.remove")) { //NOI18N @Override public void actionPerformed (ActionEvent e) { EventQueue.invokeLater(new Runnable() { diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteAction.java b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteAction.java new file mode 100644 index 000000000000..f66eb2f79325 --- /dev/null +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteAction.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.netbeans.modules.git.ui.repository.remote; + +import java.io.File; +import org.netbeans.modules.git.ui.actions.SingleRepositoryAction; +import org.netbeans.modules.versioning.spi.VCSContext; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; + +/** + * + * @author Christian Lenz + */ +@ActionID(id = "org.netbeans.modules.git.ui.repository.remote.AddRemoteAction", category = "Git") +@ActionRegistration(displayName = "#LBL_AddRemoteAction_Name") +public class AddRemoteAction extends SingleRepositoryAction { + @Override + protected void performAction(File repository, File[] roots, VCSContext context) { + new AddRemoteConfig().addRemote(repository); + } +} diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteConfig.java b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteConfig.java new file mode 100644 index 000000000000..6fba80fc3cc0 --- /dev/null +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteConfig.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.netbeans.modules.git.ui.repository.remote; + +import java.io.File; +import java.util.Collections; +import org.netbeans.libs.git.GitException; +import org.netbeans.libs.git.GitRemoteConfig; +import org.netbeans.modules.git.Git; +import org.netbeans.modules.git.client.GitClient; +import org.netbeans.modules.git.client.GitClientExceptionHandler; +import org.netbeans.modules.git.client.GitProgressSupport; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle; + +/** + * + * @author Christian Lenz + * + * Adds a new remote configuration to a repository. + */ +public class AddRemoteConfig { + + public void addRemote(File repository) { + AddRemotePanel panel = new AddRemotePanel(); + DialogDescriptor dd = new DialogDescriptor(panel, + NbBundle.getMessage(RemoteRepositoryPanel.class, "LBL_AddRemoteConfig.title")); + if (DialogDisplayer.getDefault().notify(dd) != NotifyDescriptor.OK_OPTION) { + return; + } + + final String remoteName = panel.getRemoteName(); + final String remoteUrl = panel.getRemoteURL(); + if (remoteName.isEmpty() || remoteUrl.isEmpty()) { + return; + } + + GitProgressSupport supp = new GitProgressSupport() { + @Override + protected void perform() { + try { + GitClient client = getClient(); + GitRemoteConfig cfg = new GitRemoteConfig( + remoteName, + Collections.singletonList(remoteUrl), + Collections.emptyList(), + Collections.singletonList("+refs/heads/*:refs/remotes/" + remoteName + "/*"), + Collections.emptyList()); + client.setRemote(cfg, getProgressMonitor()); + } catch (GitException ex) { + GitClientExceptionHandler.notifyException(ex, true); + } + } + }; + supp.start(Git.getInstance().getRequestProcessor(repository), repository, + NbBundle.getMessage(AddRemoteConfig.class, "LBL_AddRemoteConfig.progressName")); + } +} diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.form b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.form new file mode 100644 index 000000000000..2db2f2c006ac --- /dev/null +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.form @@ -0,0 +1,89 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.java b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.java new file mode 100644 index 000000000000..949880b8862f --- /dev/null +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.netbeans.modules.git.ui.repository.remote; + +/** + * + * @author Christian Lenz + */ +public class AddRemotePanel extends javax.swing.JPanel { + + public AddRemotePanel() { + initComponents(); + } + + public String getRemoteName() { + return txtRemoteName.getText().trim(); + } + + public String getRemoteURL() { + return txtRemoteURL.getText().trim(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + lblRemoteName = new javax.swing.JLabel(); + lblRemoteURL = new javax.swing.JLabel(); + txtRemoteName = new javax.swing.JTextField(); + txtRemoteURL = new javax.swing.JTextField(); + + org.openide.awt.Mnemonics.setLocalizedText(lblRemoteName, "Remote Name:"); + + org.openide.awt.Mnemonics.setLocalizedText(lblRemoteURL, "Remote URL:"); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(20, 20, 20) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lblRemoteName) + .addComponent(lblRemoteURL)) + .addGap(20, 20, 20) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(txtRemoteName, javax.swing.GroupLayout.DEFAULT_SIZE, 219, Short.MAX_VALUE) + .addComponent(txtRemoteURL)) + .addGap(20, 20, 20)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(20, 20, 20) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lblRemoteName) + .addComponent(txtRemoteName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(15, 15, 15) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lblRemoteURL) + .addComponent(txtRemoteURL, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(20, 20, 20)) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel lblRemoteName; + private javax.swing.JLabel lblRemoteURL; + private javax.swing.JTextField txtRemoteName; + private javax.swing.JTextField txtRemoteURL; + // End of variables declaration//GEN-END:variables +} diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/Bundle.properties b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/Bundle.properties index 160cd189d290..8961e12e6bb0 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/Bundle.properties +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/Bundle.properties @@ -100,3 +100,16 @@ Scheme.RSYNC=rsync://host.xz/path/to/repo.git/ Scheme.GIT=git://host.xz[:port]/path/to/repo.git/ SelectUriPanel.cbPersistRemote.text=&Persist Remote SelectUriPanel.cbPersistRemote.TTtext=Adds the configured remote to the git configuration file +#AddRemoteConfigAction +LBL_AddRemoteConfig.progressName=Adding remote configuration +LBL_AddRemoteConfig.remoteName=Remote Name +LBL_AddRemoteConfig.remoteUrl=Remote URL +LBL_AddRemoteConfig.title=Add Remote +#AddRemoteAction +LBL_AddRemoteAction_Name=Add Remote... +LBL_AddRemoteAction_PopupName=Add Remote... +#RemoveRemoteAction +LBL_RemoveRemoteAction_Name=Remove Remote... +LBL_RemoveRemoteAction_PopupName=Remove Remote... +LBL_RemoveRemoteAction_Select=Select Remote +LBL_RemoveRemoteAction_noRemotes=No remote configured \ No newline at end of file diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/RemoveRemoteAction.java b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/RemoveRemoteAction.java new file mode 100644 index 000000000000..48860d11b29c --- /dev/null +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/RemoveRemoteAction.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.netbeans.modules.git.ui.repository.remote; + +import java.io.File; +import java.util.Map; +import javax.swing.JComboBox; +import org.netbeans.libs.git.GitRemoteConfig; +import org.netbeans.modules.git.ui.actions.SingleRepositoryAction; +import org.netbeans.modules.git.ui.repository.RepositoryInfo; +import org.netbeans.modules.versioning.spi.VCSContext; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; +import org.openide.util.NbBundle; + +/** + * + * @author Christian Lenz + */ +@ActionID(id = "org.netbeans.modules.git.ui.repository.remote.RemoveRemoteAction", category = "Git") +@ActionRegistration(displayName = "#LBL_RemoveRemoteAction_Name") +public class RemoveRemoteAction extends SingleRepositoryAction { + + @Override + public void performAction(File repository, File[] roots, VCSContext context) { + GitRemoteConfig remote = context != null ? context.getElements().lookup(GitRemoteConfig.class) : null; + String remoteName = remote != null ? remote.getRemoteName() : null; + if (remoteName == null) { + Map remotes = RepositoryInfo.getInstance(repository).getRemotes(); + if (remotes.isEmpty()) { + DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(NbBundle.getMessage(RemoteRepositoryPanel.class, "LBL_RemoveRemoteAction_noRemotes"))); + return; + } + var combo = new JComboBox(remotes.keySet().toArray(new String[0])); + NotifyDescriptor nd = new NotifyDescriptor(combo, NbBundle.getMessage(RemoteRepositoryPanel.class, "LBL_RemoveRemoteAction_Select"), NotifyDescriptor.OK_CANCEL_OPTION, NotifyDescriptor.PLAIN_MESSAGE, null, null); + if (DialogDisplayer.getDefault().notify(nd) != NotifyDescriptor.OK_OPTION) { + return; + } + remoteName = (String) combo.getSelectedItem(); + } + + new RemoveRemoteConfig().removeRemote(repository, remoteName); + } +} From 49f6dd932e40148c1aea2e81537ed1d6da1cd042 Mon Sep 17 00:00:00 2001 From: Christian Lenz Date: Wed, 30 Jul 2025 22:27:08 +0200 Subject: [PATCH 2/4] Remove "Remove remote" action from parent node "Remotes". Move "Add remote" action close to "Clone" action --- .../modules/git/ui/menu/RemoteMenu.java | 37 +++++++------------ .../git/ui/repository/Bundle.properties | 2 +- .../ui/repository/RepositoryBrowserPanel.java | 11 +----- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/ide/git/src/org/netbeans/modules/git/ui/menu/RemoteMenu.java b/ide/git/src/org/netbeans/modules/git/ui/menu/RemoteMenu.java index 0274dddb3875..22f9c26f7589 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/menu/RemoteMenu.java +++ b/ide/git/src/org/netbeans/modules/git/ui/menu/RemoteMenu.java @@ -37,7 +37,6 @@ import org.netbeans.modules.git.ui.push.PushAction; import org.netbeans.modules.git.ui.push.PushToUpstreamAction; import org.netbeans.modules.git.ui.repository.remote.AddRemoteAction; -import org.netbeans.modules.git.ui.repository.remote.RemoveRemoteAction; import org.netbeans.modules.versioning.spi.VCSAnnotator.ActionDestination; import org.netbeans.modules.versioning.spi.VCSContext; import org.netbeans.modules.versioning.util.SystemActionBridge; @@ -85,10 +84,16 @@ protected JMenu createMenu () { item = new JMenuItem(); Actions.connect(item, action, false); menu.add(item); - menu.addSeparator(); } - + item = new JMenuItem(); + action = (Action) SystemAction.get(AddRemoteAction.class); + Utils.setAcceleratorBindings(Annotator.ACTIONS_PATH_PREFIX, action); + Actions.connect(item, action, false); + menu.add(item); + + menu.addSeparator(); + item = new JMenuItem(); action = (Action) SystemAction.get(FetchFromUpstreamAction.class); Utils.setAcceleratorBindings(Annotator.ACTIONS_PATH_PREFIX, action); @@ -154,28 +159,18 @@ protected JMenu createMenu () { Utils.setAcceleratorBindings(Annotator.ACTIONS_PATH_PREFIX, action); Actions.connect(item, action, false); menu.add(item); - - menu.addSeparator(); - item = new JMenuItem(); - action = (Action) SystemAction.get(AddRemoteAction.class); - Utils.setAcceleratorBindings(Annotator.ACTIONS_PATH_PREFIX, action); - Actions.connect(item, action, false); - menu.add(item); - - item = new JMenuItem(); - action = (Action) SystemAction.get(RemoveRemoteAction.class); - Utils.setAcceleratorBindings(Annotator.ACTIONS_PATH_PREFIX, action); - Actions.connect(item, action, false); - menu.add(item); } else { // or use Actions.forID Action action = (Action) FileUtil.getConfigObject(ACTIONS_PATH_PREFIX + CLONE_ACTION, Action.class); if (action != null) { item = menu.add(SystemActionBridge.createAction(action, NbBundle.getMessage(CloneAction.class, "LBL_CloneAction_PopupName"), Lookups.singleton(new ContextHolder(ctx)))); //NOI18N org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); - menu.addSeparator(); } - + + item = menu.add(SystemActionBridge.createAction(SystemAction.get(AddRemoteAction.class), NbBundle.getMessage(AddRemoteAction.class, "LBL_AddRemoteAction_PopupName"), lkp)); //NOI18N + org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); + + menu.addSeparator(); item = menu.add(SystemActionBridge.createAction(SystemAction.get(FetchFromUpstreamAction.class), NbBundle.getMessage(FetchFromUpstreamAction.class, "LBL_FetchFromUpstreamAction_PopupName"), lkp)); //NOI18N org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); item = menu.add(SystemActionBridge.createAction(SystemAction.get(FetchAction.class), NbBundle.getMessage(FetchAction.class, "LBL_FetchAction_PopupName"), lkp)); //NOI18N @@ -202,12 +197,6 @@ protected JMenu createMenu () { org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); item = menu.add(SystemActionBridge.createAction(SystemAction.get(SearchOutgoingWithContextAction.class), NbBundle.getMessage(SearchOutgoingWithContextAction.class, "LBL_SearchOutgoingWithContextAction_PopupName"), lkp)); //NOI18N org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); - - menu.addSeparator(); - item = menu.add(SystemActionBridge.createAction(SystemAction.get(AddRemoteAction.class), NbBundle.getMessage(AddRemoteAction.class, "LBL_AddRemoteAction_PopupName"), lkp)); //NOI18N - org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); - item = menu.add(SystemActionBridge.createAction(SystemAction.get(RemoveRemoteAction.class), NbBundle.getMessage(RemoveRemoteAction.class, "LBL_RemoveRemoteAction_PopupName"), lkp)); //NOI18N - org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); } return menu; } diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/Bundle.properties b/ide/git/src/org/netbeans/modules/git/ui/repository/Bundle.properties index 69669d123c81..e73bc6d0aeb1 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/repository/Bundle.properties +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/Bundle.properties @@ -38,7 +38,7 @@ LBL_RepositoryPanel.RemotesNode.name = Remotes MSG_RepositoryPanel.refreshingRemotes = Refreshing Remotes LBL_RepositoryPanel.RemotesNode.refresh = Refresh Remotes LBL_RepositoryPanel.RemotesNode.add = Add Remote -LBL_RepositoryPanel.RemotesNode.remove = Remove Remote +LBL_RepositoryPanel.RemoteNode.remove = Remove Remote #RevisionInfoPanel RevisionInfoPanel.jLabel1.text=Commit &ID: RevisionInfoPanel.jLabel2.text=&Author: diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java b/ide/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java index 7f0815e59ee2..27d4dac077cd 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/RepositoryBrowserPanel.java @@ -2108,14 +2108,7 @@ public void actionPerformed (ActionEvent e) { }); } }); - actions.add(new AbstractAction(NbBundle.getMessage(RepositoryBrowserPanel.class, "LBL_RepositoryPanel.RemotesNode.remove")) { //NOI18N - @Override - public void actionPerformed (ActionEvent e) { - EventQueue.invokeLater(() -> { - new RemoveRemoteAction().performAction(repo, null, VCSContext.EMPTY); - }); - } - }); + return actions.toArray(Action[]::new); } else { return new Action[0]; @@ -2249,7 +2242,7 @@ public void actionPerformed (ActionEvent e) { } }); actions.add(null); - actions.add(new AbstractAction(NbBundle.getMessage(RepositoryBrowserPanel.class, "LBL_RepositoryPanel.RemotesNode.remove")) { //NOI18N + actions.add(new AbstractAction(NbBundle.getMessage(RepositoryBrowserPanel.class, "LBL_RepositoryPanel.RemoteNode.remove")) { //NOI18N @Override public void actionPerformed (ActionEvent e) { EventQueue.invokeLater(new Runnable() { From 206eb7420cd36f5204ef8cdc2db97c79d380aa75 Mon Sep 17 00:00:00 2001 From: Christian Lenz Date: Thu, 25 Sep 2025 18:33:36 +0200 Subject: [PATCH 3/4] Add suggested change to reuse UI from clone/fetch/push --- .../ui/repository/remote/AddRemoteConfig.java | 21 +++++++-- .../ui/repository/remote/AddRemotePanel.form | 45 ++++++++---------- .../ui/repository/remote/AddRemotePanel.java | 47 +++++++++---------- 3 files changed, 56 insertions(+), 57 deletions(-) diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteConfig.java b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteConfig.java index 6fba80fc3cc0..7a442d112dc4 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteConfig.java +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemoteConfig.java @@ -26,6 +26,7 @@ import org.netbeans.modules.git.client.GitClient; import org.netbeans.modules.git.client.GitClientExceptionHandler; import org.netbeans.modules.git.client.GitProgressSupport; +import org.netbeans.modules.git.utils.GitUtils; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; @@ -40,19 +41,29 @@ public class AddRemoteConfig { public void addRemote(File repository) { - AddRemotePanel panel = new AddRemotePanel(); - DialogDescriptor dd = new DialogDescriptor(panel, + RemoteRepository remoteRepository = new RemoteRepository(null); + AddRemotePanel arp = new AddRemotePanel(remoteRepository.getPanel()); + DialogDescriptor dd = new DialogDescriptor(arp, NbBundle.getMessage(RemoteRepositoryPanel.class, "LBL_AddRemoteConfig.title")); + + Runnable validityUpdate = () -> { + dd.setValid(!arp.getRemoteName().isBlank() && remoteRepository.isValid()); + }; + arp.txtRemoteName.addActionListener(actionEvent -> validityUpdate.run()); + remoteRepository.addChangeListener(changeEvent -> validityUpdate.run()); + if (DialogDisplayer.getDefault().notify(dd) != NotifyDescriptor.OK_OPTION) { return; } - final String remoteName = panel.getRemoteName(); - final String remoteUrl = panel.getRemoteURL(); + final String remoteName = arp.getRemoteName(); + final String remoteUrl = remoteRepository.getURI().toPrivateString(); if (remoteName.isEmpty() || remoteUrl.isEmpty()) { return; } + remoteRepository.store(); + GitProgressSupport supp = new GitProgressSupport() { @Override protected void perform() { @@ -62,7 +73,7 @@ protected void perform() { remoteName, Collections.singletonList(remoteUrl), Collections.emptyList(), - Collections.singletonList("+refs/heads/*:refs/remotes/" + remoteName + "/*"), + Collections.singletonList(GitUtils.getGlobalRefSpec(remoteName)), Collections.emptyList()); client.setRemote(cfg, getProgressMonitor()); } catch (GitException ex) { diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.form b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.form index 2db2f2c006ac..d4997b6ab21e 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.form +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.form @@ -31,41 +31,37 @@ - + - - - - - - - + + - - + + + + + + - + - - + + - - - - - - + + + @@ -76,14 +72,11 @@ - - - - - - - + + + + diff --git a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.java b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.java index 949880b8862f..44365386fe0f 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.java +++ b/ide/git/src/org/netbeans/modules/git/ui/repository/remote/AddRemotePanel.java @@ -18,24 +18,24 @@ */ package org.netbeans.modules.git.ui.repository.remote; +import java.util.Objects; + /** * * @author Christian Lenz */ public class AddRemotePanel extends javax.swing.JPanel { - public AddRemotePanel() { + public AddRemotePanel(javax.swing.JPanel urlPanel) { + Objects.requireNonNull(urlPanel); initComponents(); + panelUrlWrapper.add(urlPanel); } public String getRemoteName() { return txtRemoteName.getText().trim(); } - public String getRemoteURL() { - return txtRemoteURL.getText().trim(); - } - /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -46,48 +46,43 @@ public String getRemoteURL() { private void initComponents() { lblRemoteName = new javax.swing.JLabel(); - lblRemoteURL = new javax.swing.JLabel(); txtRemoteName = new javax.swing.JTextField(); - txtRemoteURL = new javax.swing.JTextField(); + panelUrlWrapper = new javax.swing.JPanel(); org.openide.awt.Mnemonics.setLocalizedText(lblRemoteName, "Remote Name:"); - org.openide.awt.Mnemonics.setLocalizedText(lblRemoteURL, "Remote URL:"); + panelUrlWrapper.setLayout(new java.awt.BorderLayout()); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(20, 20, 20) + .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lblRemoteName) - .addComponent(lblRemoteURL)) - .addGap(20, 20, 20) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(txtRemoteName, javax.swing.GroupLayout.DEFAULT_SIZE, 219, Short.MAX_VALUE) - .addComponent(txtRemoteURL)) - .addGap(20, 20, 20)) + .addComponent(panelUrlWrapper, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(lblRemoteName) + .addGap(12, 12, 12) + .addComponent(txtRemoteName, javax.swing.GroupLayout.DEFAULT_SIZE, 411, Short.MAX_VALUE))) + .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(20, 20, 20) + .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(lblRemoteName) .addComponent(txtRemoteName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(15, 15, 15) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lblRemoteURL) - .addComponent(txtRemoteURL, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(20, 20, 20)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelUrlWrapper, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JLabel lblRemoteName; - private javax.swing.JLabel lblRemoteURL; - private javax.swing.JTextField txtRemoteName; - private javax.swing.JTextField txtRemoteURL; + javax.swing.JLabel lblRemoteName; + javax.swing.JPanel panelUrlWrapper; + javax.swing.JTextField txtRemoteName; // End of variables declaration//GEN-END:variables } From 7320033f6561931730d96ace6359a88379ac38cb Mon Sep 17 00:00:00 2001 From: Christian Lenz Date: Wed, 15 Oct 2025 20:51:54 +0200 Subject: [PATCH 4/4] Add possibility to choose from more than one remote for pull and push --- .../modules/git/ui/fetch/PullAction.java | 115 ++++++++++++------ .../git/ui/fetch/PullBranchesStep.java | 19 ++- .../git/ui/fetch/PullFromUpstreamAction.java | 16 ++- .../modules/git/ui/fetch/PullWizard.java | 8 +- .../modules/git/ui/push/PushAction.java | 34 ++++-- .../modules/git/ui/push/PushBranchesStep.java | 17 +++ .../git/ui/push/PushToUpstreamAction.java | 24 ++-- .../modules/git/ui/push/PushWizard.java | 8 +- 8 files changed, 171 insertions(+), 70 deletions(-) diff --git a/ide/git/src/org/netbeans/modules/git/ui/fetch/PullAction.java b/ide/git/src/org/netbeans/modules/git/ui/fetch/PullAction.java index fa95939931cd..d560a7fe8942 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/fetch/PullAction.java +++ b/ide/git/src/org/netbeans/modules/git/ui/fetch/PullAction.java @@ -52,6 +52,7 @@ import org.netbeans.modules.git.ui.actions.ActionProgressSupport; import org.netbeans.modules.git.ui.actions.GitAction; import org.netbeans.modules.git.ui.actions.SingleRepositoryAction; +import org.netbeans.modules.git.ui.branch.SetTrackingAction; import org.netbeans.modules.git.ui.merge.MergeRevisionAction; import org.netbeans.modules.git.ui.output.OutputLogger; import org.netbeans.modules.git.ui.rebase.RebaseAction; @@ -65,6 +66,7 @@ import org.openide.awt.ActionRegistration; import org.openide.awt.Mnemonics; import org.openide.util.NbBundle; +import org.openide.util.actions.SystemAction; /** * @@ -107,6 +109,10 @@ public void run () { } private void pull (final File repository) { + pull(repository, null); + } + + public void pull (final File repository, final GitBranch branchToSelect) { RepositoryInfo info = RepositoryInfo.getInstance(repository); try { info.refreshRemotes(); @@ -117,7 +123,7 @@ private void pull (final File repository) { EventQueue.invokeLater(new Runnable() { @Override public void run () { - PullWizard wiz = new PullWizard(repository, remotes); + PullWizard wiz = new PullWizard(repository, remotes, branchToSelect); if (wiz.show()) { Utils.logVCSExternalRepository("GIT", wiz.getFetchUri()); //NOI18N pull(repository, wiz.getFetchUri(), wiz.getFetchRefSpecs(), wiz.getBranchToMerge(), wiz.getRemoteToPersist()); @@ -144,6 +150,7 @@ private static class GitProgressSupportImpl extends GitProgressSupport { private final String branchToMerge; private final String target; private final String remoteNameToUpdate; + private boolean pullSuccessful = false; public GitProgressSupportImpl (List fetchRefSpecs, String branchToMerge, String target, String remoteNameToUpdate) { this.fetchRefSpecs = fetchRefSpecs; @@ -182,48 +189,50 @@ protected void perform () { return; } } - GitUtils.runWithoutIndexing(new Callable() { - @Override - public Void call () throws Exception { - for (String branch : toDelete) { - client.deleteBranch(branch, true, getProgressMonitor()); - getLogger().outputLine(Bundle.MSG_PullAction_branchDeleted(branch)); - } - setDisplayName(Bundle.MSG_PullAction_fetching()); - Map fetchResult = FetchAction.fetchRepeatedly( - client, getProgressMonitor(), target, fetchRefSpecs); - if (isCanceled()) { - return null; + GitUtils.runWithoutIndexing(() -> { + for (String branch : toDelete) { + client.deleteBranch(branch, true, getProgressMonitor()); + getLogger().outputLine(Bundle.MSG_PullAction_branchDeleted(branch)); + } + setDisplayName(Bundle.MSG_PullAction_fetching()); + Map fetchResult = FetchAction.fetchRepeatedly( + client, getProgressMonitor(), target, fetchRefSpecs); + if (isCanceled()) { + return null; + } + FetchUtils.log(repository, fetchResult, getLogger()); + if (!isCanceled()) { + setDisplayName(Bundle.MSG_PullAction_progress_syncBranches()); + FetchUtils.syncTrackingBranches(repository, fetchResult, GitProgressSupportImpl.this, GitProgressSupportImpl.this.getProgress(), false); + } + if (isCanceled() || branchToMerge == null) { + return null; + } + new BranchSynchronizer(branchToMerge, repository, new BranchSynchronizer.GitProgressSupportDelegate() { + + @Override + public GitClient getClient () throws GitException { + return client; } - FetchUtils.log(repository, fetchResult, getLogger()); - if (!isCanceled()) { - setDisplayName(Bundle.MSG_PullAction_progress_syncBranches()); - FetchUtils.syncTrackingBranches(repository, fetchResult, GitProgressSupportImpl.this, GitProgressSupportImpl.this.getProgress(), false); + + @Override + public OutputLogger getLogger () { + return GitProgressSupportImpl.this.getLogger(); } - if (isCanceled() || branchToMerge == null) { - return null; + + @Override + public ProgressDelegate getProgress () { + return GitProgressSupportImpl.this.getProgress(); } - new BranchSynchronizer(branchToMerge, repository, new BranchSynchronizer.GitProgressSupportDelegate() { - - @Override - public GitClient getClient () throws GitException { - return client; - } - - @Override - public OutputLogger getLogger () { - return GitProgressSupportImpl.this.getLogger(); - } - - @Override - public ProgressDelegate getProgress () { - return GitProgressSupportImpl.this.getProgress(); - } - - }).execute(); - return null; + + }).execute(); + + if (!isCanceled()) { + pullSuccessful = true; } - }, repository); + + return null; + }, repository); } catch (GitException ex) { setError(true); GitClientExceptionHandler.notifyException(ex, true); @@ -231,6 +240,20 @@ public ProgressDelegate getProgress () { setDisplayName(NbBundle.getMessage(GitAction.class, "LBL_Progress.RefreshingStatuses")); //NOI18N Git.getInstance().getFileStatusCache().refreshAllRoots(Collections.>singletonMap(repository, Git.getInstance().getSeenRoots(repository))); GitUtils.headChanged(repository); + + // Check if tracking should be set up after successful pull + if (pullSuccessful && branchToMerge != null) { + RepositoryInfo info = RepositoryInfo.getInstance(repository); + info.refresh(); + GitBranch activeBranch = info.getActiveBranch(); + if (activeBranch != null && activeBranch.getTrackedBranch() == null) { + // branchToMerge is the remote branch name (e.g., "origin/master") + if (shallSetupTracking(activeBranch, branchToMerge)) { + SystemAction.get(SetTrackingAction.class).setupTrackedBranchImmediately( + repository, activeBranch.getName(), branchToMerge); + } + } + } } } } @@ -378,5 +401,17 @@ public ActionProgress call () throws GitException { } } } - + + @NbBundle.Messages({ + "LBL_Pull.setupTracking=Set Up Remote Tracking?", + "# {0} - remote branch name", "# {1} - branch name", "MSG_Pull.setupTracking=Pulled from \"{0}\".\n" + + "Do you want to set up branch \"{1}\" to track the remote branch?" + }) + private static boolean shallSetupTracking (GitBranch branch, String remoteBranchName) { + return NotifyDescriptor.YES_OPTION == DialogDisplayer.getDefault().notify(new NotifyDescriptor.Confirmation( + Bundle.MSG_Pull_setupTracking(remoteBranchName, branch.getName()), + Bundle.LBL_Pull_setupTracking(), + NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.QUESTION_MESSAGE)); + } + } diff --git a/ide/git/src/org/netbeans/modules/git/ui/fetch/PullBranchesStep.java b/ide/git/src/org/netbeans/modules/git/ui/fetch/PullBranchesStep.java index 4d3704b82141..f4d08dcdc218 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/fetch/PullBranchesStep.java +++ b/ide/git/src/org/netbeans/modules/git/ui/fetch/PullBranchesStep.java @@ -129,7 +129,11 @@ public void setRemote (GitRemoteConfig remote) { } public void fillRemoteBranches (final Map branches) { - fillRemoteBranches(Collections.emptyMap(), Collections.emptyMap()); + fillRemoteBranches(branches, null); + } + + public void fillRemoteBranches (final Map branches, final GitBranch branchToSelect) { + fillRemoteBranchesInternal(Collections.emptyMap(), Collections.emptyMap(), null); new GitProgressSupport.NoOutputLogging() { @Override protected void perform () { @@ -140,14 +144,14 @@ protected void perform () { EventQueue.invokeLater(new Runnable () { @Override public void run () { - fillRemoteBranches(branches, localBranches); + fillRemoteBranchesInternal(branches, localBranches, branchToSelect); } }); } }.start(Git.getInstance().getRequestProcessor(repository), repository, NbBundle.getMessage(PullBranchesStep.class, "MSG_FetchBranchesPanel.loadingLocalBranches")); //NOI18N } - private void fillRemoteBranches (Map branches, Map localBranches) { + private void fillRemoteBranchesInternal (Map branches, Map localBranches, GitBranch branchToSelect) { List l = new ArrayList(branches.size()); Set displayedBranches = new HashSet(localBranches.size()); for (GitBranch branch : localBranches.values()) { @@ -163,9 +167,14 @@ private void fillRemoteBranches (Map branches, Map 1) { + SystemAction.get(PullAction.class).pull(repository, activeBranch); + return; + } + if (trackedBranch == null) { return; } - GitRemoteConfig cfg = FetchUtils.getRemoteConfigForActiveBranch(trackedBranch, info, errorLabel); + + GitRemoteConfig cfg = FetchUtils.getRemoteConfigForActiveBranch(trackedBranch, info, errorLabel); if (cfg == null) { return; } diff --git a/ide/git/src/org/netbeans/modules/git/ui/fetch/PullWizard.java b/ide/git/src/org/netbeans/modules/git/ui/fetch/PullWizard.java index 572ba4eada1f..55c817b8ef9d 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/fetch/PullWizard.java +++ b/ide/git/src/org/netbeans/modules/git/ui/fetch/PullWizard.java @@ -46,10 +46,16 @@ class PullWizard implements ChangeListener { private PanelsIterator wizardIterator; private WizardDescriptor wizardDescriptor; private final File repository; + private final GitBranch branchToSelect; public PullWizard (File repository, Map remotes) { + this(repository, remotes, null); + } + + public PullWizard (File repository, Map remotes, GitBranch branchToSelect) { this.repository = repository; this.remotes = remotes; + this.branchToSelect = branchToSelect; } boolean show () { @@ -154,7 +160,7 @@ public synchronized void nextPanel () { Map remoteBranches = selectUriStep.getRemoteBranches(); pullBranchesStep.setRemote(remote); if (remoteBranches != null) { - pullBranchesStep.fillRemoteBranches(remoteBranches); + pullBranchesStep.fillRemoteBranches(remoteBranches, branchToSelect); } selectUriStep.storeURI(); } diff --git a/ide/git/src/org/netbeans/modules/git/ui/push/PushAction.java b/ide/git/src/org/netbeans/modules/git/ui/push/PushAction.java index 2418fec9e673..f2296a972e94 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/push/PushAction.java +++ b/ide/git/src/org/netbeans/modules/git/ui/push/PushAction.java @@ -121,12 +121,13 @@ public void push (final File repository, GitRemoteConfig remote, Collection { + push(repository, branchToSelect); }); return; } @@ -140,7 +141,7 @@ public void run () { EventQueue.invokeLater(new Runnable() { @Override public void run () { - PushWizard wiz = new PushWizard(repository, remotes); + PushWizard wiz = new PushWizard(repository, remotes, branchToSelect); if (wiz.show()) { Utils.logVCSExternalRepository("GIT", wiz.getPushUri()); //NOI18N push(repository, wiz.getPushUri(), wiz.getPushMappings(), wiz.getFetchRefSpecs(), wiz.getRemoteName()); @@ -325,12 +326,7 @@ protected void logUpdates (File repository, Map upda logger.outputLine(Bundle.MSG_PushAction_updates_addBranch(update.getLocalName(), update.getNewObjectId(), update.getResult())); if (!remote && update.getNewObjectId() != null) { - int pos = update.getLocalName().indexOf('/'); - if (pos >= 0 && update.getLocalName().substring(pos + 1).equals(currBranch.getName())) { - if (shallSetupTracking(currBranch, update.getLocalName())) { - SystemAction.get(SetTrackingAction.class).setupTrackedBranchImmediately(repository, currBranch.getName(), update.getLocalName()); - } - } + callSetTrackingAction(update, currBranch, repository); } } else { logger.outputLine(Bundle.MSG_PushAction_updates_updateBranch(update.getRemoteName(), @@ -344,6 +340,11 @@ protected void logUpdates (File repository, Map upda update.getOldObjectId(), update.getNewObjectId(), logger); } } + + // Check if tracking should be set up for updated branches without tracking + if (!remote && update.getNewObjectId() != null && currBranch.getTrackedBranch() == null) { + callSetTrackingAction(update, currBranch, repository); + } } } else { //tag deleting or updating @@ -364,6 +365,15 @@ protected void logUpdates (File repository, Map upda } } } + + private void callSetTrackingAction(GitTransportUpdate update, GitBranch currBranch, File repository1) { + int pos = update.getLocalName().indexOf('/'); + if (pos >= 0 && update.getLocalName().substring(pos + 1).equals(currBranch.getName())) { + if (shallSetupTracking(currBranch, update.getLocalName())) { + SystemAction.get(SetTrackingAction.class).setupTrackedBranchImmediately(repository1, currBranch.getName(), update.getLocalName()); + } + } + } private void logTrackingUpdate (GitBranch b) { if (b != null && b.getTrackedBranch() != null) { diff --git a/ide/git/src/org/netbeans/modules/git/ui/push/PushBranchesStep.java b/ide/git/src/org/netbeans/modules/git/ui/push/PushBranchesStep.java index 4925ef2ac24a..c540327522b8 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/push/PushBranchesStep.java +++ b/ide/git/src/org/netbeans/modules/git/ui/push/PushBranchesStep.java @@ -106,6 +106,18 @@ public HelpCtx getHelp() { */ public void fillRemoteBranches (final GitRemoteConfig cfg, final Map branches, final Map tags) { + fillRemoteBranches(cfg, branches, tags, null); + } + + /** + * + * @param cfg configuration of the remote repository including URLs of remote + * @param branches list of all branches in the remote repo + * @param tags list of all tags in the remote repo + * @param branchToSelect branch that should be preselected in the list + */ + public void fillRemoteBranches (final GitRemoteConfig cfg, final Map branches, + final Map tags, final GitBranch branchToSelect) { fillLocalObjects(Collections.emptyList()); new GitProgressSupport.NoOutputLogging() { @Override @@ -162,6 +174,11 @@ protected void perform () { } boolean preselected = !conflicted && updateNeeded; + // If a specific branch should be selected, override preselection + if (branchToSelect != null && branch.getName().equals(branchToSelect.getName())) { + preselected = true; + } + //add current branch in the list for update or for adding l.add(new PushMapping.PushBranchMapping(remoteBranch == null ? null : remoteBranch.getName(), remoteBranch == null ? null : remoteBranch.getId(), diff --git a/ide/git/src/org/netbeans/modules/git/ui/push/PushToUpstreamAction.java b/ide/git/src/org/netbeans/modules/git/ui/push/PushToUpstreamAction.java index aeaf42541f0c..1af09292d310 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/push/PushToUpstreamAction.java +++ b/ide/git/src/org/netbeans/modules/git/ui/push/PushToUpstreamAction.java @@ -47,7 +47,6 @@ import org.netbeans.modules.git.utils.GitUtils; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; -import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.openide.util.RequestProcessor; import org.openide.util.RequestProcessor.Task; @@ -100,7 +99,13 @@ protected void perform () { } RepositoryInfo.PushMode pushMode = info.getPushMode(); GitBranch trackedBranch = getTrackedBranch(activeBranch, pushMode, errorLabel); - GitRemoteConfig cfg = getRemoteConfigForActiveBranch(trackedBranch, info, errorLabel); + + if (trackedBranch == null && info.getRemotes().size() > 1) { + SystemAction.get(PushAction.class).push(repository, activeBranch); + return; + } + + GitRemoteConfig cfg = getRemoteConfigForActiveBranch(trackedBranch, info, errorLabel); if (cfg == null) { return; } @@ -128,12 +133,14 @@ protected void perform () { GitBranch remoteBranch = getClient() .listRemoteBranches(uri, getProgressMonitor()) .get(remoteBranchName); - GitRevisionInfo rev = getClient().getCommonAncestor(new String[]{activeBranch.getId(), remoteBranch.getId()}, getProgressMonitor()); - // conflict if - // A) rev == null : completely unrelated commits - // B) ancestor is neither remote branch (opposite means EQUAL or PUSH needed but not CONFLICT) - // nor local head (opposite means EQUAL or pull needed but not CONFLICT) - conflicted = rev == null || (!remoteBranch.getId().equals(rev.getRevision()) && !activeBranch.getId().equals(rev.getRevision())); + if (remoteBranch != null) { + GitRevisionInfo rev = getClient().getCommonAncestor(new String[]{activeBranch.getId(), remoteBranch.getId()}, getProgressMonitor()); + // conflict if + // A) rev == null : completely unrelated commits + // B) ancestor is neither remote branch (opposite means EQUAL or PUSH needed but not CONFLICT) + // nor local head (opposite means EQUAL or pull needed but not CONFLICT) + conflicted = rev == null || (!remoteBranch.getId().equals(rev.getRevision()) && !activeBranch.getId().equals(rev.getRevision())); + } } catch (GitException ex) { Logger.getLogger(PushBranchesStep.class.getName()).log(Level.INFO, activeBranch.getId() + ", " + remoteBranchName, ex); //NOI18N } @@ -193,7 +200,6 @@ protected static GitRemoteConfig getRemoteConfigForActiveBranch (GitBranch track GitUtils.notifyError(errorLabel, MSG_Err_noRemote()); return null; } else { - GitUtils.notifyError(errorLabel, MSG_Err_moreRemotes(remotes.size())); return null; } } else { diff --git a/ide/git/src/org/netbeans/modules/git/ui/push/PushWizard.java b/ide/git/src/org/netbeans/modules/git/ui/push/PushWizard.java index 761310e2dbc5..ee5708090639 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/push/PushWizard.java +++ b/ide/git/src/org/netbeans/modules/git/ui/push/PushWizard.java @@ -50,10 +50,16 @@ class PushWizard implements ChangeListener { private PanelsIterator wizardIterator; private WizardDescriptor wizardDescriptor; private final File repository; + private final GitBranch branchToSelect; public PushWizard (File repository, Map remotes) { + this(repository, remotes, null); + } + + public PushWizard (File repository, Map remotes, GitBranch branchToSelect) { this.repository = repository; this.remotes = remotes; + this.branchToSelect = branchToSelect; } boolean show () { @@ -161,7 +167,7 @@ public synchronized void nextPanel () { Map remoteTags = selectUriStep.getRemoteTags(); if (remoteBranches != null) { pushBranchesStep.fillRemoteBranches(selectUriStep.getSelectedRemote(), - remoteBranches, remoteTags == null ? Collections.emptyMap() : remoteTags); + remoteBranches, remoteTags == null ? Collections.emptyMap() : remoteTags, branchToSelect); } pushBranchesStep.setAsLastPanel(!selectUriStep.isConfiguredRemoteSelected() && selectUriStep.getRemoteName() == null); selectUriStep.storeURI();