import java.io.OutputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.TimeZone;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.lib.RefWriter;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TagBuilder;
+import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
return new Date(now);
}
+ /** @return timezone used for default identities. */
+ public TimeZone getTimeZone() {
+ return defaultCommitter.getTimeZone();
+ }
+
/**
* Adjust the current time that will used by the next commit.
*
}
}
+ /**
+ * Cherry-pick a commit onto HEAD.
+ * <p>
+ * This differs from {@code git cherry-pick} in that it works in a bare
+ * repository. As a result, any merge failure results in an exception, as
+ * there is no way to recover.
+ *
+ * @param id
+ * commit-ish to cherry-pick.
+ * @return newly created commit, or null if no work was done due to the
+ * resulting tree being identical.
+ * @throws Exception
+ */
+ public RevCommit cherryPick(AnyObjectId id) throws Exception {
+ RevCommit commit = pool.parseCommit(id);
+ pool.parseBody(commit);
+ if (commit.getParentCount() != 1)
+ throw new IOException(String.format(
+ "Expected 1 parent for %s, found: %s",
+ id.name(), Arrays.asList(commit.getParents())));
+ RevCommit parent = commit.getParent(0);
+ pool.parseHeaders(parent);
+
+ Ref headRef = db.getRef(Constants.HEAD);
+ if (headRef == null)
+ throw new IOException("Missing HEAD");
+ RevCommit head = pool.parseCommit(headRef.getObjectId());
+
+ ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true);
+ merger.setBase(parent.getTree());
+ if (merger.merge(head, commit)) {
+ if (AnyObjectId.equals(head.getTree(), merger.getResultTreeId()))
+ return null;
+ tick(1);
+ org.eclipse.jgit.lib.CommitBuilder b =
+ new org.eclipse.jgit.lib.CommitBuilder();
+ b.setParentId(head);
+ b.setTreeId(merger.getResultTreeId());
+ b.setAuthor(commit.getAuthorIdent());
+ b.setCommitter(new PersonIdent(defaultCommitter, new Date(now)));
+ b.setMessage(commit.getFullMessage());
+ ObjectId result;
+ try (ObjectInserter ins = inserter) {
+ result = ins.insert(b);
+ ins.flush();
+ }
+ update(Constants.HEAD, result);
+ return pool.parseCommit(result);
+ } else {
+ throw new IOException("Merge conflict");
+ }
+ }
+
/**
* Update the dumb client server info files.
*
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
assertEquals("refs/heads/master", ref.getTarget().getName());
}
+ @Test
+ public void cherryPick() throws Exception {
+ repo.updateRef("HEAD").link("refs/heads/master");
+ RevCommit head = tr.branch("master").commit()
+ .add("foo", "foo contents\n")
+ .create();
+ rw.parseBody(head);
+ RevCommit toPick = tr.commit()
+ .parent(tr.commit().create()) // Can't cherry-pick root.
+ .author(new PersonIdent("Cherrypick Author", "cpa@example.com",
+ tr.getClock(), tr.getTimeZone()))
+ .author(new PersonIdent("Cherrypick Committer", "cpc@example.com",
+ tr.getClock(), tr.getTimeZone()))
+ .message("message to cherry-pick")
+ .add("bar", "bar contents\n")
+ .create();
+ RevCommit result = tr.cherryPick(toPick);
+ rw.parseBody(result);
+
+ Ref headRef = tr.getRepository().getRef("HEAD");
+ assertEquals(result, headRef.getObjectId());
+ assertTrue(headRef.isSymbolic());
+ assertEquals("refs/heads/master", headRef.getLeaf().getName());
+
+ assertEquals(1, result.getParentCount());
+ assertEquals(head, result.getParent(0));
+ assertEquals(toPick.getAuthorIdent(), result.getAuthorIdent());
+
+ // Committer name/email matches default, and time was incremented.
+ assertEquals(new PersonIdent(head.getCommitterIdent(), new Date(0)),
+ new PersonIdent(result.getCommitterIdent(), new Date(0)));
+ assertTrue(toPick.getCommitTime() < result.getCommitTime());
+
+ assertEquals("message to cherry-pick", result.getFullMessage());
+ assertEquals("foo contents\n", blobAsString(result, "foo"));
+ assertEquals("bar contents\n", blobAsString(result, "bar"));
+ }
+
+ @Test
+ public void cherryPickWithContentMerge() throws Exception {
+ RevCommit base = tr.branch("HEAD").commit()
+ .add("foo", "foo contents\n\n")
+ .create();
+ tr.branch("HEAD").commit()
+ .add("foo", "foo contents\n\nlast line\n")
+ .create();
+ RevCommit toPick = tr.commit()
+ .message("message to cherry-pick")
+ .parent(base)
+ .add("foo", "changed foo contents\n\n")
+ .create();
+ RevCommit result = tr.cherryPick(toPick);
+ rw.parseBody(result);
+
+ assertEquals("message to cherry-pick", result.getFullMessage());
+ assertEquals("changed foo contents\n\nlast line\n",
+ blobAsString(result, "foo"));
+ }
+
+ @Test
+ public void cherryPickWithIdenticalContents() throws Exception {
+ RevCommit base = tr.branch("HEAD").commit().add("foo", "foo contents\n")
+ .create();
+ RevCommit head = tr.branch("HEAD").commit()
+ .parent(base)
+ .add("bar", "bar contents\n")
+ .create();
+ RevCommit toPick = tr.commit()
+ .parent(base)
+ .message("message to cherry-pick")
+ .add("bar", "bar contents\n")
+ .create();
+ assertNotEquals(head, toPick);
+ assertNull(tr.cherryPick(toPick));
+ assertEquals(head, repo.getRef("HEAD").getObjectId());
+ }
+
private String blobAsString(AnyObjectId treeish, String path)
throws Exception {
RevObject obj = tr.get(rw.parseTree(treeish), path);