Browse Source
JGit already had some fsck-like classes like ObjectChecker which can check for an individual object. The read-only FsckPackParser which will parse all objects within a pack file and check it with ObjectChecker. It will also check the pack index file against the object information from the pack parser. Change-Id: Ifd8e0d28eb68ff0b8edd2b51b2fa3a50a544c855 Signed-off-by: Zhen Chen <czhen@google.com>stable-4.9
Zhen Chen
8 years ago
14 changed files with 1010 additions and 45 deletions
@ -0,0 +1,201 @@ |
|||||||
|
/* |
||||||
|
* 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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,145 @@ |
|||||||
|
/* |
||||||
|
* 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<>(); |
||||||
|
|
||||||
|
/** @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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
/** |
||||||
|
* Git fsck support. |
||||||
|
*/ |
||||||
|
package org.eclipse.jgit.internal.fsck; |
@ -0,0 +1,134 @@ |
|||||||
|
/* |
||||||
|
* 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.ObjectChecker; |
||||||
|
import org.eclipse.jgit.lib.ProgressMonitor; |
||||||
|
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())); |
||||||
|
} |
||||||
|
} |
||||||
|
} 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; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue