--- a/libs.git/apichanges.xml Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/apichanges.xml Tue Jul 01 13:16:35 2014 +0200
@@ -112,6 +112,29 @@
New method for updating a reference (branch) to a new commit id.
+
+
+
+
+
+
+ - New method GitClient.cherryPick used to cherry-pick commits and apply them in the
+ current branch. The command may interrupt its progress and require some user actions
+ (such as resolve conflicts or commit) and may be continued with different kinds
+ of operation types passed as its arguments.
+ - Introduciong new repository state: CHERRY_PICKING and CHERRY_PICKING_RESOLVED
+ marking the states of the repository when a cherry-picking is not finished or
+ it requires resolving conflicts.
+
+
+
+
+
+
+
+
+
+ New method for updating a reference (branch) to a new commit id.
--- a/libs.git/manifest.mf Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/manifest.mf Tue Jul 01 13:16:35 2014 +0200
@@ -1,4 +1,4 @@
Manifest-Version: 1.0
OpenIDE-Module: org.netbeans.libs.git/1
OpenIDE-Module-Localizing-Bundle: org/netbeans/libs/git/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.26
+OpenIDE-Module-Specification-Version: 1.27
--- a/libs.git/src/org/netbeans/libs/git/Bundle.properties Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/src/org/netbeans/libs/git/Bundle.properties Tue Jul 01 13:16:35 2014 +0200
@@ -53,6 +53,8 @@
LBL_RepositoryInfo_Rebasing = Rebasing
LBL_RepositoryInfo_Apply = Apply
LBL_RepositoryInfo_Bisecting = Bisecting
+LBL_RepositoryInfo_CherryPicking=Cherry-picking
+LBL_RepositoryInfo_CherryPickingResolved=Cherry-picked
#GitException
MSG_Exception_ObjectDoesNotExist = {0} [{1}] does not exist
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ d68b118f158f Tue Jul 01 13:16:35 2014 +0200
@@ -0,0 +1,150 @@
+/*
+ * 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.
+ */
+package org.netbeans.libs.git;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Returned by a git cherry-pick command, represents its result.
+ *
+ * @author Ondra Vrabec
+ * @since 1.27
+ */
+public final class GitCherryPickResult {
+
+ private final CherryPickStatus status;
+ private final List conflicts;
+ private final List failures;
+ private final GitRevisionInfo currentHead;
+ private final List cherryPickedCommits;
+
+ /**
+ * The status rebase resulted in.
+ */
+ public enum CherryPickStatus {
+ /**
+ * Command successfully finished. No action is required.
+ */
+ OK,
+ /**
+ * Command was aborted and reset to the original state. No action is
+ * required.
+ */
+ ABORTED,
+ /**
+ * Failed because a dirty working tree prevents from starting the command.
+ * Local modifications preventing from applying commit changes must be
+ * reverted.
+ */
+ FAILED,
+ /**
+ * The cherry-picking stopped in a state where it requires a manual commit.
+ * E.g. after resolving conflicts client is required to commit the changes
+ * before continuing with rebase.
+ */
+ UNCOMMITTED,
+ /**
+ * Conflicts when merging the cherry-picked commits.
+ * Conflicts must be resolved and the command must be continued/aborted.
+ */
+ CONFLICTING;
+ }
+
+ GitCherryPickResult (CherryPickStatus status, List conflicts, List failures,
+ GitRevisionInfo currentHead, List cherryPickedCommits) {
+ this.status = status;
+ this.currentHead = currentHead;
+ this.conflicts = conflicts;
+ this.failures = failures;
+ this.cherryPickedCommits = cherryPickedCommits;
+ }
+
+ /**
+ * @return result of the cherry-pick command.
+ */
+ public CherryPickStatus getCherryPickStatus () {
+ return status;
+ }
+
+ /**
+ * @return current HEAD commit after the cherry-pick command.
+ */
+ public GitRevisionInfo getCurrentHead () {
+ return currentHead;
+ }
+
+ /**
+ * If the cherry-pick started but was unable to finish because of unresolved
+ * conflicts then the method returns a collection of such files in conflict.
+ * To complete the command you need to resolve the conflicts and continue the
+ * unfinished command.
+ *
+ * @return files in conflict
+ */
+ public Collection getConflicts () {
+ return Collections.unmodifiableList(conflicts);
+ }
+
+ /**
+ * When the command fails because of local modifications then this method
+ * returns a collections of files causing the failure.
+ *
+ * @return files that cause the cherry-pick to fail.
+ */
+ public Collection getFailures () {
+ return Collections.unmodifiableList(failures);
+ }
+
+ /**
+ * Returns commits cherry-picked to the current branch by the last run of the
+ * cherry-pick command.
+ *
+ * @return array of commits cherry-picked to head.
+ */
+ public GitRevisionInfo[] getCherryPickedCommits () {
+ return cherryPickedCommits.toArray(new GitRevisionInfo[cherryPickedCommits.size()]);
+ }
+
+}
--- a/libs.git/src/org/netbeans/libs/git/GitClassFactoryImpl.java Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/src/org/netbeans/libs/git/GitClassFactoryImpl.java Tue Jul 01 13:16:35 2014 +0200
@@ -193,4 +193,11 @@
return new GitSubmoduleStatus(status, folder);
}
+ @Override
+ public GitCherryPickResult createCherryPickResult (GitCherryPickResult.CherryPickStatus status,
+ List conflicts, List failures, GitRevisionInfo head,
+ List cherryPickedCommits) {
+ return new GitCherryPickResult(status, conflicts, failures, head, cherryPickedCommits);
+ }
+
}
--- a/libs.git/src/org/netbeans/libs/git/GitClient.java Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/src/org/netbeans/libs/git/GitClient.java Tue Jul 01 13:16:35 2014 +0200
@@ -63,6 +63,7 @@
import org.netbeans.libs.git.jgit.commands.CatCommand;
import org.netbeans.libs.git.jgit.commands.CheckoutIndexCommand;
import org.netbeans.libs.git.jgit.commands.CheckoutRevisionCommand;
+import org.netbeans.libs.git.jgit.commands.CherryPickCommand;
import org.netbeans.libs.git.jgit.commands.CleanCommand;
import org.netbeans.libs.git.jgit.commands.CommitCommand;
import org.netbeans.libs.git.jgit.commands.CompareCommand;
@@ -254,6 +255,52 @@
};
}
+ /**
+ * Used as a parameter of {@link #cherryPick(org.netbeans.libs.git.GitClient.CherryPickOperation, java.lang.String[], org.netbeans.libs.git.progress.ProgressMonitor) to set the behavior of the command.
+ * @since 1.27
+ */
+ public enum CherryPickOperation {
+
+ /**
+ * A fresh cherry-pick command will be started.
+ */
+ BEGIN,
+ /**
+ * Continues an interrupted cherry-pick command after conflicts are resolved.
+ */
+ CONTINUE {
+
+ @Override
+ public String toString () {
+ return "--continue"; //NOI18N
+ }
+
+ },
+ /**
+ * Tries to finish cherry-picking the current commit but stops in
+ * cherry-picking other scheduled commits.
+ */
+ QUIT {
+
+ @Override
+ public String toString () {
+ return "--quit"; //NOI18N
+ }
+
+ },
+ /**
+ * Aborts and resets an interrupted cherry-pick command.
+ */
+ ABORT {
+
+ @Override
+ public String toString () {
+ return "--abort"; //NOI18N
+ }
+
+ };
+ }
+
private final JGitRepository gitRepository;
private final Set listeners;
private JGitCredentialsProvider credentialsProvider;
@@ -371,6 +418,26 @@
CheckoutRevisionCommand cmd = new CheckoutRevisionCommand(repository, getClassFactory(), revision, failOnConflict, monitor, delegateListener);
cmd.execute();
}
+
+ /**
+ * Cherry-picks (transplants) selected revisions (commits) onto the current
+ * HEAD.
+ *
+ * @param operation kind of cherry-pick operation you want to perform
+ * @param revisions commits you want to cherry-pick. Makes sense only when
+ * operation
is set to CherryPickOperation.BEGIN
+ * otherwise it's meaningless.
+ * @param monitor progress monitor
+ * @return result of the command
+ * @throws GitException an unexpected error occurs
+ * @since 1.27
+ */
+ public GitCherryPickResult cherryPick (CherryPickOperation operation, String[] revisions, ProgressMonitor monitor) throws GitException {
+ Repository repository = gitRepository.getRepository();
+ CherryPickCommand cmd = new CherryPickCommand(repository, getClassFactory(), revisions, operation, monitor, delegateListener);
+ cmd.execute();
+ return cmd.getResult();
+ }
/**
* Cleans the working tree by recursively removing files that are not under
--- a/libs.git/src/org/netbeans/libs/git/GitRepositoryState.java Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/src/org/netbeans/libs/git/GitRepositoryState.java Tue Jul 01 13:16:35 2014 +0200
@@ -78,7 +78,8 @@
public String toString () { return Utils.getBundle(GitRepositoryState.class).getString("LBL_RepositoryInfo_Safe"); } //NOI18N
},
- /** An unfinished merge or cherry-picking. Must resolve or reset before continuing normally
+ /**
+ * An unfinished merge. Must resolve or reset before continuing normally
*/
MERGING {
@Override
@@ -107,6 +108,37 @@
},
/**
+ * An unfinished cherry-pick. Must resolve or reset before continuing normally
+ * @since 1.27
+ */
+ CHERRY_PICKING {
+ @Override
+ public boolean canCheckout () { return false; }
+ @Override
+ public boolean canResetHead () { return true; }
+ @Override
+ public boolean canCommit () { return false; }
+ @Override
+ public String toString () { return Utils.getBundle(GitRepositoryState.class).getString("LBL_RepositoryInfo_CherryPicking"); } //NOI18N
+ },
+
+ /**
+ * A cherry-picked commit where all conflicts have been resolved. The index does not
+ * contain any unmerged paths and the repository requires a commit.
+ * @since 1.27
+ */
+ CHERRY_PICKING_RESOLVED {
+ @Override
+ public boolean canCheckout () { return true; }
+ @Override
+ public boolean canResetHead () { return true; }
+ @Override
+ public boolean canCommit () { return true; }
+ @Override
+ public String toString () { return Utils.getBundle(GitRepositoryState.class).getString("LBL_RepositoryInfo_CherryPickingResolved"); } //NOI18N
+ },
+
+ /**
* An unfinished rebase or am. Must resolve, skip or abort before normal work can take place
*/
REBASING {
@@ -178,11 +210,13 @@
case BISECTING:
return GitRepositoryState.BISECTING;
case MERGING:
- case CHERRY_PICKING:
case REVERTING:
return GitRepositoryState.MERGING;
+ case CHERRY_PICKING:
+ return GitRepositoryState.CHERRY_PICKING;
+ case CHERRY_PICKING_RESOLVED:
+ return GitRepositoryState.CHERRY_PICKING_RESOLVED;
case MERGING_RESOLVED:
- case CHERRY_PICKING_RESOLVED:
case REVERTING_RESOLVED:
return GitRepositoryState.MERGING_RESOLVED;
case REBASING:
--- a/libs.git/src/org/netbeans/libs/git/jgit/GitClassFactory.java Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/src/org/netbeans/libs/git/jgit/GitClassFactory.java Tue Jul 01 13:16:35 2014 +0200
@@ -62,6 +62,7 @@
import org.eclipse.jgit.transport.URIish;
import org.netbeans.libs.git.GitBlameResult;
import org.netbeans.libs.git.GitBranch;
+import org.netbeans.libs.git.GitCherryPickResult;
import org.netbeans.libs.git.GitConflictDescriptor;
import org.netbeans.libs.git.GitConflictDescriptor.Type;
import org.netbeans.libs.git.GitMergeResult;
@@ -89,6 +90,10 @@
public abstract GitBranch createBranch (String name, boolean remote, boolean active, ObjectId id);
+ public abstract GitCherryPickResult createCherryPickResult (
+ GitCherryPickResult.CherryPickStatus status, List conflicts,
+ List failures, GitRevisionInfo head, List cherryPickedCommits);
+
public abstract GitConflictDescriptor createConflictDescriptor (Type type);
public abstract GitFileInfo createFileInfo (File file, String oldPath, GitFileInfo.Status status, File originalFile, String originalPath);
--- a/libs.git/src/org/netbeans/libs/git/jgit/Utils.java Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/src/org/netbeans/libs/git/jgit/Utils.java Tue Jul 01 13:16:35 2014 +0200
@@ -263,16 +263,20 @@
}
public static RevCommit findCommit (Repository repository, String revision, RevWalk walk) throws GitException.MissingObjectException, GitException {
+ ObjectId commitId = parseObjectId(repository, revision);
+ if (commitId == null) {
+ throw new GitException.MissingObjectException(revision, GitObjectType.COMMIT);
+ }
+ return findCommit(repository, commitId, walk);
+ }
+
+ public static RevCommit findCommit (Repository repository, ObjectId commitId, RevWalk walk) throws GitException.MissingObjectException, GitException {
try {
- ObjectId commitId = parseObjectId(repository, revision);
- if (commitId == null) {
- throw new GitException.MissingObjectException(revision, GitObjectType.COMMIT);
- }
return (walk == null ? new RevWalk(repository) : walk).parseCommit(commitId);
} catch (MissingObjectException ex) {
- throw new GitException.MissingObjectException(revision, GitObjectType.COMMIT, ex);
+ throw new GitException.MissingObjectException(commitId.name(), GitObjectType.COMMIT, ex);
} catch (IncorrectObjectTypeException ex) {
- throw new GitException(MessageFormat.format(Utils.getBundle(Utils.class).getString("MSG_Exception_IdNotACommit"), revision)); //NOI18N
+ throw new GitException(MessageFormat.format(Utils.getBundle(Utils.class).getString("MSG_Exception_IdNotACommit"), commitId.name())); //NOI18N
} catch (IOException ex) {
throw new GitException(ex);
}
--- a/libs.git/src/org/netbeans/libs/git/jgit/commands/Bundle.properties Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/src/org/netbeans/libs/git/jgit/commands/Bundle.properties Tue Jul 01 13:16:35 2014 +0200
@@ -54,6 +54,7 @@
MSG_Error_RepositoryExists = Git repository already exists at {0}: {1}
MSG_Error_CannotCatRoot = Cannot cat root: {0}
MSG_Error_Commit_ConflictsInIndex = Index contains files in conflict, please resolve them before commit
+MSG_Error_Commit_CannotAmend=Cannot amend a commit in this repository state.
MSG_Error_Commit_PartialCommitAfterMerge = Cannot do a partial commit during a merge.
MSG_Error_Commit_NotAllowedInCurrentState = Cannot commit in current state: {0}
MSG_Error_UpdateTracking_InvalidReference=Invalid reference: {0}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ d68b118f158f Tue Jul 01 13:16:35 2014 +0200
@@ -0,0 +1,426 @@
+/*
+ * 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.libs.git.jgit.commands;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.eclipse.jgit.api.CherryPickResult;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.RebaseCommand.Operation;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.RebaseTodoFile;
+import org.eclipse.jgit.lib.RebaseTodoLine;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryState;
+import org.eclipse.jgit.merge.ResolveMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.io.SafeBufferedOutputStream;
+import org.netbeans.libs.git.GitCherryPickResult;
+import org.netbeans.libs.git.GitClient;
+import org.netbeans.libs.git.GitException;
+import org.netbeans.libs.git.GitRevisionInfo;
+import org.netbeans.libs.git.GitStatus;
+import org.netbeans.libs.git.jgit.DelegatingGitProgressMonitor;
+import org.netbeans.libs.git.jgit.GitClassFactory;
+import org.netbeans.libs.git.jgit.Utils;
+import org.netbeans.libs.git.progress.FileListener;
+import org.netbeans.libs.git.progress.ProgressMonitor;
+import org.netbeans.libs.git.progress.StatusListener;
+
+/**
+ *
+ * @author ondra
+ */
+public class CherryPickCommand extends GitCommand {
+
+ private final String[] revisions;
+ private GitCherryPickResult result;
+ private final ProgressMonitor monitor;
+ private final GitClient.CherryPickOperation operation;
+ private final FileListener listener;
+ private static final String SEQUENCER = "sequencer";
+ private static final String SEQUENCER_HEAD = "head";
+ private static final String SEQUENCER_TODO = "todo";
+
+ public CherryPickCommand (Repository repository, GitClassFactory gitFactory, String[] revisions,
+ GitClient.CherryPickOperation operation, ProgressMonitor monitor, FileListener listener) {
+ super(repository, gitFactory, monitor);
+ this.revisions = revisions;
+ this.operation = operation;
+ this.monitor = monitor;
+ this.listener = listener;
+ }
+
+ @Override
+ protected void run () throws GitException {
+ Repository repository = getRepository();
+ ObjectId originalCommit = getOriginalCommit();
+ ObjectId head = getHead();
+ List steps;
+ try {
+ switch (operation) {
+ case BEGIN:
+ // initialize sequencer and cherry-pick steps if there are
+ // more commits to cherry-pick
+ steps = prepareCommand(head);
+ // apply the selected steps
+ applySteps(steps, false);
+ break;
+ case ABORT:
+ // delete the sequencer and reset to the original head
+ if (repository.getRepositoryState() == RepositoryState.CHERRY_PICKING
+ || repository.getRepositoryState() == RepositoryState.CHERRY_PICKING_RESOLVED) {
+ if (originalCommit == null) {
+ // maybe the sequencer is not created in that case simply reset to HEAD
+ originalCommit = head;
+ }
+ }
+ Utils.deleteRecursively(getSequencerFolder());
+ if (originalCommit != null) {
+ ResetCommand reset = new ResetCommand(repository, getClassFactory(),
+ originalCommit.name(), GitClient.ResetType.HARD, new DelegatingGitProgressMonitor(monitor), listener);
+ reset.execute();
+ }
+ result = createCustomResult(GitCherryPickResult.CherryPickStatus.ABORTED);
+ break;
+ case QUIT:
+ // used to reset the sequencer only
+ Utils.deleteRecursively(getSequencerFolder());
+ switch (repository.getRepositoryState()) {
+ case CHERRY_PICKING:
+ // unresolved conflicts
+ result = createResult(CherryPickResult.CONFLICT);
+ break;
+ case CHERRY_PICKING_RESOLVED:
+ result = createCustomResult(GitCherryPickResult.CherryPickStatus.UNCOMMITTED);
+ break;
+ default:
+ result = createCustomResult(GitCherryPickResult.CherryPickStatus.OK);
+ break;
+ }
+ break;
+ case CONTINUE:
+ switch (repository.getRepositoryState()) {
+ case CHERRY_PICKING:
+ // unresolved conflicts, cannot continue
+ result = createResult(CherryPickResult.CONFLICT);
+ break;
+ case CHERRY_PICKING_RESOLVED:
+ // cannot continue without manual commit
+ result = createCustomResult(GitCherryPickResult.CherryPickStatus.UNCOMMITTED);
+ break;
+ default:
+ // read steps from sequencer and apply them
+ // if sequencer is empty this will be a noop
+ steps = readTodoFile(repository);
+ applySteps(steps, true);
+ break;
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unexpected operation " + operation.name());
+ }
+ } catch (GitAPIException | IOException ex) {
+ throw new GitException(ex);
+ }
+ }
+
+ @Override
+ protected String getCommandDescription () {
+ StringBuilder sb = new StringBuilder();
+ sb.append("git cherry-pick "); //NOI18N
+ if (operation == GitClient.CherryPickOperation.BEGIN) {
+ for (String rev : revisions) {
+ sb.append(rev).append(" "); //NOI18N
+ }
+ } else {
+ sb.append(operation.toString());
+ }
+ return sb.toString();
+ }
+
+ public GitCherryPickResult getResult () {
+ return result;
+ }
+
+ static Operation getOperation (GitClient.RebaseOperationType operation) {
+ return Operation.valueOf(operation.name());
+ }
+
+ private void applySteps (List steps, boolean skipFirstStep) throws GitAPIException, IOException {
+ Repository repository = getRepository();
+ ObjectReader or = repository.newObjectReader();
+ CherryPickResult res = null;
+ boolean skipped = false;
+ List[ cherryPickedRefs = new ArrayList<>();
+ for (Iterator it = steps.iterator(); it.hasNext(); ) {
+ RebaseTodoLine step = it.next();
+ if (step.getAction() == RebaseTodoLine.Action.PICK) {
+ if (skipFirstStep && !skipped) {
+ it.remove();
+ skipped = true;
+ continue;
+ }
+ Collection ids = or.resolve(step.getCommit());
+ if (ids.size() != 1) {
+ throw new JGitInternalException("Could not resolve uniquely the abbreviated object ID");
+ }
+ org.eclipse.jgit.api.CherryPickCommand command = new Git(repository).cherryPick();
+ command.include(ids.iterator().next());
+ res = command.call();
+ if (res.getStatus() == CherryPickResult.CherryPickStatus.OK) {
+ it.remove();
+ writeTodoFile(repository, steps);
+ cherryPickedRefs.addAll(res.getCherryPickedRefs());
+ } else {
+ break;
+ }
+ } else {
+ it.remove();
+ }
+ }
+ if (res == null) {
+ result = createCustomResult(GitCherryPickResult.CherryPickStatus.OK, cherryPickedRefs);
+ } else {
+ result = createResult(res, cherryPickedRefs);
+ }
+ if (steps.isEmpty()) {
+ // sequencer no longer needed
+ Utils.deleteRecursively(getSequencerFolder());
+ }
+ }
+
+ private GitCherryPickResult createResult (CherryPickResult res) {
+ return createResult(res, Collections.][emptyList());
+ }
+
+ private GitCherryPickResult createResult (CherryPickResult res, List][ cherryPickedRefs) {
+ GitRevisionInfo currHead = getCurrentHead();
+
+ GitCherryPickResult.CherryPickStatus status = GitCherryPickResult.CherryPickStatus.valueOf(res.getStatus().name());
+ List conflicts;
+ if (res.getStatus() == CherryPickResult.CherryPickStatus.CONFLICTING) {
+ conflicts = getConflicts(currHead);
+ } else {
+ conflicts = Collections.emptyList();
+ }
+ List commits = toCommits(cherryPickedRefs);
+ return getClassFactory().createCherryPickResult(status, conflicts, getFailures(res), currHead, commits);
+ }
+
+ private List toCommits (List][ cherryPickedRefs) {
+ List commits = new ArrayList<>(cherryPickedRefs.size());
+ Repository repository = getRepository();
+ RevWalk walk = new RevWalk(repository);
+ for (Ref ref : cherryPickedRefs) {
+ try {
+ commits.add(getClassFactory().createRevisionInfo(Utils.findCommit(repository,
+ ref.getLeaf().getObjectId(), walk), repository));
+ } catch (GitException ex) {
+ Logger.getLogger(CherryPickCommand.class.getName()).log(Level.INFO, null, ex);
+ }
+ }
+ return commits;
+ }
+
+ private GitRevisionInfo getCurrentHead () {
+ GitRevisionInfo currHead;
+ Repository repository = getRepository();
+ try {
+ currHead = getClassFactory().createRevisionInfo(Utils.findCommit(repository, Constants.HEAD), repository);
+ } catch (GitException ex) {
+ currHead = null;
+ }
+ return currHead;
+ }
+
+ private GitCherryPickResult createCustomResult (GitCherryPickResult.CherryPickStatus status) {
+ return createCustomResult(status, Collections.][emptyList());
+ }
+
+ private GitCherryPickResult createCustomResult (GitCherryPickResult.CherryPickStatus status, List][ cherryPickedRefs) {
+ return getClassFactory().createCherryPickResult(status, Collections.emptyList(),
+ Collections.emptyList(), getCurrentHead(), toCommits(cherryPickedRefs));
+ }
+
+ private List getConflicts (GitRevisionInfo info) {
+ List conflicts;
+ try {
+ Repository repository = getRepository();
+ Map modifiedFiles = info.getModifiedFiles();
+ ConflictCommand cmd = new ConflictCommand(repository, getClassFactory(), modifiedFiles.keySet().toArray(
+ new File[modifiedFiles.keySet().size()]),
+ new DelegatingGitProgressMonitor(monitor),
+ new StatusListener() {
+ @Override
+ public void notifyStatus (GitStatus status) { }
+ });
+ cmd.execute();
+ Map statuses = cmd.getStatuses();
+ conflicts = new ArrayList<>(statuses.size());
+ for (Map.Entry e : statuses.entrySet()) {
+ if (e.getValue().isConflict()) {
+ conflicts.add(e.getKey());
+ }
+ }
+ } catch (GitException ex) {
+ Logger.getLogger(CherryPickCommand.class.getName()).log(Level.INFO, null, ex);
+ conflicts = Collections.emptyList();
+ }
+ return conflicts;
+ }
+
+ private List getFailures (CherryPickResult result) {
+ List files = new ArrayList<>();
+ File workDir = getRepository().getWorkTree();
+ if (result.getStatus() == CherryPickResult.CherryPickStatus.FAILED) {
+ Map obstructions = result.getFailingPaths();
+ if (obstructions != null) {
+ for (Map.Entry failure : obstructions.entrySet()) {
+ files.add(new File(workDir, failure.getKey()));
+ }
+ }
+ }
+ return Collections.unmodifiableList(files);
+ }
+
+ private File getSequencerFolder () {
+ return new File(getRepository().getDirectory(), SEQUENCER);
+ }
+
+ private ObjectId getOriginalCommit () throws GitException {
+ Repository repository = getRepository();
+ File seqHead = new File(getSequencerFolder(), SEQUENCER_HEAD);
+ ObjectId originalCommitId = null;
+ if (seqHead.canRead()) {
+ try {
+ byte[] content = IO.readFully(seqHead);
+ if (content.length > 0) {
+ originalCommitId = ObjectId.fromString(content, 0);
+ }
+ if (originalCommitId != null) {
+ originalCommitId = repository.resolve(originalCommitId.getName() + "^{commit}");
+ }
+ } catch (IOException e) {
+ }
+ }
+ return originalCommitId;
+ }
+
+ private ObjectId getHead () throws GitException {
+ return Utils.findCommit(getRepository(), Constants.HEAD);
+ }
+
+ private List prepareCommand (ObjectId head) throws GitException, IOException {
+ Repository repository = getRepository();
+ ObjectReader or = repository.newObjectReader();
+ RevWalk walk = new RevWalk(or);
+ List commits = new ArrayList<>(revisions.length);
+ for (String rev : revisions) {
+ RevCommit commit = Utils.findCommit(repository, rev, walk);
+ commits.add(commit);
+ }
+ List steps = new ArrayList<>(commits.size());
+ if (commits.size() == 1) {
+ RevCommit commit = commits.get(0);
+ steps.add(new RebaseTodoLine(RebaseTodoLine.Action.PICK,
+ or.abbreviate(commit), commit.getShortMessage()));
+ } else if (!commits.isEmpty()) {
+ File sequencer = getSequencerFolder();
+ sequencer.mkdirs();
+ try {
+ for (RevCommit commit : commits) {
+ steps.add(new RebaseTodoLine(RebaseTodoLine.Action.PICK,
+ or.abbreviate(commit), commit.getShortMessage()));
+ }
+ writeTodoFile(repository, steps);
+ writeFile(new File(sequencer, SEQUENCER_HEAD), head);
+ } catch (IOException ex) {
+ Utils.deleteRecursively(sequencer);
+ throw new GitException(ex);
+ }
+ }
+ return steps;
+ }
+
+ private void writeFile (File file, ObjectId id) throws IOException {
+ try (BufferedOutputStream bos = new SafeBufferedOutputStream(new FileOutputStream(file))) {
+ id.copyTo(bos);
+ bos.write('\n');
+ }
+ }
+
+ private void writeTodoFile (Repository repository, List steps) throws IOException {
+ File f = new File(repository.getDirectory(), SEQUENCER);
+ if (f.canWrite()) {
+ RebaseTodoFile todoFile = new RebaseTodoFile(repository);
+ todoFile.writeRebaseTodoFile(SEQUENCER + File.separator + SEQUENCER_TODO, steps, false);
+ }
+ }
+
+ private List readTodoFile (Repository repository) throws IOException {
+ String path = SEQUENCER + File.separator + SEQUENCER_TODO;
+ File f = new File(repository.getDirectory(), path);
+ if (f.canRead()) {
+ RebaseTodoFile todoFile = new RebaseTodoFile(repository);
+ return todoFile.readRebaseTodo(SEQUENCER + File.separator + SEQUENCER_TODO, true);
+ }
+ return Collections.emptyList();
+ }
+}
--- a/libs.git/src/org/netbeans/libs/git/jgit/commands/CommitCommand.java Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/src/org/netbeans/libs/git/jgit/commands/CommitCommand.java Tue Jul 01 13:16:35 2014 +0200
@@ -57,7 +57,6 @@
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException;
-import org.eclipse.jgit.errors.UnmergedPathException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
@@ -105,11 +104,17 @@
boolean retval = super.prepareCommand();
if (retval) {
RepositoryState state = getRepository().getRepositoryState();
- if (RepositoryState.MERGING.equals(state)) {
+ if (amend && !state.canAmend()) {
+ String errorMessage = Utils.getBundle(CommitCommand.class).getString("MSG_Error_Commit_CannotAmend"); //NOI18N
+ monitor.preparationsFailed(errorMessage);
+ throw new GitException(errorMessage);
+ }
+ if (RepositoryState.MERGING.equals(state) || RepositoryState.CHERRY_PICKING.equals(state)) {
String errorMessage = Utils.getBundle(CommitCommand.class).getString("MSG_Error_Commit_ConflictsInIndex"); //NOI18N
monitor.preparationsFailed(errorMessage);
throw new GitException(errorMessage);
- } else if (RepositoryState.MERGING_RESOLVED.equals(state) && roots.length > 0) {
+ } else if ((RepositoryState.MERGING_RESOLVED.equals(state)
+ || RepositoryState.CHERRY_PICKING_RESOLVED.equals(state)) && roots.length > 0) {
boolean fullWorkingTree = false;
File repositoryRoot = getRepository().getWorkTree();
for (File root : roots) {
@@ -149,10 +154,7 @@
if(commiter != null) {
commit.setCommitter(commiter.getName(), commiter.getEmailAddress());
}
- if (amend) {
- RevCommit lastCommit = Utils.findCommit(repository, "HEAD^{commit}");
- transferTimestamp(commit, lastCommit);
- }
+ setAuthorshipIfNeeded(repository, commit);
commit.setMessage(message);
commit.setAmend(amend);
@@ -170,18 +172,20 @@
}
}
}
- } catch (GitAPIException ex) {
+ } catch (GitAPIException | JGitInternalException | NoWorkTreeException | IOException ex) {
throw new GitException(ex);
- } catch (UnmergedPathException ex) {
- throw new GitException(ex);
- } catch (JGitInternalException ex) {
- throw new GitException(ex);
- } catch (NoWorkTreeException ex) {
- throw new GitException(ex);
- } catch (CorruptObjectException ex) {
- throw new GitException(ex);
- } catch (IOException ex) {
- throw new GitException(ex);
+ }
+ }
+
+ private void setAuthorshipIfNeeded (Repository repository, org.eclipse.jgit.api.CommitCommand cmd)
+ throws GitException, NoWorkTreeException, IOException {
+ if (amend) {
+ RevCommit lastCommit = Utils.findCommit(repository, "HEAD^{commit}");
+ transferTimestamp(cmd, lastCommit);
+ }
+ if (repository.getRepositoryState() == RepositoryState.CHERRY_PICKING_RESOLVED) {
+ RevCommit lastCommit = Utils.findCommit(repository, repository.readCherryPickHead(), null);
+ transferTimestamp(cmd, lastCommit);
}
}
--- a/libs.git/src/org/netbeans/libs/git/jgit/commands/RebaseCommand.java Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/src/org/netbeans/libs/git/jgit/commands/RebaseCommand.java Tue Jul 01 13:16:35 2014 +0200
@@ -162,8 +162,8 @@
Repository repository = getRepository();
GitRevisionInfo info = getClassFactory().createRevisionInfo(currentCommit, repository);
Map modifiedFiles = info.getModifiedFiles();
- StatusCommand cmd = new StatusCommand(repository, Constants.HEAD, modifiedFiles.keySet().toArray(
- new File[modifiedFiles.keySet().size()]), getClassFactory(),
+ ConflictCommand cmd = new ConflictCommand(repository, getClassFactory(), modifiedFiles.keySet().toArray(
+ new File[modifiedFiles.keySet().size()]),
new DelegatingGitProgressMonitor(monitor),
new StatusListener() {
@Override
--- a/libs.git/test/unit/src/org/netbeans/libs/git/GitEnumsStateTest.java Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/test/unit/src/org/netbeans/libs/git/GitEnumsStateTest.java Tue Jul 01 13:16:35 2014 +0200
@@ -43,6 +43,7 @@
package org.netbeans.libs.git;
import java.io.IOException;
+import org.eclipse.jgit.api.CherryPickResult;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.RebaseResult;
import org.eclipse.jgit.lib.RefUpdate;
@@ -96,4 +97,10 @@
assertNotNull(GitSubmoduleStatus.parseStatus(status));
}
}
+
+ public void testCherryPickStatus () {
+ for (CherryPickResult.CherryPickStatus status : CherryPickResult.CherryPickStatus.values()) {
+ assertNotNull(GitCherryPickResult.CherryPickStatus.valueOf(status.name()));
+ }
+ }
}
--- a/libs.git/test/unit/src/org/netbeans/libs/git/jgit/CommandsTestSuite.java Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/test/unit/src/org/netbeans/libs/git/jgit/CommandsTestSuite.java Tue Jul 01 13:16:35 2014 +0200
@@ -51,6 +51,7 @@
import org.netbeans.libs.git.jgit.commands.BranchTest;
import org.netbeans.libs.git.jgit.commands.CatTest;
import org.netbeans.libs.git.jgit.commands.CheckoutTest;
+import org.netbeans.libs.git.jgit.commands.CherryPickTest;
import org.netbeans.libs.git.jgit.commands.CleanTest;
import org.netbeans.libs.git.jgit.commands.CommitTest;
import org.netbeans.libs.git.jgit.commands.CompareCommitTest;
@@ -100,6 +101,7 @@
suite.addTestSuite(BranchTest.class);
suite.addTestSuite(CatTest.class);
suite.addTestSuite(CheckoutTest.class);
+ suite.addTestSuite(CherryPickTest.class);
suite.addTestSuite(CleanTest.class);
suite.addTestSuite(CommitTest.class);
suite.addTestSuite(CompareCommitTest.class);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ d68b118f158f Tue Jul 01 13:16:35 2014 +0200
@@ -0,0 +1,359 @@
+package org.netbeans.libs.git.jgit.commands;
+
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryState;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.netbeans.libs.git.GitCherryPickResult;
+import org.netbeans.libs.git.GitClient;
+import org.netbeans.libs.git.GitRevisionInfo;
+import org.netbeans.libs.git.GitStatus.Status;
+import org.netbeans.libs.git.SearchCriteria;
+import org.netbeans.libs.git.jgit.AbstractGitTestCase;
+import org.netbeans.libs.git.jgit.Utils;
+
+/**
+ *
+ * @author ondra
+ */
+public class CherryPickTest extends AbstractGitTestCase {
+
+ private File workDir;
+ private Repository repository;
+ private static final String BRANCH = "b";
+
+ public CherryPickTest (String testName) throws IOException {
+ super(testName);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ workDir = getWorkingDirectory();
+ repository = getRepository(getLocalGitRepository());
+ }
+
+ public void testCherryPickCommit () throws Exception {
+ File f = new File(workDir, "f");
+ write(f, "init");
+ add(f);
+ commit(f);
+
+ GitClient client = getClient(workDir);
+ client.createBranch(BRANCH, Constants.MASTER, NULL_PROGRESS_MONITOR);
+ client.checkoutRevision(BRANCH, true, NULL_PROGRESS_MONITOR);
+
+ write(f, "change on branch");
+ add(f);
+ GitRevisionInfo c = client.commit(new File[] { f }, "on branch", null, null, NULL_PROGRESS_MONITOR);
+
+ client.checkoutRevision(Constants.MASTER, true, NULL_PROGRESS_MONITOR);
+ GitRevisionInfo initCommit = client.log("HEAD", NULL_PROGRESS_MONITOR);
+
+ Thread.sleep(1100);
+
+ GitCherryPickResult res = client.cherryPick(GitClient.CherryPickOperation.BEGIN, new String[] { BRANCH }, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.OK, res.getCherryPickStatus());
+ assertEquals(initCommit.getRevision(), res.getCurrentHead().getParents()[0]);
+ assertEquals(c.getCommitTime(), res.getCurrentHead().getCommitTime());
+ assertEquals(1, res.getCherryPickedCommits().length);
+ assertEquals(c.getRevision(), res.getCherryPickedCommits()[0].getRevision());
+ assertFalse(new File(workDir, ".git/sequencer").exists());
+ }
+
+ public void testCherryPickCommits () throws Exception {
+ File f = new File(workDir, "f");
+ write(f, "init");
+ add(f);
+ commit(f);
+
+ File[] roots = new File[] { f };
+
+ GitClient client = getClient(workDir);
+ client.createBranch(BRANCH, Constants.MASTER, NULL_PROGRESS_MONITOR);
+ client.checkoutRevision(BRANCH, true, NULL_PROGRESS_MONITOR);
+
+ write(f, "change 1 on branch");
+ add(f);
+ GitRevisionInfo c1 = client.commit(roots, "on branch 1", null, null, NULL_PROGRESS_MONITOR);
+ write(f, "change 2 on branch");
+ add(f);
+ GitRevisionInfo c2 = client.commit(roots, "on branch 2", null, null, NULL_PROGRESS_MONITOR);
+
+ client.checkoutRevision(Constants.MASTER, true, NULL_PROGRESS_MONITOR);
+ GitRevisionInfo initCommit = client.log("HEAD", NULL_PROGRESS_MONITOR);
+
+ Thread.sleep(1100);
+
+ GitCherryPickResult res = client.cherryPick(GitClient.CherryPickOperation.BEGIN,
+ new String[] { c1.getRevision(), c2.getRevision() }, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.OK, res.getCherryPickStatus());
+ SearchCriteria sc = new SearchCriteria();
+ sc.setRevisionTo("HEAD");
+ GitRevisionInfo[] logs = client.log(sc, NULL_PROGRESS_MONITOR);
+ assertEquals(3, logs.length);
+ assertEquals(c2.getFullMessage(), logs[0].getFullMessage());
+ assertEquals(c2.getCommitTime(), logs[0].getCommitTime());
+ assertEquals(c1.getFullMessage(), logs[1].getFullMessage());
+ assertEquals(c1.getCommitTime(), logs[1].getCommitTime());
+ assertEquals(initCommit.getRevision(), logs[2].getRevision());
+
+ assertEquals(2, res.getCherryPickedCommits().length);
+ assertEquals(c1.getRevision(), res.getCherryPickedCommits()[0].getRevision());
+ assertEquals(c2.getRevision(), res.getCherryPickedCommits()[1].getRevision());
+ assertFalse(new File(workDir, ".git/sequencer").exists());
+ }
+
+ public void testCherryPickFailure () throws Exception {
+ File f = new File(workDir, "f");
+ write(f, "init");
+ add(f);
+ commit(f);
+
+ File[] roots = new File[] { f };
+
+ GitClient client = getClient(workDir);
+ client.createBranch(BRANCH, Constants.MASTER, NULL_PROGRESS_MONITOR);
+ client.checkoutRevision(BRANCH, true, NULL_PROGRESS_MONITOR);
+
+ write(f, "change on branch");
+ add(f);
+ GitRevisionInfo c = client.commit(roots, "on branch", null, null, NULL_PROGRESS_MONITOR);
+
+ client.checkoutRevision(Constants.MASTER, true, NULL_PROGRESS_MONITOR);
+ // make modification so cherry-pick cannot start
+ write(f, "change in master");
+
+ GitCherryPickResult res = client.cherryPick(GitClient.CherryPickOperation.BEGIN, new String[] { BRANCH }, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.FAILED, res.getCherryPickStatus());
+ assertEquals(0, res.getCurrentHead().getParents().length);
+ }
+
+ public void testCherryPickCommitsConflictAbort () throws Exception {
+ File f = new File(workDir, "f");
+ write(f, "init");
+ add(f);
+ commit(f);
+
+ File[] roots = new File[] { f };
+
+ GitClient client = getClient(workDir);
+ client.createBranch(BRANCH, Constants.MASTER, NULL_PROGRESS_MONITOR);
+ client.checkoutRevision(BRANCH, true, NULL_PROGRESS_MONITOR);
+
+ write(f, "change 1 on branch");
+ add(f);
+ GitRevisionInfo c1 = client.commit(roots, "on branch 1", null, null, NULL_PROGRESS_MONITOR);
+ write(f, "change 2 on branch");
+ add(f);
+ GitRevisionInfo c2 = client.commit(roots, "on branch 2", null, null, NULL_PROGRESS_MONITOR);
+ write(f, "change 3 on branch");
+ add(f);
+ GitRevisionInfo c3 = client.commit(roots, "on branch 3", null, null, NULL_PROGRESS_MONITOR);
+
+ client.checkoutRevision(Constants.MASTER, true, NULL_PROGRESS_MONITOR);
+ GitRevisionInfo initCommit = client.log("HEAD", NULL_PROGRESS_MONITOR);
+ GitCherryPickResult res = client.cherryPick(GitClient.CherryPickOperation.BEGIN,
+ new String[] { c1.getRevision(), c3.getRevision() }, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.CONFLICTING, res.getCherryPickStatus());
+ assertEquals(initCommit.getRevision(), res.getCurrentHead().getParents()[0]);
+
+ res = client.cherryPick(GitClient.CherryPickOperation.ABORT, null, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.ABORTED, res.getCherryPickStatus());
+ assertEquals(initCommit.getRevision(), res.getCurrentHead().getRevision());
+ assertStatus(client.getStatus(roots, NULL_PROGRESS_MONITOR), workDir, f, true,
+ Status.STATUS_NORMAL, Status.STATUS_NORMAL, Status.STATUS_NORMAL, false);
+ }
+
+ public void testCherryPickCommitsConflictQuit () throws Exception {
+ File f = new File(workDir, "f");
+ write(f, "init");
+ add(f);
+ commit(f);
+
+ File[] roots = new File[] { f };
+
+ GitClient client = getClient(workDir);
+ client.createBranch(BRANCH, Constants.MASTER, NULL_PROGRESS_MONITOR);
+ client.checkoutRevision(BRANCH, true, NULL_PROGRESS_MONITOR);
+
+ write(f, "change 1 on branch");
+ add(f);
+ GitRevisionInfo c1 = client.commit(roots, "on branch 1\nblablabla", null, null, NULL_PROGRESS_MONITOR);
+ write(f, "change 2 on branch");
+ add(f);
+ GitRevisionInfo c2 = client.commit(roots, "on branch 2", null, null, NULL_PROGRESS_MONITOR);
+ write(f, "change 3 on branch");
+ add(f);
+ GitRevisionInfo c3 = client.commit(roots, "on branch 3\nBLABLABLA", null, null, NULL_PROGRESS_MONITOR);
+
+ client.checkoutRevision(Constants.MASTER, true, NULL_PROGRESS_MONITOR);
+ GitRevisionInfo initCommit = client.log("HEAD", NULL_PROGRESS_MONITOR);
+ GitCherryPickResult res = client.cherryPick(GitClient.CherryPickOperation.BEGIN,
+ new String[] { c1.getRevision(), c3.getRevision() }, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.CONFLICTING, res.getCherryPickStatus());
+ assertEquals(initCommit.getRevision(), res.getCurrentHead().getParents()[0]);
+
+ ObjectReader or = repository.newObjectReader();
+ RevCommit commit = Utils.findCommit(repository, c3.getRevision());
+ assertEquals("pick " + or.abbreviate(commit).name() + " " + commit.getShortMessage(),
+ read(new File(repository.getDirectory(), "sequencer/todo")));
+
+ res = client.cherryPick(GitClient.CherryPickOperation.QUIT, null, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.CONFLICTING, res.getCherryPickStatus());
+ assertEquals(initCommit.getRevision(), res.getCurrentHead().getParents()[0]);
+ assertStatus(client.getStatus(roots, NULL_PROGRESS_MONITOR), workDir, f, true,
+ Status.STATUS_NORMAL, Status.STATUS_NORMAL, Status.STATUS_NORMAL, true);
+ assertEquals(RepositoryState.CHERRY_PICKING, repository.getRepositoryState());
+
+ res = client.cherryPick(GitClient.CherryPickOperation.ABORT, null, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.ABORTED, res.getCherryPickStatus());
+ assertEquals(initCommit.getRevision(), res.getCurrentHead().getParents()[0]);
+ assertStatus(client.getStatus(roots, NULL_PROGRESS_MONITOR), workDir, f, true,
+ Status.STATUS_NORMAL, Status.STATUS_NORMAL, Status.STATUS_NORMAL, false);
+ }
+
+ public void testCherryPickCommitConflictResolve () throws Exception {
+ File f = new File(workDir, "f");
+ write(f, "init");
+ add(f);
+ commit(f);
+
+ File[] roots = new File[] { f };
+
+ GitClient client = getClient(workDir);
+ client.createBranch(BRANCH, Constants.MASTER, NULL_PROGRESS_MONITOR);
+ client.checkoutRevision(BRANCH, true, NULL_PROGRESS_MONITOR);
+
+ write(f, "change 1 on branch");
+ add(f);
+ GitRevisionInfo c1 = client.commit(roots, "on branch 1", null, null, NULL_PROGRESS_MONITOR);
+ write(f, "change 2 on branch");
+ add(f);
+ GitRevisionInfo c2 = client.commit(roots, "on branch 2", null, null, NULL_PROGRESS_MONITOR);
+
+ client.checkoutRevision(Constants.MASTER, true, NULL_PROGRESS_MONITOR);
+ GitRevisionInfo initCommit = client.log("HEAD", NULL_PROGRESS_MONITOR);
+ GitCherryPickResult res = client.cherryPick(GitClient.CherryPickOperation.BEGIN,
+ new String[] { c2.getRevision() }, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.CONFLICTING, res.getCherryPickStatus());
+ assertEquals(initCommit.getRevision(), res.getCurrentHead().getRevision());
+ assertFalse(new File(repository.getDirectory(), "sequencer").exists());
+
+ write(f, "init\nchange 2 on branch");
+ add(f);
+
+ // try continue, should interrupt and ask for commit
+ res = client.cherryPick(GitClient.CherryPickOperation.CONTINUE, null, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.UNCOMMITTED, res.getCherryPickStatus());
+ assertEquals(initCommit.getRevision(), res.getCurrentHead().getRevision());
+ assertFalse(new File(repository.getDirectory(), "sequencer").exists());
+
+ GitRevisionInfo commit = client.commit(new File[0], c2.getFullMessage(), null, null, NULL_PROGRESS_MONITOR);
+ assertEquals(c2.getCommitTime(), commit.getCommitTime());
+ assertEquals(RepositoryState.SAFE, repository.getRepositoryState());
+ assertFalse(new File(repository.getDirectory(), "sequencer").exists());
+
+ res = client.cherryPick(GitClient.CherryPickOperation.CONTINUE, null, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.OK, res.getCherryPickStatus());
+ }
+
+ public void testCherryPickCommitsConflictResolve () throws Exception {
+ File f = new File(workDir, "f");
+ write(f, "init");
+ add(f);
+ commit(f);
+
+ File[] roots = new File[] { f };
+
+ GitClient client = getClient(workDir);
+ client.createBranch(BRANCH, Constants.MASTER, NULL_PROGRESS_MONITOR);
+ client.checkoutRevision(BRANCH, true, NULL_PROGRESS_MONITOR);
+
+ write(f, "change 1 on branch");
+ add(f);
+ GitRevisionInfo c1 = client.commit(roots, "on branch 1", null, null, NULL_PROGRESS_MONITOR);
+ write(f, "init\nchange 2 on branch");
+ add(f);
+ GitRevisionInfo c2 = client.commit(roots, "on branch 2", null, null, NULL_PROGRESS_MONITOR);
+ write(f, "init\nchange 3 on branch");
+ add(f);
+ GitRevisionInfo c3 = client.commit(roots, "on branch 3", null, null, NULL_PROGRESS_MONITOR);
+
+ Thread.sleep(1100);
+
+ client.checkoutRevision(Constants.MASTER, true, NULL_PROGRESS_MONITOR);
+ GitRevisionInfo initCommit = client.log("HEAD", NULL_PROGRESS_MONITOR);
+ GitCherryPickResult res = client.cherryPick(GitClient.CherryPickOperation.BEGIN,
+ new String[] { c2.getRevision(), c3.getRevision() }, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.CONFLICTING, res.getCherryPickStatus());
+ assertEquals(initCommit.getRevision(), res.getCurrentHead().getRevision());
+
+ write(f, "init\nchange 2 on branch");
+ add(f);
+
+ // try continue, should interrupt and ask for commit
+ res = client.cherryPick(GitClient.CherryPickOperation.CONTINUE, null, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.UNCOMMITTED, res.getCherryPickStatus());
+ assertEquals(initCommit.getRevision(), res.getCurrentHead().getRevision());
+ assertTrue(new File(repository.getDirectory(), "sequencer").exists());
+
+ GitRevisionInfo commit = client.commit(new File[0], c2.getFullMessage(), null, null, NULL_PROGRESS_MONITOR);
+ assertEquals(c2.getCommitTime(), commit.getCommitTime());
+ assertEquals(RepositoryState.SAFE, repository.getRepositoryState());
+ assertTrue(new File(repository.getDirectory(), "sequencer").exists());
+
+ res = client.cherryPick(GitClient.CherryPickOperation.CONTINUE, null, NULL_PROGRESS_MONITOR);
+ assertEquals(GitCherryPickResult.CherryPickStatus.OK, res.getCherryPickStatus());
+ assertEquals(commit.getRevision(), res.getCurrentHead().getParents()[0]);
+ assertEquals(c3.getCommitTime(), res.getCurrentHead().getCommitTime());
+ assertFalse(new File(repository.getDirectory(), "sequencer").exists());
+ assertEquals(1, res.getCherryPickedCommits().length);
+ assertEquals(c3.getRevision(), res.getCherryPickedCommits()[0].getRevision());
+ }
+}
--- a/libs.git/test/unit/src/org/netbeans/libs/git/jgit/commands/CommitTest.java Mon Jun 30 12:40:30 2014 +0200
+++ a/libs.git/test/unit/src/org/netbeans/libs/git/jgit/commands/CommitTest.java Tue Jul 01 13:16:35 2014 +0200
@@ -679,4 +679,43 @@
assertEquals(info.getRevision(), lastCommit.getParents()[0]);
assertEquals(lastCommit.getRevision(), client.getBranches(false, NULL_PROGRESS_MONITOR).get("master").getId());
}
+
+ public void testCherryPickCommit () throws Exception {
+ repository.getConfig().setString("user", null, "name", "John");
+ repository.getConfig().setString("user", null, "email", "john@git.com");
+ repository.getConfig().save();
+
+ File f = new File(workDir, "f");
+ write(f, "init");
+ File[] files = new File[] { f };
+
+ add(f);
+ commit(f);
+
+ GitClient client = getClient(workDir);
+ write(f, "change");
+ add(f);
+ GitRevisionInfo info = client.commit(files, "change to CherryPick", null, null, NULL_PROGRESS_MONITOR);
+
+ Thread.sleep(1100);
+
+ client.reset("HEAD~1", GitClient.ResetType.MIXED, NULL_PROGRESS_MONITOR);
+ repository.writeCherryPickHead(repository.resolve(info.getRevision()));
+
+ // now we are cherry-picking
+ // amend is not allowed
+ try {
+ client.commit(new File[0], info.getFullMessage(), null, null, true, NULL_PROGRESS_MONITOR);
+ fail("Amend not allowed");
+ } catch (GitException ex) {
+ assertEquals(Utils.getBundle(CommitCommand.class).getString("MSG_Error_Commit_CannotAmend"), ex.getMessage());
+ }
+
+ // doing commit should preserve authorship of the original commit (info)
+ GitRevisionInfo commit = client.commit(new File[0], info.getFullMessage(), null, null, NULL_PROGRESS_MONITOR);
+ assertEquals(info.getAuthor(), commit.getAuthor());
+ assertEquals(info.getCommitTime(), commit.getCommitTime());
+ assertEquals(Utils.findCommit(repository, info.getRevision()).getAuthorIdent().getWhen(),
+ Utils.findCommit(repository, commit.getRevision()).getAuthorIdent().getWhen());
+ }
}
]