return update(ref, to.create());
}
+ /**
+ * Amend an existing ref.
+ *
+ * @param ref
+ * the name of the reference to amend, which must already exist.
+ * If {@code ref} does not start with {@code refs/} and is not the
+ * magic names {@code HEAD} {@code FETCH_HEAD} or {@code
+ * MERGE_HEAD}, then {@code refs/heads/} will be prefixed in front
+ * of the given name, thereby assuming it is a branch.
+ * @return commit builder that amends the branch on commit.
+ * @throws Exception
+ */
+ public CommitBuilder amendRef(String ref) throws Exception {
+ String name = normalizeRef(ref);
+ Ref r = db.getRef(name);
+ if (r == null)
+ throw new IOException("Not a ref: " + ref);
+ return amend(pool.parseCommit(r.getObjectId()), branch(name).commit());
+ }
+
+ /**
+ * Amend an existing commit.
+ *
+ * @param id
+ * the id of the commit to amend.
+ * @return commit builder.
+ * @throws Exception
+ */
+ public CommitBuilder amend(AnyObjectId id) throws Exception {
+ return amend(pool.parseCommit(id), commit());
+ }
+
+ private CommitBuilder amend(RevCommit old, CommitBuilder b) throws Exception {
+ pool.parseBody(old);
+ b.author(old.getAuthorIdent());
+ b.committer(old.getCommitterIdent());
+ b.message(old.getFullMessage());
+ // Use the committer name from the old commit, but update it after ticking
+ // the clock in CommitBuilder#create().
+ b.updateCommitterTime = true;
+
+ // Reset parents to original parents.
+ b.noParents();
+ for (int i = 0; i < old.getParentCount(); i++)
+ b.parent(old.getParent(i));
+
+ // Reset tree to original tree; resetting parents reset tree contents to the
+ // first parent.
+ b.tree.clear();
+ try (TreeWalk tw = new TreeWalk(db)) {
+ tw.reset(old.getTree());
+ tw.setRecursive(true);
+ while (tw.next()) {
+ b.edit(new PathEdit(tw.getPathString()) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(tw.getFileMode(0));
+ ent.setObjectId(tw.getObjectId(0));
+ }
+ });
+ }
+ }
+
+ return b;
+ }
+
/**
* Update a reference to point to an object.
*
* @throws Exception
*/
public <T extends AnyObjectId> T update(String ref, T obj) throws Exception {
- if (Constants.HEAD.equals(ref)) {
- // nothing
- } else if ("FETCH_HEAD".equals(ref)) {
- // nothing
- } else if ("MERGE_HEAD".equals(ref)) {
- // nothing
- } else if (ref.startsWith(Constants.R_REFS)) {
- // nothing
- } else
- ref = Constants.R_HEADS + ref;
-
+ ref = normalizeRef(ref);
RefUpdate u = db.updateRef(ref);
u.setNewObjectId(obj);
switch (u.forceUpdate()) {
}
}
+ private static String normalizeRef(String ref) {
+ if (Constants.HEAD.equals(ref)) {
+ // nothing
+ } else if ("FETCH_HEAD".equals(ref)) {
+ // nothing
+ } else if ("MERGE_HEAD".equals(ref)) {
+ // nothing
+ } else if (ref.startsWith(Constants.R_REFS)) {
+ // nothing
+ } else
+ ref = Constants.R_HEADS + ref;
+ return ref;
+ }
+
/**
* Soft-reset HEAD to a detached state.
* <p>
private boolean insertChangeId;
+ private boolean updateCommitterTime;
+
CommitBuilder() {
branch = null;
}
setAuthorAndCommitter(c);
if (author != null)
c.setAuthor(author);
- if (committer != null)
+ if (committer != null) {
+ if (updateCommitterTime)
+ committer = new PersonIdent(committer, new Date(now));
c.setCommitter(committer);
+ }
ObjectId commitId;
try (ObjectInserter ins = inserter) {
package org.eclipse.jgit.junit;
+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.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import java.util.Date;
import java.util.regex.Pattern;
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.ObjectLoader;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@After
public void tearDown() {
rw.close();
- tr.getRepository().close();
+ repo.close();
}
@Test
assertEquals(detached, head.getObjectId());
assertFalse(head.isSymbolic());
}
+
+ @Test
+ public void amendRef() throws Exception {
+ RevCommit root = tr.commit()
+ .add("todelete", "to be deleted")
+ .create();
+ RevCommit orig = tr.commit().parent(root)
+ .rm("todelete")
+ .add("foo", "foo contents")
+ .add("bar", "bar contents")
+ .add("dir/baz", "baz contents")
+ .create();
+ rw.parseBody(orig);
+ tr.branch("master").update(orig);
+ assertEquals("foo contents", blobAsString(orig, "foo"));
+ assertEquals("bar contents", blobAsString(orig, "bar"));
+ assertEquals("baz contents", blobAsString(orig, "dir/baz"));
+
+ RevCommit amended = tr.amendRef("master")
+ .tick(3)
+ .add("bar", "fixed bar contents")
+ .create();
+ assertEquals(amended, repo.getRef("refs/heads/master").getObjectId());
+ rw.parseBody(amended);
+
+ assertEquals(1, amended.getParentCount());
+ assertEquals(root, amended.getParent(0));
+ assertEquals(orig.getFullMessage(), amended.getFullMessage());
+ assertEquals(orig.getAuthorIdent(), amended.getAuthorIdent());
+
+ // Committer name/email is the same, but time was incremented.
+ assertEquals(new PersonIdent(orig.getCommitterIdent(), new Date(0)),
+ new PersonIdent(amended.getCommitterIdent(), new Date(0)));
+ assertTrue(orig.getCommitTime() < amended.getCommitTime());
+
+ assertEquals("foo contents", blobAsString(amended, "foo"));
+ assertEquals("fixed bar contents", blobAsString(amended, "bar"));
+ assertEquals("baz contents", blobAsString(amended, "dir/baz"));
+ assertNull(TreeWalk.forPath(repo, "todelete", amended.getTree()));
+ }
+
+ @Test
+ public void amendHead() throws Exception {
+ repo.updateRef("HEAD").link("refs/heads/master");
+ RevCommit root = tr.commit()
+ .add("foo", "foo contents")
+ .create();
+ RevCommit orig = tr.commit().parent(root)
+ .message("original message")
+ .add("bar", "bar contents")
+ .create();
+ tr.branch("master").update(orig);
+
+ RevCommit amended = tr.amendRef("HEAD")
+ .add("foo", "fixed foo contents")
+ .create();
+
+ Ref head = repo.getRef(Constants.HEAD);
+ assertEquals(amended, head.getObjectId());
+ assertTrue(head.isSymbolic());
+ assertEquals("refs/heads/master", head.getTarget().getName());
+
+ rw.parseBody(amended);
+ assertEquals("original message", amended.getFullMessage());
+ assertEquals("fixed foo contents", blobAsString(amended, "foo"));
+ assertEquals("bar contents", blobAsString(amended, "bar"));
+ }
+
+ @Test
+ public void amendCommit() throws Exception {
+ RevCommit root = tr.commit()
+ .add("foo", "foo contents")
+ .create();
+ RevCommit orig = tr.commit().parent(root)
+ .message("original message")
+ .add("bar", "bar contents")
+ .create();
+ RevCommit amended = tr.amend(orig.copy())
+ .add("foo", "fixed foo contents")
+ .create();
+
+ rw.parseBody(amended);
+ assertEquals("original message", amended.getFullMessage());
+ assertEquals("fixed foo contents", blobAsString(amended, "foo"));
+ assertEquals("bar contents", blobAsString(amended, "bar"));
+ }
+
+ private String blobAsString(AnyObjectId treeish, String path)
+ throws Exception {
+ RevObject obj = tr.get(rw.parseTree(treeish), path);
+ assertSame(RevBlob.class, obj.getClass());
+ ObjectLoader loader = rw.getObjectReader().open(obj);
+ return new String(loader.getCachedBytes(), UTF_8);
+ }
}