# HG changeset patch # Parent aaa074f13bd1fc27c30d1798619e5912611d8c51 # User Ondrej Vrabec #235267 - [git] [Show history] "Cherry pick this revision" action is missing add cherry-pick action into menus and search history window diff -r aaa074f13bd1 git/nbproject/project.xml --- a/git/nbproject/project.xml Tue Jul 01 16:57:50 2014 +0200 +++ b/git/nbproject/project.xml Tue Jul 01 17:18:16 2014 +0200 @@ -25,7 +25,7 @@ 1 - 1.26 + 1.27 diff -r aaa074f13bd1 git/src/org/netbeans/modules/git/client/GitClient.java --- a/git/src/org/netbeans/modules/git/client/GitClient.java Tue Jul 01 16:57:50 2014 +0200 +++ b/git/src/org/netbeans/modules/git/client/GitClient.java Tue Jul 01 17:18:16 2014 +0200 @@ -55,6 +55,8 @@ import java.util.logging.Logger; import org.netbeans.libs.git.GitBlameResult; import org.netbeans.libs.git.GitBranch; +import org.netbeans.libs.git.GitCherryPickResult; +import org.netbeans.libs.git.GitClient.CherryPickOperation; import org.netbeans.libs.git.GitClient.DiffMode; import org.netbeans.libs.git.GitClient.RebaseOperationType; import org.netbeans.libs.git.GitClient.ResetType; @@ -172,6 +174,7 @@ private static final HashSet NEED_REPOSITORY_REFRESH_COMMANDS = new HashSet(Arrays.asList("add",//NOI18N // may change state, e.g. MERGING->MERGED "checkout", //NOI18N "checkoutRevision", //NOI18N // current head changes + "cherryPick", //NOI18N "commit", //NOI18N "createBranch", //NOI18N // should refresh set of known branches "createTag", //NOI18N - should refresh set of available tags @@ -279,6 +282,16 @@ }, "checkoutRevision", new File[] { repositoryRoot }); //NOI18N } + public GitCherryPickResult cherryPick (final CherryPickOperation op, final String[] revisions, final ProgressMonitor monitor) throws GitException.MissingObjectException, GitException { + return new CommandInvoker().runMethod(new Callable() { + + @Override + public GitCherryPickResult call () throws Exception { + return delegate.cherryPick(op, revisions, monitor); + } + }, "cherryPick", new File[] { repositoryRoot }); //NOI18N + } + public void clean(final File[] roots, final ProgressMonitor monitor) throws GitException { new CommandInvoker().runMethod(new Callable() { diff -r aaa074f13bd1 git/src/org/netbeans/modules/git/ui/branch/Bundle.properties --- a/git/src/org/netbeans/modules/git/ui/branch/Bundle.properties Tue Jul 01 16:57:50 2014 +0200 +++ b/git/src/org/netbeans/modules/git/ui/branch/Bundle.properties Tue Jul 01 17:18:16 2014 +0200 @@ -62,3 +62,4 @@ LBL_SetTrackingAction_PopupName=Set Tracked Branch... SelectTrackedBranchPanel.errorLabel.text=jLabel1 +CherryPickPanel.jLabel1.text=Select revision to apply (cherry-pick) into HEAD diff -r aaa074f13bd1 git/src/org/netbeans/modules/git/ui/branch/CherryPick.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/git/src/org/netbeans/modules/git/ui/branch/CherryPick.java Tue Jul 01 17:18:16 2014 +0200 @@ -0,0 +1,188 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.git.ui.branch; + +import java.awt.Dialog; +import java.awt.EventQueue; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JButton; +import org.netbeans.libs.git.GitBranch; +import org.netbeans.libs.git.GitException; +import org.netbeans.libs.git.GitRevisionInfo; +import org.netbeans.libs.git.progress.ProgressMonitor; +import org.netbeans.modules.git.Git; +import org.netbeans.modules.git.client.GitClient; +import org.netbeans.modules.git.ui.repository.RevisionDialogController; +import org.netbeans.modules.git.utils.GitUtils; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import static org.netbeans.modules.git.ui.branch.Bundle.*; +import org.netbeans.modules.git.ui.repository.RepositoryInfo; +import org.netbeans.modules.versioning.util.Utils; +import org.openide.util.Cancellable; +import org.openide.util.RequestProcessor; + +/** + * + * @author ondra + */ +public class CherryPick { + private final CherryPickPanel panel; + private final RevisionDialogController revisionPicker; + private JButton okButton; + private DialogDescriptor dd; + private boolean valid; + private final File repository; + private final RequestProcessor.Task mergedIntoTask; + private String revision; + + CherryPick (File repository, String initialRevision) { + this.repository = repository; + revisionPicker = new RevisionDialogController(repository, new File[] { repository }, initialRevision); + revisionPicker.setMergingInto(GitUtils.HEAD); + panel = new CherryPickPanel(revisionPicker.getPanel()); + mergedIntoTask = Utils.createTask(new MergedIntoTask()); + } + + String getRevision() { + return revisionPicker.getRevision().getCommitId(); + } + + @NbBundle.Messages({ + "LBL_CherryPick.OKButton.text=&Apply", + "# {0} - repository name", "LBL_CherryPick.title=Cherry Pick - {0}", + }) + boolean showDialog () { + okButton = new JButton(LBL_CherryPick_OKButton_text()); + org.openide.awt.Mnemonics.setLocalizedText(okButton, okButton.getText()); + dd = new DialogDescriptor(panel, Bundle.LBL_CherryPick_title(repository), true, + new Object[] { okButton, DialogDescriptor.CANCEL_OPTION }, okButton, DialogDescriptor.DEFAULT_ALIGN, + new HelpCtx("org.netbeans.modules.git.ui.branch.CherryPick"), null); //NOI18N + enableRevisionPanel(); + revisionPicker.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange (PropertyChangeEvent evt) { + mergedIntoTask.cancel(); + if (evt.getPropertyName() == RevisionDialogController.PROP_VALID) { + boolean v = Boolean.TRUE.equals(evt.getNewValue()); + setValid(v); + if (v) { + revision = getRevision(); + mergedIntoTask.schedule(500); + } + } + } + }); + Dialog d = DialogDisplayer.getDefault().createDialog(dd); + d.setVisible(true); + return okButton == dd.getValue(); + } + + private void enableRevisionPanel () { + setValid(valid); + } + + private void setValid (boolean flag) { + this.valid = flag; + okButton.setEnabled(flag); + dd.setValid(flag); + panel.lblError.setVisible(false); + } + + @NbBundle.Messages({ + "# {0} - branch name", "CherryPickPanel.info.merged=Already part of \"{0}\"!" + }) + private class MergedIntoTask implements Runnable, Cancellable { + + private ProgressMonitor.DefaultProgressMonitor pm; + + @Override + public void run () { + pm = new ProgressMonitor.DefaultProgressMonitor(); + final GitBranch activeBranch = RepositoryInfo.getInstance(repository).getActiveBranch(); + boolean mergedInto = false; + final String rev = revision; + if (activeBranch.getId().equals(rev)) { + mergedInto = true; + } else { + GitClient client = null; + try { + client = Git.getInstance().getClient(repository); + GitRevisionInfo ancestor = client.getCommonAncestor(new String[] { revision, GitUtils.HEAD }, pm); + if (ancestor != null && ancestor.getRevision().equals(rev)) { + mergedInto = true; + } + } catch (GitException ex) { + Logger.getLogger(CherryPick.class.getName()).log(Level.FINE, null, ex); + } finally { + if (client != null) { + client.release(); + } + } + } + final boolean merged = mergedInto; + EventQueue.invokeLater(new Runnable() { + + @Override + public void run () { + if (rev.equals(revision) && merged) { + panel.lblError.setText(Bundle.CherryPickPanel_info_merged(activeBranch.getName())); + panel.lblError.setVisible(true); + } + } + }); + } + + @Override + public boolean cancel () { + return pm != null && pm.cancel(); + } + + } +} diff -r aaa074f13bd1 git/src/org/netbeans/modules/git/ui/branch/CherryPickAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/git/src/org/netbeans/modules/git/ui/branch/CherryPickAction.java Tue Jul 01 17:18:16 2014 +0200 @@ -0,0 +1,367 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2014 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.git.ui.branch; + +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.logging.Logger; +import javax.swing.JButton; +import org.netbeans.libs.git.GitCherryPickResult; +import org.netbeans.libs.git.GitClient.CherryPickOperation; +import org.netbeans.libs.git.GitException; +import org.netbeans.libs.git.GitRepositoryState; +import org.netbeans.libs.git.GitRevisionInfo; +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.netbeans.modules.git.ui.actions.GitAction; +import org.netbeans.modules.git.ui.actions.SingleRepositoryAction; +import org.netbeans.modules.git.ui.commit.CommitAction; +import org.netbeans.modules.git.ui.conflicts.ResolveConflictsExecutor; +import org.netbeans.modules.git.ui.output.OutputLogger; +import org.netbeans.modules.git.ui.repository.RepositoryInfo; +import org.netbeans.modules.git.ui.status.StatusAction; +import org.netbeans.modules.git.utils.GitUtils; +import org.netbeans.modules.git.utils.ResultProcessor; +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.awt.Mnemonics; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.NbBundle; +import org.openide.util.actions.SystemAction; +import org.openide.util.lookup.Lookups; + +/** + * + * @author ondra + */ +@ActionID(id = "org.netbeans.modules.git.ui.branch.CherryPickAction", category = "Git") +@ActionRegistration(displayName = "#LBL_CherryPickAction_Name", lazy = false) +@NbBundle.Messages({ + "LBL_CherryPickAction_Name=C&herry Pick...", + "LBL_CherryPickAction_PopupName=Cherry Pick...", + "CTL_CherryPickAction_continueButton_text=&Continue", + "CTL_CherryPickAction_continueButton_TTtext=Continue in cherry-picking scheduled commits.", + "CTL_CherryPickAction_abortButton_text=&Abort", + "CTL_CherryPickAction_abortButton_TTtext=Abort interrupted cherry-picking and rollback to the original commit.", + "CTL_CherryPickAction_quitButton_text=&Quit", + "CTL_CherryPickAction_quitButton_TTtext=Finish the currently cherry-picked commit but do not apply any other.", + "LBL_CherryPick_cherryPickingState_title=Unfinished Cherry-Pick", + "# {0} - repository name", "MSG_CherryPick_cherryPickingState_text=Repository {0} seems to be in the middle of an unfinished cherry-pick.\n\n" + + "You may continue with applying all scheduled commits\n" + + "or abort and rollback to the state before the cherry-pick started.", + "# {0} - repository name", "MSG_CherryPick_cherryPickingScheduledState_text=Repository {0} seems to be in the middle of an unfinished cherry-pick.\n\n" + + "You may continue with applying all scheduled commits,\n" + + "abort and rollback to the state before the cherry-pick started\n" + + "or quit the cherry-pick and leave the already applied commits.", + "# {0} - repository state", "MSG_CherryPickAction_notAllowed=Cherry picking not allowed in this state: \"{0}\"." +}) +public class CherryPickAction extends SingleRepositoryAction { + + private static final Logger LOG = Logger.getLogger(CherryPickAction.class.getName()); + + public void cherryPick (File repository, String preselectedRevision) { + RepositoryInfo info = RepositoryInfo.getInstance(repository); + info.refresh(); + GitRepositoryState state = info.getRepositoryState(); + boolean interrupted = isInterrupted(repository, state); + if (state == GitRepositoryState.SAFE && !interrupted) { + CherryPick cherryPick = new CherryPick(repository, preselectedRevision); + if (cherryPick.showDialog()) { + runCherryPick(repository, CherryPickOperation.BEGIN, new String[] { cherryPick.getRevision() }); + } + } else if (interrupted) { + // abort or continue? + JButton btnContinue = new JButton(); + Mnemonics.setLocalizedText(btnContinue, Bundle.CTL_CherryPickAction_continueButton_text()); + btnContinue.setToolTipText(Bundle.CTL_CherryPickAction_continueButton_TTtext()); + JButton btnAbort = new JButton(); + Mnemonics.setLocalizedText(btnAbort, Bundle.CTL_CherryPickAction_abortButton_text()); + btnAbort.setToolTipText(Bundle.CTL_CherryPickAction_abortButton_TTtext()); + JButton btnQuit = new JButton(); + Mnemonics.setLocalizedText(btnQuit, Bundle.CTL_CherryPickAction_quitButton_text()); + btnQuit.setToolTipText(Bundle.CTL_CherryPickAction_quitButton_TTtext()); + Map operations = new HashMap<>(); + operations.put(btnContinue, CherryPickOperation.CONTINUE); + operations.put(btnQuit, CherryPickOperation.QUIT); + operations.put(btnAbort, CherryPickOperation.ABORT); + Object[] options = interrupted + ? new Object[] { btnContinue, btnAbort, btnQuit, NotifyDescriptor.CANCEL_OPTION } + : new Object[] { btnContinue, btnAbort, NotifyDescriptor.CANCEL_OPTION }; + Object value = DialogDisplayer.getDefault().notify(new NotifyDescriptor( + interrupted + ? Bundle.MSG_CherryPick_cherryPickingScheduledState_text(repository.getName()) + : Bundle.MSG_CherryPick_cherryPickingState_text(repository.getName()), + Bundle.LBL_CherryPick_cherryPickingState_title(), + NotifyDescriptor.YES_NO_CANCEL_OPTION, + NotifyDescriptor.QUESTION_MESSAGE, + options, + btnContinue)); + CherryPickOperation op = operations.get(value); + if (op != null) { + runCherryPick(repository, op, null); + } + } else { + GitClientExceptionHandler.annotate(Bundle.MSG_CherryPickAction_notAllowed(state)); + } + + } + + public void finish (File repository) { + RepositoryInfo info = RepositoryInfo.getInstance(repository); + info.refresh(); + if (isInterrupted(repository, info.getRepositoryState())) { + cherryPick(repository, null); + } + } + + @Override + protected void performAction (File repository, File[] roots, VCSContext context) { + cherryPick(repository, null); + } + + private boolean isInterrupted (File repository, GitRepositoryState state) { + if (state == GitRepositoryState.CHERRY_PICKING || state == GitRepositoryState.CHERRY_PICKING_RESOLVED) { + return true; + } + File sequencer = new File(GitUtils.getGitFolderForRoot(repository), "sequencer"); + String[] fileNames = sequencer.list(); + return fileNames != null && Arrays.asList(fileNames).contains("todo"); + } + + @NbBundle.Messages("MSG_CherryPickAction_progress=Cherry Picking...") + private void runCherryPick (final File repository, final CherryPickOperation op, final String[] revisions) { + GitProgressSupport supp = new GitProgressSupport() { + + @Override + protected void perform () { + try { + GitUtils.runWithoutIndexing(new Callable() { + @Override + public Void call () throws Exception { + GitClient client = getClient(); + CherryPickResultProcessor rp = new CherryPickResultProcessor(client, repository, getProgressSupport()); + CherryPickOperation nextAction = op; + while (nextAction != null && !isCanceled()) { + GitCherryPickResult result = client.cherryPick(nextAction, revisions, getProgressMonitor()); + rp.processResult(result, nextAction); + nextAction = rp.getNextAction(); + } + return null; + } + }); + } catch (GitException ex) { + GitClientExceptionHandler.notifyException(ex, true); + } finally { + setDisplayName(NbBundle.getMessage(GitAction.class, "LBL_Progress.RefreshingStatuses")); //NOI18N + Git.getInstance().getFileStatusCache().refreshAllRoots(Collections.>singletonMap(repository, Git.getInstance().getSeenRoots(repository))); + GitUtils.headChanged(repository); + } + } + + private GitProgressSupport getProgressSupport () { + return this; + } + }; + supp.start(Git.getInstance().getRequestProcessor(repository), repository, Bundle.MSG_CherryPickAction_progress()); + } + + public static class CherryPickResultProcessor extends ResultProcessor { + + private final OutputLogger logger; + private CherryPickOperation nextAction; + private final GitProgressSupport supp; + + public CherryPickResultProcessor (GitClient client, File repository, GitProgressSupport supp) { + super(client, repository, GitUtils.HEAD, supp.getProgressMonitor()); + this.logger = supp.getLogger(); + this.supp = supp; + } + + @NbBundle.Messages({ + "# {0} - rebase status", "MSG_CherryPickAction.result=Cherry-Pick Result: {0}\n", + "# {0} - head commit id", "MSG_CherryPickAction.result.aborted=Cherry-picking aborted and the current branch reset to {0}\n", + "MSG_CherryPickAction.result.failed=Working tree modifications prevent from cherry-picking:\n", + "MSG_CherryPickAction.result.conflict=Cherry-picking interrupted because of conflicts in:\n", + "MSG_CherryPickAction.result.ok=Cherry-picking successfully finished\n" + }) + public void processResult (GitCherryPickResult result, CherryPickOperation currentOp) { + nextAction = null; + StringBuilder sb = new StringBuilder(Bundle.MSG_CherryPickAction_result(result.getCherryPickStatus().toString())); + GitRevisionInfo info = result.getCurrentHead(); + switch (result.getCherryPickStatus()) { + case ABORTED: + sb.append(Bundle.MSG_CherryPickAction_result_aborted(info.getRevision())); + GitUtils.printInfo(sb, info); + break; + case FAILED: + sb.append(Bundle.MSG_CherryPickAction_result_failed()); + printConflicts(logger, sb, result.getFailures()); + try { + if (resolveLocalChanges(result.getFailures().toArray(new File[result.getFailures().size()]))) { + nextAction = CherryPickOperation.CONTINUE; + } else if (currentOp == CherryPickOperation.BEGIN) { + nextAction = CherryPickOperation.QUIT; + } + } catch (GitException ex) { + GitClientExceptionHandler.notifyException(ex, true); + } + break; + case CONFLICTING: + sb.append(Bundle.MSG_CherryPickAction_result_conflict()); + printConflicts(logger, sb, result.getConflicts()); + nextAction = resolveCherryPickConflicts(result.getConflicts()); + break; + case OK: + sb.append(Bundle.MSG_CherryPickAction_result_ok()); + break; + case UNCOMMITTED: + askForCommit(); + break; + } + for (GitRevisionInfo commit : result.getCherryPickedCommits()) { + GitUtils.printInfo(sb, commit); + } + if (sb.length() > 0) { + logger.outputLine(sb.toString()); + } + } + + public CherryPickOperation getNextAction () { + return nextAction; + } + + @NbBundle.Messages({ + "LBL_CherryPickResultProcessor.abortButton.text=&Abort", + "LBL_CherryPickResultProcessor.abortButton.TTtext=Abort the interrupted process and reset back to the original commit.", + "LBL_CherryPickResultProcessor.resolveButton.text=&Resolve", + "LBL_CherryPickResultProcessor.resolveButton.TTtext=Files in conflict will be opened in the Resolve Conflict dialog.", + "LBL_CherryPickResultProcessor.resolveConflicts=Resolve Conflicts", + "MSG_CherryPickResultProcessor.resolveConflicts=Cherry-picking produced unresolved conflicts.\n" + + "You can resolve them manually, review them in the Versioning view\n" + + "or completely abort the process and reset back to the original state.", + "LBL_CherryPickResultProcessor.revertButton.text=&Revert", + "LBL_CherryPickResultProcessor.revertButton.TTtext=Revert local changes to the state in the HEAD and removes unversioned files.", + "LBL_CherryPickResultProcessor.reviewButton.text=Re&view", + "LBL_CherryPickResultProcessor.reviewButton.TTtext=Opens the Versioning view and lists the conflicted files.", + "MSG_CherryPick.resolving=Resolving conflicts..." + }) + private CherryPickOperation resolveCherryPickConflicts (Collection conflicts) { + CherryPickOperation action = null; + JButton abort = new JButton(); + Mnemonics.setLocalizedText(abort, Bundle.LBL_CherryPickResultProcessor_abortButton_text()); + abort.setToolTipText(Bundle.LBL_CherryPickResultProcessor_abortButton_TTtext()); + JButton resolve = new JButton(); + Mnemonics.setLocalizedText(resolve, Bundle.LBL_CherryPickResultProcessor_resolveButton_text()); + resolve.setToolTipText(Bundle.LBL_CherryPickResultProcessor_resolveButton_TTtext()); + JButton review = new JButton(); + Mnemonics.setLocalizedText(review, Bundle.LBL_CherryPickResultProcessor_reviewButton_text()); + review.setToolTipText(Bundle.LBL_CherryPickResultProcessor_reviewButton_TTtext()); + Object o = DialogDisplayer.getDefault().notify(new NotifyDescriptor( + Bundle.MSG_CherryPickResultProcessor_resolveConflicts(), + Bundle.LBL_CherryPickResultProcessor_resolveConflicts(), + NotifyDescriptor.OK_CANCEL_OPTION, NotifyDescriptor.QUESTION_MESSAGE, + new Object[] { resolve, review, abort, NotifyDescriptor.CANCEL_OPTION }, resolve)); + if (o == review) { + openInVersioningView(conflicts); + } else if (o == resolve) { + GitProgressSupport executor = new ResolveConflictsExecutor(conflicts.toArray(new File[conflicts.size()])); + executor.start(Git.getInstance().getRequestProcessor(repository), repository, Bundle.MSG_CherryPick_resolving()); + } else if (o == abort) { + action = CherryPickOperation.ABORT; + } + return action; + } + + @NbBundle.Messages({ + "LBL_CherryPickResultProcessor.commit=Commit Required", + "MSG_CherryPickResultProcessor.commit=Commit changes were applied into the current branch\n" + + "but a manual commit is required to make these changes permanent.\n\n" + + "Do you want to commit the changes now or review them first?", + "LBL_CherryPickResultProcessor.commit.commitButton.text=&Commit", + "LBL_CherryPickResultProcessor.commit.commitButton.TTtext=Opens the commit dialog.", + "LBL_CherryPickResultProcessor.commit.reviewButton.text=&Review", + "LBL_CherryPickResultProcessor.commit.reviewButton.TTtext=Review the changes in the status window." + }) + private void askForCommit () { + JButton commit = new JButton(); + Mnemonics.setLocalizedText(commit, Bundle.LBL_CherryPickResultProcessor_commit_commitButton_text()); + commit.setToolTipText(Bundle.LBL_CherryPickResultProcessor_commit_commitButton_TTtext()); + JButton review = new JButton(); + Mnemonics.setLocalizedText(review, Bundle.LBL_CherryPickResultProcessor_commit_reviewButton_text()); + review.setToolTipText(Bundle.LBL_CherryPickResultProcessor_commit_reviewButton_TTtext()); + Object o = DialogDisplayer.getDefault().notify(new NotifyDescriptor( + Bundle.MSG_CherryPickResultProcessor_commit(), + Bundle.LBL_CherryPickResultProcessor_commit(), + NotifyDescriptor.OK_CANCEL_OPTION, NotifyDescriptor.QUESTION_MESSAGE, + new Object[] { commit, review, NotifyDescriptor.CANCEL_OPTION }, commit)); + VCSContext context = VCSContext.forNodes(new Node[] { + new AbstractNode(Children.LEAF, Lookups.fixed(repository)) { + + @Override + public String getName () { + return repository.getName(); + } + } + }); + if (o == commit) { + SystemAction.get(CommitAction.class).performAction(context); + } else if (o == review) { + SystemAction.get(StatusAction.class).performContextAction(context); + } + } + + } + +} diff -r aaa074f13bd1 git/src/org/netbeans/modules/git/ui/branch/CherryPickPanel.form --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/git/src/org/netbeans/modules/git/ui/branch/CherryPickPanel.form Tue Jul 01 17:18:16 2014 +0200 @@ -0,0 +1,77 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff -r aaa074f13bd1 git/src/org/netbeans/modules/git/ui/branch/CherryPickPanel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/git/src/org/netbeans/modules/git/ui/branch/CherryPickPanel.java Tue Jul 01 17:18:16 2014 +0200 @@ -0,0 +1,117 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2011 Sun Microsystems, Inc. + */ + +/* + * MergeRevisionPanel.java + * + * Created on Jan 18, 2011, 10:50:16 AM + */ + +package org.netbeans.modules.git.ui.branch; + +import org.netbeans.modules.git.ui.repository.RevisionDialog; + +/** + * + * @author ondra + */ +public class CherryPickPanel extends javax.swing.JPanel { + private final RevisionDialog revisionPanel; + + /** Creates new form MergeRevisionPanel */ + public CherryPickPanel (RevisionDialog revisionPanel) { + this.revisionPanel = revisionPanel; + initComponents(); + } + + /** 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() { + + rbFFOptions = new javax.swing.ButtonGroup(); + org.netbeans.modules.git.ui.repository.RevisionDialog revisionPickerDialog1 = this.revisionPanel; + jLabel1 = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(CherryPickPanel.class, "CherryPickPanel.jLabel1.text")); // NOI18N + + lblError.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/netbeans/modules/git/resources/icons/info.png"))); // NOI18N + + 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() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(lblError, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(revisionPickerDialog1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblError) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel jLabel1; + final javax.swing.JLabel lblError = new javax.swing.JLabel(); + private javax.swing.ButtonGroup rbFFOptions; + // End of variables declaration//GEN-END:variables + +} diff -r aaa074f13bd1 git/src/org/netbeans/modules/git/ui/commit/CommitAction.java --- a/git/src/org/netbeans/modules/git/ui/commit/CommitAction.java Tue Jul 01 16:57:50 2014 +0200 +++ b/git/src/org/netbeans/modules/git/ui/commit/CommitAction.java Tue Jul 01 17:18:16 2014 +0200 @@ -80,6 +80,7 @@ import org.netbeans.modules.git.ui.commit.GitCommitPanel.GitCommitPanelMerged; import org.netbeans.modules.git.GitFileNode.GitLocalFileNode; import org.netbeans.modules.git.ui.actions.GitAction; +import org.netbeans.modules.git.ui.branch.CherryPickAction; import org.netbeans.modules.git.ui.repository.RepositoryInfo; import org.netbeans.modules.git.utils.GitUtils; import org.netbeans.modules.versioning.hooks.GitHook; @@ -141,7 +142,7 @@ @Override public void run() { - GitCommitPanel panel = state == GitRepositoryState.MERGING_RESOLVED + GitCommitPanel panel = state == GitRepositoryState.MERGING_RESOLVED || state == GitRepositoryState.CHERRY_PICKING_RESOLVED ? GitCommitPanelMerged.create(roots, repository, user, mergeCommitMessage) : GitCommitPanel.create(roots, repository, user, isFromGitView(context)); VCSCommitTable table = panel.getCommitTable(); @@ -182,7 +183,8 @@ private String getMergeCommitMessage (File repository, GitRepositoryState state) { String message = null; - if (EnumSet.of(GitRepositoryState.MERGING, GitRepositoryState.MERGING_RESOLVED).contains(state)) { + if (EnumSet.of(GitRepositoryState.MERGING, GitRepositoryState.MERGING_RESOLVED, + GitRepositoryState.CHERRY_PICKING, GitRepositoryState.CHERRY_PICKING_RESOLVED).contains(state)) { File f = new File(GitUtils.getGitFolderForRoot(repository), "MERGE_MSG"); //NOI18N try { message = new String(FileUtils.getFileContentsAsByteArray(f), "UTF-8"); //NOI18N @@ -245,6 +247,7 @@ String origMessage = message; message = beforeCommitHook(commitCandidates, hooks, message); + GitRepositoryState prevState = RepositoryInfo.getInstance(getRepositoryRoot()).getRepositoryState(); GitRevisionInfo info = commit(commitCandidates, message, author, commiter, amend); GitModuleConfig.getDefault().putRecentCommitAuthors(GitCommitParameters.getUserString(author)); @@ -256,6 +259,10 @@ LOG.log(Level.INFO, null, ex); } afterCommitHook(commitCandidates, hooks, info, origMessage); + if (prevState == GitRepositoryState.CHERRY_PICKING_RESOLVED) { + // should continue with cherry-picking + SystemAction.get(CherryPickAction.class).finish(getRepositoryRoot()); + } } catch (GitException ex) { GitClientExceptionHandler.notifyException(ex, true); @@ -348,7 +355,8 @@ private GitRevisionInfo commit (Collection commitCandidates, String message, GitUser author, GitUser commiter, boolean amend) throws GitException { try { GitRevisionInfo info = getClient().commit( - state == GitRepositoryState.MERGING_RESOLVED ? new File[0] : commitCandidates.toArray(new File[commitCandidates.size()]), + state == GitRepositoryState.MERGING_RESOLVED || state == GitRepositoryState.CHERRY_PICKING_RESOLVED + ? new File[0] : commitCandidates.toArray(new File[commitCandidates.size()]), message, author, commiter, amend, getProgressMonitor()); printInfo(info); return info; @@ -388,7 +396,7 @@ if (!state.canCommit()) { commitPermitted = false; Map conflicts = Collections.emptyMap(); - if (state.equals(GitRepositoryState.MERGING)) { + if (state.equals(GitRepositoryState.MERGING) || state.equals(GitRepositoryState.CHERRY_PICKING)) { GitClient client = null; try { client = Git.getInstance().getClient(repository); diff -r aaa074f13bd1 git/src/org/netbeans/modules/git/ui/history/RepositoryRevision.java --- a/git/src/org/netbeans/modules/git/ui/history/RepositoryRevision.java Tue Jul 01 16:57:50 2014 +0200 +++ b/git/src/org/netbeans/modules/git/ui/history/RepositoryRevision.java Tue Jul 01 17:18:16 2014 +0200 @@ -67,9 +67,11 @@ import org.netbeans.modules.git.Git; import org.netbeans.modules.git.client.GitClientExceptionHandler; import org.netbeans.modules.git.client.GitProgressSupport; +import org.netbeans.modules.git.ui.branch.CherryPickAction; import org.netbeans.modules.git.ui.checkout.CheckoutRevisionAction; import org.netbeans.modules.git.ui.checkout.RevertChangesAction; import org.netbeans.modules.git.ui.diff.ExportCommitAction; +import org.netbeans.modules.git.ui.repository.RepositoryInfo; import org.netbeans.modules.git.ui.revert.RevertCommitAction; import org.netbeans.modules.git.ui.tag.CreateTagAction; import org.netbeans.modules.git.utils.GitUtils; @@ -232,6 +234,21 @@ } }); if (getLog().getParents().length < 2) { + if (!isInCurrentBranch()) { + actions.add(new AbstractAction(NbBundle.getMessage(CherryPickAction.class, "LBL_CherryPickAction_PopupName")) { //NOI18N + @Override + public void actionPerformed (ActionEvent e) { + final String revision = getLog().getRevision(); + Utils.post(new Runnable() { + + @Override + public void run () { + SystemAction.get(CherryPickAction.class).cherryPick(repositoryRoot, revision); + } + }); + } + }); + } actions.add(new AbstractAction(NbBundle.getMessage(ExportCommitAction.class, "LBL_ExportCommitAction_PopupName")) { //NOI18N @Override public void actionPerformed (ActionEvent e) { @@ -278,6 +295,16 @@ return preferredRevision; } + private boolean isInCurrentBranch () { + GitBranch activeBranch = RepositoryInfo.getInstance(repositoryRoot).getActiveBranch(); + for (GitBranch b : getLog().getBranches().values()) { + if (activeBranch.getName().equals(b.getName()) || activeBranch.getId().equals(b.getId())) { + return true; + } + } + return false; + } + public class Event implements Comparable { /** * The file or folder that this event is about. It may be null if the File cannot be computed. diff -r aaa074f13bd1 git/src/org/netbeans/modules/git/ui/menu/BranchMenu.java --- a/git/src/org/netbeans/modules/git/ui/menu/BranchMenu.java Tue Jul 01 16:57:50 2014 +0200 +++ b/git/src/org/netbeans/modules/git/ui/menu/BranchMenu.java Tue Jul 01 17:18:16 2014 +0200 @@ -52,6 +52,7 @@ import javax.swing.JMenuItem; import org.netbeans.libs.git.GitBranch; import org.netbeans.modules.git.Annotator; +import org.netbeans.modules.git.ui.branch.CherryPickAction; import org.netbeans.modules.git.ui.branch.CreateBranchAction; import org.netbeans.modules.git.ui.branch.SetTrackingAction; import org.netbeans.modules.git.ui.checkout.AbstractCheckoutAction; @@ -140,6 +141,12 @@ Utils.setAcceleratorBindings(Annotator.ACTIONS_PATH_PREFIX, action); Actions.connect(item, action, false); menu.add(item); + + item = new JMenuItem(); + action = (Action) SystemAction.get(CherryPickAction.class); + Utils.setAcceleratorBindings(Annotator.ACTIONS_PATH_PREFIX, action); + Actions.connect(item, action, false); + menu.add(item); } else { item = menu.add(SystemActionBridge.createAction(SystemAction.get(CreateBranchAction.class), NbBundle.getMessage(CreateBranchAction.class, "LBL_CreateBranchAction_PopupName"), lkp)); //NOI18N org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); @@ -181,6 +188,8 @@ org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); item = menu.add(SystemActionBridge.createAction(SystemAction.get(RebaseAction.class), NbBundle.getMessage(RebaseAction.class, "LBL_RebaseAction_PopupName"), lkp)); //NOI18N org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); + item = menu.add(SystemActionBridge.createAction(SystemAction.get(CherryPickAction.class), NbBundle.getMessage(CherryPickAction.class, "LBL_CherryPickAction_PopupName"), lkp)); //NOI18N + org.openide.awt.Mnemonics.setLocalizedText(item, item.getText()); } return menu; } diff -r aaa074f13bd1 git/src/org/netbeans/modules/git/ui/repository/RevisionInfoPanelController.java --- a/git/src/org/netbeans/modules/git/ui/repository/RevisionInfoPanelController.java Tue Jul 01 16:57:50 2014 +0200 +++ b/git/src/org/netbeans/modules/git/ui/repository/RevisionInfoPanelController.java Tue Jul 01 17:18:16 2014 +0200 @@ -120,8 +120,12 @@ if (!panel.tbAuthor.getText().isEmpty()) { panel.tbAuthor.setCaretPosition(0); } + String id = info.getRevision(); + if (id.length() > 10) { + id = id.substring(0, 10); + } if (revision.equals(info.getRevision())) { - panel.tbRevisionId.setText(new StringBuilder(info.getRevision()).append(getMergedStatus(revisionMerged)).toString()); + panel.tbRevisionId.setText(new StringBuilder(id).append(getMergedStatus(revisionMerged)).toString()); this.info = new Revision(revision, revision, info.getShortMessage(), info.getFullMessage()); } else { this.info = new Revision(info.getRevision(), revision, info.getShortMessage(), info.getFullMessage()); diff -r aaa074f13bd1 git/test/unit/src/org/netbeans/modules/git/GitClientTest.java --- a/git/test/unit/src/org/netbeans/modules/git/GitClientTest.java Tue Jul 01 16:57:50 2014 +0200 +++ b/git/test/unit/src/org/netbeans/modules/git/GitClientTest.java Tue Jul 01 17:18:16 2014 +0200 @@ -111,6 +111,7 @@ "catIndexEntry", "checkout", "checkoutRevision", + "cherryPick", "clean", "commit", "copyAfter", @@ -227,6 +228,7 @@ "catIndexEntry", "checkout", "checkoutRevision", + "cherryPick", "clean", "commit", "copyAfter", @@ -276,6 +278,7 @@ Set expectedMethods = new HashSet(Arrays.asList( "checkout", "checkoutRevision", + "cherryPick", "commit", "createBranch", "createTag", @@ -330,6 +333,7 @@ "catIndexEntry", "checkout", "checkoutRevision", + "cherryPick", "clean", "commit", "copyAfter", @@ -505,6 +509,7 @@ "catIndexEntry", "checkout", "checkoutRevision", + "cherryPick", "clean", "commit", "copyAfter",