From da43d8d79890e561a993a4d90e6a2724a04cd60f Mon Sep 17 00:00:00 2001 From: Christian Halstrick Date: Wed, 14 Oct 2015 16:25:45 +0200 Subject: [PATCH] Add option to allow empty commits to CommitCommand CommitCommand should allow to specify whether empty commits (commits having the same tree as the sole predecessor commit) are allowed or not. Similar to native git's "--allow-empty" flag. The defaults differ between JGit and native git even after this change. When not specifying paths then by default JGit allows to create empty commits while native git does not. It would be API breaking to change this now. Bug: 460301 Change-Id: I88feb0c3ffb2c686b1d0594e669729b065cda4cb Signed-off-by: Matthias Sohn --- .../eclipse/jgit/api/CommitCommandTest.java | 31 ++++++++++ .../org/eclipse/jgit/api/CommitCommand.java | 42 +++++++++++++ .../jgit/api/errors/EmtpyCommitException.java | 62 +++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java index 0d03047d5..b39a68a22 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java @@ -46,12 +46,15 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.fail; import java.io.File; import java.util.Date; import java.util.List; import java.util.TimeZone; +import org.eclipse.jgit.api.errors.EmtpyCommitException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.dircache.DirCache; @@ -476,6 +479,34 @@ public void commitAmendWithAuthorShouldUseIt() throws Exception { assertEquals("newauthor@example.org", amendedAuthor.getEmailAddress()); } + @Test + public void commitEmptyCommits() throws Exception { + try (Git git = new Git(db)) { + + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + RevCommit initial = git.commit().setMessage("initial commit") + .call(); + + RevCommit emptyFollowUp = git.commit() + .setAuthor("New Author", "newauthor@example.org") + .setMessage("no change").call(); + + assertNotEquals(initial.getId(), emptyFollowUp.getId()); + assertEquals(initial.getTree().getId(), + emptyFollowUp.getTree().getId()); + + try { + git.commit().setAuthor("New Author", "newauthor@example.org") + .setMessage("again no change").setAllowEmpty(false) + .call(); + fail("Didn't get the expected EmtpyCommitException"); + } catch (EmtpyCommitException e) { + // expect this exception + } + } + } + @Test public void commitOnlyShouldCommitUnmergedPathAndNotAffectOthers() throws Exception { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index 4f9a5a317..b5057ad28 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -53,6 +53,7 @@ import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.EmtpyCommitException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoFilepatternException; @@ -130,6 +131,8 @@ public class CommitCommand extends GitCommand { private PrintStream hookOutRedirect; + private Boolean allowEmpty; + /** * @param repo */ @@ -231,6 +234,16 @@ public RevCommit call() throws GitAPIException, NoHeadException, if (insertChangeId) insertChangeId(indexTreeId); + // Check for empty commits + if (headId != null && !allowEmpty.booleanValue()) { + RevCommit headCommit = rw.parseCommit(headId); + headCommit.getTree(); + if (indexTreeId.equals(headCommit.getTree())) { + throw new EmtpyCommitException( + JGitText.get().emptyCommit); + } + } + // Create a Commit object, populate it and write it CommitBuilder commit = new CommitBuilder(); commit.setCommitter(committer); @@ -457,6 +470,8 @@ private DirCache createTemporaryIndex(ObjectId headId, DirCache index, // there must be at least one change if (emptyCommit) + // Would like to throw a EmptyCommitException. But this would break the API + // TODO(ch): Change this in the next release throw new JGitInternalException(JGitText.get().emptyCommit); // update index @@ -510,6 +525,12 @@ private void processOptions(RepositoryState state, RevWalk rw) committer = new PersonIdent(repo); if (author == null && !amend) author = committer; + if (allowEmpty == null) + // JGit allows empty commits by default. Only when pathes are + // specified the commit should not be empty. This behaviour differs + // from native git but can only be adapted in the next release. + // TODO(ch) align the defaults with native git + allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE; // when doing a merge commit parse MERGE_HEAD and MERGE_MSG files if (state == RepositoryState.MERGING_RESOLVED @@ -578,6 +599,27 @@ public CommitCommand setMessage(String message) { return this; } + /** + * @param allowEmpty + * whether it should be allowed to create a commit which has the + * same tree as it's sole predecessor (a commit which doesn't + * change anything). By default when creating standard commits + * (without specifying paths) JGit allows to create such commits. + * When this flag is set to false an attempt to create an "empty" + * standard commit will lead to an EmptyCommitException. + *

+ * By default when creating a commit containing only specified + * paths an attempt to create an empty commit leads to a + * {@link JGitInternalException}. By setting this flag to + * true this exception will not be thrown. + * @return {@code this} + * @since 4.2 + */ + public CommitCommand setAllowEmpty(boolean allowEmpty) { + this.allowEmpty = Boolean.valueOf(allowEmpty); + return this; + } + /** * @return the commit message used for the commit */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java new file mode 100644 index 000000000..b3cc1bfcf --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015, Christian Halstrick + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v1.0 which accompanies this + * distribution, is reproduced below, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api.errors; + +/** + * Exception thrown when a newly created commit does not contain any changes + * + * @since 4.2 + */ +public class EmtpyCommitException extends GitAPIException { + private static final long serialVersionUID = 1L; + + /** + * @param message + * @param cause + */ + public EmtpyCommitException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param message + */ + public EmtpyCommitException(String message) { + super(message); + } +}