--- git/nbproject/project.xml +++ git/nbproject/project.xml @@ -25,7 +25,7 @@ 1 - 1.25 + 1.26 --- git/src/org/netbeans/modules/git/Git.java +++ git/src/org/netbeans/modules/git/Git.java @@ -59,6 +59,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.netbeans.libs.git.GitException; +import org.netbeans.libs.git.GitRepository; import org.netbeans.modules.git.client.CredentialsCallback; import org.netbeans.modules.git.client.GitClient; import org.netbeans.modules.git.ui.shelve.ShelveChangesAction; @@ -212,15 +213,14 @@ } public GitClient getClient (File repository, GitProgressSupport progressSupport, boolean handleAuthenticationIssues) throws GitException { - // get the only instance for the repository folder, so we can synchronize on it - File repositoryFolder = getRepositoryRoot(repository); - if (repositoryFolder != null && repository.equals(repositoryFolder)) { - repository = repositoryFolder; - } - GitClient client = new GitClient(repository, progressSupport, handleAuthenticationIssues); + GitClient client = new GitClient(singleInstanceRepositoryRoot(repository), progressSupport, handleAuthenticationIssues); client.setCallback(new CredentialsCallback()); return client; } + + public GitRepository getRepository (File repository) throws GitException { + return GitRepository.getInstance(singleInstanceRepositoryRoot(repository)); + } public RequestProcessor getRequestProcessor() { return getRequestProcessor(null); @@ -361,6 +361,15 @@ return topmost; } + + private File singleInstanceRepositoryRoot (File repository) { + // get the only instance for the repository folder, so we can synchronize on it + File repositoryFolder = getRepositoryRoot(repository); + if (repositoryFolder != null && repository.equals(repositoryFolder)) { + repository = repositoryFolder; + } + return repository; + } private File getKnownParent(File file) { File[] roots = knownRoots.toArray(new File[knownRoots.size()]); --- git/src/org/netbeans/modules/git/client/GitClient.java +++ git/src/org/netbeans/modules/git/client/GitClient.java @@ -67,6 +67,7 @@ import org.netbeans.libs.git.GitRebaseResult; import org.netbeans.libs.git.GitRefUpdateResult; import org.netbeans.libs.git.GitRemoteConfig; +import org.netbeans.libs.git.GitRepository.FastForwardOption; import org.netbeans.libs.git.GitRepositoryState; import org.netbeans.libs.git.GitRevertResult; import org.netbeans.libs.git.GitRevisionInfo; @@ -608,12 +609,12 @@ }, "log"); //NOI18N } - public GitMergeResult merge (final String revision, final ProgressMonitor monitor) throws GitException.CheckoutConflictException, GitException { + public GitMergeResult merge (final String revision, final FastForwardOption ffOption, final ProgressMonitor monitor) throws GitException.CheckoutConflictException, GitException { return new CommandInvoker().runMethod(new Callable() { @Override public GitMergeResult call () throws Exception { - return delegate.merge(revision, monitor); + return delegate.merge(revision, ffOption, monitor); } }, "merge"); //NOI18N } --- git/src/org/netbeans/modules/git/ui/fetch/PullAction.java +++ git/src/org/netbeans/modules/git/ui/fetch/PullAction.java @@ -63,6 +63,7 @@ import org.netbeans.libs.git.GitMergeResult; import org.netbeans.libs.git.GitRebaseResult; import org.netbeans.libs.git.GitRemoteConfig; +import org.netbeans.libs.git.GitRepository; import org.netbeans.libs.git.GitRevisionInfo; import org.netbeans.libs.git.GitTransportUpdate; import org.netbeans.modules.git.Git; @@ -315,15 +316,16 @@ @Override public ActionProgress call () throws GitException { - boolean cont; GitClient client = getClient(); File repository = getRepositoryRoot(); setDisplayName(Bundle.MSG_PullAction_merging()); + MergeRevisionAction.MergeContext ctx = new MergeRevisionAction.MergeContext(branchToMerge, null); + MergeRevisionAction.MergeResultProcessor mrp = new MergeRevisionAction.MergeResultProcessor(client, repository, ctx, getLogger(), getProgressMonitor()); do { - MergeRevisionAction.MergeResultProcessor mrp = new MergeRevisionAction.MergeResultProcessor(client, repository, branchToMerge, getLogger(), getProgressMonitor()); - cont = false; + ctx.setContinue(false); + GitRepository.FastForwardOption ffOption = null; try { - GitMergeResult result = client.merge(branchToMerge, getProgressMonitor()); + GitMergeResult result = client.merge(branchToMerge, ffOption, getProgressMonitor()); mrp.processResult(result); if (result.getMergeStatus() == GitMergeResult.MergeStatus.ALREADY_UP_TO_DATE || result.getMergeStatus() == GitMergeResult.MergeStatus.FAST_FORWARD @@ -334,9 +336,9 @@ if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Local modifications in WT during merge: {0} - {1}", new Object[] { repository, Arrays.asList(ex.getConflicts()) }); //NOI18N } - cont = mrp.resolveLocalChanges(ex.getConflicts()); + ctx.setContinue(mrp.resolveLocalChanges(ex.getConflicts())); } - } while (cont && !isCanceled()); + } while (ctx.isContinue() && !isCanceled()); return new ActionProgress.ActionResult(isCanceled(), true); } --- git/src/org/netbeans/modules/git/ui/merge/Bundle.properties +++ git/src/org/netbeans/modules/git/ui/merge/Bundle.properties @@ -47,7 +47,15 @@ MSG_MergeRevisionAction.result.conflict=Merge of HEAD with {0} produced conflicts in:\n MSG_MergeRevisionAction.result.failed=Merge of HEAD with {0} failed. For more information see the output. MSG_MergeRevisionAction.result.failedFiles=Merge of HEAD with {0} failed because of these files:\n +MSG_MergeRevisionAction.result.aborted=Merge of HEAD with {0} failed because it requires a merge commit and only fast-forward merges were allowed. MSG_MergeRevisionAction.result.unsupported=Merge unsupported. LBL_MergeRevision.OKButton.text=Mer&ge LBL_MergeRevision.title=Merge Revision MergeRevisionPanel.jLabel1.text=Select a revision to merge into HEAD +MergeRevision.ffoption.ff=Fast-forward if possible (--ff) +MergeRevision.ffoption.ff.tt=Where possible merge will not create new commit but will move the branch to the merged commit. +MergeRevision.ffoption.ffonly=Fast-forward only (--ff-only) +MergeRevision.ffoption.ffonly.tt=Merge will fail if the merge commit is required (both branches have their own commits) +MergeRevision.ffoption.noff=Always create commit (--no-ff) +MergeRevision.ffoption.noff.tt=Merge will always create a merge commit, useful for merging feature branches. +MergeRevisionPanel.ffModePanel.text=Select Fast-Forward mode --- git/src/org/netbeans/modules/git/ui/merge/MergeRevision.java +++ git/src/org/netbeans/modules/git/ui/merge/MergeRevision.java @@ -47,6 +47,7 @@ import java.beans.PropertyChangeListener; import java.io.File; import javax.swing.JButton; +import org.netbeans.libs.git.GitRepository.FastForwardOption; import org.netbeans.modules.git.ui.repository.RevisionDialogController; import org.netbeans.modules.git.utils.GitUtils; import org.openide.DialogDescriptor; @@ -59,16 +60,19 @@ * @author ondra */ public class MergeRevision { - private MergeRevisionPanel panel; - private RevisionDialogController revisionPicker; + private final MergeRevisionPanel panel; + private final RevisionDialogController revisionPicker; private JButton okButton; private DialogDescriptor dd; private boolean valid = true; + private final FastForwardOption ffOption; - MergeRevision (File repository, File[] roots, String initialRevision) { + MergeRevision (File repository, File[] roots, String initialRevision, FastForwardOption defaultFFOption) { + ffOption = defaultFFOption; revisionPicker = new RevisionDialogController(repository, roots, initialRevision); revisionPicker.setMergingInto(GitUtils.HEAD); panel = new MergeRevisionPanel(revisionPicker.getPanel()); + initFFOptions(); } String getRevision() { @@ -79,7 +83,8 @@ okButton = new JButton(NbBundle.getMessage(MergeRevision.class, "LBL_MergeRevision.OKButton.text")); //NOI18N org.openide.awt.Mnemonics.setLocalizedText(okButton, okButton.getText()); dd = new DialogDescriptor(panel, NbBundle.getMessage(MergeRevision.class, "LBL_MergeRevision.title"), true, //NOI18N - new Object[] { okButton, DialogDescriptor.CANCEL_OPTION }, okButton, DialogDescriptor.DEFAULT_ALIGN, new HelpCtx(MergeRevision.class), null); + new Object[] { okButton, DialogDescriptor.CANCEL_OPTION }, okButton, DialogDescriptor.DEFAULT_ALIGN, + new HelpCtx("org.netbeans.modules.git.ui.merge.MergeRevision"), null); //NOI18N enableRevisionPanel(); revisionPicker.addPropertyChangeListener(new PropertyChangeListener() { @Override @@ -94,6 +99,16 @@ return okButton == dd.getValue(); } + FastForwardOption getFFOption () { + if (panel.rbFFOptionOnly.isSelected()) { + return FastForwardOption.FAST_FORWARD_ONLY; + } else if (panel.rbFFOptionNever.isSelected()) { + return FastForwardOption.NO_FAST_FORWARD; + } else { + return FastForwardOption.FAST_FORWARD; + } + } + private void enableRevisionPanel () { setValid(valid); } @@ -103,4 +118,18 @@ okButton.setEnabled(flag); dd.setValid(flag); } + + private void initFFOptions () { + switch (ffOption) { + case FAST_FORWARD: + panel.rbFFOption.setSelected(true); + break; + case FAST_FORWARD_ONLY: + panel.rbFFOptionOnly.setSelected(true); + break; + case NO_FAST_FORWARD: + panel.rbFFOptionNever.setSelected(true); + break; + } + } } --- git/src/org/netbeans/modules/git/ui/merge/MergeRevisionAction.java +++ git/src/org/netbeans/modules/git/ui/merge/MergeRevisionAction.java @@ -58,6 +58,7 @@ import org.netbeans.modules.git.client.GitClient; import org.netbeans.libs.git.GitException; import org.netbeans.libs.git.GitMergeResult; +import org.netbeans.libs.git.GitRepository.FastForwardOption; import org.netbeans.libs.git.GitRevisionInfo; import org.netbeans.libs.git.progress.ProgressMonitor; import org.netbeans.modules.git.Git; @@ -94,7 +95,13 @@ } public void mergeRevision (final File repository, String preselectedRevision) { - final MergeRevision mergeRevision = new MergeRevision(repository, new File[0], preselectedRevision); + FastForwardOption defaultFFOption = FastForwardOption.FAST_FORWARD; + try { + defaultFFOption = Git.getInstance().getRepository(repository).getDefaultFastForwardOption(); + } catch (GitException ex) { + LOG.log(Level.INFO, null, ex); + } + final MergeRevision mergeRevision = new MergeRevision(repository, new File[0], preselectedRevision, defaultFFOption); if (mergeRevision.show()) { GitProgressSupport supp = new GitProgressSupport() { private String revision; @@ -109,20 +116,20 @@ client.addNotificationListener(new DefaultFileListener(new File[] { repository })); revision = mergeRevision.getRevision(); LOG.log(Level.FINE, "Merging revision {0} into HEAD", revision); //NOI18N - boolean cont; - MergeResultProcessor mrp = new MergeResultProcessor(client, repository, revision, getLogger(), getProgressMonitor()); + MergeContext ctx = new MergeContext(revision, mergeRevision.getFFOption()); + MergeResultProcessor mrp = new MergeResultProcessor(client, repository, ctx, getLogger(), getProgressMonitor()); do { - cont = false; + ctx.setContinue(false); try { - GitMergeResult result = client.merge(revision, getProgressMonitor()); + GitMergeResult result = client.merge(revision, ctx.getFFOption(), getProgressMonitor()); mrp.processResult(result); } catch (GitException.CheckoutConflictException ex) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Local modifications in WT during merge: {0} - {1}", new Object[] { repository, Arrays.asList(ex.getConflicts()) }); //NOI18N } - cont = mrp.resolveLocalChanges(ex.getConflicts()); + ctx.setContinue(mrp.resolveLocalChanges(ex.getConflicts())); } - } while (cont); + } while (ctx.isContinue() && !isCanceled()); return null; } }, repository); @@ -139,20 +146,26 @@ } } + @NbBundle.Messages({ + "LBL_Merge.failed.title=Cannot Merge", + "MSG_Merge.failed.aborted.text=Merge requires a merge commit and cannot be a fast-forward merge.\n\n" + + "Do you want to restart the merge and allow merge commits (--ff option)." + }) public static class MergeResultProcessor extends ResultProcessor { private final OutputLogger logger; - private final String revision; private final GitBranch current; + private final MergeContext context; - public MergeResultProcessor (GitClient client, File repository, String revision, OutputLogger logger, ProgressMonitor pm) { - super(client, repository, revision, pm); + public MergeResultProcessor (GitClient client, File repository, MergeContext context, OutputLogger logger, ProgressMonitor pm) { + super(client, repository, context.getRevision(), pm); + this.context = context; this.current = RepositoryInfo.getInstance(repository).getActiveBranch(); - this.revision = revision; this.logger = logger; } public void processResult (GitMergeResult result) { + String revision = context.getRevision(); StringBuilder sb = new StringBuilder(NbBundle.getMessage(MergeRevisionAction.class, "MSG_MergeRevisionAction.result", result.getMergeStatus().toString())); //NOI18N GitRevisionInfo info = null; if (result.getNewHead() != null) { @@ -163,6 +176,7 @@ } } boolean logActions = false; + final Action openAction = logger.getOpenOutputAction(); switch (result.getMergeStatus()) { case ALREADY_UP_TO_DATE: sb.append(NbBundle.getMessage(MergeRevisionAction.class, "MSG_MergeRevisionAction.result.alreadyUpToDate", revision)); //NOI18N @@ -183,7 +197,6 @@ resolveConflicts(result.getConflicts()); break; case FAILED: - final Action openAction = logger.getOpenOutputAction(); if (openAction != null) { try { EventQueue.invokeAndWait(new Runnable() { @@ -201,6 +214,29 @@ DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message( NbBundle.getMessage(MergeRevisionAction.class, "MSG_MergeRevisionAction.result.failed", revision), NotifyDescriptor.ERROR_MESSAGE)); //NOI18N break; + case ABORTED: + Object o = DialogDisplayer.getDefault().notify(new NotifyDescriptor(Bundle.MSG_Merge_failed_aborted_text(), + Bundle.LBL_Merge_failed_title(), NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.QUESTION_MESSAGE, + new Object[] { NotifyDescriptor.YES_OPTION, NotifyDescriptor.NO_OPTION }, + NotifyDescriptor.NO_OPTION)); + if (o == NotifyDescriptor.YES_OPTION) { + context.setFFOption(FastForwardOption.FAST_FORWARD); + context.setContinue(true); + } else { + if (openAction != null) { + try { + EventQueue.invokeAndWait(new Runnable() { + @Override + public void run () { + openAction.actionPerformed(new ActionEvent(MergeResultProcessor.this, ActionEvent.ACTION_PERFORMED, null)); + } + }); + } catch (InterruptedException | InvocationTargetException ex) { + } + } + } + sb.append(NbBundle.getMessage(MergeRevisionAction.class, "MSG_MergeRevisionAction.result.aborted", revision)); //NOI18N + break; case NOT_SUPPORTED: sb.append(NbBundle.getMessage(MergeRevisionAction.class, "MSG_MergeRevisionAction.result.unsupported")); //NOI18N DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message( @@ -216,4 +252,36 @@ } } } + + public static class MergeContext { + private FastForwardOption ffOption; + private final String revision; + private boolean cont; + + public MergeContext (String revision, FastForwardOption ffOption) { + this.revision = revision; + this.ffOption = ffOption; + } + + public String getRevision () { + return revision; + } + + public FastForwardOption getFFOption () { + return ffOption; + } + + private void setFFOption (FastForwardOption ffOption) { + this.ffOption = ffOption; + } + + public void setContinue (boolean cont) { + this.cont = cont; + } + + public boolean isContinue () { + return cont; + } + + } } --- git/src/org/netbeans/modules/git/ui/merge/MergeRevisionPanel.form +++ git/src/org/netbeans/modules/git/ui/merge/MergeRevisionPanel.form @@ -1,11 +1,15 @@ - +
+ + + + - + @@ -16,12 +20,18 @@ + - - + + + + + + + + - @@ -30,7 +40,10 @@ - + + + + @@ -50,5 +63,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --- git/src/org/netbeans/modules/git/ui/merge/MergeRevisionPanel.java +++ git/src/org/netbeans/modules/git/ui/merge/MergeRevisionPanel.java @@ -72,20 +72,61 @@ // //GEN-BEGIN:initComponents private void initComponents() { + rbFFOptions = new javax.swing.ButtonGroup(); org.netbeans.modules.git.ui.repository.RevisionDialog revisionPickerDialog1 = this.revisionPanel; jLabel1 = new javax.swing.JLabel(); + jPanel1 = new javax.swing.JPanel(); - jLabel1.setText(org.openide.util.NbBundle.getMessage(MergeRevisionPanel.class, "MergeRevisionPanel.jLabel1.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(MergeRevisionPanel.class, "MergeRevisionPanel.jLabel1.text")); // NOI18N + jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(MergeRevisionPanel.class, "MergeRevisionPanel.ffModePanel.text"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(rbFFOption, org.openide.util.NbBundle.getMessage(MergeRevisionPanel.class, "MergeRevision.ffoption.ff")); // NOI18N + rbFFOption.setToolTipText(org.openide.util.NbBundle.getMessage(MergeRevisionPanel.class, "MergeRevision.ffoption.ff.tt")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(rbFFOptionNever, org.openide.util.NbBundle.getMessage(MergeRevisionPanel.class, "MergeRevision.ffoption.noff")); // NOI18N + rbFFOptionNever.setToolTipText(org.openide.util.NbBundle.getMessage(MergeRevisionPanel.class, "MergeRevision.ffoption.noff.tt")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(rbFFOptionOnly, org.openide.util.NbBundle.getMessage(MergeRevisionPanel.class, "MergeRevision.ffoption.ffonly")); // NOI18N + rbFFOptionOnly.setToolTipText(org.openide.util.NbBundle.getMessage(MergeRevisionPanel.class, "MergeRevision.ffoption.ffonly.tt")); // NOI18N + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(rbFFOptionNever) + .addComponent(rbFFOptionOnly) + .addComponent(rbFFOption)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(rbFFOption) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(rbFFOptionOnly) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(rbFFOptionNever) + .addContainerGap()) + ); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(revisionPickerDialog1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addContainerGap() - .addComponent(jLabel1) - .addContainerGap(106, Short.MAX_VALUE)) - .addComponent(revisionPickerDialog1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -93,13 +134,21 @@ .addContainerGap() .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(revisionPickerDialog1, javax.swing.GroupLayout.DEFAULT_SIZE, 186, Short.MAX_VALUE)) + .addComponent(revisionPickerDialog1, javax.swing.GroupLayout.DEFAULT_SIZE, 187, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel jLabel1; + private javax.swing.JPanel jPanel1; + final javax.swing.JRadioButton rbFFOption = new javax.swing.JRadioButton(); + final javax.swing.JRadioButton rbFFOptionNever = new javax.swing.JRadioButton(); + final javax.swing.JRadioButton rbFFOptionOnly = new javax.swing.JRadioButton(); + private javax.swing.ButtonGroup rbFFOptions; // End of variables declaration//GEN-END:variables }