Merge "Use w1 for hashCode of AbbreviatedObjectId"
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
index 9c3ed50..47443f5 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
@@ -60,4 +60,4 @@
* the servlet to execute on this path.
*/
public void with(HttpServlet servlet);
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
index 9defcd9..03c0816 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
@@ -88,4 +88,4 @@
public ServletContext getServletContext() {
return null;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
index 2962e71..5bf61f0 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
@@ -258,4 +258,27 @@
target);
}
+ /**
+ * Concatenate byte arrays.
+ *
+ * @param b
+ * byte arrays to combine together.
+ * @return a single byte array that contains all bytes copied from input
+ * byte arrays.
+ * @since 4.9
+ */
+ public static byte[] concat(byte[]... b) {
+ int n = 0;
+ for (byte[] a : b) {
+ n += a.length;
+ }
+
+ byte[] data = new byte[n];
+ n = 0;
+ for (byte[] a : b) {
+ System.arraycopy(a, 0, data, n, a.length);
+ n += a.length;
+ }
+ return data;
+ }
}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
index 22b5007..a3c869f 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
@@ -50,4 +50,4 @@
@Target({ java.lang.annotation.ElementType.METHOD })
public @interface Repeat {
public abstract int n();
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
index 75e1a67..4230073 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
@@ -128,4 +128,4 @@
}
return result;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java
new file mode 100644
index 0000000..22b69a3
--- /dev/null
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ * 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.junit;
+
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.jgit.lib.ProgressMonitor;
+
+public final class StrictWorkMonitor implements ProgressMonitor {
+ private int lastWork, totalWork;
+
+ @Override
+ public void start(int totalTasks) {
+ // empty
+ }
+
+ @Override
+ public void beginTask(String title, int total) {
+ this.totalWork = total;
+ lastWork = 0;
+ }
+
+ @Override
+ public void update(int completed) {
+ lastWork += completed;
+ }
+
+ @Override
+ public void endTask() {
+ assertEquals("Units of work recorded", totalWork, lastWork);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+}
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
index e10660d..5da502e 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
@@ -265,4 +265,4 @@
}
return Files.size(f);
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
index 86ca2d3..4fea92e 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
@@ -164,4 +164,4 @@
}
abstract Response.Body process() throws IOException;
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java
index 867cca5..1598b9e 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java
@@ -146,4 +146,4 @@
locked.unlock();
aborted = true;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java
index 7330ee9..bf6bacb 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java
@@ -80,4 +80,4 @@
"" }, execute("git reflog refs/heads/side"));
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
index faae13a..f5c3f9a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
@@ -122,4 +122,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
index 1008593..ceabe93 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
@@ -204,4 +204,4 @@
}
});
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/UntrackedFilesHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/UntrackedFilesHandler.java
index c4e8b05..d6ff5f0 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/UntrackedFilesHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/UntrackedFilesHandler.java
@@ -111,4 +111,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 0400f1e..670b42b 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -23,6 +23,7 @@
org.eclipse.jgit.ignore;version="[4.9.0,4.10.0)",
org.eclipse.jgit.ignore.internal;version="[4.9.0,4.10.0)",
org.eclipse.jgit.internal;version="[4.9.0,4.10.0)",
+ org.eclipse.jgit.internal.fsck;version="[4.9.0,4.10.0)",
org.eclipse.jgit.internal.storage.dfs;version="[4.9.0,4.10.0)",
org.eclipse.jgit.internal.storage.file;version="[4.9.0,4.10.0)",
org.eclipse.jgit.internal.storage.pack;version="[4.9.0,4.10.0)",
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 7e657e6..a0834e7 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
@@ -76,6 +76,7 @@
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FS;
+import org.junit.Ignore;
import org.junit.Test;
/**
@@ -305,6 +306,7 @@
}
}
+ @Ignore("very flaky when run with Hudson")
@Test
public void commitUpdatesSmudgedEntries() throws Exception {
try (Git git = new Git(db)) {
@@ -361,6 +363,7 @@
}
}
+ @Ignore("very flaky when run with Hudson")
@Test
public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception {
try (Git git = new Git(db)) {
@@ -554,6 +557,11 @@
} catch (EmtpyCommitException e) {
// expect this exception
}
+
+ // Allow empty commits also when setOnly was set
+ git.commit().setAuthor("New Author", "newauthor@example.org")
+ .setMessage("again no change").setOnly("file1")
+ .setAllowEmpty(true).call();
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
index 38178bf..bd0efad 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
@@ -289,4 +289,4 @@
.setMessage("merge s0 with m1").call();
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
index 823516b..a341284 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
@@ -620,4 +620,4 @@
fis.close();
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
index ec2370e..f0d3c36 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
@@ -166,6 +166,25 @@
assertAttribute("file.type3", node, asSet(A_UNSET_ATTR, B_SET_ATTR));
}
+ @Test
+ public void testDoubleAsteriskAtEnd() throws IOException {
+ String attributeFileContent = "dir/** \tA -B\tC=value";
+
+ is = new ByteArrayInputStream(attributeFileContent.getBytes());
+ AttributesNode node = new AttributesNode();
+ node.parse(is);
+ assertAttribute("dir", node,
+ asSet(new Attribute[]{}));
+ assertAttribute("dir/", node,
+ asSet(new Attribute[]{}));
+ assertAttribute("dir/file.type1", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ assertAttribute("dir/sub/", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ assertAttribute("dir/sub/file.type1", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ }
+
private void assertAttribute(String path, AttributesNode node,
Attributes attrs) throws IOException {
Attributes attributes = new Attributes();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
index 1863b80..bcc8f7e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
@@ -391,7 +391,6 @@
assertMatched("/**/a/b", "c/d/a/b");
assertMatched("/**/**/a/b", "c/d/a/b");
- assertMatched("a/b/**", "a/b");
assertMatched("a/b/**", "a/b/c");
assertMatched("a/b/**", "a/b/c/d/");
assertMatched("a/b/**/**", "a/b/c/d");
@@ -415,6 +414,12 @@
@Test
public void testWildmatchDoNotMatch() {
+ assertNotMatched("a/**", "a/");
+ assertNotMatched("a/b/**", "a/b/");
+ assertNotMatched("a/**", "a");
+ assertNotMatched("a/b/**", "a/b");
+ assertNotMatched("a/b/**/", "a/b");
+ assertNotMatched("a/b/**/**", "a/b");
assertNotMatched("**/a/b", "a/c/b");
assertNotMatched("!/**/*.zip", "c/a/b.zip");
assertNotMatched("!**/*.zip", "c/a/b.zip");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java
new file mode 100644
index 0000000..804d744
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.internal.storage.dfs;
+
+import static org.eclipse.jgit.junit.JGitTestUtil.concat;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.internal.fsck.FsckError;
+import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectChecker.ErrorType;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DfsFsckTest {
+ private TestRepository<InMemoryRepository> git;
+
+ private InMemoryRepository repo;
+
+ private ObjectInserter ins;
+
+ @Before
+ public void setUp() throws IOException {
+ DfsRepositoryDescription desc = new DfsRepositoryDescription("test");
+ git = new TestRepository<>(new InMemoryRepository(desc));
+ repo = git.getRepository();
+ ins = repo.newObjectInserter();
+ }
+
+ @Test
+ public void testHealthyRepo() throws Exception {
+ RevCommit commit0 = git.commit().message("0").create();
+ RevCommit commit1 = git.commit().message("1").parent(commit0).create();
+ git.update("master", commit1);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 0);
+ assertEquals(errors.getMissingObjects().size(), 0);
+ assertEquals(errors.getCorruptIndices().size(), 0);
+ }
+
+ @Test
+ public void testCommitWithCorruptAuthor() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("tree be9bfa841874ccc9f2ef7c48d0c76226f89b7189\n");
+ b.append("author b <b@c> <b@c> 0 +0000\n");
+ b.append("committer <> 0 +0000\n");
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id = ins.insert(Constants.OBJ_COMMIT, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.BAD_DATE);
+ }
+
+ @Test
+ public void testCommitWithoutTree() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("parent ");
+ b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
+ b.append('\n');
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id = ins.insert(Constants.OBJ_COMMIT, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.MISSING_TREE);
+ }
+
+ @Test
+ public void testTagWithoutObject() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("type commit\n");
+ b.append("tag test-tag\n");
+ b.append("tagger A. U. Thor <author@localhost> 1 +0000\n");
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id = ins.insert(Constants.OBJ_TAG, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.MISSING_OBJECT);
+ }
+
+ @Test
+ public void testTreeWithNullSha() throws Exception {
+ byte[] data = concat(encodeASCII("100644 A"), new byte[] { '\0' },
+ new byte[OBJECT_ID_LENGTH]);
+ ObjectId id = ins.insert(Constants.OBJ_TREE, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.NULL_SHA1);
+ }
+
+ @Test
+ public void testMultipleInvalidObjects() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("tree ");
+ b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
+ b.append('\n');
+ b.append("parent ");
+ b.append("\n");
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id1 = ins.insert(Constants.OBJ_COMMIT, data);
+
+ b = new StringBuilder();
+ b.append("100644");
+ data = encodeASCII(b.toString());
+ ObjectId id2 = ins.insert(Constants.OBJ_TREE, data);
+
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 2);
+ for (CorruptObject o : errors.getCorruptObjects()) {
+ if (o.getId().equals(id1)) {
+ assertEquals(o.getErrorType(), ErrorType.BAD_PARENT_SHA1);
+ } else if (o.getId().equals(id2)) {
+ assertNull(o.getErrorType());
+ } else {
+ fail();
+ }
+ }
+ }
+
+ @Test
+ public void testValidConnectivity() throws Exception {
+ ObjectId blobId = ins
+ .insert(Constants.OBJ_BLOB, Constants.encode("foo"));
+
+ byte[] blobIdBytes = new byte[OBJECT_ID_LENGTH];
+ blobId.copyRawTo(blobIdBytes, 0);
+ byte[] data = concat(encodeASCII("100644 regular-file\0"), blobIdBytes);
+ ObjectId treeId = ins.insert(Constants.OBJ_TREE, data);
+ ins.flush();
+
+ RevCommit commit = git.commit().message("0").setTopLevelTree(treeId)
+ .create();
+
+ git.update("master", commit);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+ assertEquals(errors.getMissingObjects().size(), 0);
+ }
+
+ @Test
+ public void testMissingObject() throws Exception {
+ ObjectId blobId = ObjectId
+ .fromString("19102815663d23f8b75a47e7a01965dcdc96468c");
+ byte[] blobIdBytes = new byte[OBJECT_ID_LENGTH];
+ blobId.copyRawTo(blobIdBytes, 0);
+ byte[] data = concat(encodeASCII("100644 regular-file\0"), blobIdBytes);
+ ObjectId treeId = ins.insert(Constants.OBJ_TREE, data);
+ ins.flush();
+
+ RevCommit commit = git.commit().message("0").setTopLevelTree(treeId)
+ .create();
+
+ git.update("master", commit);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+ assertEquals(errors.getMissingObjects().size(), 1);
+ assertEquals(errors.getMissingObjects().iterator().next(), blobId);
+ }
+
+ @Test
+ public void testNonCommitHead() throws Exception {
+ RevCommit commit0 = git.commit().message("0").create();
+ StringBuilder b = new StringBuilder();
+ b.append("object ");
+ b.append(commit0.getName());
+ b.append('\n');
+ b.append("type commit\n");
+ b.append("tag test-tag\n");
+ b.append("tagger A. U. Thor <author@localhost> 1 +0000\n");
+
+ byte[] data = encodeASCII(b.toString());
+ ObjectId tagId = ins.insert(Constants.OBJ_TAG, data);
+ ins.flush();
+
+ git.update("master", tagId);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+ assertEquals(errors.getCorruptObjects().size(), 0);
+ assertEquals(errors.getNonCommitHeads().size(), 1);
+ assertEquals(errors.getNonCommitHeads().iterator().next(),
+ "refs/heads/master");
+ }
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
new file mode 100644
index 0000000..34f6c71
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ * 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.internal.storage.file;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.OK;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_MISSING_OBJECT;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_NONFASTFORWARD;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.TRANSACTION_ABORTED;
+import static org.eclipse.jgit.lib.ObjectId.zeroId;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Predicate;
+
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.StrictWorkMonitor;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.CheckoutEntry;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.ReflogReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@SuppressWarnings("boxing")
+@RunWith(Parameterized.class)
+public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
+ @Parameter
+ public boolean atomic;
+
+ @Parameters(name = "atomic={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{ {Boolean.FALSE}, {Boolean.TRUE} });
+ }
+
+ private Repository diskRepo;
+ private TestRepository<Repository> repo;
+ private RefDirectory refdir;
+ private RevCommit A;
+ private RevCommit B;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ diskRepo = createBareRepository();
+ StoredConfig cfg = diskRepo.getConfig();
+ cfg.load();
+ cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
+ cfg.save();
+
+ refdir = (RefDirectory) diskRepo.getRefDatabase();
+ refdir.setRetrySleepMs(Arrays.asList(0, 0));
+
+ repo = new TestRepository<>(diskRepo);
+ A = repo.commit().create();
+ B = repo.commit(repo.getRevWalk().parseCommit(A));
+ }
+
+ @Test
+ public void simpleNoForce() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(B, A, "refs/heads/masters", UPDATE_NONFASTFORWARD));
+ execute(newBatchUpdate(cmds));
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, REJECTED_NONFASTFORWARD);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/masters", B);
+ } else {
+ assertResults(cmds, OK, REJECTED_NONFASTFORWARD);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters", B);
+ }
+ }
+
+ @Test
+ public void simpleForce() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(B, A, "refs/heads/masters", UPDATE_NONFASTFORWARD));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters", A);
+ }
+
+ @Test
+ public void nonFastForwardDoesNotDoExpensiveMergeCheck() throws IOException {
+ writeLooseRef("refs/heads/master", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(B, A, "refs/heads/master", UPDATE_NONFASTFORWARD));
+ try (RevWalk rw = new RevWalk(diskRepo) {
+ @Override
+ public boolean isMergedInto(RevCommit base, RevCommit tip) {
+ throw new AssertionError("isMergedInto() should not be called");
+ }
+ }) {
+ newBatchUpdate(cmds)
+ .setAllowNonFastForwards(true)
+ .execute(rw, new StrictWorkMonitor());
+ }
+
+ assertResults(cmds, OK);
+ assertRefs("refs/heads/master", A);
+ }
+
+ @Test
+ public void fileDirectoryConflict() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
+
+ if (atomic) {
+ // Atomic update sees that master and master/x are conflicting, then marks
+ // the first one in the list as LOCK_FAILURE and aborts the rest.
+ assertResults(cmds,
+ LOCK_FAILURE, TRANSACTION_ABORTED, TRANSACTION_ABORTED);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/masters", B);
+ } else {
+ // Non-atomic updates are applied in order: master succeeds, then master/x
+ // fails due to conflict.
+ assertResults(cmds, OK, LOCK_FAILURE, LOCK_FAILURE);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters", B);
+ }
+ }
+
+ @Test
+ public void conflictThanksToDelete() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", CREATE),
+ new ReceiveCommand(B, zeroId(), "refs/heads/masters", DELETE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertResults(cmds, OK, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters/x", A);
+ }
+
+ @Test
+ public void updateToMissingObject() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, bad, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
+
+ if (atomic) {
+ assertResults(cmds, REJECTED_MISSING_OBJECT, TRANSACTION_ABORTED);
+ assertRefs("refs/heads/master", A);
+ } else {
+ assertResults(cmds, REJECTED_MISSING_OBJECT, OK);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/foo2", B);
+ }
+ }
+
+ @Test
+ public void addMissingObject() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, REJECTED_MISSING_OBJECT);
+ assertRefs("refs/heads/master", A);
+ } else {
+ assertResults(cmds, OK, REJECTED_MISSING_OBJECT);
+ assertRefs("refs/heads/master", B);
+ }
+ }
+
+ @Test
+ public void oneNonExistentRef() throws IOException {
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/foo1", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ if (atomic) {
+ assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
+ assertRefs();
+ } else {
+ assertResults(cmds, LOCK_FAILURE, OK);
+ assertRefs("refs/heads/foo2", B);
+ }
+ }
+
+ @Test
+ public void oneRefWrongOldValue() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(B, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ if (atomic) {
+ assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
+ assertRefs("refs/heads/master", A);
+ } else {
+ assertResults(cmds, LOCK_FAILURE, OK);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/foo2", B);
+ }
+ }
+
+ @Test
+ public void nonExistentRef() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(A, zeroId(), "refs/heads/foo2", DELETE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
+ assertRefs("refs/heads/master", A);
+ } else {
+ assertResults(cmds, OK, LOCK_FAILURE);
+ assertRefs("refs/heads/master", B);
+ }
+ }
+
+ @Test
+ public void noRefLog() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+ assertEquals(Collections.singleton("refs/heads/master"), oldLogs.keySet());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ }
+
+ @Test
+ public void reflogDefaultIdent() throws IOException {
+ writeRef("refs/heads/master", A);
+ writeRef("refs/heads/branch2", A);
+
+ Map<String, ReflogEntry> oldLogs = getLastReflogs(
+ "refs/heads/master", "refs/heads/branch1", "refs/heads/branch2");
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch1", CREATE));
+ execute(
+ newBatchUpdate(cmds)
+ .setAllowNonFastForwards(true)
+ .setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch1", B,
+ "refs/heads/branch2", A);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/branch1"));
+ assertReflogUnchanged(oldLogs, "refs/heads/branch2");
+ }
+
+ @Test
+ public void reflogAppendStatusNoMessage() throws IOException {
+ writeRef("refs/heads/master", A);
+ writeRef("refs/heads/branch1", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(B, A, "refs/heads/branch1", UPDATE_NONFASTFORWARD),
+ new ReceiveCommand(zeroId(), A, "refs/heads/branch2", CREATE));
+ execute(
+ newBatchUpdate(cmds)
+ .setAllowNonFastForwards(true)
+ .setRefLogMessage(null, true));
+
+ assertResults(cmds, OK, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch1", A,
+ "refs/heads/branch2", A);
+ assertReflogEquals(
+ // Always forced; setAllowNonFastForwards(true) bypasses the check.
+ reflog(A, B, new PersonIdent(diskRepo), "forced-update"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(B, A, new PersonIdent(diskRepo), "forced-update"),
+ getLastReflog("refs/heads/branch1"));
+ assertReflogEquals(
+ reflog(zeroId(), A, new PersonIdent(diskRepo), "created"),
+ getLastReflog("refs/heads/branch2"));
+ }
+
+ @Test
+ public void reflogAppendStatusFastForward() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage(null, true));
+
+ assertResults(cmds, OK);
+ assertRefs("refs/heads/master", B);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "fast-forward"),
+ getLastReflog("refs/heads/master"));
+ }
+
+ @Test
+ public void reflogAppendStatusWithMessage() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/branch", CREATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", A);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog: fast-forward"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog: created"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void reflogCustomIdent() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ PersonIdent ident = new PersonIdent("A Reflog User", "reflog@example.com");
+ execute(
+ newBatchUpdate(cmds)
+ .setRefLogMessage("a reflog", false)
+ .setRefLogIdent(ident));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ assertReflogEquals(
+ reflog(A, B, ident, "a reflog"),
+ getLastReflog("refs/heads/master"),
+ true);
+ assertReflogEquals(
+ reflog(zeroId(), B, ident, "a reflog"),
+ getLastReflog("refs/heads/branch"),
+ true);
+ }
+
+ @Test
+ public void reflogDelete() throws IOException {
+ writeRef("refs/heads/master", A);
+ writeRef("refs/heads/branch", A);
+ assertEquals(
+ 2, getLastReflogs("refs/heads/master", "refs/heads/branch").size());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
+ new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertRefs("refs/heads/branch", B);
+ assertNull(getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void reflogFileDirectoryConflict() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertRefs("refs/heads/master/x", A);
+ assertNull(getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master/x"));
+ }
+
+ @Test
+ public void reflogOnLockFailure() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ } else {
+ assertResults(cmds, OK, LOCK_FAILURE);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master"));
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ }
+ }
+
+ @Test
+ public void overrideRefLogMessage() throws Exception {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ cmds.get(0).setRefLogMessage("custom log", false);
+ PersonIdent ident = new PersonIdent(diskRepo);
+ execute(
+ newBatchUpdate(cmds)
+ .setRefLogIdent(ident)
+ .setRefLogMessage("a reflog", true));
+
+ assertResults(cmds, OK, OK);
+ assertReflogEquals(
+ reflog(A, B, ident, "custom log"),
+ getLastReflog("refs/heads/master"),
+ true);
+ assertReflogEquals(
+ reflog(zeroId(), B, ident, "a reflog: created"),
+ getLastReflog("refs/heads/branch"),
+ true);
+ }
+
+ @Test
+ public void overrideDisableRefLog() throws Exception {
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ cmds.get(0).disableRefLog();
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
+
+ assertResults(cmds, OK, OK);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogEquals(
+ reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog: created"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void packedRefsLockFailure() throws Exception {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+
+ LockFile myLock = refdir.lockPackedRefs();
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertFalse(getLockFile("refs/heads/master").exists());
+ assertFalse(getLockFile("refs/heads/branch").exists());
+
+ if (atomic) {
+ assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
+ assertRefs("refs/heads/master", A);
+ } else {
+ // Only operates on loose refs, doesn't care that packed-refs is locked.
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ }
+ } finally {
+ myLock.unlock();
+ }
+ }
+
+ @Test
+ public void oneRefLockFailure() throws Exception {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE),
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
+
+ LockFile myLock = new LockFile(refdir.fileFor("refs/heads/master"));
+ assertTrue(myLock.lock());
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertFalse(LockFile.getLockFile(refdir.packedRefsFile).exists());
+ assertFalse(getLockFile("refs/heads/branch").exists());
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
+ assertRefs("refs/heads/master", A);
+ } else {
+ assertResults(cmds, OK, LOCK_FAILURE);
+ assertRefs(
+ "refs/heads/branch", B,
+ "refs/heads/master", A);
+ }
+ } finally {
+ myLock.unlock();
+ }
+ }
+
+ @Test
+ public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
+
+ LockFile myLock = refdir.lockPackedRefs();
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertFalse(getLockFile("refs/heads/master").exists());
+ assertResults(cmds, OK);
+ assertRefs("refs/heads/master", B);
+ } finally {
+ myLock.unlock();
+ }
+ }
+
+ @Test
+ public void atomicUpdateRespectsInProcessLock() throws Exception {
+ assumeTrue(atomic);
+
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+
+ Thread t = new Thread(() -> {
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ ReentrantLock l = refdir.inProcessPackedRefsLock;
+ l.lock();
+ try {
+ t.start();
+ long timeoutSecs = 10;
+ long startNanos = System.nanoTime();
+
+ // Hold onto the lock until we observe the worker thread has attempted to
+ // acquire it.
+ while (l.getQueueLength() == 0) {
+ long elapsedNanos = System.nanoTime() - startNanos;
+ assertTrue(
+ "timed out waiting for work thread to attempt to acquire lock",
+ NANOSECONDS.toSeconds(elapsedNanos) < timeoutSecs);
+ Thread.sleep(3);
+ }
+
+ // Once we unlock, the worker thread should finish the update promptly.
+ l.unlock();
+ t.join(SECONDS.toMillis(timeoutSecs));
+ assertFalse(t.isAlive());
+ } finally {
+ if (l.isHeldByCurrentThread()) {
+ l.unlock();
+ }
+ }
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ }
+
+ private void writeLooseRef(String name, AnyObjectId id) throws IOException {
+ write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
+ }
+
+ private void writeRef(String name, AnyObjectId id) throws IOException {
+ RefUpdate u = diskRepo.updateRef(name);
+ u.setRefLogMessage(getClass().getSimpleName(), false);
+ u.setForceUpdate(true);
+ u.setNewObjectId(id);
+ RefUpdate.Result r = u.update();
+ switch (r) {
+ case NEW:
+ case FORCED:
+ return;
+ default:
+ throw new IOException("Got " + r + " while updating " + name);
+ }
+ }
+
+ private BatchRefUpdate newBatchUpdate(List<ReceiveCommand> cmds) {
+ BatchRefUpdate u = refdir.newBatchUpdate();
+ if (atomic) {
+ assertTrue(u.isAtomic());
+ } else {
+ u.setAtomic(false);
+ }
+ u.addCommand(cmds);
+ return u;
+ }
+
+ private void execute(BatchRefUpdate u) throws IOException {
+ execute(u, false);
+ }
+
+ private void execute(BatchRefUpdate u, boolean strictWork) throws IOException {
+ try (RevWalk rw = new RevWalk(diskRepo)) {
+ u.execute(rw,
+ strictWork ? new StrictWorkMonitor() : NullProgressMonitor.INSTANCE);
+ }
+ }
+
+ private void assertRefs(Object... args) throws IOException {
+ if (args.length % 2 != 0) {
+ throw new IllegalArgumentException(
+ "expected even number of args: " + Arrays.toString(args));
+ }
+
+ Map<String, AnyObjectId> expected = new LinkedHashMap<>();
+ for (int i = 0; i < args.length; i += 2) {
+ expected.put((String) args[i], (AnyObjectId) args[i + 1]);
+ }
+
+ Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
+ Ref actualHead = refs.remove(Constants.HEAD);
+ if (actualHead != null) {
+ String actualLeafName = actualHead.getLeaf().getName();
+ assertEquals(
+ "expected HEAD to point to refs/heads/master, got: " + actualLeafName,
+ "refs/heads/master", actualLeafName);
+ AnyObjectId expectedMaster = expected.get("refs/heads/master");
+ assertNotNull("expected master ref since HEAD exists", expectedMaster);
+ assertEquals(expectedMaster, actualHead.getObjectId());
+ }
+
+ Map<String, AnyObjectId> actual = new LinkedHashMap<>();
+ refs.forEach((n, r) -> actual.put(n, r.getObjectId()));
+
+ assertEquals(expected.keySet(), actual.keySet());
+ actual.forEach((n, a) -> assertEquals(n, expected.get(n), a));
+ }
+
+ enum Result {
+ OK(ReceiveCommand.Result.OK),
+ LOCK_FAILURE(ReceiveCommand.Result.LOCK_FAILURE),
+ REJECTED_NONFASTFORWARD(ReceiveCommand.Result.REJECTED_NONFASTFORWARD),
+ REJECTED_MISSING_OBJECT(ReceiveCommand.Result.REJECTED_MISSING_OBJECT),
+ TRANSACTION_ABORTED(ReceiveCommand::isTransactionAborted);
+
+ final Predicate<? super ReceiveCommand> p;
+
+ private Result(Predicate<? super ReceiveCommand> p) {
+ this.p = p;
+ }
+
+ private Result(ReceiveCommand.Result result) {
+ this(c -> c.getResult() == result);
+ }
+ }
+
+ private void assertResults(
+ List<ReceiveCommand> cmds, Result... expected) {
+ if (expected.length != cmds.size()) {
+ throw new IllegalArgumentException(
+ "expected " + cmds.size() + " result args");
+ }
+ for (int i = 0; i < cmds.size(); i++) {
+ ReceiveCommand c = cmds.get(i);
+ Result r = expected[i];
+ assertTrue(
+ String.format(
+ "result of command (%d) should be %s: %s %s%s",
+ Integer.valueOf(i), r, c,
+ c.getResult(),
+ c.getMessage() != null ? " (" + c.getMessage() + ")" : ""),
+ r.p.test(c));
+ }
+ }
+
+ private Map<String, ReflogEntry> getLastReflogs(String... names)
+ throws IOException {
+ Map<String, ReflogEntry> result = new LinkedHashMap<>();
+ for (String name : names) {
+ ReflogEntry e = getLastReflog(name);
+ if (e != null) {
+ result.put(name, e);
+ }
+ }
+ return result;
+ }
+
+ private ReflogEntry getLastReflog(String name) throws IOException {
+ ReflogReader r = diskRepo.getReflogReader(name);
+ if (r == null) {
+ return null;
+ }
+ return r.getLastEntry();
+ }
+
+ private File getLockFile(String refName) {
+ return LockFile.getLockFile(refdir.fileFor(refName));
+ }
+
+ private void assertReflogUnchanged(
+ Map<String, ReflogEntry> old, String name) throws IOException {
+ assertReflogEquals(old.get(name), getLastReflog(name), true);
+ }
+
+ private static void assertReflogEquals(
+ ReflogEntry expected, ReflogEntry actual) {
+ assertReflogEquals(expected, actual, false);
+ }
+
+ private static void assertReflogEquals(
+ ReflogEntry expected, ReflogEntry actual, boolean strictTime) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.getOldId(), actual.getOldId());
+ assertEquals(expected.getNewId(), actual.getNewId());
+ if (strictTime) {
+ assertEquals(expected.getWho(), actual.getWho());
+ } else {
+ assertEquals(expected.getWho().getName(), actual.getWho().getName());
+ assertEquals(
+ expected.getWho().getEmailAddress(),
+ actual.getWho().getEmailAddress());
+ }
+ assertEquals(expected.getComment(), actual.getComment());
+ }
+
+ private static ReflogEntry reflog(ObjectId oldId, ObjectId newId,
+ PersonIdent who, String comment) {
+ return new ReflogEntry() {
+ @Override
+ public ObjectId getOldId() {
+ return oldId;
+ }
+
+ @Override
+ public ObjectId getNewId() {
+ return newId;
+ }
+
+ @Override
+ public PersonIdent getWho() {
+ return who;
+ }
+
+ @Override
+ public String getComment() {
+ return comment;
+ }
+
+ @Override
+ public CheckoutEntry parseCheckout() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
index 11a2a22..c43bdbd 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
@@ -43,12 +43,13 @@
package org.eclipse.jgit.internal.storage.file;
-import static java.lang.Integer.valueOf;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
import java.io.File;
import java.io.IOException;
@@ -74,6 +75,7 @@
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.junit.Test;
+@SuppressWarnings("boxing")
public class GcPackRefsTest extends GcTestCase {
@Test
public void looseRefPacked() throws Exception {
@@ -100,27 +102,23 @@
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);
- final CyclicBarrier syncPoint = new CyclicBarrier(2);
+ CyclicBarrier syncPoint = new CyclicBarrier(2);
- Callable<Integer> packRefs = new Callable<Integer>() {
-
- /** @return 0 for success, 1 in case of error when writing pack */
- @Override
- public Integer call() throws Exception {
- syncPoint.await();
- try {
- gc.packRefs();
- return valueOf(0);
- } catch (IOException e) {
- return valueOf(1);
- }
+ // Returns 0 for success, 1 in case of error when writing pack.
+ Callable<Integer> packRefs = () -> {
+ syncPoint.await();
+ try {
+ gc.packRefs();
+ return 0;
+ } catch (IOException e) {
+ return 1;
}
};
ExecutorService pool = Executors.newFixedThreadPool(2);
try {
Future<Integer> p1 = pool.submit(packRefs);
Future<Integer> p2 = pool.submit(packRefs);
- assertEquals(1, p1.get().intValue() + p2.get().intValue());
+ assertThat(p1.get() + p2.get(), lessThanOrEqualTo(1));
} finally {
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index 53db123..fefccf3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -61,32 +61,27 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.events.RefsChangedListener;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTag;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.transport.ReceiveCommand.Type;
import org.junit.Before;
import org.junit.Test;
+@SuppressWarnings("boxing")
public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
private Repository diskRepo;
@@ -1293,125 +1288,20 @@
}
@Test
- public void testBatchRefUpdateSimpleNoForce() throws IOException {
+ public void testPackedRefsLockFailure() throws Exception {
writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(B, A, "refs/heads/masters",
- ReceiveCommand.Type.UPDATE_NONFASTFORWARD));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.REJECTED_NONFASTFORWARD, commands
- .get(1).getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs
- .keySet().toString());
- assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
- assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateSimpleForce() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(B, A, "refs/heads/masters",
- ReceiveCommand.Type.UPDATE_NONFASTFORWARD));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs
- .keySet().toString());
- assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
- assertEquals(A.getId(), refs.get("refs/heads/masters").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateNonFastForwardDoesNotDoExpensiveMergeCheck()
- throws IOException {
- writeLooseRef("refs/heads/master", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(B, A, "refs/heads/master",
- ReceiveCommand.Type.UPDATE_NONFASTFORWARD));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo) {
- @Override
- public boolean isMergedInto(RevCommit base, RevCommit tip) {
- throw new AssertionError("isMergedInto() should not be called");
- }
- }, new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateConflict() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(null, A, "refs/heads/master/x",
- ReceiveCommand.Type.CREATE),
- newCommand(null, A, "refs/heads", ReceiveCommand.Type.CREATE));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate
- .execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE);
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(1)
- .getResult());
- assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(2)
- .getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs
- .keySet().toString());
- assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
- assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateConflictThanksToDelete() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(null, A, "refs/heads/masters/x",
- ReceiveCommand.Type.CREATE),
- newCommand(B, null, "refs/heads/masters",
- ReceiveCommand.Type.DELETE));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult());
- assertEquals(ReceiveCommand.Result.OK, commands.get(2).getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters/x]", refs
- .keySet().toString());
- assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId());
- }
-
- private static ReceiveCommand newCommand(RevCommit a, RevCommit b,
- String string, Type update) {
- return new ReceiveCommand(a != null ? a.getId() : null,
- b != null ? b.getId() : null, string, update);
+ refdir.setRetrySleepMs(Arrays.asList(0, 0));
+ LockFile myLock = refdir.lockPackedRefs();
+ try {
+ refdir.pack(Arrays.asList("refs/heads/master"));
+ fail("expected LockFailedException");
+ } catch (LockFailedException e) {
+ assertEquals(refdir.packedRefsFile.getPath(), e.getFile().getPath());
+ } finally {
+ myLock.unlock();
+ }
+ Ref ref = refdir.getRef("refs/heads/master");
+ assertEquals(Storage.LOOSE, ref.getStorage());
}
private void writeLooseRef(String name, AnyObjectId id) throws IOException {
@@ -1439,34 +1329,4 @@
File path = new File(diskRepo.getDirectory(), name);
assertTrue("deleted " + name, path.delete());
}
-
- private static final class StrictWorkMonitor implements ProgressMonitor {
- private int lastWork, totalWork;
-
- @Override
- public void start(int totalTasks) {
- // empty
- }
-
- @Override
- public void beginTask(String title, int total) {
- this.totalWork = total;
- lastWork = 0;
- }
-
- @Override
- public void update(int completed) {
- lastWork += completed;
- }
-
- @Override
- public void endTask() {
- assertEquals("Units of work recorded", totalWork, lastWork);
- }
-
- @Override
- public boolean isCancelled() {
- return false;
- }
- }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index daef91d..34f9eb9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -45,6 +45,7 @@
package org.eclipse.jgit.internal.storage.file;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.junit.Assert.assertEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -64,6 +65,7 @@
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefRename;
@@ -240,14 +242,73 @@
@Test
public void testDeleteHeadInBareRepo() throws IOException {
Repository bareRepo = createBareRepository();
+ String master = "refs/heads/master";
+ Ref head = bareRepo.exactRef(Constants.HEAD);
+ assertNotNull(head);
+ assertTrue(head.isSymbolic());
+ assertEquals(master, head.getLeaf().getName());
+ assertNull(head.getObjectId());
+ assertNull(bareRepo.exactRef(master));
+
+ ObjectId blobId;
+ try (ObjectInserter ins = bareRepo.newObjectInserter()) {
+ blobId = ins.insert(Constants.OBJ_BLOB, "contents".getBytes(UTF_8));
+ ins.flush();
+ }
+
+ // Create master via HEAD, so we delete it.
RefUpdate ref = bareRepo.updateRef(Constants.HEAD);
- ref.setNewObjectId(ObjectId
- .fromString("0123456789012345678901234567890123456789"));
- // Create the HEAD ref so we can delete it.
+ ref.setNewObjectId(blobId);
assertEquals(Result.NEW, ref.update());
+
+ head = bareRepo.exactRef(Constants.HEAD);
+ assertTrue(head.isSymbolic());
+ assertEquals(master, head.getLeaf().getName());
+ assertEquals(blobId, head.getLeaf().getObjectId());
+ assertEquals(blobId, bareRepo.exactRef(master).getObjectId());
+
+ // Unlike in a non-bare repo, deleting the HEAD is allowed, and leaves HEAD
+ // back in a dangling state.
ref = bareRepo.updateRef(Constants.HEAD);
- delete(bareRepo, ref, Result.NO_CHANGE, true, true);
+ ref.setExpectedOldObjectId(blobId);
+ ref.setForceUpdate(true);
+ delete(bareRepo, ref, Result.FORCED, true, true);
+
+ head = bareRepo.exactRef(Constants.HEAD);
+ assertNotNull(head);
+ assertTrue(head.isSymbolic());
+ assertEquals(master, head.getLeaf().getName());
+ assertNull(head.getObjectId());
+ assertNull(bareRepo.exactRef(master));
}
+
+ @Test
+ public void testDeleteSymref() throws IOException {
+ RefUpdate dst = updateRef("refs/heads/abc");
+ assertEquals(Result.NEW, dst.update());
+ ObjectId id = dst.getNewObjectId();
+
+ RefUpdate u = db.updateRef("refs/symref");
+ assertEquals(Result.NEW, u.link(dst.getName()));
+
+ Ref ref = db.exactRef(u.getName());
+ assertNotNull(ref);
+ assertTrue(ref.isSymbolic());
+ assertEquals(dst.getName(), ref.getLeaf().getName());
+ assertEquals(id, ref.getLeaf().getObjectId());
+
+ u = db.updateRef(u.getName());
+ u.setDetachingSymbolicRef();
+ u.setForceUpdate(true);
+ assertEquals(Result.FORCED, u.delete());
+
+ assertNull(db.exactRef(u.getName()));
+ ref = db.exactRef(dst.getName());
+ assertNotNull(ref);
+ assertFalse(ref.isSymbolic());
+ assertEquals(id, ref.getObjectId());
+ }
+
/**
* Delete a loose ref and make sure the directory in refs is deleted too,
* and the reflog dir too
@@ -898,6 +959,60 @@
"HEAD").getReverseEntries().get(0).getComment());
}
+ @Test
+ public void testCreateMissingObject() throws IOException {
+ String name = "refs/heads/abc";
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ RefUpdate ru = db.updateRef(name);
+ ru.setNewObjectId(bad);
+ Result update = ru.update();
+ assertEquals(Result.REJECTED_MISSING_OBJECT, update);
+
+ Ref ref = db.exactRef(name);
+ assertNull(ref);
+ }
+
+ @Test
+ public void testUpdateMissingObject() throws IOException {
+ String name = "refs/heads/abc";
+ RefUpdate ru = updateRef(name);
+ Result update = ru.update();
+ assertEquals(Result.NEW, update);
+ ObjectId oldId = ru.getNewObjectId();
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ ru = db.updateRef(name);
+ ru.setNewObjectId(bad);
+ update = ru.update();
+ assertEquals(Result.REJECTED_MISSING_OBJECT, update);
+
+ Ref ref = db.exactRef(name);
+ assertNotNull(ref);
+ assertEquals(oldId, ref.getObjectId());
+ }
+
+ @Test
+ public void testForceUpdateMissingObject() throws IOException {
+ String name = "refs/heads/abc";
+ RefUpdate ru = updateRef(name);
+ Result update = ru.update();
+ assertEquals(Result.NEW, update);
+ ObjectId oldId = ru.getNewObjectId();
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ ru = db.updateRef(name);
+ ru.setNewObjectId(bad);
+ update = ru.forceUpdate();
+ assertEquals(Result.REJECTED_MISSING_OBJECT, update);
+
+ Ref ref = db.exactRef(name);
+ assertNotNull(ref);
+ assertEquals(oldId, ref.getObjectId());
+ }
+
private static void writeReflog(Repository db, ObjectId newId, String msg,
String refName) throws IOException {
RefDirectory refs = (RefDirectory) db.getRefDatabase();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
index ae1e531..9d23d83 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
@@ -661,33 +661,39 @@
@Test
public void test028_LockPackedRef() throws IOException {
+ ObjectId id1;
+ ObjectId id2;
+ try (ObjectInserter ins = db.newObjectInserter()) {
+ id1 = ins.insert(
+ Constants.OBJ_BLOB, "contents1".getBytes(Constants.CHARSET));
+ id2 = ins.insert(
+ Constants.OBJ_BLOB, "contents2".getBytes(Constants.CHARSET));
+ ins.flush();
+ }
+
writeTrashFile(".git/packed-refs",
- "7f822839a2fe9760f386cbbbcb3f92c5fe81def7 refs/heads/foobar");
+ id1.name() + " refs/heads/foobar");
writeTrashFile(".git/HEAD", "ref: refs/heads/foobar\n");
BUG_WorkAroundRacyGitIssues("packed-refs");
BUG_WorkAroundRacyGitIssues("HEAD");
ObjectId resolve = db.resolve("HEAD");
- assertEquals("7f822839a2fe9760f386cbbbcb3f92c5fe81def7", resolve.name());
+ assertEquals(id1, resolve);
RefUpdate lockRef = db.updateRef("HEAD");
- ObjectId newId = ObjectId
- .fromString("07f822839a2fe9760f386cbbbcb3f92c5fe81def");
- lockRef.setNewObjectId(newId);
+ lockRef.setNewObjectId(id2);
assertEquals(RefUpdate.Result.FORCED, lockRef.forceUpdate());
assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists());
- assertEquals(newId, db.resolve("refs/heads/foobar"));
+ assertEquals(id2, db.resolve("refs/heads/foobar"));
// Again. The ref already exists
RefUpdate lockRef2 = db.updateRef("HEAD");
- ObjectId newId2 = ObjectId
- .fromString("7f822839a2fe9760f386cbbbcb3f92c5fe81def7");
- lockRef2.setNewObjectId(newId2);
+ lockRef2.setNewObjectId(id1);
assertEquals(RefUpdate.Result.FORCED, lockRef2.forceUpdate());
assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists());
- assertEquals(newId2, db.resolve("refs/heads/foobar"));
+ assertEquals(id1, db.resolve("refs/heads/foobar"));
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
index 43160fb..c8729d9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
@@ -45,6 +45,7 @@
package org.eclipse.jgit.lib;
import static java.lang.Integer.valueOf;
+import static org.eclipse.jgit.junit.JGitTestUtil.concat;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
@@ -1054,20 +1055,7 @@
checker.checkTree(data);
}
- private static byte[] concat(byte[]... b) {
- int n = 0;
- for (byte[] a : b) {
- n += a.length;
- }
- byte[] data = new byte[n];
- n = 0;
- for (byte[] a : b) {
- System.arraycopy(a, 0, data, n, a.length);
- n += a.length;
- }
- return data;
- }
@Test
public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd()
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
index 7db9f60..15f28af 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
@@ -179,4 +179,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java
index 2451c50..077645e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java
@@ -171,4 +171,4 @@
assertNull(rw.next());
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java
index 353a487..cf02aa8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java
@@ -81,4 +81,4 @@
public void testSkipRevFilterNegative() throws Exception {
SkipRevFilter.create(-1);
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
index 5c46659..f42dd02 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
@@ -269,4 +269,4 @@
ConfigConstants.CONFIG_KEY_URL));
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
index 61df9d9..5832518 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
@@ -59,11 +59,11 @@
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -256,11 +256,16 @@
}
@Test
- public void repositoryWithInitializedSubmodule() throws IOException,
- GitAPIException {
- final ObjectId id = ObjectId
- .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
- final String path = "sub";
+ public void repositoryWithInitializedSubmodule() throws Exception {
+ String path = "sub";
+ Repository subRepo = Git.init().setBare(false)
+ .setDirectory(new File(db.getWorkTree(), path)).call()
+ .getRepository();
+ assertNotNull(subRepo);
+
+ TestRepository<?> subTr = new TestRepository<>(subRepo);
+ ObjectId id = subTr.branch(Constants.HEAD).commit().create().copy();
+
DirCache cache = db.lockDirCache();
DirCacheEditor editor = cache.editor();
editor.add(new PathEdit(path) {
@@ -287,15 +292,6 @@
ConfigConstants.CONFIG_KEY_URL, url);
modulesConfig.save();
- Repository subRepo = Git.init().setBare(false)
- .setDirectory(new File(db.getWorkTree(), path)).call()
- .getRepository();
- assertNotNull(subRepo);
-
- RefUpdate update = subRepo.updateRef(Constants.HEAD, true);
- update.setNewObjectId(id);
- update.forceUpdate();
-
SubmoduleStatusCommand command = new SubmoduleStatusCommand(db);
Map<String, SubmoduleStatus> statuses = command.call();
assertNotNull(statuses);
@@ -312,11 +308,16 @@
}
@Test
- public void repositoryWithDifferentRevCheckedOutSubmodule()
- throws IOException, GitAPIException {
- final ObjectId id = ObjectId
- .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
- final String path = "sub";
+ public void repositoryWithDifferentRevCheckedOutSubmodule() throws Exception {
+ String path = "sub";
+ Repository subRepo = Git.init().setBare(false)
+ .setDirectory(new File(db.getWorkTree(), path)).call()
+ .getRepository();
+ assertNotNull(subRepo);
+
+ TestRepository<?> subTr = new TestRepository<>(subRepo);
+ ObjectId id = subTr.branch(Constants.HEAD).commit().create().copy();
+
DirCache cache = db.lockDirCache();
DirCacheEditor editor = cache.editor();
editor.add(new PathEdit(path) {
@@ -343,15 +344,7 @@
ConfigConstants.CONFIG_KEY_URL, url);
modulesConfig.save();
- Repository subRepo = Git.init().setBare(false)
- .setDirectory(new File(db.getWorkTree(), path)).call()
- .getRepository();
- assertNotNull(subRepo);
-
- RefUpdate update = subRepo.updateRef(Constants.HEAD, true);
- update.setNewObjectId(ObjectId
- .fromString("aaaa0000aaaa0000aaaa0000aaaa0000aaaa0000"));
- update.forceUpdate();
+ ObjectId newId = subTr.branch(Constants.HEAD).commit().create().copy();
SubmoduleStatusCommand command = new SubmoduleStatusCommand(db);
Map<String, SubmoduleStatus> statuses = command.call();
@@ -365,7 +358,7 @@
assertNotNull(status);
assertEquals(path, status.getPath());
assertEquals(id, status.getIndexId());
- assertEquals(update.getNewObjectId(), status.getHeadId());
+ assertEquals(newId, status.getHeadId());
assertEquals(SubmoduleStatusType.REV_CHECKED_OUT, status.getType());
}
}
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 478071f..4719ceb 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -59,6 +59,7 @@
org.eclipse.jgit.ignore;version="4.9.0",
org.eclipse.jgit.ignore.internal;version="4.9.0";x-friends:="org.eclipse.jgit.test",
org.eclipse.jgit.internal;version="4.9.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.fsck;version="4.9.0";x-friends:="org.eclipse.jgit.test",
org.eclipse.jgit.internal.ketch;version="4.9.0";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
org.eclipse.jgit.internal.storage.dfs;version="4.9.0";
x-friends:="org.eclipse.jgit.test,
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 1f341e6..8306eb5 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -206,8 +206,8 @@
<pluginManagement>
<plugins>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
+ <groupId>com.github.hazendaz.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
<configuration>
<excludeFilterFile>findBugs/FindBugsExcludeFilter.xml</excludeFilterFile>
</configuration>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 627007d..fc29f48 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -208,12 +208,14 @@
createBranchFailedUnknownReason=Create branch failed for unknown reason
createBranchUnexpectedResult=Create branch returned unexpected result {0}
createNewFileFailed=Could not create new file {0}
+createRequiresZeroOldId=Create requires old ID to be zero
credentialPassword=Password
credentialUsername=Username
daemonAlreadyRunning=Daemon already running
daysAgo={0} days ago
deleteBranchUnexpectedResult=Delete branch returned unexpected result {0}
deleteFileFailed=Could not delete file {0}
+deleteRequiresZeroNewId=Delete requires new ID to be zero
deleteTagUnexpectedResult=Delete tag returned unexpected result {0}
deletingNotSupported=Deleting {0} not supported.
destinationIsNotAWildcard=Destination is not a wildcard.
@@ -242,6 +244,7 @@
encryptionOnlyPBE=Encryption error: only password-based encryption (PBE) algorithms are supported.
endOfFileInEscape=End of file in escape
entryNotFoundByPath=Entry not found by path: {0}
+enumValueNotSupported0=Invalid value: {0}
enumValueNotSupported2=Invalid value: {0}.{1}={2}
enumValueNotSupported3=Invalid value: {0}.{1}.{2}={3}
enumValuesNotAvailable=Enumerated values of type {0} not available
@@ -406,8 +409,11 @@
mergeRecursiveTooManyMergeBasesFor = "More than {0} merge bases for:\n a {1}\n b {2} found:\n count {3}"
messageAndTaggerNotAllowedInUnannotatedTags = Unannotated tags cannot have a message or tagger
minutesAgo={0} minutes ago
+mismatchOffset=mismatch offset for object {0}
+mismatchCRC=mismatch CRC for object {0}
missingAccesskey=Missing accesskey.
missingConfigurationForKey=No value for key {0} found in configuration
+missingCRC=missing CRC for object {0}
missingDeltaBase=delta base
missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch
missingObject=Missing {0} {1}
@@ -425,6 +431,7 @@
needPackOut=need packOut
needsAtLeastOneEntry=Needs at least one entry
needsWorkdir=Needs workdir
+newIdMustNotBeNull=New ID must not be null
newlineInQuotesNotAllowed=Newline in quotes not allowed
noApplyInDelete=No apply in delete
noClosingBracket=No closing {0} found for {1} at index {2}.
@@ -458,6 +465,7 @@
objectNotFoundIn=Object {0} not found in {1}.
obtainingCommitsForCherryPick=Obtaining commits that need to be cherry-picked
offsetWrittenDeltaBaseForObjectNotFoundInAPack=Offset-written delta base for object not found in a pack
+oldIdMustNotBeNull=Expected old ID must not be null
onlyAlreadyUpToDateAndFastForwardMergesAreAvailable=only already-up-to-date and fast forward merges are available
onlyOneFetchSupported=Only one fetch supported
onlyOneOperationCallPerConnectionIsSupported=Only one operation call per connection is supported.
@@ -662,6 +670,7 @@
unknownHost=unknown host
unknownIndexVersionOrCorruptIndex=Unknown index version (or corrupt index): {0}
unknownObject=unknown object
+unknownObjectInIndex=unknown object {0} found in index but not in pack file
unknownObjectType=Unknown object type {0}.
unknownObjectType2=unknown
unknownRepositoryFormat=Unknown repository format
@@ -684,6 +693,7 @@
unsupportedPackIndexVersion=Unsupported pack index version {0}
unsupportedPackVersion=Unsupported pack version {0}.
unsupportedRepositoryDescription=Repository description not supported
+updateRequiresOldIdAndNewId=Update requires both old ID and new ID to be nonzero
updatingHeadFailed=Updating HEAD failed
updatingReferences=Updating references
updatingRefFailed=Updating the ref {0} to {1} failed. ReturnCode from RefUpdate.update() was {2}
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 274ece6..e29fc05 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -482,7 +482,7 @@
JGitText.get().entryNotFoundByPath, only.get(i)));
// there must be at least one change
- if (emptyCommit)
+ if (emptyCommit && !allowEmpty.booleanValue())
// 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);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
index 04caa0f..394bea5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
@@ -109,4 +109,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
index f97dce9..b5c0b15 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
@@ -162,4 +162,4 @@
throw new JGitInternalException(e.getMessage(), e);
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
index 905ad76..c256b73 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
@@ -193,4 +193,4 @@
return key + "=" + value; //$NON-NLS-1$
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
index c9c69db..b88a16e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
@@ -225,4 +225,4 @@
return sb.toString();
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
index e1dfcff..5eb1942 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
@@ -525,4 +525,4 @@
buf.append("]");
return buf.toString();
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptPackIndexException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptPackIndexException.java
new file mode 100644
index 0000000..65d83b3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptPackIndexException.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.errors;
+
+import org.eclipse.jgit.annotations.Nullable;
+
+/**
+ * Exception thrown when encounters a corrupt pack index file.
+ *
+ * @since 4.9
+ */
+public class CorruptPackIndexException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /** The error type of a corrupt index file. */
+ public enum ErrorType {
+ /** Offset does not match index in pack file. */
+ MISMATCH_OFFSET,
+ /** CRC does not match CRC of the object data in pack file. */
+ MISMATCH_CRC,
+ /** CRC is not present in index file. */
+ MISSING_CRC,
+ /** Object in pack is not present in index file. */
+ MISSING_OBJ,
+ /** Object in index file is not present in pack file. */
+ UNKNOWN_OBJ,
+ }
+
+ private ErrorType errorType;
+
+ /**
+ * Report a specific error condition discovered in an index file.
+ *
+ * @param message
+ * the error message.
+ * @param errorType
+ * the error type of corruption.
+ */
+ public CorruptPackIndexException(String message, ErrorType errorType) {
+ super(message);
+ this.errorType = errorType;
+ }
+
+ /**
+ * Specific the reason of the corrupt index file.
+ *
+ * @return error condition or null.
+ */
+ @Nullable
+ public ErrorType getErrorType() {
+ return errorType;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java
index b5b1af5..ece76ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java
@@ -92,4 +92,4 @@
public TooLargeObjectInPackException(URIish uri, String s) {
super(uri.setPass(null) + ": " + s); //$NON-NLS-1$
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java
index 4f297b9..6cb332d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java
@@ -69,4 +69,4 @@
+ bundleClass.getName() + ", " + locale.toString() + "]", //$NON-NLS-1$ //$NON-NLS-2$
bundleClass, locale, cause);
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
index 62a6749..b684dd6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
@@ -167,4 +167,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
index 65224ea..ce9ad80 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
@@ -227,29 +227,30 @@
int left = right;
right = path.indexOf(slash, right);
if (right == -1) {
- if (left < endExcl)
+ if (left < endExcl) {
match = matches(matcher, path, left, endExcl,
assumeDirectory);
+ } else {
+ // a/** should not match a/ or a
+ match = match && matchers.get(matcher) != WILD;
+ }
if (match) {
- if (matcher == matchers.size() - 2
- && matchers.get(matcher + 1) == WILD)
- // ** can match *nothing*: a/b/** match also a/b
- return true;
if (matcher < matchers.size() - 1
&& matchers.get(matcher) == WILD) {
// ** can match *nothing*: a/**/b match also a/b
matcher++;
match = matches(matcher, path, left, endExcl,
assumeDirectory);
- } else if (dirOnly && !assumeDirectory)
+ } else if (dirOnly && !assumeDirectory) {
// Directory expectations not met
return false;
+ }
}
return match && matcher + 1 == matchers.size();
}
- if (right - left > 0)
+ if (right - left > 0) {
match = matches(matcher, path, left, right, assumeDirectory);
- else {
+ } else {
// path starts with slash???
right++;
continue;
@@ -261,12 +262,14 @@
right = left - 1;
}
matcher++;
- if (matcher == matchers.size())
+ if (matcher == matchers.size()) {
return true;
- } else if (lastWildmatch != -1)
+ }
+ } else if (lastWildmatch != -1) {
matcher = lastWildmatch + 1;
- else
+ } else {
return false;
+ }
right++;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 2ba3b8f..a68f839 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -267,12 +267,14 @@
/***/ public String createBranchFailedUnknownReason;
/***/ public String createBranchUnexpectedResult;
/***/ public String createNewFileFailed;
+ /***/ public String createRequiresZeroOldId;
/***/ public String credentialPassword;
/***/ public String credentialUsername;
/***/ public String daemonAlreadyRunning;
/***/ public String daysAgo;
/***/ public String deleteBranchUnexpectedResult;
/***/ public String deleteFileFailed;
+ /***/ public String deleteRequiresZeroNewId;
/***/ public String deleteTagUnexpectedResult;
/***/ public String deletingNotSupported;
/***/ public String destinationIsNotAWildcard;
@@ -301,6 +303,7 @@
/***/ public String encryptionOnlyPBE;
/***/ public String endOfFileInEscape;
/***/ public String entryNotFoundByPath;
+ /***/ public String enumValueNotSupported0;
/***/ public String enumValueNotSupported2;
/***/ public String enumValueNotSupported3;
/***/ public String enumValuesNotAvailable;
@@ -465,8 +468,11 @@
/***/ public String mergeRecursiveTooManyMergeBasesFor;
/***/ public String messageAndTaggerNotAllowedInUnannotatedTags;
/***/ public String minutesAgo;
+ /***/ public String mismatchOffset;
+ /***/ public String mismatchCRC;
/***/ public String missingAccesskey;
/***/ public String missingConfigurationForKey;
+ /***/ public String missingCRC;
/***/ public String missingDeltaBase;
/***/ public String missingForwardImageInGITBinaryPatch;
/***/ public String missingObject;
@@ -484,6 +490,7 @@
/***/ public String needPackOut;
/***/ public String needsAtLeastOneEntry;
/***/ public String needsWorkdir;
+ /***/ public String newIdMustNotBeNull;
/***/ public String newlineInQuotesNotAllowed;
/***/ public String noApplyInDelete;
/***/ public String noClosingBracket;
@@ -517,6 +524,7 @@
/***/ public String objectNotFoundIn;
/***/ public String obtainingCommitsForCherryPick;
/***/ public String offsetWrittenDeltaBaseForObjectNotFoundInAPack;
+ /***/ public String oldIdMustNotBeNull;
/***/ public String onlyAlreadyUpToDateAndFastForwardMergesAreAvailable;
/***/ public String onlyOneFetchSupported;
/***/ public String onlyOneOperationCallPerConnectionIsSupported;
@@ -721,6 +729,7 @@
/***/ public String unknownHost;
/***/ public String unknownIndexVersionOrCorruptIndex;
/***/ public String unknownObject;
+ /***/ public String unknownObjectInIndex;
/***/ public String unknownObjectType;
/***/ public String unknownObjectType2;
/***/ public String unknownRepositoryFormat;
@@ -743,6 +752,7 @@
/***/ public String unsupportedPackIndexVersion;
/***/ public String unsupportedPackVersion;
/***/ public String unsupportedRepositoryDescription;
+ /***/ public String updateRequiresOldIdAndNewId;
/***/ public String updatingHeadFailed;
/***/ public String updatingReferences;
/***/ public String updatingRefFailed;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
new file mode 100644
index 0000000..588ed9b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.internal.fsck;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.CorruptPackIndexException;
+import org.eclipse.jgit.errors.CorruptPackIndexException.ErrorType;
+import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ObjectId;
+
+/** Holds all fsck errors of a git repository. */
+public class FsckError {
+ /** Represents a corrupt object. */
+ public static class CorruptObject {
+ final ObjectId id;
+
+ final int type;
+
+ ObjectChecker.ErrorType errorType;
+
+ /**
+ * @param id
+ * the object identifier.
+ * @param type
+ * type of the object.
+ */
+ public CorruptObject(ObjectId id, int type) {
+ this.id = id;
+ this.type = type;
+ }
+
+ void setErrorType(ObjectChecker.ErrorType errorType) {
+ this.errorType = errorType;
+ }
+
+ /** @return identifier of the object. */
+ public ObjectId getId() {
+ return id;
+ }
+
+ /** @return type of the object. */
+ public int getType() {
+ return type;
+ }
+
+ /** @return error type of the corruption. */
+ @Nullable
+ public ObjectChecker.ErrorType getErrorType() {
+ return errorType;
+ }
+ }
+
+ /** Represents a corrupt pack index file. */
+ public static class CorruptIndex {
+ String fileName;
+
+ CorruptPackIndexException.ErrorType errorType;
+
+ /**
+ * @param fileName
+ * the file name of the pack index.
+ * @param errorType
+ * the type of error as reported in
+ * {@link CorruptPackIndexException}.
+ */
+ public CorruptIndex(String fileName, ErrorType errorType) {
+ this.fileName = fileName;
+ this.errorType = errorType;
+ }
+
+ /** @return the file name of the index file. */
+ public String getFileName() {
+ return fileName;
+ }
+
+ /** @return the error type of the corruption. */
+ public ErrorType getErrorType() {
+ return errorType;
+ }
+ }
+
+ private final Set<CorruptObject> corruptObjects = new HashSet<>();
+
+ private final Set<ObjectId> missingObjects = new HashSet<>();
+
+ private final Set<CorruptIndex> corruptIndices = new HashSet<>();
+
+ private final Set<String> nonCommitHeads = new HashSet<>();
+
+ /** @return corrupt objects from all pack files. */
+ public Set<CorruptObject> getCorruptObjects() {
+ return corruptObjects;
+ }
+
+ /** @return missing objects that should present in pack files. */
+ public Set<ObjectId> getMissingObjects() {
+ return missingObjects;
+ }
+
+ /** @return corrupt index files associated with the packs. */
+ public Set<CorruptIndex> getCorruptIndices() {
+ return corruptIndices;
+ }
+
+ /** @return refs/heads/* point to non-commit object. */
+ public Set<String> getNonCommitHeads() {
+ return nonCommitHeads;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
new file mode 100644
index 0000000..e6ec681
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.internal.fsck;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.CRC32;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.CorruptPackIndexException;
+import org.eclipse.jgit.errors.CorruptPackIndexException.ErrorType;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject;
+import org.eclipse.jgit.internal.storage.dfs.ReadableChannel;
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.transport.PackParser;
+import org.eclipse.jgit.transport.PackedObjectInfo;
+
+/** A read-only pack parser for object validity checking. */
+public class FsckPackParser extends PackParser {
+ private final CRC32 crc;
+
+ private final ReadableChannel channel;
+
+ private final Set<CorruptObject> corruptObjects = new HashSet<>();
+
+ private long expectedObjectCount = -1L;
+
+ private long offset;
+
+ private int blockSize;
+
+ /**
+ * @param db
+ * the object database which stores repository's data.
+ * @param channel
+ * readable channel of the pack file.
+ */
+ public FsckPackParser(ObjectDatabase db, ReadableChannel channel) {
+ super(db, Channels.newInputStream(channel));
+ this.channel = channel;
+ setCheckObjectCollisions(false);
+ this.crc = new CRC32();
+ this.blockSize = channel.blockSize() > 0 ? channel.blockSize() : 65536;
+ }
+
+ @Override
+ protected void onPackHeader(long objCnt) throws IOException {
+ if (expectedObjectCount >= 0) {
+ // Some DFS pack files don't contain the correct object count, e.g.
+ // INSERT/RECEIVE packs don't always contain the correct object
+ // count in their headers. Overwrite the expected object count
+ // after parsing the pack header.
+ setExpectedObjectCount(expectedObjectCount);
+ }
+ }
+
+ @Override
+ protected void onBeginWholeObject(long streamPosition, int type,
+ long inflatedSize) throws IOException {
+ crc.reset();
+ }
+
+ @Override
+ protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
+ throws IOException {
+ crc.update(raw, pos, len);
+ }
+
+ @Override
+ protected void onObjectData(Source src, byte[] raw, int pos, int len)
+ throws IOException {
+ crc.update(raw, pos, len);
+ }
+
+ @Override
+ protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
+ info.setCRC((int) crc.getValue());
+ }
+
+ @Override
+ protected void onBeginOfsDelta(long deltaStreamPosition,
+ long baseStreamPosition, long inflatedSize) throws IOException {
+ crc.reset();
+ }
+
+ @Override
+ protected void onBeginRefDelta(long deltaStreamPosition, AnyObjectId baseId,
+ long inflatedSize) throws IOException {
+ crc.reset();
+ }
+
+ @Override
+ protected UnresolvedDelta onEndDelta() throws IOException {
+ UnresolvedDelta delta = new UnresolvedDelta();
+ delta.setCRC((int) crc.getValue());
+ return delta;
+ }
+
+ @Override
+ protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode,
+ byte[] data) throws IOException {
+ // FsckPackParser ignores this event.
+ }
+
+ @Override
+ protected void verifySafeObject(final AnyObjectId id, final int type,
+ final byte[] data) {
+ try {
+ super.verifySafeObject(id, type, data);
+ } catch (CorruptObjectException e) {
+ // catch the exception and continue parse the pack file
+ CorruptObject o = new CorruptObject(id.toObjectId(), type);
+ if (e.getErrorType() != null) {
+ o.setErrorType(e.getErrorType());
+ }
+ corruptObjects.add(o);
+ }
+ }
+
+ @Override
+ protected void onPackFooter(byte[] hash) throws IOException {
+ }
+
+ @Override
+ protected boolean onAppendBase(int typeCode, byte[] data,
+ PackedObjectInfo info) throws IOException {
+ // Do nothing.
+ return false;
+ }
+
+ @Override
+ protected void onEndThinPack() throws IOException {
+ }
+
+ @Override
+ protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
+ ObjectTypeAndSize info) throws IOException {
+ crc.reset();
+ offset = obj.getOffset();
+ return readObjectHeader(info);
+ }
+
+ @Override
+ protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
+ ObjectTypeAndSize info) throws IOException {
+ crc.reset();
+ offset = delta.getOffset();
+ return readObjectHeader(info);
+ }
+
+ @Override
+ protected int readDatabase(byte[] dst, int pos, int cnt)
+ throws IOException {
+ // read from input instead of database.
+ int n = read(offset, dst, pos, cnt);
+ if (n > 0) {
+ offset += n;
+ }
+ return n;
+ }
+
+ int read(long channelPosition, byte[] dst, int pos, int cnt)
+ throws IOException {
+ long block = channelPosition / blockSize;
+ byte[] bytes = readFromChannel(block);
+ if (bytes == null) {
+ return -1;
+ }
+ int offset = (int) (channelPosition - block * blockSize);
+ int bytesToCopy = Math.min(cnt, bytes.length - offset);
+ if (bytesToCopy < 1) {
+ return -1;
+ }
+ System.arraycopy(bytes, offset, dst, pos, bytesToCopy);
+ return bytesToCopy;
+ }
+
+ private byte[] readFromChannel(long block) throws IOException {
+ channel.position(block * blockSize);
+ ByteBuffer buf = ByteBuffer.allocate(blockSize);
+ int totalBytesRead = 0;
+ while (totalBytesRead < blockSize) {
+ int bytesRead = channel.read(buf);
+ if (bytesRead == -1) {
+ if (totalBytesRead == 0) {
+ return null;
+ }
+ return Arrays.copyOf(buf.array(), totalBytesRead);
+ }
+ totalBytesRead += bytesRead;
+ }
+ return buf.array();
+ }
+
+ @Override
+ protected boolean checkCRC(int oldCRC) {
+ return oldCRC == (int) crc.getValue();
+ }
+
+ @Override
+ protected void onStoreStream(byte[] raw, int pos, int len)
+ throws IOException {
+ }
+
+ /**
+ * @return corrupt objects that reported by {@link ObjectChecker}.
+ */
+ public Set<CorruptObject> getCorruptObjects() {
+ return corruptObjects;
+ }
+
+ /**
+ * Verify the existing index file with all objects from the pack.
+ *
+ * @param entries
+ * all the entries that are expected in the index file
+ * @param idx
+ * index file associate with the pack
+ * @throws CorruptPackIndexException
+ * when the index file is corrupt.
+ */
+ public void verifyIndex(List<PackedObjectInfo> entries, PackIndex idx)
+ throws CorruptPackIndexException {
+ Set<String> all = new HashSet<>();
+ for (PackedObjectInfo entry : entries) {
+ all.add(entry.getName());
+ long offset = idx.findOffset(entry);
+ if (offset == -1) {
+ throw new CorruptPackIndexException(
+ MessageFormat.format(JGitText.get().missingObject,
+ entry.getType(), entry.getName()),
+ ErrorType.MISSING_OBJ);
+ } else if (offset != entry.getOffset()) {
+ throw new CorruptPackIndexException(MessageFormat
+ .format(JGitText.get().mismatchOffset, entry.getName()),
+ ErrorType.MISMATCH_OFFSET);
+ }
+
+ try {
+ if (idx.hasCRC32Support()
+ && (int) idx.findCRC32(entry) != entry.getCRC()) {
+ throw new CorruptPackIndexException(
+ MessageFormat.format(JGitText.get().mismatchCRC,
+ entry.getName()),
+ ErrorType.MISMATCH_CRC);
+ }
+ } catch (MissingObjectException e) {
+ throw new CorruptPackIndexException(MessageFormat
+ .format(JGitText.get().missingCRC, entry.getName()),
+ ErrorType.MISSING_CRC);
+ }
+ }
+
+ for (MutableEntry entry : idx) {
+ if (!all.contains(entry.name())) {
+ throw new CorruptPackIndexException(MessageFormat.format(
+ JGitText.get().unknownObjectInIndex, entry.name()),
+ ErrorType.UNKNOWN_OBJ);
+ }
+ }
+ }
+
+ /**
+ * Set the object count for overwriting the expected object count from pack
+ * header.
+ *
+ * @param expectedObjectCount
+ * the actual expected object count.
+ */
+ public void overwriteObjectCount(long expectedObjectCount) {
+ this.expectedObjectCount = expectedObjectCount;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/package-info.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/package-info.java
new file mode 100644
index 0000000..361b61f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Git fsck support.
+ */
+package org.eclipse.jgit.internal.fsck;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
new file mode 100644
index 0000000..f90ba7d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.internal.storage.dfs;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.errors.CorruptPackIndexException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.fsck.FsckError;
+import org.eclipse.jgit.internal.fsck.FsckError.CorruptIndex;
+import org.eclipse.jgit.internal.fsck.FsckPackParser;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.transport.PackedObjectInfo;
+
+/** Verify the validity and connectivity of a DFS repository. */
+public class DfsFsck {
+ private final DfsRepository repo;
+
+ private final DfsObjDatabase objdb;
+
+ private final DfsReader ctx;
+
+ private ObjectChecker objChecker = new ObjectChecker();
+
+ /**
+ * Initialize DFS fsck.
+ *
+ * @param repository
+ * the dfs repository to check.
+ */
+ public DfsFsck(DfsRepository repository) {
+ repo = repository;
+ objdb = repo.getObjectDatabase();
+ ctx = objdb.newReader();
+ }
+
+
+ /**
+ * Verify the integrity and connectivity of all objects in the object
+ * database.
+ *
+ * @param pm
+ * callback to provide progress feedback during the check.
+ * @return all errors about the repository.
+ * @throws IOException
+ * if encounters IO errors during the process.
+ */
+ public FsckError check(ProgressMonitor pm) throws IOException {
+ FsckError errors = new FsckError();
+ try {
+ for (DfsPackFile pack : objdb.getPacks()) {
+ DfsPackDescription packDesc = pack.getPackDescription();
+ try (ReadableChannel channel = repo.getObjectDatabase()
+ .openFile(packDesc, PackExt.PACK)) {
+ List<PackedObjectInfo> objectsInPack;
+ FsckPackParser parser = new FsckPackParser(
+ repo.getObjectDatabase(), channel);
+ parser.setObjectChecker(objChecker);
+ parser.overwriteObjectCount(packDesc.getObjectCount());
+ parser.parse(pm);
+ errors.getCorruptObjects()
+ .addAll(parser.getCorruptObjects());
+ objectsInPack = parser.getSortedObjectList(null);
+ parser.verifyIndex(objectsInPack, pack.getPackIndex(ctx));
+ } catch (MissingObjectException e) {
+ errors.getMissingObjects().add(e.getObjectId());
+ } catch (CorruptPackIndexException e) {
+ errors.getCorruptIndices().add(new CorruptIndex(
+ pack.getPackDescription()
+ .getFileName(PackExt.INDEX),
+ e.getErrorType()));
+ }
+ }
+
+ try (ObjectWalk ow = new ObjectWalk(ctx)) {
+ for (Ref r : repo.getAllRefs().values()) {
+ try {
+ RevObject tip = ow.parseAny(r.getObjectId());
+ if (r.getLeaf().getName().startsWith(Constants.R_HEADS)) {
+ // check if heads point to a commit object
+ if (tip.getType() != Constants.OBJ_COMMIT) {
+ errors.getNonCommitHeads()
+ .add(r.getLeaf().getName());
+ }
+ }
+ ow.markStart(tip);
+ ow.checkConnectivity();
+ ow.markUninteresting(tip);
+ } catch (MissingObjectException e) {
+ errors.getMissingObjects().add(e.getObjectId());
+ }
+ }
+ }
+ } finally {
+ ctx.close();
+ }
+ return errors;
+ }
+
+ /**
+ * Use a customized object checker instead of the default one. Caller can
+ * specify a skip list to ignore some errors.
+ *
+ * @param objChecker
+ * A customized object checker.
+ */
+ public void setObjectChecker(ObjectChecker objChecker) {
+ this.objChecker = objChecker;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
index 4b4337d..2eacb7a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
@@ -74,4 +74,4 @@
public String getToBranch() {
return to;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
index bda5cbe..3f82e2a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
@@ -78,4 +78,4 @@
return r.getRules().isEmpty() ? null : r;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
index d2fcacf..6221cfa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
@@ -374,7 +374,7 @@
};
}
- private void requireLock() {
+ void requireLock() {
if (os == null) {
unlock();
throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java
index 154809b..962f765 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java
@@ -82,4 +82,4 @@
public void close() {
wc.close();
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
new file mode 100644
index 0000000..b661ae7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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.internal.storage.file;
+
+import static java.util.stream.Collectors.toList;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.LockFailedException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.RefDirectory.PackedRefList;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.util.RefList;
+
+/**
+ * Implementation of {@link BatchRefUpdate} that uses the {@code packed-refs}
+ * file to support atomically updating multiple refs.
+ * <p>
+ * The algorithm is designed to be compatible with traditional single ref
+ * updates operating on single refs only. Regardless of success or failure, the
+ * results are atomic: from the perspective of any reader, either all updates in
+ * the batch will be visible, or none will. In the case of process failure
+ * during any of the following steps, removal of stale lock files is always
+ * safe, and will never result in an inconsistent state, although the update may
+ * or may not have been applied.
+ * <p>
+ * The algorithm is:
+ * <ol>
+ * <li>Pack loose refs involved in the transaction using the normal pack-refs
+ * operation. This ensures that creating lock files in the following step
+ * succeeds even if a batch contains both a delete of {@code refs/x} (loose) and
+ * a create of {@code refs/x/y}.</li>
+ * <li>Create locks for all loose refs involved in the transaction, even if they
+ * are not currently loose.</li>
+ * <li>Pack loose refs again, this time while holding all lock files (see {@link
+ * RefDirectory#pack(Map)}), without deleting them afterwards. This covers a
+ * potential race where new loose refs were created after the initial packing
+ * step. If no new loose refs were created during this race, this step does not
+ * modify any files on disk. Keep the merged state in memory.</li>
+ * <li>Update the in-memory packed refs with the commands in the batch, possibly
+ * failing the whole batch if any old ref values do not match.</li>
+ * <li>If the update succeeds, lock {@code packed-refs} and commit by atomically
+ * renaming the lock file.</li>
+ * <li>Delete loose ref lock files.</li>
+ * </ol>
+ *
+ * Because the packed-refs file format is a sorted list, this algorithm is
+ * linear in the total number of refs, regardless of the batch size. This can be
+ * a significant slowdown on repositories with large numbers of refs; callers
+ * that prefer speed over atomicity should use {@code setAtomic(false)}. As an
+ * optimization, an update containing a single ref update does not use the
+ * packed-refs protocol.
+ */
+class PackedBatchRefUpdate extends BatchRefUpdate {
+ private RefDirectory refdb;
+
+ PackedBatchRefUpdate(RefDirectory refdb) {
+ super(refdb);
+ this.refdb = refdb;
+ }
+
+ @Override
+ public void execute(RevWalk walk, ProgressMonitor monitor,
+ List<String> options) throws IOException {
+ if (!isAtomic()) {
+ // Use default one-by-one implementation.
+ super.execute(walk, monitor, options);
+ return;
+ }
+ List<ReceiveCommand> pending =
+ ReceiveCommand.filter(getCommands(), NOT_ATTEMPTED);
+ if (pending.isEmpty()) {
+ return;
+ }
+ if (pending.size() == 1) {
+ // Single-ref updates are always atomic, no need for packed-refs.
+ super.execute(walk, monitor, options);
+ return;
+ }
+
+ // Required implementation details copied from super.execute.
+ if (!blockUntilTimestamps(MAX_WAIT)) {
+ return;
+ }
+ if (options != null) {
+ setPushOptions(options);
+ }
+ // End required implementation details.
+
+ // Check for conflicting names before attempting to acquire locks, since
+ // lockfile creation may fail on file/directory conflicts.
+ if (!checkConflictingNames(pending)) {
+ return;
+ }
+
+ if (!checkObjectExistence(walk, pending)) {
+ return;
+ }
+
+ if (!checkNonFastForwards(walk, pending)) {
+ return;
+ }
+
+ // Pack refs normally, so we can create lock files even in the case where
+ // refs/x is deleted and refs/x/y is created in this batch.
+ try {
+ refdb.pack(
+ pending.stream().map(ReceiveCommand::getRefName).collect(toList()));
+ } catch (LockFailedException e) {
+ lockFailure(pending.get(0), pending);
+ return;
+ }
+
+ Map<String, LockFile> locks = null;
+ refdb.inProcessPackedRefsLock.lock();
+ try {
+ locks = lockLooseRefs(pending);
+ if (locks == null) {
+ return;
+ }
+ PackedRefList oldPackedList = refdb.pack(locks);
+ RefList<Ref> newRefs = applyUpdates(walk, oldPackedList, pending);
+ if (newRefs == null) {
+ return;
+ }
+ LockFile packedRefsLock = refdb.lockPackedRefs();
+ if (packedRefsLock == null) {
+ lockFailure(pending.get(0), pending);
+ return;
+ }
+ // commitPackedRefs removes lock file (by renaming over real file).
+ refdb.commitPackedRefs(packedRefsLock, newRefs, oldPackedList);
+ } finally {
+ try {
+ unlockAll(locks);
+ } finally {
+ refdb.inProcessPackedRefsLock.unlock();
+ }
+ }
+
+ refdb.fireRefsChanged();
+ pending.forEach(c -> c.setResult(ReceiveCommand.Result.OK));
+ writeReflog(pending);
+ }
+
+ private boolean checkConflictingNames(List<ReceiveCommand> commands)
+ throws IOException {
+ Set<String> takenNames = new HashSet<>();
+ Set<String> takenPrefixes = new HashSet<>();
+ Set<String> deletes = new HashSet<>();
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getType() != ReceiveCommand.Type.DELETE) {
+ takenNames.add(cmd.getRefName());
+ addPrefixesTo(cmd.getRefName(), takenPrefixes);
+ } else {
+ deletes.add(cmd.getRefName());
+ }
+ }
+ Set<String> initialRefs = refdb.getRefs(RefDatabase.ALL).keySet();
+ for (String name : initialRefs) {
+ if (!deletes.contains(name)) {
+ takenNames.add(name);
+ addPrefixesTo(name, takenPrefixes);
+ }
+ }
+
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getType() != ReceiveCommand.Type.DELETE &&
+ takenPrefixes.contains(cmd.getRefName())) {
+ // This ref is a prefix of some other ref. This check doesn't apply when
+ // this command is a delete, because if the ref is deleted nobody will
+ // ever be creating a loose ref with that name.
+ lockFailure(cmd, commands);
+ return false;
+ }
+ for (String prefix : getPrefixes(cmd.getRefName())) {
+ if (takenNames.contains(prefix)) {
+ // A prefix of this ref is already a refname. This check does apply
+ // when this command is a delete, because we would need to create the
+ // refname as a directory in order to create a lockfile for the
+ // to-be-deleted ref.
+ lockFailure(cmd, commands);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean checkObjectExistence(RevWalk walk,
+ List<ReceiveCommand> commands) throws IOException {
+ for (ReceiveCommand cmd : commands) {
+ try {
+ if (!cmd.getNewId().equals(ObjectId.zeroId())) {
+ walk.parseAny(cmd.getNewId());
+ }
+ } catch (MissingObjectException e) {
+ // ReceiveCommand#setResult(Result) converts REJECTED to
+ // REJECTED_NONFASTFORWARD, even though that result is also used for a
+ // missing object. Eagerly handle this case so we can set the right
+ // result.
+ reject(cmd, ReceiveCommand.Result.REJECTED_MISSING_OBJECT, commands);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean checkNonFastForwards(RevWalk walk,
+ List<ReceiveCommand> commands) throws IOException {
+ if (isAllowNonFastForwards()) {
+ return true;
+ }
+ for (ReceiveCommand cmd : commands) {
+ cmd.updateType(walk);
+ if (cmd.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD) {
+ reject(cmd, REJECTED_NONFASTFORWARD, commands);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Lock loose refs corresponding to a list of commands.
+ *
+ * @param commands
+ * commands that we intend to execute.
+ * @return map of ref name in the input commands to lock file. Always contains
+ * one entry for each ref in the input list. All locks are acquired
+ * before returning. If any lock was not able to be acquired: the
+ * return value is null; no locks are held; and all commands that were
+ * pending are set to fail with {@code LOCK_FAILURE}.
+ * @throws IOException
+ * an error occurred other than a failure to acquire; no locks are
+ * held if this exception is thrown.
+ */
+ @Nullable
+ private Map<String, LockFile> lockLooseRefs(List<ReceiveCommand> commands)
+ throws IOException {
+ ReceiveCommand failed = null;
+ Map<String, LockFile> locks = new HashMap<>();
+ try {
+ RETRY: for (int ms : refdb.getRetrySleepMs()) {
+ failed = null;
+ // Release all locks before trying again, to prevent deadlock.
+ unlockAll(locks);
+ locks.clear();
+ RefDirectory.sleep(ms);
+
+ for (ReceiveCommand c : commands) {
+ String name = c.getRefName();
+ LockFile lock = new LockFile(refdb.fileFor(name));
+ if (locks.put(name, lock) != null) {
+ throw new IOException(
+ MessageFormat.format(JGitText.get().duplicateRef, name));
+ }
+ if (!lock.lock()) {
+ failed = c;
+ continue RETRY;
+ }
+ }
+ Map<String, LockFile> result = locks;
+ locks = null;
+ return result;
+ }
+ } finally {
+ unlockAll(locks);
+ }
+ lockFailure(failed != null ? failed : commands.get(0), commands);
+ return null;
+ }
+
+ private static RefList<Ref> applyUpdates(RevWalk walk, RefList<Ref> refs,
+ List<ReceiveCommand> commands) throws IOException {
+ int nDeletes = 0;
+ List<ReceiveCommand> adds = new ArrayList<>(commands.size());
+ for (ReceiveCommand c : commands) {
+ if (c.getType() == ReceiveCommand.Type.CREATE) {
+ adds.add(c);
+ } else if (c.getType() == ReceiveCommand.Type.DELETE) {
+ nDeletes++;
+ }
+ }
+ int addIdx = 0;
+
+ // Construct a new RefList by linearly scanning the old list, and merging in
+ // any updates.
+ Map<String, ReceiveCommand> byName = byName(commands);
+ RefList.Builder<Ref> b =
+ new RefList.Builder<>(refs.size() - nDeletes + adds.size());
+ for (Ref ref : refs) {
+ String name = ref.getName();
+ ReceiveCommand cmd = byName.remove(name);
+ if (cmd == null) {
+ b.add(ref);
+ continue;
+ }
+ if (!cmd.getOldId().equals(ref.getObjectId())) {
+ lockFailure(cmd, commands);
+ return null;
+ }
+
+ // Consume any adds between the last and current ref.
+ while (addIdx < adds.size()) {
+ ReceiveCommand currAdd = adds.get(addIdx);
+ if (currAdd.getRefName().compareTo(name) < 0) {
+ b.add(peeledRef(walk, currAdd));
+ byName.remove(currAdd.getRefName());
+ } else {
+ break;
+ }
+ addIdx++;
+ }
+
+ if (cmd.getType() != ReceiveCommand.Type.DELETE) {
+ b.add(peeledRef(walk, cmd));
+ }
+ }
+
+ // All remaining adds are valid, since the refs didn't exist.
+ while (addIdx < adds.size()) {
+ ReceiveCommand cmd = adds.get(addIdx++);
+ byName.remove(cmd.getRefName());
+ b.add(peeledRef(walk, cmd));
+ }
+
+ // Any remaining updates/deletes do not correspond to any existing refs, so
+ // they are lock failures.
+ if (!byName.isEmpty()) {
+ lockFailure(byName.values().iterator().next(), commands);
+ return null;
+ }
+
+ return b.toRefList();
+ }
+
+ private void writeReflog(List<ReceiveCommand> commands) {
+ PersonIdent ident = getRefLogIdent();
+ if (ident == null) {
+ ident = new PersonIdent(refdb.getRepository());
+ }
+ ReflogWriter w = refdb.getLogWriter();
+ for (ReceiveCommand cmd : commands) {
+ // Assume any pending commands have already been executed atomically.
+ if (cmd.getResult() != ReceiveCommand.Result.OK) {
+ continue;
+ }
+ String name = cmd.getRefName();
+
+ if (cmd.getType() == ReceiveCommand.Type.DELETE) {
+ try {
+ RefDirectory.delete(w.logFor(name), RefDirectory.levelsIn(name));
+ } catch (IOException e) {
+ // Ignore failures, see below.
+ }
+ continue;
+ }
+
+ if (isRefLogDisabled(cmd)) {
+ continue;
+ }
+
+ String msg = getRefLogMessage(cmd);
+ if (isRefLogIncludingResult(cmd)) {
+ String strResult = toResultString(cmd);
+ if (strResult != null) {
+ msg = msg.isEmpty()
+ ? strResult : msg + ": " + strResult; //$NON-NLS-1$
+ }
+ }
+ try {
+ w.log(name, cmd.getOldId(), cmd.getNewId(), ident, msg);
+ } catch (IOException e) {
+ // Ignore failures, but continue attempting to write more reflogs.
+ //
+ // In this storage format, it is impossible to atomically write the
+ // reflog with the ref updates, so we have to choose between:
+ // a. Propagating this exception and claiming failure, even though the
+ // actual ref updates succeeded.
+ // b. Ignoring failures writing the reflog, so we claim success if and
+ // only if the ref updates succeeded.
+ // We choose (b) in order to surprise callers the least.
+ //
+ // Possible future improvements:
+ // * Log a warning to a logger.
+ // * Retry a fixed number of times in case the error was transient.
+ }
+ }
+ }
+
+ private String toResultString(ReceiveCommand cmd) {
+ switch (cmd.getType()) {
+ case CREATE:
+ return ReflogEntry.PREFIX_CREATED;
+ case UPDATE:
+ // Match the behavior of a single RefUpdate. In that case, setting the
+ // force bit completely bypasses the potentially expensive isMergedInto
+ // check, by design, so the reflog message may be inaccurate.
+ //
+ // Similarly, this class bypasses the isMergedInto checks when the force
+ // bit is set, meaning we can't actually distinguish between UPDATE and
+ // UPDATE_NONFASTFORWARD when isAllowNonFastForwards() returns true.
+ return isAllowNonFastForwards()
+ ? ReflogEntry.PREFIX_FORCED_UPDATE : ReflogEntry.PREFIX_FAST_FORWARD;
+ case UPDATE_NONFASTFORWARD:
+ return ReflogEntry.PREFIX_FORCED_UPDATE;
+ default:
+ return null;
+ }
+ }
+
+ private static Map<String, ReceiveCommand> byName(
+ List<ReceiveCommand> commands) {
+ Map<String, ReceiveCommand> ret = new LinkedHashMap<>();
+ for (ReceiveCommand cmd : commands) {
+ ret.put(cmd.getRefName(), cmd);
+ }
+ return ret;
+ }
+
+ private static Ref peeledRef(RevWalk walk, ReceiveCommand cmd)
+ throws IOException {
+ ObjectId newId = cmd.getNewId().copy();
+ RevObject obj = walk.parseAny(newId);
+ if (obj instanceof RevTag) {
+ return new ObjectIdRef.PeeledTag(
+ Ref.Storage.PACKED, cmd.getRefName(), newId, walk.peel(obj).copy());
+ }
+ return new ObjectIdRef.PeeledNonTag(
+ Ref.Storage.PACKED, cmd.getRefName(), newId);
+ }
+
+ private static void unlockAll(@Nullable Map<?, LockFile> locks) {
+ if (locks != null) {
+ locks.values().forEach(LockFile::unlock);
+ }
+ }
+
+ private static void lockFailure(ReceiveCommand cmd,
+ List<ReceiveCommand> commands) {
+ reject(cmd, LOCK_FAILURE, commands);
+ }
+
+ private static void reject(ReceiveCommand cmd, ReceiveCommand.Result result,
+ List<ReceiveCommand> commands) {
+ cmd.setResult(result);
+ for (ReceiveCommand c2 : commands) {
+ if (c2.getResult() == ReceiveCommand.Result.OK) {
+ // Undo OK status so ReceiveCommand#abort aborts it. Assumes this method
+ // is always called before committing any updates to disk.
+ c2.setResult(ReceiveCommand.Result.NOT_ATTEMPTED);
+ }
+ }
+ ReceiveCommand.abort(commands);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index 5d66a4f..ecf7ef9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -63,17 +63,22 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.text.MessageFormat;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.InvalidObjectIdException;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -135,6 +140,10 @@
Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
Constants.CHERRY_PICK_HEAD };
+ @SuppressWarnings("boxing")
+ private static final List<Integer> RETRY_SLEEP_MS =
+ Collections.unmodifiableList(Arrays.asList(0, 100, 200, 400, 800, 1600));
+
private final FileRepository parent;
private final File gitDir;
@@ -143,7 +152,7 @@
private final ReflogWriter logWriter;
- private final File packedRefsFile;
+ final File packedRefsFile;
/**
* Immutable sorted list of loose references.
@@ -159,6 +168,22 @@
final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();
/**
+ * Lock for coordinating operations within a single process that may contend
+ * on the {@code packed-refs} file.
+ * <p>
+ * All operations that write {@code packed-refs} must still acquire a
+ * {@link LockFile} on {@link #packedRefsFile}, even after they have acquired
+ * this lock, since there may be multiple {@link RefDirectory} instances or
+ * other processes operating on the same repo on disk.
+ * <p>
+ * This lock exists so multiple threads in the same process can wait in a fair
+ * queue without trying, failing, and retrying to acquire the on-disk lock. If
+ * {@code RepositoryCache} is used, this lock instance will be used by all
+ * threads.
+ */
+ final ReentrantLock inProcessPackedRefsLock = new ReentrantLock(true);
+
+ /**
* Number of modifications made to this database.
* <p>
* This counter is incremented when a change is made, or detected from the
@@ -174,6 +199,8 @@
*/
private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
+ private List<Integer> retrySleepMs = RETRY_SLEEP_MS;
+
RefDirectory(final FileRepository db) {
final FS fs = db.getFS();
parent = db;
@@ -183,7 +210,7 @@
packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
looseRefs.set(RefList.<LooseRef> emptyList());
- packedRefs.set(PackedRefList.NO_PACKED_REFS);
+ packedRefs.set(NO_PACKED_REFS);
}
Repository getRepository() {
@@ -209,7 +236,7 @@
private void clearReferences() {
looseRefs.set(RefList.<LooseRef> emptyList());
- packedRefs.set(PackedRefList.NO_PACKED_REFS);
+ packedRefs.set(NO_PACKED_REFS);
}
@Override
@@ -562,6 +589,16 @@
return new RefDirectoryRename(from, to);
}
+ @Override
+ public PackedBatchRefUpdate newBatchUpdate() {
+ return new PackedBatchRefUpdate(this);
+ }
+
+ @Override
+ public boolean performsAtomicTransactions() {
+ return true;
+ }
+
void stored(RefDirectoryUpdate update, FileSnapshot snapshot) {
final ObjectId target = update.getNewObjectId().copy();
final Ref leaf = update.getRef().getLeaf();
@@ -580,6 +617,9 @@
void delete(RefDirectoryUpdate update) throws IOException {
Ref dst = update.getRef();
+ if (!update.isDetachingSymbolicRef()) {
+ dst = dst.getLeaf();
+ }
String name = dst.getName();
// Write the packed-refs file using an atomic update. We might
@@ -587,16 +627,19 @@
// we don't miss an edit made externally.
final PackedRefList packed = getPackedRefs();
if (packed.contains(name)) {
- LockFile lck = new LockFile(packedRefsFile);
- if (!lck.lock())
- throw new LockFailedException(packedRefsFile);
+ inProcessPackedRefsLock.lock();
try {
- PackedRefList cur = readPackedRefs();
- int idx = cur.find(name);
- if (0 <= idx)
- commitPackedRefs(lck, cur.remove(idx), packed);
+ LockFile lck = lockPackedRefsOrThrow();
+ try {
+ PackedRefList cur = readPackedRefs();
+ int idx = cur.find(name);
+ if (0 <= idx)
+ commitPackedRefs(lck, cur.remove(idx), packed);
+ } finally {
+ lck.unlock();
+ }
} finally {
- lck.unlock();
+ inProcessPackedRefsLock.unlock();
}
}
@@ -632,91 +675,144 @@
* @throws IOException
*/
public void pack(List<String> refs) throws IOException {
- if (refs.size() == 0)
- return;
+ pack(refs, Collections.emptyMap());
+ }
+
+ PackedRefList pack(Map<String, LockFile> heldLocks) throws IOException {
+ return pack(heldLocks.keySet(), heldLocks);
+ }
+
+ private PackedRefList pack(Collection<String> refs,
+ Map<String, LockFile> heldLocks) throws IOException {
+ for (LockFile ol : heldLocks.values()) {
+ ol.requireLock();
+ }
+ if (refs.size() == 0) {
+ return null;
+ }
FS fs = parent.getFS();
// Lock the packed refs file and read the content
- LockFile lck = new LockFile(packedRefsFile);
- if (!lck.lock())
- throw new IOException(MessageFormat.format(
- JGitText.get().cannotLock, packedRefsFile));
-
+ inProcessPackedRefsLock.lock();
try {
- final PackedRefList packed = getPackedRefs();
- RefList<Ref> cur = readPackedRefs();
+ LockFile lck = lockPackedRefsOrThrow();
+ try {
+ final PackedRefList packed = getPackedRefs();
+ RefList<Ref> cur = readPackedRefs();
- // Iterate over all refs to be packed
- boolean dirty = false;
- for (String refName : refs) {
- Ref oldRef = readRef(refName, cur);
- if (oldRef.isSymbolic()) {
- continue; // can't pack symbolic refs
- }
- // Add/Update it to packed-refs
- Ref newRef = peeledPackedRef(oldRef);
- if (newRef == oldRef) {
- // No-op; peeledPackedRef returns the input ref only if it's already
- // packed, and readRef returns a packed ref only if there is no loose
- // ref.
- continue;
- }
-
- dirty = true;
- int idx = cur.find(refName);
- if (idx >= 0) {
- cur = cur.set(idx, newRef);
- } else {
- cur = cur.add(idx, newRef);
- }
- }
- if (!dirty) {
- // All requested refs were already packed accurately
- return;
- }
-
- // The new content for packed-refs is collected. Persist it.
- commitPackedRefs(lck, cur, packed);
-
- // Now delete the loose refs which are now packed
- for (String refName : refs) {
- // Lock the loose ref
- File refFile = fileFor(refName);
- if (!fs.exists(refFile))
- continue;
- LockFile rLck = new LockFile(refFile);
- if (!rLck.lock())
- continue;
- try {
- LooseRef currentLooseRef = scanRef(null, refName);
- if (currentLooseRef == null || currentLooseRef.isSymbolic())
- continue;
- Ref packedRef = cur.get(refName);
- ObjectId clr_oid = currentLooseRef.getObjectId();
- if (clr_oid != null
- && clr_oid.equals(packedRef.getObjectId())) {
- RefList<LooseRef> curLoose, newLoose;
- do {
- curLoose = looseRefs.get();
- int idx = curLoose.find(refName);
- if (idx < 0)
- break;
- newLoose = curLoose.remove(idx);
- } while (!looseRefs.compareAndSet(curLoose, newLoose));
- int levels = levelsIn(refName) - 2;
- delete(refFile, levels, rLck);
+ // Iterate over all refs to be packed
+ boolean dirty = false;
+ for (String refName : refs) {
+ Ref oldRef = readRef(refName, cur);
+ if (oldRef == null) {
+ continue; // A non-existent ref is already correctly packed.
}
- } finally {
- rLck.unlock();
+ if (oldRef.isSymbolic()) {
+ continue; // can't pack symbolic refs
+ }
+ // Add/Update it to packed-refs
+ Ref newRef = peeledPackedRef(oldRef);
+ if (newRef == oldRef) {
+ // No-op; peeledPackedRef returns the input ref only if it's already
+ // packed, and readRef returns a packed ref only if there is no
+ // loose ref.
+ continue;
+ }
+
+ dirty = true;
+ int idx = cur.find(refName);
+ if (idx >= 0) {
+ cur = cur.set(idx, newRef);
+ } else {
+ cur = cur.add(idx, newRef);
+ }
}
+ if (!dirty) {
+ // All requested refs were already packed accurately
+ return packed;
+ }
+
+ // The new content for packed-refs is collected. Persist it.
+ PackedRefList result = commitPackedRefs(lck, cur, packed);
+
+ // Now delete the loose refs which are now packed
+ for (String refName : refs) {
+ // Lock the loose ref
+ File refFile = fileFor(refName);
+ if (!fs.exists(refFile)) {
+ continue;
+ }
+
+ LockFile rLck = heldLocks.get(refName);
+ boolean shouldUnlock;
+ if (rLck == null) {
+ rLck = new LockFile(refFile);
+ if (!rLck.lock()) {
+ continue;
+ }
+ shouldUnlock = true;
+ } else {
+ shouldUnlock = false;
+ }
+
+ try {
+ LooseRef currentLooseRef = scanRef(null, refName);
+ if (currentLooseRef == null || currentLooseRef.isSymbolic()) {
+ continue;
+ }
+ Ref packedRef = cur.get(refName);
+ ObjectId clr_oid = currentLooseRef.getObjectId();
+ if (clr_oid != null
+ && clr_oid.equals(packedRef.getObjectId())) {
+ RefList<LooseRef> curLoose, newLoose;
+ do {
+ curLoose = looseRefs.get();
+ int idx = curLoose.find(refName);
+ if (idx < 0) {
+ break;
+ }
+ newLoose = curLoose.remove(idx);
+ } while (!looseRefs.compareAndSet(curLoose, newLoose));
+ int levels = levelsIn(refName) - 2;
+ delete(refFile, levels, rLck);
+ }
+ } finally {
+ if (shouldUnlock) {
+ rLck.unlock();
+ }
+ }
+ }
+ // Don't fire refsChanged. The refs have not change, only their
+ // storage.
+ return result;
+ } finally {
+ lck.unlock();
}
- // Don't fire refsChanged. The refs have not change, only their
- // storage.
} finally {
- lck.unlock();
+ inProcessPackedRefsLock.unlock();
}
}
+ @Nullable
+ LockFile lockPackedRefs() throws IOException {
+ LockFile lck = new LockFile(packedRefsFile);
+ for (int ms : getRetrySleepMs()) {
+ sleep(ms);
+ if (lck.lock()) {
+ return lck;
+ }
+ }
+ return null;
+ }
+
+ private LockFile lockPackedRefsOrThrow() throws IOException {
+ LockFile lck = lockPackedRefs();
+ if (lck == null) {
+ throw new LockFailedException(packedRefsFile);
+ }
+ return lck;
+ }
+
/**
* Make sure a ref is peeled and has the Storage PACKED. If the given ref
* has this attributes simply return it. Otherwise create a new peeled
@@ -810,7 +906,7 @@
throw noPackedRefs;
}
// Ignore it and leave the new list empty.
- return PackedRefList.NO_PACKED_REFS;
+ return NO_PACKED_REFS;
}
try {
return new PackedRefList(parsePackedRefs(br), snapshot,
@@ -891,8 +987,11 @@
return new StringBuilder(end - off).append(src, off, end).toString();
}
- private void commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
+ PackedRefList commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
final PackedRefList oldPackedList) throws IOException {
+ // Can't just return packedRefs.get() from this method; it might have been
+ // updated again after writePackedRefs() returns.
+ AtomicReference<PackedRefList> result = new AtomicReference<>();
new RefWriter(refs) {
@Override
protected void writeFile(String name, byte[] content)
@@ -914,10 +1013,28 @@
throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));
byte[] digest = Constants.newMessageDigest().digest(content);
- packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs,
- lck.getCommitSnapshot(), ObjectId.fromRaw(digest)));
+ PackedRefList newPackedList = new PackedRefList(
+ refs, lck.getCommitSnapshot(), ObjectId.fromRaw(digest));
+
+ // This thread holds the file lock, so no other thread or process should
+ // be able to modify the packed-refs file on disk. If the list changed,
+ // it means something is very wrong, so throw an exception.
+ //
+ // However, we can't use a naive compareAndSet to check whether the
+ // update was successful, because another thread might _read_ the
+ // packed refs file that was written out by this thread while holding
+ // the lock, and update the packedRefs reference to point to that. So
+ // compare the actual contents instead.
+ PackedRefList afterUpdate = packedRefs.updateAndGet(
+ p -> p.id.equals(oldPackedList.id) ? newPackedList : p);
+ if (!afterUpdate.id.equals(newPackedList.id)) {
+ throw new ObjectWritingException(
+ MessageFormat.format(JGitText.get().unableToWrite, name));
+ }
+ result.set(newPackedList);
}
}.writePackedRefs();
+ return result.get();
}
private Ref readRef(String name, RefList<Ref> packed) throws IOException {
@@ -1039,7 +1156,7 @@
}
/** If the parent should fire listeners, fires them. */
- private void fireRefsChanged() {
+ void fireRefsChanged() {
final int last = lastNotifiedModCnt.get();
final int curr = modCnt.get();
if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0)
@@ -1106,22 +1223,80 @@
}
}
- private static class PackedRefList extends RefList<Ref> {
- static final PackedRefList NO_PACKED_REFS = new PackedRefList(
- RefList.emptyList(), FileSnapshot.MISSING_FILE,
- ObjectId.zeroId());
+ /**
+ * Get times to sleep while retrying a possibly contentious operation.
+ * <p>
+ * For retrying an operation that might have high contention, such as locking
+ * the {@code packed-refs} file, the caller may implement a retry loop using
+ * the returned values:
+ *
+ * <pre>
+ * for (int toSleepMs : getRetrySleepMs()) {
+ * sleep(toSleepMs);
+ * if (isSuccessful(doSomething())) {
+ * return success;
+ * }
+ * }
+ * return failure;
+ * </pre>
+ *
+ * The first value in the returned iterable is 0, and the caller should treat
+ * a fully-consumed iterator as a timeout.
+ *
+ * @return iterable of times, in milliseconds, that the caller should sleep
+ * before attempting an operation.
+ */
+ Iterable<Integer> getRetrySleepMs() {
+ return retrySleepMs;
+ }
- final FileSnapshot snapshot;
+ void setRetrySleepMs(List<Integer> retrySleepMs) {
+ if (retrySleepMs == null || retrySleepMs.isEmpty()
+ || retrySleepMs.get(0).intValue() != 0) {
+ throw new IllegalArgumentException();
+ }
+ this.retrySleepMs = retrySleepMs;
+ }
- final ObjectId id;
+ /**
+ * Sleep with {@link Thread#sleep(long)}, converting {@link
+ * InterruptedException} to {@link InterruptedIOException}.
+ *
+ * @param ms
+ * time to sleep, in milliseconds; zero or negative is a no-op.
+ * @throws InterruptedIOException
+ * if sleeping was interrupted.
+ */
+ static void sleep(long ms) throws InterruptedIOException {
+ if (ms <= 0) {
+ return;
+ }
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ InterruptedIOException ie = new InterruptedIOException();
+ ie.initCause(e);
+ throw ie;
+ }
+ }
- PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
+ static class PackedRefList extends RefList<Ref> {
+
+ private final FileSnapshot snapshot;
+
+ private final ObjectId id;
+
+ private PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
super(src);
snapshot = s;
id = i;
}
}
+ private static final PackedRefList NO_PACKED_REFS = new PackedRefList(
+ RefList.emptyList(), FileSnapshot.MISSING_FILE,
+ ObjectId.zeroId());
+
private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
String name, String target) {
Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
index 3c1916b..1105352 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
@@ -50,6 +50,7 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.Repository;
/** Updates any reference stored by {@link RefDirectory}. */
@@ -127,14 +128,14 @@
return status;
}
- private String toResultString(final Result status) {
+ private String toResultString(Result status) {
switch (status) {
case FORCED:
- return "forced-update"; //$NON-NLS-1$
+ return ReflogEntry.PREFIX_FORCED_UPDATE;
case FAST_FORWARD:
- return "fast forward"; //$NON-NLS-1$
+ return ReflogEntry.PREFIX_FAST_FORWARD;
case NEW:
- return "created"; //$NON-NLS-1$
+ return ReflogEntry.PREFIX_CREATED;
default:
return null;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
index 16b2a46..8723a8b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
@@ -139,4 +139,4 @@
else
return null;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
index 24d2c79..9873798 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
@@ -287,4 +287,4 @@
|| refName.startsWith(R_REMOTES) //
|| refName.equals(R_STASH);
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
index 373a494..5fe0429 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
@@ -136,4 +136,4 @@
public void writeUTF(String s) throws IOException {
throw new UnsupportedOperationException();
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java
index 7e10878..969d02b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java
@@ -127,4 +127,4 @@
sz <<= 1;
return sz;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
index de1003b..825c1f7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -738,4 +738,4 @@
protected final B self() {
return (B) this;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
index 3043d4f..956607c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
@@ -58,6 +58,8 @@
import java.util.List;
import java.util.concurrent.TimeoutException;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -80,8 +82,10 @@
* clock skew between machines on the same LAN using an NTP server also on
* the same LAN should be under 5 seconds. 5 seconds is also not that long
* for a large `git push` operation to complete.
+ *
+ * @since 4.9
*/
- private static final Duration MAX_WAIT = Duration.ofSeconds(5);
+ protected static final Duration MAX_WAIT = Duration.ofSeconds(5);
private final RefDatabase refdb;
@@ -173,25 +177,36 @@
* @return message the caller wants to include in the reflog; null if the
* update should not be logged.
*/
+ @Nullable
public String getRefLogMessage() {
return refLogMessage;
}
- /** @return {@code true} if the ref log message should show the result. */
+ /**
+ * Check whether the reflog message should include the result of the update,
+ * such as fast-forward or force-update.
+ * <p>
+ * Describes the default for commands in this batch that do not override it
+ * with {@link ReceiveCommand#setRefLogMessage(String, boolean)}.
+ *
+ * @return true if the message should include the result.
+ */
public boolean isRefLogIncludingResult() {
return refLogIncludeResult;
}
/**
* Set the message to include in the reflog.
+ * <p>
+ * Describes the default for commands in this batch that do not override it
+ * with {@link ReceiveCommand#setRefLogMessage(String, boolean)}.
*
* @param msg
- * the message to describe this change. It may be null if
- * appendStatus is null in order not to append to the reflog
+ * the message to describe this change. If null and appendStatus is
+ * false, the reflog will not be updated.
* @param appendStatus
* true if the status of the ref change (fast-forward or
- * forced-update) should be appended to the user supplied
- * message.
+ * forced-update) should be appended to the user supplied message.
* @return {@code this}.
*/
public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
@@ -209,6 +224,8 @@
/**
* Don't record this update in the ref's associated reflog.
+ * <p>
+ * Equivalent to {@code setRefLogMessage(null, false)}.
*
* @return {@code this}.
*/
@@ -218,7 +235,11 @@
return this;
}
- /** @return true if log has been disabled by {@link #disableRefLog()}. */
+ /**
+ * Check whether log has been disabled by {@link #disableRefLog()}.
+ *
+ * @return true if disabled.
+ */
public boolean isRefLogDisabled() {
return refLogMessage == null;
}
@@ -323,14 +344,29 @@
/**
* Gets the list of option strings associated with this update.
*
- * @return pushOptions
+ * @return push options that were passed to {@link #execute}; prior to calling
+ * {@link #execute}, always returns null.
* @since 4.5
*/
+ @Nullable
public List<String> getPushOptions() {
return pushOptions;
}
/**
+ * Set push options associated with this update.
+ * <p>
+ * Implementations must call this at the top of {@link #execute(RevWalk,
+ * ProgressMonitor, List)}.
+ *
+ * @param options options passed to {@code execute}.
+ * @since 4.9
+ */
+ protected void setPushOptions(List<String> options) {
+ pushOptions = options;
+ }
+
+ /**
* @return list of timestamps the batch must wait for.
* @since 4.6
*/
@@ -396,7 +432,7 @@
}
if (options != null) {
- pushOptions = options;
+ setPushOptions(options);
}
monitor.beginTask(JGitText.get().updatingReferences, commands.size());
@@ -407,6 +443,11 @@
for (ReceiveCommand cmd : commands) {
try {
if (cmd.getResult() == NOT_ATTEMPTED) {
+ if (isMissing(walk, cmd.getOldId())
+ || isMissing(walk, cmd.getNewId())) {
+ cmd.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
+ continue;
+ }
cmd.updateType(walk);
switch (cmd.getType()) {
case CREATE:
@@ -478,6 +519,19 @@
monitor.endTask();
}
+ private static boolean isMissing(RevWalk walk, ObjectId id)
+ throws IOException {
+ if (id.equals(ObjectId.zeroId())) {
+ return false; // Explicit add or delete is not missing.
+ }
+ try {
+ walk.parseAny(id);
+ return false;
+ } catch (MissingObjectException e) {
+ return true;
+ }
+ }
+
/**
* Wait for timestamps to be in the past, aborting commands on timeout.
*
@@ -531,17 +585,36 @@
return ref;
}
- static Collection<String> getPrefixes(String s) {
+ /**
+ * Get all path prefixes of a ref name.
+ *
+ * @param name
+ * ref name.
+ * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns
+ * {@code refs} and {@code refs/heads}.
+ * @since 4.9
+ */
+ protected static Collection<String> getPrefixes(String name) {
Collection<String> ret = new HashSet<>();
- addPrefixesTo(s, ret);
+ addPrefixesTo(name, ret);
return ret;
}
- static void addPrefixesTo(String s, Collection<String> out) {
- int p1 = s.indexOf('/');
+ /**
+ * Add prefixes of a ref name to an existing collection.
+ *
+ * @param name
+ * ref name.
+ * @param out
+ * path prefixes of the ref name. For {@code refs/heads/foo},
+ * returns {@code refs} and {@code refs/heads}.
+ * @since 4.9
+ */
+ protected static void addPrefixesTo(String name, Collection<String> out) {
+ int p1 = name.indexOf('/');
while (p1 > 0) {
- out.add(s.substring(0, p1));
- p1 = s.indexOf('/', p1 + 1);
+ out.add(name.substring(0, p1));
+ p1 = name.indexOf('/', p1 + 1);
}
}
@@ -557,11 +630,11 @@
*/
protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false);
- if (isRefLogDisabled())
+ if (isRefLogDisabled(cmd)) {
ru.disableRefLog();
- else {
+ } else {
ru.setRefLogIdent(refLogIdent);
- ru.setRefLogMessage(refLogMessage, refLogIncludeResult);
+ ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd));
}
ru.setPushCertificate(pushCert);
switch (cmd.getType()) {
@@ -582,6 +655,47 @@
}
}
+ /**
+ * Check whether reflog is disabled for a command.
+ *
+ * @param cmd
+ * specific command.
+ * @return whether the reflog is disabled, taking into account the state from
+ * this instance as well as overrides in the given command.
+ * @since 4.9
+ */
+ protected boolean isRefLogDisabled(ReceiveCommand cmd) {
+ return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled();
+ }
+
+ /**
+ * Get reflog message for a command.
+ *
+ * @param cmd
+ * specific command.
+ * @return reflog message, taking into account the state from this instance as
+ * well as overrides in the given command.
+ * @since 4.9
+ */
+ protected String getRefLogMessage(ReceiveCommand cmd) {
+ return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage();
+ }
+
+ /**
+ * Check whether the reflog message for a command should include the result.
+ *
+ * @param cmd
+ * specific command.
+ * @return whether the reflog message should show the result, taking into
+ * account the state from this instance as well as overrides in the
+ * given command.
+ * @since 4.9
+ */
+ protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
+ return cmd.hasCustomRefLog()
+ ? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
+ }
+
@Override
public String toString() {
StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java
index 345016c..4e0dc2c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java
@@ -62,4 +62,4 @@
* @return unique hash of this object.
*/
public abstract ObjectId getObjectId();
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
index d6608cd..34d0b14 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
@@ -17,4 +17,4 @@
*/
public abstract String getToBranch();
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
index fc334f0..0778645 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -58,7 +58,13 @@
* Creates, updates or deletes any reference.
*/
public abstract class RefUpdate {
- /** Status of an update request. */
+ /**
+ * Status of an update request.
+ * <p>
+ * New values may be added to this enum in the future. Callers may assume that
+ * unknown values are failures, and may generally treat them the same as
+ * {@link #REJECTED_OTHER_REASON}.
+ */
public static enum Result {
/** The ref update/delete has not been attempted by the caller. */
NOT_ATTEMPTED,
@@ -114,6 +120,10 @@
* merged into the new value. The configuration did not allow a forced
* update/delete to take place, so ref still contains the old value. No
* previous history was lost.
+ * <p>
+ * <em>Note:</em> Despite the general name, this result only refers to the
+ * non-fast-forward case. For more general errors, see {@link
+ * #REJECTED_OTHER_REASON}.
*/
REJECTED,
@@ -139,7 +149,25 @@
* The ref was renamed from another name
* <p>
*/
- RENAMED
+ RENAMED,
+
+ /**
+ * One or more objects aren't in the repository.
+ * <p>
+ * This is severe indication of either repository corruption on the
+ * server side, or a bug in the client wherein the client did not supply
+ * all required objects during the pack transfer.
+ *
+ * @since 4.9
+ */
+ REJECTED_MISSING_OBJECT,
+
+ /**
+ * Rejected for some other reason not covered by another enum value.
+ *
+ * @since 4.9
+ */
+ REJECTED_OTHER_REASON;
}
/** New value the caller wants this ref to have. */
@@ -278,6 +306,16 @@
}
/**
+ * Return whether this update is actually detaching a symbolic ref.
+ *
+ * @return true if detaching a symref.
+ * @since 4.9
+ */
+ public boolean isDetachingSymbolicRef() {
+ return detachingSymbolicRef;
+ }
+
+ /**
* Set the new value the ref will update to.
*
* @param id
@@ -627,34 +665,47 @@
RevObject oldObj;
// don't make expensive conflict check if this is an existing Ref
- if (oldValue == null && checkConflicting && getRefDatabase().isNameConflicting(getName()))
+ if (oldValue == null && checkConflicting
+ && getRefDatabase().isNameConflicting(getName())) {
return Result.LOCK_FAILURE;
+ }
try {
// If we're detaching a symbolic reference, we should update the reference
// itself. Otherwise, we will update the leaf reference, which should be
// an ObjectIdRef.
- if (!tryLock(!detachingSymbolicRef))
+ if (!tryLock(!detachingSymbolicRef)) {
return Result.LOCK_FAILURE;
+ }
if (expValue != null) {
final ObjectId o;
o = oldValue != null ? oldValue : ObjectId.zeroId();
- if (!AnyObjectId.equals(expValue, o))
+ if (!AnyObjectId.equals(expValue, o)) {
return Result.LOCK_FAILURE;
+ }
}
- if (oldValue == null)
+ try {
+ newObj = safeParseNew(walk, newValue);
+ } catch (MissingObjectException e) {
+ return Result.REJECTED_MISSING_OBJECT;
+ }
+
+ if (oldValue == null) {
return store.execute(Result.NEW);
+ }
- newObj = safeParse(walk, newValue);
- oldObj = safeParse(walk, oldValue);
- if (newObj == oldObj && !detachingSymbolicRef)
+ oldObj = safeParseOld(walk, oldValue);
+ if (newObj == oldObj && !detachingSymbolicRef) {
return store.execute(Result.NO_CHANGE);
+ }
- if (isForceUpdate())
+ if (isForceUpdate()) {
return store.execute(Result.FORCED);
+ }
if (newObj instanceof RevCommit && oldObj instanceof RevCommit) {
- if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj))
+ if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj)) {
return store.execute(Result.FAST_FORWARD);
+ }
}
return Result.REJECTED;
@@ -674,16 +725,23 @@
checkConflicting = check;
}
- private static RevObject safeParse(final RevWalk rw, final AnyObjectId id)
+ private static RevObject safeParseNew(RevWalk rw, AnyObjectId newId)
+ throws IOException {
+ if (newId == null || ObjectId.zeroId().equals(newId)) {
+ return null;
+ }
+ return rw.parseAny(newId);
+ }
+
+ private static RevObject safeParseOld(RevWalk rw, AnyObjectId oldId)
throws IOException {
try {
- return id != null ? rw.parseAny(id) : null;
+ return oldId != null ? rw.parseAny(oldId) : null;
} catch (MissingObjectException e) {
- // We can expect some objects to be missing, like if we are
- // trying to force a deletion of a branch and the object it
- // points to has been pruned from the database due to freak
- // corruption accidents (it happens with 'git new-work-dir').
- //
+ // We can expect some old objects to be missing, like if we are trying to
+ // force a deletion of a branch and the object it points to has been
+ // pruned from the database due to freak corruption accidents (it happens
+ // with 'git new-work-dir').
return null;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
index 0504646..afa6521 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
@@ -50,6 +50,39 @@
public interface ReflogEntry {
/**
+ * Prefix used in reflog messages when the ref was first created.
+ * <p>
+ * Does not have a corresponding constant in C git, but is untranslated like
+ * the other constants.
+ *
+ * @since 4.9
+ */
+ public static final String PREFIX_CREATED = "created"; //$NON-NLS-1$
+
+ /**
+ * Prefix used in reflog messages when the ref was updated with a fast
+ * forward.
+ * <p>
+ * Untranslated, and exactly matches the
+ * <a href="https://git.kernel.org/pub/scm/git/git.git/tree/builtin/fetch.c?id=f3da2b79be9565779e4f76dc5812c68e156afdf0#n680">
+ * untranslated string in C git</a>.
+ *
+ * @since 4.9
+ */
+ public static final String PREFIX_FAST_FORWARD = "fast-forward"; //$NON-NLS-1$
+
+ /**
+ * Prefix used in reflog messages when the ref was force updated.
+ * <p>
+ * Untranslated, and exactly matches the
+ * <a href="https://git.kernel.org/pub/scm/git/git.git/tree/builtin/fetch.c?id=f3da2b79be9565779e4f76dc5812c68e156afdf0#n695">
+ * untranslated string in C git</a>.
+ *
+ * @since 4.9
+ */
+ public static final String PREFIX_FORCED_UPDATE = "forced-update"; //$NON-NLS-1$
+
+ /**
* @return the commit id before the change
*/
public abstract ObjectId getOldId();
@@ -75,4 +108,4 @@
*/
public abstract CheckoutEntry parseCheckout();
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
index fdab883..d3f2536 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
@@ -86,4 +86,4 @@
public abstract List<ReflogEntry> getReverseEntries(int max)
throws IOException;
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
index 0345921..060f068 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
@@ -143,4 +143,4 @@
if (out.isBeginln())
out.write('\n');
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java b/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java
index c85c179..bde69c0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java
@@ -184,4 +184,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java
index e230c9b..51dd2ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java
@@ -91,4 +91,4 @@
public RevFilter clone() {
return new SkipRevFilter(skip);
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
index f445bcb..82d6ed4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
@@ -229,4 +229,4 @@
return exitValue();
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
index bab5bf0..5727b03 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
@@ -317,4 +317,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
index 19dfa34..db3578b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
@@ -550,29 +550,7 @@
}
if (deltaCount > 0) {
- if (resolving instanceof BatchingProgressMonitor) {
- ((BatchingProgressMonitor) resolving).setDelayStart(
- 1000,
- TimeUnit.MILLISECONDS);
- }
- resolving.beginTask(JGitText.get().resolvingDeltas, deltaCount);
- resolveDeltas(resolving);
- if (entryCount < expectedObjectCount) {
- if (!isAllowThin()) {
- throw new IOException(MessageFormat.format(
- JGitText.get().packHasUnresolvedDeltas,
- Long.valueOf(expectedObjectCount - entryCount)));
- }
-
- resolveDeltasWithExternalBases(resolving);
-
- if (entryCount < expectedObjectCount) {
- throw new IOException(MessageFormat.format(
- JGitText.get().packHasUnresolvedDeltas,
- Long.valueOf(expectedObjectCount - entryCount)));
- }
- }
- resolving.endTask();
+ processDeltas(resolving);
}
packDigest = null;
@@ -595,6 +573,31 @@
return null; // By default there is no locking.
}
+ private void processDeltas(ProgressMonitor resolving) throws IOException {
+ if (resolving instanceof BatchingProgressMonitor) {
+ ((BatchingProgressMonitor) resolving).setDelayStart(1000,
+ TimeUnit.MILLISECONDS);
+ }
+ resolving.beginTask(JGitText.get().resolvingDeltas, deltaCount);
+ resolveDeltas(resolving);
+ if (entryCount < expectedObjectCount) {
+ if (!isAllowThin()) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().packHasUnresolvedDeltas,
+ Long.valueOf(expectedObjectCount - entryCount)));
+ }
+
+ resolveDeltasWithExternalBases(resolving);
+
+ if (entryCount < expectedObjectCount) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().packHasUnresolvedDeltas,
+ Long.valueOf(expectedObjectCount - entryCount)));
+ }
+ }
+ resolving.endTask();
+ }
+
private void resolveDeltas(final ProgressMonitor progress)
throws IOException {
final int last = entryCount;
@@ -684,6 +687,7 @@
PackedObjectInfo oe;
oe = newInfo(tempObjectId, visit.delta, visit.parent.id);
oe.setOffset(visit.delta.position);
+ oe.setType(type);
onInflatedObjectData(oe, type, visit.data);
addObjectAndTrack(oe);
visit.id = oe;
@@ -854,10 +858,9 @@
visit.id = baseId;
final int typeCode = ldr.getType();
final PackedObjectInfo oe = newInfo(baseId, null, null);
-
+ oe.setType(typeCode);
if (onAppendBase(typeCode, visit.data, oe))
entries[entryCount++] = oe;
-
visit.nextChild = firstChildOf(oe);
resolveDeltas(visit.next(), typeCode,
new ObjectTypeAndSize(), progress);
@@ -1059,6 +1062,7 @@
PackedObjectInfo obj = newInfo(tempObjectId, null, null);
obj.setOffset(pos);
+ obj.setType(type);
onEndWholeObject(obj);
if (data != null)
onInflatedObjectData(obj, type, data);
@@ -1069,8 +1073,21 @@
}
}
- private void verifySafeObject(final AnyObjectId id, final int type,
- final byte[] data) throws IOException {
+ /**
+ * Verify the integrity of the object.
+ *
+ * @param id
+ * identity of the object to be checked.
+ * @param type
+ * the type of the object.
+ * @param data
+ * raw content of the object.
+ * @throws CorruptObjectException
+ * @since 4.9
+ *
+ */
+ protected void verifySafeObject(final AnyObjectId id, final int type,
+ final byte[] data) throws CorruptObjectException {
if (objCheck != null) {
try {
objCheck.check(id, type, data);
@@ -1078,11 +1095,11 @@
if (e.getErrorType() != null) {
throw e;
}
- throw new CorruptObjectException(MessageFormat.format(
- JGitText.get().invalidObject,
- Constants.typeString(type),
- id.name(),
- e.getMessage()), e);
+ throw new CorruptObjectException(
+ MessageFormat.format(JGitText.get().invalidObject,
+ Constants.typeString(type), id.name(),
+ e.getMessage()),
+ e);
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
index 6da1c57..381c228 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
@@ -45,6 +45,7 @@
package org.eclipse.jgit.transport;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectIdOwnerMap;
/**
@@ -59,6 +60,8 @@
private int crc;
+ private int type = Constants.OBJ_BAD;
+
PackedObjectInfo(final long headerOffset, final int packedCRC,
final AnyObjectId id) {
super(id);
@@ -112,4 +115,24 @@
public void setCRC(final int crc) {
this.crc = crc;
}
+
+ /**
+ * @return the object type. The default type is OBJ_BAD, which is considered
+ * as unknown or invalid type.
+ * @since 4.9
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Record the object type if applicable.
+ *
+ * @param type
+ * the object type.
+ * @since 4.9
+ */
+ public void setType(int type) {
+ this.type = type;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
index 2b21c4a..14b35c9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
@@ -52,6 +52,7 @@
import java.util.Collection;
import java.util.List;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
@@ -190,6 +191,20 @@
}
}
+ /**
+ * Check whether a command failed due to transaction aborted.
+ *
+ * @param cmd
+ * command.
+ * @return whether the command failed due to transaction aborted, as in {@link
+ * #abort(Iterable)}.
+ * @since 4.9
+ */
+ public static boolean isTransactionAborted(ReceiveCommand cmd) {
+ return cmd.getResult() == REJECTED_OTHER_REASON
+ && cmd.getMessage().equals(JGitText.get().transactionAborted);
+ }
+
private final ObjectId oldId;
private final ObjectId newId;
@@ -204,13 +219,19 @@
private String message;
+ private boolean customRefLog;
+
+ private String refLogMessage;
+
+ private boolean refLogIncludeResult;
+
private boolean typeIsCorrect;
/**
* Create a new command for {@link BaseReceivePack}.
*
* @param oldId
- * the old object id; must not be null. Use
+ * the expected old object id; must not be null. Use
* {@link ObjectId#zeroId()} to indicate a ref creation.
* @param newId
* the new object id; must not be null. Use
@@ -220,15 +241,23 @@
*/
public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
final String name) {
+ if (oldId == null) {
+ throw new IllegalArgumentException(JGitText.get().oldIdMustNotBeNull);
+ }
+ if (newId == null) {
+ throw new IllegalArgumentException(JGitText.get().newIdMustNotBeNull);
+ }
this.oldId = oldId;
this.newId = newId;
this.name = name;
type = Type.UPDATE;
- if (ObjectId.zeroId().equals(oldId))
+ if (ObjectId.zeroId().equals(oldId)) {
type = Type.CREATE;
- if (ObjectId.zeroId().equals(newId))
+ }
+ if (ObjectId.zeroId().equals(newId)) {
type = Type.DELETE;
+ }
}
/**
@@ -243,14 +272,45 @@
* @param name
* name of the ref being affected.
* @param type
- * type of the command.
+ * type of the command. Must be {@link Type#CREATE} if {@code
+ * oldId} is zero, or {@link Type#DELETE} if {@code newId} is zero.
* @since 2.0
*/
public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
final String name, final Type type) {
+ if (oldId == null) {
+ throw new IllegalArgumentException(JGitText.get().oldIdMustNotBeNull);
+ }
+ if (newId == null) {
+ throw new IllegalArgumentException(JGitText.get().newIdMustNotBeNull);
+ }
this.oldId = oldId;
this.newId = newId;
this.name = name;
+ switch (type) {
+ case CREATE:
+ if (!ObjectId.zeroId().equals(oldId)) {
+ throw new IllegalArgumentException(
+ JGitText.get().createRequiresZeroOldId);
+ }
+ break;
+ case DELETE:
+ if (!ObjectId.zeroId().equals(newId)) {
+ throw new IllegalArgumentException(
+ JGitText.get().deleteRequiresZeroNewId);
+ }
+ break;
+ case UPDATE:
+ case UPDATE_NONFASTFORWARD:
+ if (ObjectId.zeroId().equals(newId)
+ || ObjectId.zeroId().equals(oldId)) {
+ throw new IllegalArgumentException(
+ JGitText.get().updateRequiresOldIdAndNewId);
+ }
+ break;
+ default:
+ throw new IllegalStateException(JGitText.get().enumValueNotSupported0);
+ }
this.type = type;
}
@@ -290,6 +350,90 @@
}
/**
+ * Set the message to include in the reflog.
+ * <p>
+ * Overrides the default set by {@code setRefLogMessage} on any containing
+ * {@link org.eclipse.jgit.lib.BatchRefUpdate}.
+ *
+ * @param msg
+ * the message to describe this change. If null and appendStatus is
+ * false, the reflog will not be updated.
+ * @param appendStatus
+ * true if the status of the ref change (fast-forward or
+ * forced-update) should be appended to the user supplied message.
+ * @since 4.9
+ */
+ public void setRefLogMessage(String msg, boolean appendStatus) {
+ customRefLog = true;
+ if (msg == null && !appendStatus) {
+ disableRefLog();
+ } else if (msg == null && appendStatus) {
+ refLogMessage = ""; //$NON-NLS-1$
+ refLogIncludeResult = true;
+ } else {
+ refLogMessage = msg;
+ refLogIncludeResult = appendStatus;
+ }
+ }
+
+ /**
+ * Don't record this update in the ref's associated reflog.
+ * <p>
+ * Equivalent to {@code setRefLogMessage(null, false)}.
+ *
+ * @since 4.9
+ */
+ public void disableRefLog() {
+ customRefLog = true;
+ refLogMessage = null;
+ refLogIncludeResult = false;
+ }
+
+ /**
+ * Check whether this command has a custom reflog setting that should override
+ * defaults in any containing {@link org.eclipse.jgit.lib.BatchRefUpdate}.
+ *
+ * @return whether a custom reflog is set.
+ * @since 4.9
+ */
+ public boolean hasCustomRefLog() {
+ return customRefLog;
+ }
+
+ /**
+ * Check whether log has been disabled by {@link #disableRefLog()}.
+ *
+ * @return true if disabled.
+ * @since 4.9
+ */
+ public boolean isRefLogDisabled() {
+ return refLogMessage == null;
+ }
+
+ /**
+ * Get the message to include in the reflog.
+ *
+ * @return message the caller wants to include in the reflog; null if the
+ * update should not be logged.
+ * @since 4.9
+ */
+ @Nullable
+ public String getRefLogMessage() {
+ return refLogMessage;
+ }
+
+ /**
+ * Check whether the reflog message should include the result of the update,
+ * such as fast-forward or force-update.
+ *
+ * @return true if the message should include the result.
+ * @since 4.9
+ */
+ public boolean isRefLogIncludingResult() {
+ return refLogIncludeResult;
+ }
+
+ /**
* Set the status of this command.
*
* @param s
@@ -355,6 +499,7 @@
try {
final RefUpdate ru = rp.getRepository().updateRef(getRefName());
ru.setRefLogIdent(rp.getRefLogIdent());
+ ru.setRefLogMessage(refLogMessage, refLogIncludeResult);
switch (getType()) {
case DELETE:
if (!ObjectId.zeroId().equals(getOldId())) {
@@ -428,6 +573,14 @@
setResult(Result.REJECTED_CURRENT_BRANCH);
break;
+ case REJECTED_MISSING_OBJECT:
+ setResult(Result.REJECTED_MISSING_OBJECT);
+ break;
+
+ case REJECTED_OTHER_REASON:
+ setResult(Result.REJECTED_OTHER_REASON);
+ break;
+
default:
setResult(Result.REJECTED_OTHER_REASON, r.name());
break;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
index 5a73cf5..d6a2fe6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
@@ -83,4 +83,4 @@
* Disconnect the remote session
*/
public void disconnect();
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java
index 1719416..2ea8228 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java
@@ -102,4 +102,4 @@
public String toString() {
return "INTERINDEX_DIFF"; //$NON-NLS-1$
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
index 3cb3749..21a55a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
@@ -140,4 +140,4 @@
long rounded = (n + unit / 2) / unit;
return rounded;
}
-}
\ No newline at end of file
+}
diff --git a/pom.xml b/pom.xml
index 19d0225..3e6c55b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -212,7 +212,7 @@
<maven-javadoc-plugin-version>2.10.4</maven-javadoc-plugin-version>
<tycho-extras-version>1.0.0</tycho-extras-version>
<gson-version>2.2.4</gson-version>
- <findbugs-maven-plugin-version>3.0.4</findbugs-maven-plugin-version>
+ <spotbugs-maven-plugin-version>3.0.6</spotbugs-maven-plugin-version>
<maven-surefire-report-plugin-version>2.20</maven-surefire-report-plugin-version>
<!-- Properties to enable jacoco code coverage analysis -->
@@ -371,9 +371,9 @@
</plugin>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <version>${findbugs-maven-plugin-version}</version>
+ <groupId>com.github.hazendaz.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <version>${spotbugs-maven-plugin-version}</version>
<configuration>
<findbugsXmlOutput>true</findbugsXmlOutput>
<failOnError>false</failOnError>
@@ -579,9 +579,9 @@
<version>2.5</version>
</plugin>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <version>${findbugs-maven-plugin-version}</version>
+ <groupId>com.github.hazendaz.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <version>${spotbugs-maven-plugin-version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -751,8 +751,8 @@
<build>
<plugins>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
+ <groupId>com.github.hazendaz.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>