From ad5238dc67aa5922c425e6bc829e1152c2e20439 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Jun 2010 16:56:55 -0700 Subject: Move FileRepository to storage.file.FileRepository This move isolates all of the local file specific implementation code into a single package, where their package-private methods and support classes are properly hidden away from the rest of the core library. Because of the sheer number of files impacted, I have limited this change to only the renames and the updated imports. Change-Id: Icca4884e1a418f83f8b617d0c4c78b73d8a4bd17 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/dircache/DirCache.java | 2 +- .../eclipse/jgit/lib/BaseRepositoryBuilder.java | 3 + .../src/org/eclipse/jgit/lib/ByteArrayWindow.java | 101 -- .../src/org/eclipse/jgit/lib/ByteBufferWindow.java | 92 -- .../src/org/eclipse/jgit/lib/ByteWindow.java | 175 ---- .../eclipse/jgit/lib/CachedObjectDirectory.java | 184 ---- .../jgit/lib/DeltaOfsPackedObjectLoader.java | 82 -- .../eclipse/jgit/lib/DeltaPackedObjectLoader.java | 118 --- .../jgit/lib/DeltaRefPackedObjectLoader.java | 80 -- .../src/org/eclipse/jgit/lib/FileBasedConfig.java | 163 ---- .../org/eclipse/jgit/lib/FileObjectDatabase.java | 207 ---- .../src/org/eclipse/jgit/lib/FileRepository.java | 310 ------ .../eclipse/jgit/lib/FileRepositoryBuilder.java | 88 -- .../jgit/lib/LocalObjectRepresentation.java | 80 -- .../org/eclipse/jgit/lib/LocalObjectToPack.java | 66 -- .../src/org/eclipse/jgit/lib/LockFile.java | 448 --------- .../src/org/eclipse/jgit/lib/ObjectDirectory.java | 621 ------------ .../eclipse/jgit/lib/ObjectDirectoryInserter.java | 177 ---- .../src/org/eclipse/jgit/lib/PackFile.java | 695 ------------- .../src/org/eclipse/jgit/lib/PackIndex.java | 315 ------ .../src/org/eclipse/jgit/lib/PackIndexV1.java | 202 ---- .../src/org/eclipse/jgit/lib/PackIndexV2.java | 279 ------ .../src/org/eclipse/jgit/lib/PackIndexWriter.java | 275 ------ .../org/eclipse/jgit/lib/PackIndexWriterV1.java | 85 -- .../org/eclipse/jgit/lib/PackIndexWriterV2.java | 107 -- .../src/org/eclipse/jgit/lib/PackLock.java | 90 -- .../src/org/eclipse/jgit/lib/PackReverseIndex.java | 193 ---- .../src/org/eclipse/jgit/lib/PackWriter.java | 1 + .../org/eclipse/jgit/lib/PackedObjectLoader.java | 124 --- .../src/org/eclipse/jgit/lib/RefDirectory.java | 1006 ------------------- .../org/eclipse/jgit/lib/RefDirectoryRename.java | 225 ----- .../org/eclipse/jgit/lib/RefDirectoryUpdate.java | 156 --- .../src/org/eclipse/jgit/lib/RefWriter.java | 1 + .../src/org/eclipse/jgit/lib/ReflogReader.java | 184 ---- .../src/org/eclipse/jgit/lib/Repository.java | 1 + .../org/eclipse/jgit/lib/RepositoryBuilder.java | 2 + .../src/org/eclipse/jgit/lib/RepositoryCache.java | 1 + .../org/eclipse/jgit/lib/UnpackedObjectCache.java | 195 ---- .../org/eclipse/jgit/lib/UnpackedObjectLoader.java | 223 ----- .../eclipse/jgit/lib/WholePackedObjectLoader.java | 110 --- .../src/org/eclipse/jgit/lib/WindowCache.java | 624 ------------ .../org/eclipse/jgit/lib/WindowCacheConfig.java | 176 ---- .../src/org/eclipse/jgit/lib/WindowCursor.java | 213 ---- .../eclipse/jgit/storage/file/ByteArrayWindow.java | 101 ++ .../jgit/storage/file/ByteBufferWindow.java | 92 ++ .../org/eclipse/jgit/storage/file/ByteWindow.java | 175 ++++ .../jgit/storage/file/CachedObjectDirectory.java | 194 ++++ .../storage/file/DeltaOfsPackedObjectLoader.java | 84 ++ .../jgit/storage/file/DeltaPackedObjectLoader.java | 120 +++ .../storage/file/DeltaRefPackedObjectLoader.java | 82 ++ .../eclipse/jgit/storage/file/FileBasedConfig.java | 165 ++++ .../jgit/storage/file/FileObjectDatabase.java | 214 ++++ .../eclipse/jgit/storage/file/FileRepository.java | 327 +++++++ .../jgit/storage/file/FileRepositoryBuilder.java | 90 ++ .../storage/file/LocalObjectRepresentation.java | 83 ++ .../jgit/storage/file/LocalObjectToPack.java | 68 ++ .../org/eclipse/jgit/storage/file/LockFile.java | 450 +++++++++ .../eclipse/jgit/storage/file/ObjectDirectory.java | 630 ++++++++++++ .../jgit/storage/file/ObjectDirectoryInserter.java | 182 ++++ .../org/eclipse/jgit/storage/file/PackFile.java | 700 ++++++++++++++ .../org/eclipse/jgit/storage/file/PackIndex.java | 318 ++++++ .../org/eclipse/jgit/storage/file/PackIndexV1.java | 205 ++++ .../org/eclipse/jgit/storage/file/PackIndexV2.java | 282 ++++++ .../eclipse/jgit/storage/file/PackIndexWriter.java | 277 ++++++ .../jgit/storage/file/PackIndexWriterV1.java | 85 ++ .../jgit/storage/file/PackIndexWriterV2.java | 107 ++ .../org/eclipse/jgit/storage/file/PackLock.java | 92 ++ .../jgit/storage/file/PackReverseIndex.java | 194 ++++ .../jgit/storage/file/PackedObjectLoader.java | 127 +++ .../eclipse/jgit/storage/file/RefDirectory.java | 1018 ++++++++++++++++++++ .../jgit/storage/file/RefDirectoryRename.java | 229 +++++ .../jgit/storage/file/RefDirectoryUpdate.java | 160 +++ .../eclipse/jgit/storage/file/ReflogReader.java | 188 ++++ .../jgit/storage/file/UnpackedObjectCache.java | 195 ++++ .../jgit/storage/file/UnpackedObjectLoader.java | 227 +++++ .../jgit/storage/file/WholePackedObjectLoader.java | 112 +++ .../org/eclipse/jgit/storage/file/WindowCache.java | 624 ++++++++++++ .../jgit/storage/file/WindowCacheConfig.java | 178 ++++ .../eclipse/jgit/storage/file/WindowCursor.java | 222 +++++ .../jgit/transport/BasePackFetchConnection.java | 2 +- .../jgit/transport/BundleFetchConnection.java | 2 +- .../eclipse/jgit/transport/FetchConnection.java | 2 +- .../org/eclipse/jgit/transport/FetchProcess.java | 4 +- .../src/org/eclipse/jgit/transport/IndexPack.java | 4 +- .../org/eclipse/jgit/transport/ReceivePack.java | 2 +- .../org/eclipse/jgit/transport/TransportHttp.java | 2 +- .../org/eclipse/jgit/transport/TransportLocal.java | 2 +- .../jgit/transport/WalkFetchConnection.java | 8 +- .../jgit/transport/WalkRemoteObjectDatabase.java | 2 +- .../src/org/eclipse/jgit/util/SystemReader.java | 2 +- 90 files changed, 8623 insertions(+), 8486 deletions(-) delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java (limited to 'org.eclipse.jgit/src/org/eclipse/jgit') diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java index 9ced005a45..6b1349adef 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -64,10 +64,10 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.LockFile; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.NB; 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 f5dd7eec72..90929e721c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -21,6 +21,9 @@ import java.util.List; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java deleted file mode 100644 index 0c5c818993..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.zip.CRC32; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -/** - * A {@link ByteWindow} with an underlying byte array for storage. - */ -final class ByteArrayWindow extends ByteWindow { - private final byte[] array; - - ByteArrayWindow(final PackFile pack, final long o, final byte[] b) { - super(pack, o, b.length); - array = b; - } - - @Override - protected int copy(final int p, final byte[] b, final int o, int n) { - n = Math.min(array.length - p, n); - System.arraycopy(array, p, b, o, n); - return n; - } - - @Override - protected int inflate(final int pos, final byte[] b, int o, - final Inflater inf) throws DataFormatException { - while (!inf.finished()) { - if (inf.needsInput()) { - inf.setInput(array, pos, array.length - pos); - break; - } - o += inf.inflate(b, o, b.length - o); - } - while (!inf.finished() && !inf.needsInput()) - o += inf.inflate(b, o, b.length - o); - return o; - } - - void crc32(CRC32 out, long pos, int cnt) { - out.update(array, (int) (pos - start), cnt); - } - - void write(OutputStream out, long pos, int cnt) throws IOException { - out.write(array, (int) (pos - start), cnt); - } - - void check(Inflater inf, byte[] tmp, long pos, int cnt) - throws DataFormatException { - inf.setInput(array, (int) (pos - start), cnt); - while (inf.inflate(tmp, 0, tmp.length) > 0) - continue; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java deleted file mode 100644 index 794d7428e6..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.nio.ByteBuffer; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -/** - * A window for accessing git packs using a {@link ByteBuffer} for storage. - * - * @see ByteWindow - */ -final class ByteBufferWindow extends ByteWindow { - private final ByteBuffer buffer; - - ByteBufferWindow(final PackFile pack, final long o, final ByteBuffer b) { - super(pack, o, b.capacity()); - buffer = b; - } - - @Override - protected int copy(final int p, final byte[] b, final int o, int n) { - final ByteBuffer s = buffer.slice(); - s.position(p); - n = Math.min(s.remaining(), n); - s.get(b, o, n); - return n; - } - - @Override - protected int inflate(final int pos, final byte[] b, int o, - final Inflater inf) throws DataFormatException { - final byte[] tmp = new byte[512]; - final ByteBuffer s = buffer.slice(); - s.position(pos); - while (s.remaining() > 0 && !inf.finished()) { - if (inf.needsInput()) { - final int n = Math.min(s.remaining(), tmp.length); - s.get(tmp, 0, n); - inf.setInput(tmp, 0, n); - } - o += inf.inflate(b, o, b.length - o); - } - while (!inf.finished() && !inf.needsInput()) - o += inf.inflate(b, o, b.length - o); - return o; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java deleted file mode 100644 index 69d255c781..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -/** - * A window of data currently stored within a cache. - *

- * All bytes in the window can be assumed to be "immediately available", that is - * they are very likely already in memory, unless the operating system's memory - * is very low and has paged part of this process out to disk. Therefore copying - * bytes from a window is very inexpensive. - *

- */ -abstract class ByteWindow { - protected final PackFile pack; - - protected final long start; - - protected final long end; - - protected ByteWindow(final PackFile p, final long s, final int n) { - pack = p; - start = s; - end = start + n; - } - - final int size() { - return (int) (end - start); - } - - final boolean contains(final PackFile neededFile, final long neededPos) { - return pack == neededFile && start <= neededPos && neededPos < end; - } - - /** - * Copy bytes from the window to a caller supplied buffer. - * - * @param pos - * offset within the file to start copying from. - * @param dstbuf - * destination buffer to copy into. - * @param dstoff - * offset within dstbuf to start copying into. - * @param cnt - * number of bytes to copy. This value may exceed the number of - * bytes remaining in the window starting at offset - * pos. - * @return number of bytes actually copied; this may be less than - * cnt if cnt exceeded the number of - * bytes available. - */ - final int copy(long pos, byte[] dstbuf, int dstoff, int cnt) { - return copy((int) (pos - start), dstbuf, dstoff, cnt); - } - - /** - * Copy bytes from the window to a caller supplied buffer. - * - * @param pos - * offset within the window to start copying from. - * @param dstbuf - * destination buffer to copy into. - * @param dstoff - * offset within dstbuf to start copying into. - * @param cnt - * number of bytes to copy. This value may exceed the number of - * bytes remaining in the window starting at offset - * pos. - * @return number of bytes actually copied; this may be less than - * cnt if cnt exceeded the number of - * bytes available. - */ - protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt); - - /** - * Pump bytes into the supplied inflater as input. - * - * @param pos - * offset within the file to start supplying input from. - * @param dstbuf - * destination buffer the inflater should output decompressed - * data to. - * @param dstoff - * current offset within dstbuf to inflate into. - * @param inf - * the inflater to feed input to. The caller is responsible for - * initializing the inflater as multiple windows may need to - * supply data to the same inflater to completely decompress - * something. - * @return updated dstoff based on the number of bytes - * successfully copied into dstbuf by - * inf. If the inflater is not yet finished then - * another window's data must still be supplied as input to finish - * decompression. - * @throws DataFormatException - * the inflater encountered an invalid chunk of data. Data - * stream corruption is likely. - */ - final int inflate(long pos, byte[] dstbuf, int dstoff, Inflater inf) - throws DataFormatException { - return inflate((int) (pos - start), dstbuf, dstoff, inf); - } - - /** - * Pump bytes into the supplied inflater as input. - * - * @param pos - * offset within the window to start supplying input from. - * @param dstbuf - * destination buffer the inflater should output decompressed - * data to. - * @param dstoff - * current offset within dstbuf to inflate into. - * @param inf - * the inflater to feed input to. The caller is responsible for - * initializing the inflater as multiple windows may need to - * supply data to the same inflater to completely decompress - * something. - * @return updated dstoff based on the number of bytes - * successfully copied into dstbuf by - * inf. If the inflater is not yet finished then - * another window's data must still be supplied as input to finish - * decompression. - * @throws DataFormatException - * the inflater encountered an invalid chunk of data. Data - * stream corruption is likely. - */ - protected abstract int inflate(int pos, byte[] dstbuf, int dstoff, - Inflater inf) throws DataFormatException; -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java deleted file mode 100644 index a32571e606..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2010, Constantine Plotnikov - * Copyright (C) 2010, JetBrains s.r.o. - * 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.lib; - -import java.io.File; -import java.io.IOException; - -/** - * The cached instance of an {@link ObjectDirectory}. - *

- * This class caches the list of loose objects in memory, so the file system is - * not queried with stat calls. - */ -class CachedObjectDirectory extends FileObjectDatabase { - /** - * The set that contains unpacked objects identifiers, it is created when - * the cached instance is created. - */ - private final ObjectIdSubclassMap unpackedObjects = new ObjectIdSubclassMap(); - - private final ObjectDirectory wrapped; - - private AlternateHandle[] alts; - - /** - * The constructor - * - * @param wrapped - * the wrapped database - */ - CachedObjectDirectory(ObjectDirectory wrapped) { - this.wrapped = wrapped; - - File objects = wrapped.getDirectory(); - String[] fanout = objects.list(); - if (fanout == null) - fanout = new String[0]; - for (String d : fanout) { - if (d.length() != 2) - continue; - String[] entries = new File(objects, d).list(); - if (entries == null) - continue; - for (String e : entries) { - if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) - continue; - try { - unpackedObjects.add(ObjectId.fromString(d + e)); - } catch (IllegalArgumentException notAnObject) { - // ignoring the file that does not represent loose object - } - } - } - } - - @Override - public void close() { - // Don't close anything. - } - - @Override - public ObjectInserter newInserter() { - return wrapped.newInserter(); - } - - @Override - public ObjectDatabase newCachedDatabase() { - return this; - } - - @Override - FileObjectDatabase newCachedFileObjectDatabase() { - return this; - } - - @Override - File getDirectory() { - return wrapped.getDirectory(); - } - - @Override - AlternateHandle[] myAlternates() { - if (alts == null) { - AlternateHandle[] src = wrapped.myAlternates(); - alts = new AlternateHandle[src.length]; - for (int i = 0; i < alts.length; i++) { - FileObjectDatabase s = src[i].db; - alts[i] = new AlternateHandle(s.newCachedFileObjectDatabase()); - } - } - return alts; - } - - @Override - boolean tryAgain1() { - return wrapped.tryAgain1(); - } - - @Override - public boolean hasObject(final AnyObjectId objectId) { - return hasObjectImpl1(objectId); - } - - @Override - boolean hasObject1(AnyObjectId objectId) { - return unpackedObjects.contains(objectId) - || wrapped.hasObject1(objectId); - } - - @Override - ObjectLoader openObject(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { - return openObjectImpl1(curs, objectId); - } - - @Override - ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) - throws IOException { - if (unpackedObjects.contains(objectId)) - return wrapped.openObject2(curs, objectId.name(), objectId); - return wrapped.openObject1(curs, objectId); - } - - @Override - boolean hasObject2(String objectId) { - // This method should never be invoked. - throw new UnsupportedOperationException(); - } - - @Override - ObjectLoader openObject2(WindowCursor curs, String objectName, - AnyObjectId objectId) throws IOException { - // This method should never be invoked. - throw new UnsupportedOperationException(); - } - - @Override - void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, - WindowCursor curs) throws IOException { - wrapped.selectObjectRepresentation(packer, otp, curs); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java deleted file mode 100644 index d0e98a2a92..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; - -/** Reads a deltified object which uses an offset to find its base. */ -class DeltaOfsPackedObjectLoader extends DeltaPackedObjectLoader { - private final long deltaBase; - - DeltaOfsPackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSz, final int deltaSz, final long base) { - super(pr, objectOffset, headerSz, deltaSz); - deltaBase = base; - } - - protected PackedObjectLoader getBaseLoader(final WindowCursor curs) - throws IOException { - return pack.resolveBase(curs, deltaBase); - } - - @Override - public int getRawType() { - return Constants.OBJ_OFS_DELTA; - } - - @Override - public ObjectId getDeltaBase() throws IOException { - final ObjectId id = pack.findObjectForOffset(deltaBase); - if (id == null) - throw new CorruptObjectException( - JGitText.get().offsetWrittenDeltaBaseForObjectNotFoundInAPack); - return id; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java deleted file mode 100644 index bbc1c62a8a..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.text.MessageFormat; -import java.util.zip.DataFormatException; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; - -/** Reader for a deltified object stored in a pack file. */ -abstract class DeltaPackedObjectLoader extends PackedObjectLoader { - private static final int OBJ_COMMIT = Constants.OBJ_COMMIT; - - private final int deltaSize; - - DeltaPackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSize, final int deltaSz) { - super(pr, objectOffset, headerSize); - objectType = -1; - deltaSize = deltaSz; - } - - @Override - public void materialize(final WindowCursor curs) throws IOException { - if (cachedBytes != null) { - return; - } - - if (objectType != OBJ_COMMIT) { - UnpackedObjectCache.Entry cache = pack.readCache(objectOffset); - if (cache != null) { - curs.release(); - objectType = cache.type; - objectSize = cache.data.length; - cachedBytes = cache.data; - return; - } - } - - try { - final PackedObjectLoader baseLoader = getBaseLoader(curs); - baseLoader.materialize(curs); - cachedBytes = BinaryDelta.apply(baseLoader.getCachedBytes(), pack - .decompress(objectOffset + headerSize, deltaSize, curs)); - curs.release(); - objectType = baseLoader.getType(); - objectSize = cachedBytes.length; - if (objectType != OBJ_COMMIT) - pack.saveCache(objectOffset, cachedBytes, objectType); - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream, - objectOffset, pack.getPackFile())); - coe.initCause(dfe); - throw coe; - } - } - - @Override - public long getRawSize() { - return deltaSize; - } - - /** - * @param curs - * temporary thread storage during data access. - * @return the object loader for the base object - * @throws IOException - */ - protected abstract PackedObjectLoader getBaseLoader(WindowCursor curs) - throws IOException; -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java deleted file mode 100644 index 9f7589c291..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; - -import org.eclipse.jgit.errors.MissingObjectException; - -/** Reads a deltified object which uses an {@link ObjectId} to find its base. */ -class DeltaRefPackedObjectLoader extends DeltaPackedObjectLoader { - private final ObjectId deltaBase; - - DeltaRefPackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSz, final int deltaSz, final ObjectId base) { - super(pr, objectOffset, headerSz, deltaSz); - deltaBase = base; - } - - protected PackedObjectLoader getBaseLoader(final WindowCursor curs) - throws IOException { - final PackedObjectLoader or = pack.get(curs, deltaBase); - if (or == null) - throw new MissingObjectException(deltaBase, "delta base"); - return or; - } - - @Override - public int getRawType() { - return Constants.OBJ_REF_DELTA; - } - - @Override - public ObjectId getDeltaBase() throws IOException { - return deltaBase; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java deleted file mode 100644 index eb00917917..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2009, Constantine Plotnikov - * Copyright (C) 2007, Dave Watson - * Copyright (C) 2009, Google Inc. - * Copyright (C) 2009, JetBrains s.r.o. - * Copyright (C) 2008-2009, Robin Rosenberg - * Copyright (C) 2008, Shawn O. Pearce - * Copyright (C) 2008, Thad Hughes - * 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.lib; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.text.MessageFormat; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * The configuration file that is stored in the file of the file system. - */ -public class FileBasedConfig extends Config { - private final File configFile; - private volatile long lastModified; - - /** - * Create a configuration with no default fallback. - * - * @param cfgLocation - * the location of the configuration file on the file system - */ - public FileBasedConfig(File cfgLocation) { - this(null, cfgLocation); - } - - /** - * The constructor - * - * @param base - * the base configuration file - * @param cfgLocation - * the location of the configuration file on the file system - */ - public FileBasedConfig(Config base, File cfgLocation) { - super(base); - configFile = cfgLocation; - } - - /** @return location of the configuration file on disk */ - public final File getFile() { - return configFile; - } - - /** - * Load the configuration as a Git text style configuration file. - *

- * If the file does not exist, this configuration is cleared, and thus - * behaves the same as though the file exists, but is empty. - * - * @throws IOException - * the file could not be read (but does exist). - * @throws ConfigInvalidException - * the file is not a properly formatted configuration file. - */ - public void load() throws IOException, ConfigInvalidException { - lastModified = getFile().lastModified(); - try { - fromText(RawParseUtils.decode(IO.readFully(getFile()))); - } catch (FileNotFoundException noFile) { - clear(); - } catch (IOException e) { - final IOException e2 = new IOException(MessageFormat.format(JGitText.get().cannotReadFile, getFile())); - e2.initCause(e); - throw e2; - } catch (ConfigInvalidException e) { - throw new ConfigInvalidException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()), e); - } - } - - /** - * Save the configuration as a Git text style configuration file. - *

- * Warning: Although this method uses the traditional Git file - * locking approach to protect against concurrent writes of the - * configuration file, it does not ensure that the file has not been - * modified since the last read, which means updates performed by other - * objects accessing the same backing file may be lost. - * - * @throws IOException - * the file could not be written. - */ - public void save() throws IOException { - final byte[] out = Constants.encode(toText()); - final LockFile lf = new LockFile(getFile()); - if (!lf.lock()) - throw new IOException(MessageFormat.format(JGitText.get().cannotLockFile, getFile())); - try { - lf.setNeedStatInformation(true); - lf.write(out); - if (!lf.commit()) - throw new IOException(MessageFormat.format(JGitText.get().cannotCommitWriteTo, getFile())); - } finally { - lf.unlock(); - } - lastModified = lf.getCommitLastModified(); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "[" + getFile().getPath() + "]"; - } - - /** - * @return returns true if the currently loaded configuration file is older - * than the file on disk - */ - public boolean isOutdated() { - return getFile().lastModified() != lastModified; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java deleted file mode 100644 index 5328b327ec..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2010, 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.lib; - -import java.io.File; -import java.io.IOException; - -abstract class FileObjectDatabase extends ObjectDatabase { - @Override - public ObjectReader newReader() { - return new WindowCursor(this); - } - - /** - * Does the requested object exist in this database? - *

- * Alternates (if present) are searched automatically. - * - * @param objectId - * identity of the object to test for existence of. - * @return true if the specified object is stored in this database, or any - * of the alternate databases. - */ - public boolean hasObject(final AnyObjectId objectId) { - return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name()); - } - - final boolean hasObjectImpl1(final AnyObjectId objectId) { - if (hasObject1(objectId)) - return true; - - for (final AlternateHandle alt : myAlternates()) { - if (alt.db.hasObjectImpl1(objectId)) - return true; - } - - return tryAgain1() && hasObject1(objectId); - } - - final boolean hasObjectImpl2(final String objectId) { - if (hasObject2(objectId)) - return true; - - for (final AlternateHandle alt : myAlternates()) { - if (alt.db.hasObjectImpl2(objectId)) - return true; - } - - return false; - } - - /** - * Open an object from this database. - *

- * Alternates (if present) are searched automatically. - * - * @param curs - * temporary working space associated with the calling thread. - * @param objectId - * identity of the object to open. - * @return a {@link ObjectLoader} for accessing the data of the named - * object, or null if the object does not exist. - * @throws IOException - */ - ObjectLoader openObject(final WindowCursor curs, final AnyObjectId objectId) - throws IOException { - ObjectLoader ldr; - - ldr = openObjectImpl1(curs, objectId); - if (ldr != null) - return ldr; - - ldr = openObjectImpl2(curs, objectId.name(), objectId); - if (ldr != null) - return ldr; - - return null; - } - - final ObjectLoader openObjectImpl1(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { - ObjectLoader ldr; - - ldr = openObject1(curs, objectId); - if (ldr != null) - return ldr; - - for (final AlternateHandle alt : myAlternates()) { - ldr = alt.db.openObjectImpl1(curs, objectId); - if (ldr != null) - return ldr; - } - - if (tryAgain1()) { - ldr = openObject1(curs, objectId); - if (ldr != null) - return ldr; - } - - return null; - } - - final ObjectLoader openObjectImpl2(final WindowCursor curs, - final String objectName, final AnyObjectId objectId) - throws IOException { - ObjectLoader ldr; - - ldr = openObject2(curs, objectName, objectId); - if (ldr != null) - return ldr; - - for (final AlternateHandle alt : myAlternates()) { - ldr = alt.db.openObjectImpl2(curs, objectName, objectId); - if (ldr != null) - return ldr; - } - - return null; - } - - abstract void selectObjectRepresentation(PackWriter packer, - ObjectToPack otp, WindowCursor curs) throws IOException; - - abstract File getDirectory(); - - abstract AlternateHandle[] myAlternates(); - - abstract boolean tryAgain1(); - - abstract boolean hasObject1(AnyObjectId objectId); - - abstract boolean hasObject2(String objectId); - - abstract ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) - throws IOException; - - abstract ObjectLoader openObject2(WindowCursor curs, String objectName, - AnyObjectId objectId) throws IOException; - - abstract FileObjectDatabase newCachedFileObjectDatabase(); - - static class AlternateHandle { - final FileObjectDatabase db; - - AlternateHandle(FileObjectDatabase db) { - this.db = db; - } - - void close() { - db.close(); - } - } - - static class AlternateRepository extends AlternateHandle { - final FileRepository repository; - - AlternateRepository(FileRepository r) { - super(r.getObjectDatabase()); - repository = r; - } - - void close() { - repository.close(); - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java deleted file mode 100644 index f45ffdf149..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (C) 2007, Dave Watson - * Copyright (C) 2008-2010, Google Inc. - * Copyright (C) 2006-2010, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.File; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.HashSet; -import java.util.Set; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.lib.FileObjectDatabase.AlternateHandle; -import org.eclipse.jgit.lib.FileObjectDatabase.AlternateRepository; -import org.eclipse.jgit.util.SystemReader; - -/** - * Represents a Git repository. A repository holds all objects and refs used for - * managing source code (could by any type of file, but source code is what - * SCM's are typically used for). - * - * In Git terms all data is stored in GIT_DIR, typically a directory called - * .git. A work tree is maintained unless the repository is a bare repository. - * Typically the .git directory is located at the root of the work dir. - * - *

    - *
  • GIT_DIR - *
      - *
    • objects/ - objects
    • - *
    • refs/ - tags and heads
    • - *
    • config - configuration
    • - *
    • info/ - more configurations
    • - *
    - *
  • - *
- *

- * This class is thread-safe. - *

- * This implementation only handles a subtly undocumented subset of git features. - * - */ -public class FileRepository extends Repository { - private final FileBasedConfig userConfig; - - private final FileBasedConfig repoConfig; - - private final RefDatabase refs; - - private final ObjectDirectory objectDatabase; - - /** - * Construct a representation of a Git repository. - *

- * The work tree, object directory, alternate object directories and index - * file locations are deduced from the given git directory and the default - * rules by running {@link FileRepositoryBuilder}. This constructor is the - * same as saying: - * - *

-	 * new FileRepositoryBuilder().setGitDir(gitDir).build()
-	 * 
- * - * @param gitDir - * GIT_DIR (the location of the repository metadata). - * @throws IOException - * the repository appears to already exist but cannot be - * accessed. - * @see FileRepositoryBuilder - */ - public FileRepository(final File gitDir) throws IOException { - this(new FileRepositoryBuilder().setGitDir(gitDir).setup()); - } - - FileRepository(final BaseRepositoryBuilder options) throws IOException { - super(options); - - userConfig = SystemReader.getInstance().openUserConfig(getFS()); - repoConfig = new FileBasedConfig(userConfig, getFS().resolve(getDirectory(), "config")); - - loadUserConfig(); - loadRepoConfig(); - - refs = new RefDirectory(this); - objectDatabase = new ObjectDirectory(repoConfig, // - options.getObjectDirectory(), // - options.getAlternateObjectDirectories(), // - getFS()); - - if (objectDatabase.exists()) { - final String repositoryFormatVersion = getConfig().getString( - ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION); - if (!"0".equals(repositoryFormatVersion)) { - throw new IOException(MessageFormat.format( - JGitText.get().unknownRepositoryFormat2, - repositoryFormatVersion)); - } - } - } - - private void loadUserConfig() throws IOException { - try { - userConfig.load(); - } catch (ConfigInvalidException e1) { - IOException e2 = new IOException(MessageFormat.format(JGitText - .get().userConfigFileInvalid, userConfig.getFile() - .getAbsolutePath(), e1)); - e2.initCause(e1); - throw e2; - } - } - - private void loadRepoConfig() throws IOException { - try { - repoConfig.load(); - } catch (ConfigInvalidException e1) { - IOException e2 = new IOException(JGitText.get().unknownRepositoryFormat); - e2.initCause(e1); - throw e2; - } - } - - /** - * Create a new Git repository initializing the necessary files and - * directories. - * - * @param bare - * if true, a bare repository is created. - * - * @throws IOException - * in case of IO problem - */ - public void create(boolean bare) throws IOException { - final FileBasedConfig cfg = getConfig(); - if (cfg.getFile().exists()) { - throw new IllegalStateException(MessageFormat.format( - JGitText.get().repositoryAlreadyExists, getDirectory())); - } - getDirectory().mkdirs(); - refs.create(); - objectDatabase.create(); - - new File(getDirectory(), "branches").mkdir(); - - RefUpdate head = updateRef(Constants.HEAD); - head.disableRefLog(); - head.link(Constants.R_HEADS + Constants.MASTER); - - cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0); - cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_FILEMODE, true); - if (bare) - cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_BARE, true); - cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare); - cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_AUTOCRLF, false); - cfg.save(); - } - - /** - * @return the directory containing the objects owned by this repository. - */ - public File getObjectsDirectory() { - return objectDatabase.getDirectory(); - } - - /** - * @return the object database which stores this repository's data. - */ - public ObjectDirectory getObjectDatabase() { - return objectDatabase; - } - - /** @return the reference database which stores the reference namespace. */ - public RefDatabase getRefDatabase() { - return refs; - } - - /** - * @return the configuration of this repository - */ - public FileBasedConfig getConfig() { - if (userConfig.isOutdated()) { - try { - loadUserConfig(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - if (repoConfig.isOutdated()) { - try { - loadRepoConfig(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - return repoConfig; - } - - /** - * Objects known to exist but not expressed by {@link #getAllRefs()}. - *

- * When a repository borrows objects from another repository, it can - * advertise that it safely has that other repository's references, without - * exposing any other details about the other repository. This may help - * a client trying to push changes avoid pushing more than it needs to. - * - * @return unmodifiable collection of other known objects. - */ - public Set getAdditionalHaves() { - HashSet r = new HashSet(); - for (AlternateHandle d : objectDatabase. myAlternates()) { - if (d instanceof AlternateRepository) { - Repository repo; - - repo = ((AlternateRepository) d).repository; - for (Ref ref : repo.getAllRefs().values()) - r.add(ref.getObjectId()); - r.addAll(repo.getAdditionalHaves()); - } - } - return r; - } - - /** - * Add a single existing pack to the list of available pack files. - * - * @param pack - * path of the pack file to open. - * @param idx - * path of the corresponding index file. - * @throws IOException - * index file could not be opened, read, or is not recognized as - * a Git pack file index. - */ - public void openPack(final File pack, final File idx) throws IOException { - objectDatabase.openPack(pack, idx); - } - - /** - * Force a scan for changed refs. - * - * @throws IOException - */ - public void scanForRepoChanges() throws IOException { - getAllRefs(); // This will look for changes to refs - if (!isBare()) - getIndex(); // This will detect changes in the index - } - - /** - * @param refName - * @return a {@link ReflogReader} for the supplied refname, or null if the - * named ref does not exist. - * @throws IOException the ref could not be accessed. - */ - public ReflogReader getReflogReader(String refName) throws IOException { - Ref ref = getRef(refName); - if (ref != null) - return new ReflogReader(this, ref.getName()); - return null; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java deleted file mode 100644 index c0220d979e..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2010, 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.lib; - -import java.io.File; -import java.io.IOException; - -/** - * Constructs a {@link FileRepository}. - *

- * Applications must set one of {@link #setGitDir(File)} or - * {@link #setWorkTree(File)}, or use {@link #readEnvironment()} or - * {@link #findGitDir()} in order to configure the minimum property set - * necessary to open a repository. - *

- * Single repository applications trying to be compatible with other Git - * implementations are encouraged to use a model such as: - * - *

- * new FileRepositoryBuilder() //
- * 		.setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null
- * 		.readEnviroment() // scan environment GIT_* variables
- * 		.findGitDir() // scan up the file system tree
- * 		.build()
- * 
- */ -public class FileRepositoryBuilder extends - BaseRepositoryBuilder { - /** - * Create a repository matching the configuration in this builder. - *

- * If an option was not set, the build method will try to default the option - * based on other options. If insufficient information is available, an - * exception is thrown to the caller. - * - * @return a repository matching this configuration. - * @throws IllegalArgumentException - * insufficient parameters were set. - * @throws IOException - * the repository could not be accessed to configure the rest of - * the builder's parameters. - */ - @Override - public FileRepository build() throws IOException { - return new FileRepository(setup()); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java deleted file mode 100644 index f8535bdf2d..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2010, 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.lib; - -import java.io.IOException; - -class LocalObjectRepresentation extends StoredObjectRepresentation { - final PackedObjectLoader ldr; - - LocalObjectRepresentation(PackedObjectLoader ldr) { - this.ldr = ldr; - } - - @Override - public int getFormat() { - if (ldr instanceof DeltaPackedObjectLoader) - return PACK_DELTA; - if (ldr instanceof WholePackedObjectLoader) - return PACK_WHOLE; - return FORMAT_OTHER; - } - - @Override - public int getWeight() { - long sz = ldr.getRawSize(); - if (Integer.MAX_VALUE < sz) - return WEIGHT_UNKNOWN; - return (int) sz; - } - - @Override - public ObjectId getDeltaBase() { - try { - return ldr.getDeltaBase(); - } catch (IOException e) { - return null; - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java deleted file mode 100644 index 8db58707e8..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2010, 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.lib; - -import org.eclipse.jgit.revwalk.RevObject; - -/** {@link ObjectToPack} for {@link ObjectDirectory}. */ -class LocalObjectToPack extends ObjectToPack { - /** Pack to reuse compressed data from, otherwise null. */ - PackFile copyFromPack; - - /** Offset of the object's header in {@link #copyFromPack}. */ - long copyOffset; - - LocalObjectToPack(RevObject obj) { - super(obj); - } - - @Override - public void select(StoredObjectRepresentation ref) { - LocalObjectRepresentation ptr = (LocalObjectRepresentation)ref; - this.copyFromPack = ptr.ldr.pack; - this.copyOffset = ptr.ldr.objectOffset; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java deleted file mode 100644 index 13f158dedf..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.channels.FileLock; -import java.nio.channels.OverlappingFileLockException; -import java.text.MessageFormat; - -import org.eclipse.jgit.JGitText; - -/** - * Git style file locking and replacement. - *

- * To modify a ref file Git tries to use an atomic update approach: we write the - * new data into a brand new file, then rename it in place over the old name. - * This way we can just delete the temporary file if anything goes wrong, and - * nothing has been damaged. To coordinate access from multiple processes at - * once Git tries to atomically create the new temporary file under a well-known - * name. - */ -public class LockFile { - static final String SUFFIX = ".lock"; //$NON-NLS-1$ - - /** Filter to skip over active lock files when listing a directory. */ - static final FilenameFilter FILTER = new FilenameFilter() { - public boolean accept(File dir, String name) { - return !name.endsWith(SUFFIX); - } - }; - - private final File ref; - - private final File lck; - - private FileLock fLck; - - private boolean haveLck; - - private FileOutputStream os; - - private boolean needStatInformation; - - private long commitLastModified; - - /** - * Create a new lock for any file. - * - * @param f - * the file that will be locked. - */ - public LockFile(final File f) { - ref = f; - lck = new File(ref.getParentFile(), ref.getName() + SUFFIX); - } - - /** - * Try to establish the lock. - * - * @return true if the lock is now held by the caller; false if it is held - * by someone else. - * @throws IOException - * the temporary output file could not be created. The caller - * does not hold the lock. - */ - public boolean lock() throws IOException { - lck.getParentFile().mkdirs(); - if (lck.createNewFile()) { - haveLck = true; - try { - os = new FileOutputStream(lck); - try { - fLck = os.getChannel().tryLock(); - if (fLck == null) - throw new OverlappingFileLockException(); - } catch (OverlappingFileLockException ofle) { - // We cannot use unlock() here as this file is not - // held by us, but we thought we created it. We must - // not delete it, as it belongs to some other process. - // - haveLck = false; - try { - os.close(); - } catch (IOException ioe) { - // Fail by returning haveLck = false. - } - os = null; - } - } catch (IOException ioe) { - unlock(); - throw ioe; - } - } - return haveLck; - } - - /** - * Try to establish the lock for appending. - * - * @return true if the lock is now held by the caller; false if it is held - * by someone else. - * @throws IOException - * the temporary output file could not be created. The caller - * does not hold the lock. - */ - public boolean lockForAppend() throws IOException { - if (!lock()) - return false; - copyCurrentContent(); - return true; - } - - /** - * Copy the current file content into the temporary file. - *

- * This method saves the current file content by inserting it into the - * temporary file, so that the caller can safely append rather than replace - * the primary file. - *

- * This method does nothing if the current file does not exist, or exists - * but is empty. - * - * @throws IOException - * the temporary file could not be written, or a read error - * occurred while reading from the current file. The lock is - * released before throwing the underlying IO exception to the - * caller. - * @throws RuntimeException - * the temporary file could not be written. The lock is released - * before throwing the underlying exception to the caller. - */ - public void copyCurrentContent() throws IOException { - requireLock(); - try { - final FileInputStream fis = new FileInputStream(ref); - try { - final byte[] buf = new byte[2048]; - int r; - while ((r = fis.read(buf)) >= 0) - os.write(buf, 0, r); - } finally { - fis.close(); - } - } catch (FileNotFoundException fnfe) { - // Don't worry about a file that doesn't exist yet, it - // conceptually has no current content to copy. - // - } catch (IOException ioe) { - unlock(); - throw ioe; - } catch (RuntimeException ioe) { - unlock(); - throw ioe; - } catch (Error ioe) { - unlock(); - throw ioe; - } - } - - /** - * Write an ObjectId and LF to the temporary file. - * - * @param id - * the id to store in the file. The id will be written in hex, - * followed by a sole LF. - * @throws IOException - * the temporary file could not be written. The lock is released - * before throwing the underlying IO exception to the caller. - * @throws RuntimeException - * the temporary file could not be written. The lock is released - * before throwing the underlying exception to the caller. - */ - public void write(final ObjectId id) throws IOException { - requireLock(); - try { - final BufferedOutputStream b; - b = new BufferedOutputStream(os, Constants.OBJECT_ID_STRING_LENGTH + 1); - id.copyTo(b); - b.write('\n'); - b.flush(); - fLck.release(); - b.close(); - os = null; - } catch (IOException ioe) { - unlock(); - throw ioe; - } catch (RuntimeException ioe) { - unlock(); - throw ioe; - } catch (Error ioe) { - unlock(); - throw ioe; - } - } - - /** - * Write arbitrary data to the temporary file. - * - * @param content - * the bytes to store in the temporary file. No additional bytes - * are added, so if the file must end with an LF it must appear - * at the end of the byte array. - * @throws IOException - * the temporary file could not be written. The lock is released - * before throwing the underlying IO exception to the caller. - * @throws RuntimeException - * the temporary file could not be written. The lock is released - * before throwing the underlying exception to the caller. - */ - public void write(final byte[] content) throws IOException { - requireLock(); - try { - os.write(content); - os.flush(); - fLck.release(); - os.close(); - os = null; - } catch (IOException ioe) { - unlock(); - throw ioe; - } catch (RuntimeException ioe) { - unlock(); - throw ioe; - } catch (Error ioe) { - unlock(); - throw ioe; - } - } - - /** - * Obtain the direct output stream for this lock. - *

- * The stream may only be accessed once, and only after {@link #lock()} has - * been successfully invoked and returned true. Callers must close the - * stream prior to calling {@link #commit()} to commit the change. - * - * @return a stream to write to the new file. The stream is unbuffered. - */ - public OutputStream getOutputStream() { - requireLock(); - return new OutputStream() { - @Override - public void write(final byte[] b, final int o, final int n) - throws IOException { - os.write(b, o, n); - } - - @Override - public void write(final byte[] b) throws IOException { - os.write(b); - } - - @Override - public void write(final int b) throws IOException { - os.write(b); - } - - @Override - public void flush() throws IOException { - os.flush(); - } - - @Override - public void close() throws IOException { - try { - os.flush(); - fLck.release(); - os.close(); - os = null; - } catch (IOException ioe) { - unlock(); - throw ioe; - } catch (RuntimeException ioe) { - unlock(); - throw ioe; - } catch (Error ioe) { - unlock(); - throw ioe; - } - } - }; - } - - private void requireLock() { - if (os == null) { - unlock(); - throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref)); - } - } - - /** - * Request that {@link #commit()} remember modification time. - * - * @param on - * true if the commit method must remember the modification time. - */ - public void setNeedStatInformation(final boolean on) { - needStatInformation = on; - } - - /** - * Wait until the lock file information differs from the old file. - *

- * This method tests both the length and the last modification date. If both - * are the same, this method sleeps until it can force the new lock file's - * modification date to be later than the target file. - * - * @throws InterruptedException - * the thread was interrupted before the last modified date of - * the lock file was different from the last modified date of - * the target file. - */ - public void waitForStatChange() throws InterruptedException { - if (ref.length() == lck.length()) { - long otime = ref.lastModified(); - long ntime = lck.lastModified(); - while (otime == ntime) { - Thread.sleep(25 /* milliseconds */); - lck.setLastModified(System.currentTimeMillis()); - ntime = lck.lastModified(); - } - } - } - - /** - * Commit this change and release the lock. - *

- * If this method fails (returns false) the lock is still released. - * - * @return true if the commit was successful and the file contains the new - * data; false if the commit failed and the file remains with the - * old data. - * @throws IllegalStateException - * the lock is not held. - */ - public boolean commit() { - if (os != null) { - unlock(); - throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotClosed, ref)); - } - - saveStatInformation(); - if (lck.renameTo(ref)) - return true; - if (!ref.exists() || ref.delete()) - if (lck.renameTo(ref)) - return true; - unlock(); - return false; - } - - private void saveStatInformation() { - if (needStatInformation) - commitLastModified = lck.lastModified(); - } - - /** - * Get the modification time of the output file when it was committed. - * - * @return modification time of the lock file right before we committed it. - */ - public long getCommitLastModified() { - return commitLastModified; - } - - /** - * Unlock this file and abort this change. - *

- * The temporary file (if created) is deleted before returning. - */ - public void unlock() { - if (os != null) { - if (fLck != null) { - try { - fLck.release(); - } catch (IOException ioe) { - // Huh? - } - fLck = null; - } - try { - os.close(); - } catch (IOException ioe) { - // Ignore this - } - os = null; - } - - if (haveLck) { - haveLck = false; - lck.delete(); - } - } - - @Override - public String toString() { - return "LockFile[" + lck + ", haveLck=" + haveLck + "]"; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java deleted file mode 100644 index bf8f323341..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java +++ /dev/null @@ -1,621 +0,0 @@ -/* - * Copyright (C) 2009, 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.lib; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.PackMismatchException; -import org.eclipse.jgit.lib.RepositoryCache.FileKey; -import org.eclipse.jgit.util.FS; - -/** - * Traditional file system based {@link ObjectDatabase}. - *

- * This is the classical object database representation for a Git repository, - * where objects are stored loose by hashing them into directories by their - * {@link ObjectId}, or are stored in compressed containers known as - * {@link PackFile}s. - *

- * Optionally an object database can reference one or more alternates; other - * ObjectDatabase instances that are searched in addition to the current - * database. - *

- * Databases are divided into two halves: a half that is considered to be fast - * to search (the {@code PackFile}s), and a half that is considered to be slow - * to search (loose objects). When alternates are present the fast half is fully - * searched (recursively through all alternates) before the slow half is - * considered. - */ -public class ObjectDirectory extends FileObjectDatabase { - private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]); - - private final Config config; - - private final File objects; - - private final File infoDirectory; - - private final File packDirectory; - - private final File alternatesFile; - - private final AtomicReference packList; - - private final FS fs; - - private final AtomicReference alternates; - - /** - * Initialize a reference to an on-disk object directory. - * - * @param cfg - * configuration this directory consults for write settings. - * @param dir - * the location of the objects directory. - * @param alternatePaths - * a list of alternate object directories - * @param fs - * the file system abstraction which will be necessary to perform - * certain file system operations. - * @throws IOException - * an alternate object cannot be opened. - */ - public ObjectDirectory(final Config cfg, final File dir, - File[] alternatePaths, FS fs) throws IOException { - config = cfg; - objects = dir; - infoDirectory = new File(objects, "info"); - packDirectory = new File(objects, "pack"); - alternatesFile = new File(infoDirectory, "alternates"); - packList = new AtomicReference(NO_PACKS); - this.fs = fs; - - alternates = new AtomicReference(); - if (alternatePaths != null) { - AlternateHandle[] alt; - - alt = new AlternateHandle[alternatePaths.length]; - for (int i = 0; i < alternatePaths.length; i++) - alt[i] = openAlternate(alternatePaths[i]); - alternates.set(alt); - } - } - - /** - * @return the location of the objects directory. - */ - public final File getDirectory() { - return objects; - } - - @Override - public boolean exists() { - return objects.exists(); - } - - @Override - public void create() throws IOException { - objects.mkdirs(); - infoDirectory.mkdir(); - packDirectory.mkdir(); - } - - @Override - public ObjectInserter newInserter() { - return new ObjectDirectoryInserter(this, config); - } - - @Override - public void close() { - final PackList packs = packList.get(); - packList.set(NO_PACKS); - for (final PackFile p : packs.packs) - p.close(); - - // Fully close all loaded alternates and clear the alternate list. - AlternateHandle[] alt = alternates.get(); - if (alt != null) { - alternates.set(null); - for(final AlternateHandle od : alt) - od.close(); - } - } - - /** - * Compute the location of a loose object file. - * - * @param objectId - * identity of the loose object to map to the directory. - * @return location of the object, if it were to exist as a loose object. - */ - public File fileFor(final AnyObjectId objectId) { - return fileFor(objectId.name()); - } - - private File fileFor(final String objectName) { - final String d = objectName.substring(0, 2); - final String f = objectName.substring(2); - return new File(new File(objects, d), f); - } - - /** - * @return unmodifiable collection of all known pack files local to this - * directory. Most recent packs are presented first. Packs most - * likely to contain more recent objects appear before packs - * containing objects referenced by commits further back in the - * history of the repository. - */ - public Collection getPacks() { - final PackFile[] packs = packList.get().packs; - return Collections.unmodifiableCollection(Arrays.asList(packs)); - } - - /** - * Add a single existing pack to the list of available pack files. - * - * @param pack - * path of the pack file to open. - * @param idx - * path of the corresponding index file. - * @throws IOException - * index file could not be opened, read, or is not recognized as - * a Git pack file index. - */ - public void openPack(final File pack, final File idx) throws IOException { - final String p = pack.getName(); - final String i = idx.getName(); - - if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack")) - throw new IOException(MessageFormat.format(JGitText.get().notAValidPack, pack)); - - if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx")) - throw new IOException(MessageFormat.format(JGitText.get().notAValidPack, idx)); - - if (!p.substring(0, 45).equals(i.substring(0, 45))) - throw new IOException(MessageFormat.format(JGitText.get().packDoesNotMatchIndex, pack)); - - insertPack(new PackFile(idx, pack)); - } - - @Override - public String toString() { - return "ObjectDirectory[" + getDirectory() + "]"; - } - - boolean hasObject1(final AnyObjectId objectId) { - for (final PackFile p : packList.get().packs) { - try { - if (p.hasObject(objectId)) { - return true; - } - } catch (IOException e) { - // The hasObject call should have only touched the index, - // so any failure here indicates the index is unreadable - // by this process, and the pack is likewise not readable. - // - removePack(p); - continue; - } - } - return false; - } - - ObjectLoader openObject1(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { - PackList pList = packList.get(); - SEARCH: for (;;) { - for (final PackFile p : pList.packs) { - try { - final PackedObjectLoader ldr = p.get(curs, objectId); - if (ldr != null) { - ldr.materialize(curs); - return ldr; - } - } catch (PackMismatchException e) { - // Pack was modified; refresh the entire pack list. - // - pList = scanPacks(pList); - continue SEARCH; - } catch (IOException e) { - // Assume the pack is corrupted. - // - removePack(p); - } - } - return null; - } - } - - @Override - void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, - WindowCursor curs) throws IOException { - PackList pList = packList.get(); - SEARCH: for (;;) { - for (final PackFile p : pList.packs) { - try { - PackedObjectLoader ldr = p.get(curs, otp); - if (ldr != null) - packer.select(otp, new LocalObjectRepresentation(ldr)); - } catch (PackMismatchException e) { - // Pack was modified; refresh the entire pack list. - // - pList = scanPacks(pList); - continue SEARCH; - } catch (IOException e) { - // Assume the pack is corrupted. - // - removePack(p); - } - } - break SEARCH; - } - - for (AlternateHandle h : myAlternates()) - h.db.selectObjectRepresentation(packer, otp, curs); - } - - boolean hasObject2(final String objectName) { - return fileFor(objectName).exists(); - } - - ObjectLoader openObject2(final WindowCursor curs, - final String objectName, final AnyObjectId objectId) - throws IOException { - try { - return new UnpackedObjectLoader(fileFor(objectName), objectId); - } catch (FileNotFoundException noFile) { - return null; - } - } - - boolean tryAgain1() { - final PackList old = packList.get(); - if (old.tryAgain(packDirectory.lastModified())) - return old != scanPacks(old); - return false; - } - - private void insertPack(final PackFile pf) { - PackList o, n; - do { - o = packList.get(); - - // If the pack in question is already present in the list - // (picked up by a concurrent thread that did a scan?) we - // do not want to insert it a second time. - // - final PackFile[] oldList = o.packs; - final String name = pf.getPackFile().getName(); - for (PackFile p : oldList) { - if (PackFile.SORT.compare(pf, p) < 0) - break; - if (name.equals(p.getPackFile().getName())) - return; - } - - final PackFile[] newList = new PackFile[1 + oldList.length]; - newList[0] = pf; - System.arraycopy(oldList, 0, newList, 1, oldList.length); - n = new PackList(o.lastRead, o.lastModified, newList); - } while (!packList.compareAndSet(o, n)); - } - - private void removePack(final PackFile deadPack) { - PackList o, n; - do { - o = packList.get(); - - final PackFile[] oldList = o.packs; - final int j = indexOf(oldList, deadPack); - if (j < 0) - break; - - final PackFile[] newList = new PackFile[oldList.length - 1]; - System.arraycopy(oldList, 0, newList, 0, j); - System.arraycopy(oldList, j + 1, newList, j, newList.length - j); - n = new PackList(o.lastRead, o.lastModified, newList); - } while (!packList.compareAndSet(o, n)); - deadPack.close(); - } - - private static int indexOf(final PackFile[] list, final PackFile pack) { - for (int i = 0; i < list.length; i++) { - if (list[i] == pack) - return i; - } - return -1; - } - - private PackList scanPacks(final PackList original) { - synchronized (packList) { - PackList o, n; - do { - o = packList.get(); - if (o != original) { - // Another thread did the scan for us, while we - // were blocked on the monitor above. - // - return o; - } - n = scanPacksImpl(o); - if (n == o) - return n; - } while (!packList.compareAndSet(o, n)); - return n; - } - } - - private PackList scanPacksImpl(final PackList old) { - final Map forReuse = reuseMap(old); - final long lastRead = System.currentTimeMillis(); - final long lastModified = packDirectory.lastModified(); - final Set names = listPackDirectory(); - final List list = new ArrayList(names.size() >> 2); - boolean foundNew = false; - for (final String indexName : names) { - // Must match "pack-[0-9a-f]{40}.idx" to be an index. - // - if (indexName.length() != 49 || !indexName.endsWith(".idx")) - continue; - - final String base = indexName.substring(0, indexName.length() - 4); - final String packName = base + ".pack"; - if (!names.contains(packName)) { - // Sometimes C Git's HTTP fetch transport leaves a - // .idx file behind and does not download the .pack. - // We have to skip over such useless indexes. - // - continue; - } - - final PackFile oldPack = forReuse.remove(packName); - if (oldPack != null) { - list.add(oldPack); - continue; - } - - final File packFile = new File(packDirectory, packName); - final File idxFile = new File(packDirectory, indexName); - list.add(new PackFile(idxFile, packFile)); - foundNew = true; - } - - // If we did not discover any new files, the modification time was not - // changed, and we did not remove any files, then the set of files is - // the same as the set we were given. Instead of building a new object - // return the same collection. - // - if (!foundNew && lastModified == old.lastModified && forReuse.isEmpty()) - return old.updateLastRead(lastRead); - - for (final PackFile p : forReuse.values()) { - p.close(); - } - - if (list.isEmpty()) - return new PackList(lastRead, lastModified, NO_PACKS.packs); - - final PackFile[] r = list.toArray(new PackFile[list.size()]); - Arrays.sort(r, PackFile.SORT); - return new PackList(lastRead, lastModified, r); - } - - private static Map reuseMap(final PackList old) { - final Map forReuse = new HashMap(); - for (final PackFile p : old.packs) { - if (p.invalid()) { - // The pack instance is corrupted, and cannot be safely used - // again. Do not include it in our reuse map. - // - p.close(); - continue; - } - - final PackFile prior = forReuse.put(p.getPackFile().getName(), p); - if (prior != null) { - // This should never occur. It should be impossible for us - // to have two pack files with the same name, as all of them - // came out of the same directory. If it does, we promised to - // close any PackFiles we did not reuse, so close the second, - // readers are likely to be actively using the first. - // - forReuse.put(prior.getPackFile().getName(), prior); - p.close(); - } - } - return forReuse; - } - - private Set listPackDirectory() { - final String[] nameList = packDirectory.list(); - if (nameList == null) - return Collections.emptySet(); - final Set nameSet = new HashSet(nameList.length << 1); - for (final String name : nameList) { - if (name.startsWith("pack-")) - nameSet.add(name); - } - return nameSet; - } - - AlternateHandle[] myAlternates() { - AlternateHandle[] alt = alternates.get(); - if (alt == null) { - synchronized (alternates) { - alt = alternates.get(); - if (alt == null) { - try { - alt = loadAlternates(); - } catch (IOException e) { - alt = new AlternateHandle[0]; - } - alternates.set(alt); - } - } - } - return alt; - } - - private AlternateHandle[] loadAlternates() throws IOException { - final List l = new ArrayList(4); - final BufferedReader br = open(alternatesFile); - try { - String line; - while ((line = br.readLine()) != null) { - l.add(openAlternate(line)); - } - } finally { - br.close(); - } - return l.toArray(new AlternateHandle[l.size()]); - } - - private static BufferedReader open(final File f) - throws FileNotFoundException { - return new BufferedReader(new FileReader(f)); - } - - private AlternateHandle openAlternate(final String location) - throws IOException { - final File objdir = fs.resolve(objects, location); - return openAlternate(objdir); - } - - private AlternateHandle openAlternate(File objdir) throws IOException { - final File parent = objdir.getParentFile(); - if (FileKey.isGitRepository(parent, fs)) { - FileKey key = FileKey.exact(parent, fs); - FileRepository db = (FileRepository) RepositoryCache.open(key); - return new AlternateRepository(db); - } - - ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs); - return new AlternateHandle(db); - } - - private static final class PackList { - /** Last wall-clock time the directory was read. */ - volatile long lastRead; - - /** Last modification time of {@link ObjectDirectory#packDirectory}. */ - final long lastModified; - - /** All known packs, sorted by {@link PackFile#SORT}. */ - final PackFile[] packs; - - private boolean cannotBeRacilyClean; - - PackList(final long lastRead, final long lastModified, - final PackFile[] packs) { - this.lastRead = lastRead; - this.lastModified = lastModified; - this.packs = packs; - this.cannotBeRacilyClean = notRacyClean(lastRead); - } - - private boolean notRacyClean(final long read) { - return read - lastModified > 2 * 60 * 1000L; - } - - PackList updateLastRead(final long now) { - if (notRacyClean(now)) - cannotBeRacilyClean = true; - lastRead = now; - return this; - } - - boolean tryAgain(final long currLastModified) { - // Any difference indicates the directory was modified. - // - if (lastModified != currLastModified) - return true; - - // We have already determined the last read was far enough - // after the last modification that any new modifications - // are certain to change the last modified time. - // - if (cannotBeRacilyClean) - return false; - - if (notRacyClean(lastRead)) { - // Our last read should have marked cannotBeRacilyClean, - // but this thread may not have seen the change. The read - // of the volatile field lastRead should have fixed that. - // - return false; - } - - // We last read this directory too close to its last observed - // modification time. We may have missed a modification. Scan - // the directory again, to ensure we still see the same state. - // - return true; - } - } - - @Override - public ObjectDatabase newCachedDatabase() { - return newCachedFileObjectDatabase(); - } - - FileObjectDatabase newCachedFileObjectDatabase() { - return new CachedObjectDirectory(this); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java deleted file mode 100644 index 146d2d6250..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2008, Shawn O. Pearce - * Copyright (C) 2009, 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.lib; - -import java.io.EOFException; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.DigestOutputStream; -import java.security.MessageDigest; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; - -import org.eclipse.jgit.errors.ObjectWritingException; - -/** Creates loose objects in a {@link ObjectDirectory}. */ -class ObjectDirectoryInserter extends ObjectInserter { - private final ObjectDirectory db; - - private final Config config; - - private Deflater deflate; - - ObjectDirectoryInserter(final ObjectDirectory dest, final Config cfg) { - db = dest; - config = cfg; - } - - @Override - public ObjectId insert(final int type, long len, final InputStream is) - throws IOException { - final MessageDigest md = digest(); - final File tmp = toTemp(md, type, len, is); - final ObjectId id = ObjectId.fromRaw(md.digest()); - if (db.hasObject(id)) { - // Object is already in the repository, remove temporary file. - // - tmp.delete(); - return id; - } - - final File dst = db.fileFor(id); - if (tmp.renameTo(dst)) - return id; - - // Maybe the directory doesn't exist yet as the object - // directories are always lazily created. Note that we - // try the rename first as the directory likely does exist. - // - dst.getParentFile().mkdir(); - if (tmp.renameTo(dst)) - return id; - - if (db.hasObject(id)) { - tmp.delete(); - return id; - } - - // The object failed to be renamed into its proper - // location and it doesn't exist in the repository - // either. We really don't know what went wrong, so - // fail. - // - tmp.delete(); - throw new ObjectWritingException("Unable to create new object: " + dst); - } - - @Override - public void flush() throws IOException { - // Do nothing. Objects are immediately visible. - } - - @Override - public void release() { - if (deflate != null) { - try { - deflate.end(); - } finally { - deflate = null; - } - } - } - - private File toTemp(final MessageDigest md, final int type, long len, - final InputStream is) throws IOException, FileNotFoundException, - Error { - boolean delete = true; - File tmp = File.createTempFile("noz", null, db.getDirectory()); - try { - DigestOutputStream dOut = new DigestOutputStream( - compress(new FileOutputStream(tmp)), md); - try { - dOut.write(Constants.encodedTypeString(type)); - dOut.write((byte) ' '); - dOut.write(Constants.encodeASCII(len)); - dOut.write((byte) 0); - - final byte[] buf = buffer(); - while (len > 0) { - int n = is.read(buf, 0, (int) Math.min(len, buf.length)); - if (n <= 0) - throw shortInput(len); - dOut.write(buf, 0, n); - len -= n; - } - } finally { - dOut.close(); - } - - tmp.setReadOnly(); - delete = false; - return tmp; - } finally { - if (delete) - tmp.delete(); - } - } - - private DeflaterOutputStream compress(final OutputStream out) { - if (deflate == null) - deflate = new Deflater(config.get(CoreConfig.KEY).getCompression()); - else - deflate.reset(); - return new DeflaterOutputStream(out, deflate); - } - - private static EOFException shortInput(long missing) { - return new EOFException("Input did not match supplied length. " - + missing + " bytes are missing."); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java deleted file mode 100644 index ab25f61889..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java +++ /dev/null @@ -1,695 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.EOFException; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel.MapMode; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.zip.CRC32; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.errors.PackInvalidException; -import org.eclipse.jgit.errors.PackMismatchException; -import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; -import org.eclipse.jgit.util.LongList; -import org.eclipse.jgit.util.NB; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * A Git version 2 pack file representation. A pack file contains Git objects in - * delta packed format yielding high compression of lots of object where some - * objects are similar. - */ -public class PackFile implements Iterable { - /** Sorts PackFiles to be most recently created to least recently created. */ - public static Comparator SORT = new Comparator() { - public int compare(final PackFile a, final PackFile b) { - return b.packLastModified - a.packLastModified; - } - }; - - private final File idxFile; - - private final File packFile; - - final int hash; - - private RandomAccessFile fd; - - /** Serializes reads performed against {@link #fd}. */ - private final Object readLock = new Object(); - - long length; - - private int activeWindows; - - private int activeCopyRawData; - - private int packLastModified; - - private volatile boolean invalid; - - private byte[] packChecksum; - - private PackIndex loadedIdx; - - private PackReverseIndex reverseIdx; - - /** - * Objects we have tried to read, and discovered to be corrupt. - *

- * The list is allocated after the first corruption is found, and filled in - * as more entries are discovered. Typically this list is never used, as - * pack files do not usually contain corrupt objects. - */ - private volatile LongList corruptObjects; - - /** - * Construct a reader for an existing, pre-indexed packfile. - * - * @param idxFile - * path of the .idx file listing the contents. - * @param packFile - * path of the .pack file holding the data. - */ - public PackFile(final File idxFile, final File packFile) { - this.idxFile = idxFile; - this.packFile = packFile; - this.packLastModified = (int) (packFile.lastModified() >> 10); - - // Multiply by 31 here so we can more directly combine with another - // value in WindowCache.hash(), without doing the multiply there. - // - hash = System.identityHashCode(this) * 31; - length = Long.MAX_VALUE; - } - - private synchronized PackIndex idx() throws IOException { - if (loadedIdx == null) { - if (invalid) - throw new PackInvalidException(packFile); - - try { - final PackIndex idx = PackIndex.open(idxFile); - - if (packChecksum == null) - packChecksum = idx.packChecksum; - else if (!Arrays.equals(packChecksum, idx.packChecksum)) - throw new PackMismatchException(JGitText.get().packChecksumMismatch); - - loadedIdx = idx; - } catch (IOException e) { - invalid = true; - throw e; - } - } - return loadedIdx; - } - - final PackedObjectLoader resolveBase(final WindowCursor curs, final long ofs) - throws IOException { - if (isCorrupt(ofs)) { - throw new CorruptObjectException(MessageFormat.format(JGitText - .get().objectAtHasBadZlibStream, ofs, getPackFile())); - } - return reader(curs, ofs); - } - - /** @return the File object which locates this pack on disk. */ - public File getPackFile() { - return packFile; - } - - /** - * Determine if an object is contained within the pack file. - *

- * For performance reasons only the index file is searched; the main pack - * content is ignored entirely. - *

- * - * @param id - * the object to look for. Must not be null. - * @return true if the object is in this pack; false otherwise. - * @throws IOException - * the index file cannot be loaded into memory. - */ - public boolean hasObject(final AnyObjectId id) throws IOException { - final long offset = idx().findOffset(id); - return 0 < offset && !isCorrupt(offset); - } - - /** - * Get an object from this pack. - * - * @param curs - * temporary working space associated with the calling thread. - * @param id - * the object to obtain from the pack. Must not be null. - * @return the object loader for the requested object if it is contained in - * this pack; null if the object was not found. - * @throws IOException - * the pack file or the index could not be read. - */ - public PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id) - throws IOException { - final long offset = idx().findOffset(id); - return 0 < offset && !isCorrupt(offset) ? reader(curs, offset) : null; - } - - /** - * Close the resources utilized by this repository - */ - public void close() { - UnpackedObjectCache.purge(this); - WindowCache.purge(this); - synchronized (this) { - loadedIdx = null; - reverseIdx = null; - } - } - - /** - * Provide iterator over entries in associated pack index, that should also - * exist in this pack file. Objects returned by such iterator are mutable - * during iteration. - *

- * Iterator returns objects in SHA-1 lexicographical order. - *

- * - * @return iterator over entries of associated pack index - * - * @see PackIndex#iterator() - */ - public Iterator iterator() { - try { - return idx().iterator(); - } catch (IOException e) { - return Collections. emptyList().iterator(); - } - } - - /** - * Obtain the total number of objects available in this pack. This method - * relies on pack index, giving number of effectively available objects. - * - * @return number of objects in index of this pack, likewise in this pack - * @throws IOException - * the index file cannot be loaded into memory. - */ - long getObjectCount() throws IOException { - return idx().getObjectCount(); - } - - /** - * Search for object id with the specified start offset in associated pack - * (reverse) index. - * - * @param offset - * start offset of object to find - * @return object id for this offset, or null if no object was found - * @throws IOException - * the index file cannot be loaded into memory. - */ - ObjectId findObjectForOffset(final long offset) throws IOException { - return getReverseIdx().findObject(offset); - } - - final UnpackedObjectCache.Entry readCache(final long position) { - return UnpackedObjectCache.get(this, position); - } - - final void saveCache(final long position, final byte[] data, final int type) { - UnpackedObjectCache.store(this, position, data, type); - } - - final byte[] decompress(final long position, final int totalSize, - final WindowCursor curs) throws DataFormatException, IOException { - final byte[] dstbuf = new byte[totalSize]; - if (curs.inflate(this, position, dstbuf, 0) != totalSize) - throw new EOFException(MessageFormat.format(JGitText.get().shortCompressedStreamAt, position)); - return dstbuf; - } - - final void copyAsIs(PackOutputStream out, LocalObjectToPack src, - WindowCursor curs) throws IOException, - StoredObjectRepresentationNotAvailableException { - beginCopyAsIs(src); - try { - copyAsIs2(out, src, curs); - } finally { - endCopyAsIs(); - } - } - - private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, - WindowCursor curs) throws IOException, - StoredObjectRepresentationNotAvailableException { - final CRC32 crc1 = new CRC32(); - final CRC32 crc2 = new CRC32(); - final byte[] buf = out.getCopyBuffer(); - - // Rip apart the header so we can discover the size. - // - readFully(src.copyOffset, buf, 0, 20, curs); - int c = buf[0] & 0xff; - final int typeCode = (c >> 4) & 7; - long inflatedLength = c & 15; - int shift = 4; - int headerCnt = 1; - while ((c & 0x80) != 0) { - c = buf[headerCnt++] & 0xff; - inflatedLength += (c & 0x7f) << shift; - shift += 7; - } - - if (typeCode == Constants.OBJ_OFS_DELTA) { - do { - c = buf[headerCnt++] & 0xff; - } while ((c & 128) != 0); - crc1.update(buf, 0, headerCnt); - crc2.update(buf, 0, headerCnt); - } else if (typeCode == Constants.OBJ_REF_DELTA) { - crc1.update(buf, 0, headerCnt); - crc2.update(buf, 0, headerCnt); - - readFully(src.copyOffset + headerCnt, buf, 0, 20, curs); - crc1.update(buf, 0, 20); - crc2.update(buf, 0, headerCnt); - headerCnt += 20; - } else { - crc1.update(buf, 0, headerCnt); - crc2.update(buf, 0, headerCnt); - } - - final long dataOffset = src.copyOffset + headerCnt; - final long dataLength; - final long expectedCRC; - final ByteArrayWindow quickCopy; - - // Verify the object isn't corrupt before sending. If it is, - // we report it missing instead. - // - try { - dataLength = findEndOffset(src.copyOffset) - dataOffset; - quickCopy = curs.quickCopy(this, dataOffset, dataLength); - - if (idx().hasCRC32Support()) { - // Index has the CRC32 code cached, validate the object. - // - expectedCRC = idx().findCRC32(src); - if (quickCopy != null) { - quickCopy.crc32(crc1, dataOffset, (int) dataLength); - } else { - long pos = dataOffset; - long cnt = dataLength; - while (cnt > 0) { - final int n = (int) Math.min(cnt, buf.length); - readFully(pos, buf, 0, n, curs); - crc1.update(buf, 0, n); - pos += n; - cnt -= n; - } - } - if (crc1.getValue() != expectedCRC) { - setCorrupt(src.copyOffset); - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, - src.copyOffset, getPackFile())); - } - } else { - // We don't have a CRC32 code in the index, so compute it - // now while inflating the raw data to get zlib to tell us - // whether or not the data is safe. - // - Inflater inf = curs.inflater(); - byte[] tmp = new byte[1024]; - if (quickCopy != null) { - quickCopy.check(inf, tmp, dataOffset, (int) dataLength); - } else { - long pos = dataOffset; - long cnt = dataLength; - while (cnt > 0) { - final int n = (int) Math.min(cnt, buf.length); - readFully(pos, buf, 0, n, curs); - crc1.update(buf, 0, n); - inf.setInput(buf, 0, n); - while (inf.inflate(tmp, 0, tmp.length) > 0) - continue; - pos += n; - cnt -= n; - } - } - if (!inf.finished() || inf.getBytesRead() != dataLength) { - setCorrupt(src.copyOffset); - throw new EOFException(MessageFormat.format( - JGitText.get().shortCompressedStreamAt, - src.copyOffset)); - } - expectedCRC = crc1.getValue(); - } - } catch (DataFormatException dataFormat) { - setCorrupt(src.copyOffset); - - CorruptObjectException corruptObject = new CorruptObjectException( - MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, - src.copyOffset, getPackFile())); - corruptObject.initCause(dataFormat); - - StoredObjectRepresentationNotAvailableException gone; - gone = new StoredObjectRepresentationNotAvailableException(src); - gone.initCause(corruptObject); - throw gone; - - } catch (IOException ioError) { - StoredObjectRepresentationNotAvailableException gone; - gone = new StoredObjectRepresentationNotAvailableException(src); - gone.initCause(ioError); - throw gone; - } - - if (quickCopy != null) { - // The entire object fits into a single byte array window slice, - // and we have it pinned. Write this out without copying. - // - out.writeHeader(src, inflatedLength); - quickCopy.write(out, dataOffset, (int) dataLength); - - } else if (dataLength <= buf.length) { - // Tiny optimization: Lots of objects are very small deltas or - // deflated commits that are likely to fit in the copy buffer. - // - out.writeHeader(src, inflatedLength); - out.write(buf, 0, (int) dataLength); - } else { - // Now we are committed to sending the object. As we spool it out, - // check its CRC32 code to make sure there wasn't corruption between - // the verification we did above, and us actually outputting it. - // - out.writeHeader(src, inflatedLength); - long pos = dataOffset; - long cnt = dataLength; - while (cnt > 0) { - final int n = (int) Math.min(cnt, buf.length); - readFully(pos, buf, 0, n, curs); - crc2.update(buf, 0, n); - out.write(buf, 0, n); - pos += n; - cnt -= n; - } - if (crc2.getValue() != expectedCRC) { - throw new CorruptObjectException(MessageFormat.format(JGitText - .get().objectAtHasBadZlibStream, src.copyOffset, - getPackFile())); - } - } - } - - boolean invalid() { - return invalid; - } - - private void readFully(final long position, final byte[] dstbuf, - int dstoff, final int cnt, final WindowCursor curs) - throws IOException { - if (curs.copy(this, position, dstbuf, dstoff, cnt) != cnt) - throw new EOFException(); - } - - private synchronized void beginCopyAsIs(ObjectToPack otp) - throws StoredObjectRepresentationNotAvailableException { - if (++activeCopyRawData == 1 && activeWindows == 0) { - try { - doOpen(); - } catch (IOException thisPackNotValid) { - StoredObjectRepresentationNotAvailableException gone; - - gone = new StoredObjectRepresentationNotAvailableException(otp); - gone.initCause(thisPackNotValid); - throw gone; - } - } - } - - private synchronized void endCopyAsIs() { - if (--activeCopyRawData == 0 && activeWindows == 0) - doClose(); - } - - synchronized boolean beginWindowCache() throws IOException { - if (++activeWindows == 1) { - if (activeCopyRawData == 0) - doOpen(); - return true; - } - return false; - } - - synchronized boolean endWindowCache() { - final boolean r = --activeWindows == 0; - if (r && activeCopyRawData == 0) - doClose(); - return r; - } - - private void doOpen() throws IOException { - try { - if (invalid) - throw new PackInvalidException(packFile); - synchronized (readLock) { - fd = new RandomAccessFile(packFile, "r"); - length = fd.length(); - onOpenPack(); - } - } catch (IOException ioe) { - openFail(); - throw ioe; - } catch (RuntimeException re) { - openFail(); - throw re; - } catch (Error re) { - openFail(); - throw re; - } - } - - private void openFail() { - activeWindows = 0; - activeCopyRawData = 0; - invalid = true; - doClose(); - } - - private void doClose() { - synchronized (readLock) { - if (fd != null) { - try { - fd.close(); - } catch (IOException err) { - // Ignore a close event. We had it open only for reading. - // There should not be errors related to network buffers - // not flushed, etc. - } - fd = null; - } - } - } - - ByteArrayWindow read(final long pos, int size) throws IOException { - synchronized (readLock) { - if (length < pos + size) - size = (int) (length - pos); - final byte[] buf = new byte[size]; - fd.seek(pos); - fd.readFully(buf, 0, size); - return new ByteArrayWindow(this, pos, buf); - } - } - - ByteWindow mmap(final long pos, int size) throws IOException { - synchronized (readLock) { - if (length < pos + size) - size = (int) (length - pos); - - MappedByteBuffer map; - try { - map = fd.getChannel().map(MapMode.READ_ONLY, pos, size); - } catch (IOException ioe1) { - // The most likely reason this failed is the JVM has run out - // of virtual memory. We need to discard quickly, and try to - // force the GC to finalize and release any existing mappings. - // - System.gc(); - System.runFinalization(); - map = fd.getChannel().map(MapMode.READ_ONLY, pos, size); - } - - if (map.hasArray()) - return new ByteArrayWindow(this, pos, map.array()); - return new ByteBufferWindow(this, pos, map); - } - } - - private void onOpenPack() throws IOException { - final PackIndex idx = idx(); - final byte[] buf = new byte[20]; - - fd.seek(0); - fd.readFully(buf, 0, 12); - if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) - throw new IOException(JGitText.get().notAPACKFile); - final long vers = NB.decodeUInt32(buf, 4); - final long packCnt = NB.decodeUInt32(buf, 8); - if (vers != 2 && vers != 3) - throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackVersion, vers)); - - if (packCnt != idx.getObjectCount()) - throw new PackMismatchException(MessageFormat.format( - JGitText.get().packObjectCountMismatch, packCnt, idx.getObjectCount(), getPackFile())); - - fd.seek(length - 20); - fd.read(buf, 0, 20); - if (!Arrays.equals(buf, packChecksum)) - throw new PackMismatchException(MessageFormat.format( - JGitText.get().packObjectCountMismatch - , ObjectId.fromRaw(buf).name() - , ObjectId.fromRaw(idx.packChecksum).name() - , getPackFile())); - } - - private PackedObjectLoader reader(final WindowCursor curs, - final long objOffset) throws IOException { - int p = 0; - final byte[] ib = curs.tempId; - readFully(objOffset, ib, 0, 20, curs); - int c = ib[p++] & 0xff; - final int typeCode = (c >> 4) & 7; - long dataSize = c & 15; - int shift = 4; - while ((c & 0x80) != 0) { - c = ib[p++] & 0xff; - dataSize += (c & 0x7f) << shift; - shift += 7; - } - - switch (typeCode) { - case Constants.OBJ_COMMIT: - case Constants.OBJ_TREE: - case Constants.OBJ_BLOB: - case Constants.OBJ_TAG: - return new WholePackedObjectLoader(this, objOffset, p, typeCode, - (int) dataSize); - - case Constants.OBJ_OFS_DELTA: { - c = ib[p++] & 0xff; - long ofs = c & 127; - while ((c & 128) != 0) { - ofs += 1; - c = ib[p++] & 0xff; - ofs <<= 7; - ofs += (c & 127); - } - return new DeltaOfsPackedObjectLoader(this, objOffset, p, - (int) dataSize, objOffset - ofs); - } - case Constants.OBJ_REF_DELTA: { - readFully(objOffset + p, ib, 0, 20, curs); - return new DeltaRefPackedObjectLoader(this, objOffset, p + 20, - (int) dataSize, ObjectId.fromRaw(ib)); - } - default: - throw new IOException(MessageFormat.format(JGitText.get().unknownObjectType, typeCode)); - } - } - - private long findEndOffset(final long startOffset) - throws IOException, CorruptObjectException { - final long maxOffset = length - 20; - return getReverseIdx().findNextOffset(startOffset, maxOffset); - } - - private synchronized PackReverseIndex getReverseIdx() throws IOException { - if (reverseIdx == null) - reverseIdx = new PackReverseIndex(idx()); - return reverseIdx; - } - - private boolean isCorrupt(long offset) { - LongList list = corruptObjects; - if (list == null) - return false; - synchronized (list) { - return list.contains(offset); - } - } - - private void setCorrupt(long offset) { - LongList list = corruptObjects; - if (list == null) { - synchronized (readLock) { - list = corruptObjects; - if (list == null) { - list = new LongList(); - corruptObjects = list; - } - } - } - synchronized (list) { - list.add(offset); - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java deleted file mode 100644 index 13985e78e9..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.Iterator; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.NB; - -/** - * Access path to locate objects by {@link ObjectId} in a {@link PackFile}. - *

- * Indexes are strictly redundant information in that we can rebuild all of the - * data held in the index file from the on disk representation of the pack file - * itself, but it is faster to access for random requests because data is stored - * by ObjectId. - *

- */ -public abstract class PackIndex implements Iterable { - /** - * Open an existing pack .idx file for reading. - *

- * The format of the file will be automatically detected and a proper access - * implementation for that format will be constructed and returned to the - * caller. The file may or may not be held open by the returned instance. - *

- * - * @param idxFile - * existing pack .idx to read. - * @return access implementation for the requested file. - * @throws FileNotFoundException - * the file does not exist. - * @throws IOException - * the file exists but could not be read due to security errors, - * unrecognized data version, or unexpected data corruption. - */ - public static PackIndex open(final File idxFile) throws IOException { - final FileInputStream fd = new FileInputStream(idxFile); - try { - final byte[] hdr = new byte[8]; - IO.readFully(fd, hdr, 0, hdr.length); - if (isTOC(hdr)) { - final int v = NB.decodeInt32(hdr, 4); - switch (v) { - case 2: - return new PackIndexV2(fd); - default: - throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackIndexVersion, v)); - } - } - return new PackIndexV1(fd, hdr); - } catch (IOException ioe) { - final String path = idxFile.getAbsolutePath(); - final IOException err; - err = new IOException(MessageFormat.format(JGitText.get().unreadablePackIndex, path)); - err.initCause(ioe); - throw err; - } finally { - try { - fd.close(); - } catch (IOException err2) { - // ignore - } - } - } - - private static boolean isTOC(final byte[] h) { - final byte[] toc = PackIndexWriter.TOC; - for (int i = 0; i < toc.length; i++) - if (h[i] != toc[i]) - return false; - return true; - } - - /** Footer checksum applied on the bottom of the pack file. */ - protected byte[] packChecksum; - - /** - * Determine if an object is contained within the pack file. - * - * @param id - * the object to look for. Must not be null. - * @return true if the object is listed in this index; false otherwise. - */ - public boolean hasObject(final AnyObjectId id) { - return findOffset(id) != -1; - } - - /** - * Provide iterator that gives access to index entries. Note, that iterator - * returns reference to mutable object, the same reference in each call - - * for performance reason. If client needs immutable objects, it must copy - * returned object on its own. - *

- * Iterator returns objects in SHA-1 lexicographical order. - *

- * - * @return iterator over pack index entries - */ - public abstract Iterator iterator(); - - /** - * Obtain the total number of objects described by this index. - * - * @return number of objects in this index, and likewise in the associated - * pack that this index was generated from. - */ - abstract long getObjectCount(); - - /** - * Obtain the total number of objects needing 64 bit offsets. - * - * @return number of objects in this index using a 64 bit offset; that is an - * object positioned after the 2 GB position within the file. - */ - abstract long getOffset64Count(); - - /** - * Get ObjectId for the n-th object entry returned by {@link #iterator()}. - *

- * This method is a constant-time replacement for the following loop: - * - *

-	 * Iterator<MutableEntry> eItr = index.iterator();
-	 * int curPosition = 0;
-	 * while (eItr.hasNext() && curPosition++ < nthPosition)
-	 * 	eItr.next();
-	 * ObjectId result = eItr.next().toObjectId();
-	 * 
- * - * @param nthPosition - * position within the traversal of {@link #iterator()} that the - * caller needs the object for. The first returned - * {@link MutableEntry} is 0, the second is 1, etc. - * @return the ObjectId for the corresponding entry. - */ - abstract ObjectId getObjectId(long nthPosition); - - /** - * Get ObjectId for the n-th object entry returned by {@link #iterator()}. - *

- * This method is a constant-time replacement for the following loop: - * - *

-	 * Iterator<MutableEntry> eItr = index.iterator();
-	 * int curPosition = 0;
-	 * while (eItr.hasNext() && curPosition++ < nthPosition)
-	 * 	eItr.next();
-	 * ObjectId result = eItr.next().toObjectId();
-	 * 
- * - * @param nthPosition - * unsigned 32 bit position within the traversal of - * {@link #iterator()} that the caller needs the object for. The - * first returned {@link MutableEntry} is 0, the second is 1, - * etc. Positions past 2**31-1 are negative, but still valid. - * @return the ObjectId for the corresponding entry. - */ - final ObjectId getObjectId(final int nthPosition) { - if (nthPosition >= 0) - return getObjectId((long) nthPosition); - final int u31 = nthPosition >>> 1; - final int one = nthPosition & 1; - return getObjectId(((long) u31) << 1 | one); - } - - /** - * Locate the file offset position for the requested object. - * - * @param objId - * name of the object to locate within the pack. - * @return offset of the object's header and compressed content; -1 if the - * object does not exist in this index and is thus not stored in the - * associated pack. - */ - abstract long findOffset(AnyObjectId objId); - - /** - * Retrieve stored CRC32 checksum of the requested object raw-data - * (including header). - * - * @param objId - * id of object to look for - * @return CRC32 checksum of specified object (at 32 less significant bits) - * @throws MissingObjectException - * when requested ObjectId was not found in this index - * @throws UnsupportedOperationException - * when this index doesn't support CRC32 checksum - */ - abstract long findCRC32(AnyObjectId objId) throws MissingObjectException, - UnsupportedOperationException; - - /** - * Check whether this index supports (has) CRC32 checksums for objects. - * - * @return true if CRC32 is stored, false otherwise - */ - abstract boolean hasCRC32Support(); - - /** - * Represent mutable entry of pack index consisting of object id and offset - * in pack (both mutable). - * - */ - public static class MutableEntry { - final MutableObjectId idBuffer = new MutableObjectId(); - - long offset; - - /** - * Returns offset for this index object entry - * - * @return offset of this object in a pack file - */ - public long getOffset() { - return offset; - } - - /** @return hex string describing the object id of this entry. */ - public String name() { - ensureId(); - return idBuffer.name(); - } - - /** @return a copy of the object id. */ - public ObjectId toObjectId() { - ensureId(); - return idBuffer.toObjectId(); - } - - /** @return a complete copy of this entry, that won't modify */ - public MutableEntry cloneEntry() { - final MutableEntry r = new MutableEntry(); - ensureId(); - r.idBuffer.fromObjectId(idBuffer); - r.offset = offset; - return r; - } - - void ensureId() { - // Override in implementations. - } - } - - abstract class EntriesIterator implements Iterator { - protected final MutableEntry entry = initEntry(); - - protected long returnedNumber = 0; - - protected abstract MutableEntry initEntry(); - - public boolean hasNext() { - return returnedNumber < getObjectCount(); - } - - /** - * Implementation must update {@link #returnedNumber} before returning - * element. - */ - public abstract MutableEntry next(); - - public void remove() { - throw new UnsupportedOperationException(); - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java deleted file mode 100644 index bb7cd8b53c..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007-2009, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.NB; - -class PackIndexV1 extends PackIndex { - private static final int IDX_HDR_LEN = 256 * 4; - - private final long[] idxHeader; - - private byte[][] idxdata; - - private long objectCnt; - - PackIndexV1(final InputStream fd, final byte[] hdr) - throws CorruptObjectException, IOException { - final byte[] fanoutTable = new byte[IDX_HDR_LEN]; - System.arraycopy(hdr, 0, fanoutTable, 0, hdr.length); - IO.readFully(fd, fanoutTable, hdr.length, IDX_HDR_LEN - hdr.length); - - idxHeader = new long[256]; // really unsigned 32-bit... - for (int k = 0; k < idxHeader.length; k++) - idxHeader[k] = NB.decodeUInt32(fanoutTable, k * 4); - idxdata = new byte[idxHeader.length][]; - for (int k = 0; k < idxHeader.length; k++) { - int n; - if (k == 0) { - n = (int) (idxHeader[k]); - } else { - n = (int) (idxHeader[k] - idxHeader[k - 1]); - } - if (n > 0) { - idxdata[k] = new byte[n * (Constants.OBJECT_ID_LENGTH + 4)]; - IO.readFully(fd, idxdata[k], 0, idxdata[k].length); - } - } - objectCnt = idxHeader[255]; - - packChecksum = new byte[20]; - IO.readFully(fd, packChecksum, 0, packChecksum.length); - } - - long getObjectCount() { - return objectCnt; - } - - @Override - long getOffset64Count() { - long n64 = 0; - for (final MutableEntry e : this) { - if (e.getOffset() >= Integer.MAX_VALUE) - n64++; - } - return n64; - } - - @Override - ObjectId getObjectId(final long nthPosition) { - int levelOne = Arrays.binarySearch(idxHeader, nthPosition + 1); - long base; - if (levelOne >= 0) { - // If we hit the bucket exactly the item is in the bucket, or - // any bucket before it which has the same object count. - // - base = idxHeader[levelOne]; - while (levelOne > 0 && base == idxHeader[levelOne - 1]) - levelOne--; - } else { - // The item is in the bucket we would insert it into. - // - levelOne = -(levelOne + 1); - } - - base = levelOne > 0 ? idxHeader[levelOne - 1] : 0; - final int p = (int) (nthPosition - base); - final int dataIdx = ((4 + Constants.OBJECT_ID_LENGTH) * p) + 4; - return ObjectId.fromRaw(idxdata[levelOne], dataIdx); - } - - long findOffset(final AnyObjectId objId) { - final int levelOne = objId.getFirstByte(); - byte[] data = idxdata[levelOne]; - if (data == null) - return -1; - int high = data.length / (4 + Constants.OBJECT_ID_LENGTH); - int low = 0; - do { - final int mid = (low + high) >>> 1; - final int pos = ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4; - final int cmp = objId.compareTo(data, pos); - if (cmp < 0) - high = mid; - else if (cmp == 0) { - int b0 = data[pos - 4] & 0xff; - int b1 = data[pos - 3] & 0xff; - int b2 = data[pos - 2] & 0xff; - int b3 = data[pos - 1] & 0xff; - return (((long) b0) << 24) | (b1 << 16) | (b2 << 8) | (b3); - } else - low = mid + 1; - } while (low < high); - return -1; - } - - @Override - long findCRC32(AnyObjectId objId) { - throw new UnsupportedOperationException(); - } - - @Override - boolean hasCRC32Support() { - return false; - } - - public Iterator iterator() { - return new IndexV1Iterator(); - } - - private class IndexV1Iterator extends EntriesIterator { - private int levelOne; - - private int levelTwo; - - @Override - protected MutableEntry initEntry() { - return new MutableEntry() { - protected void ensureId() { - idBuffer.fromRaw(idxdata[levelOne], levelTwo - - Constants.OBJECT_ID_LENGTH); - } - }; - } - - public MutableEntry next() { - for (; levelOne < idxdata.length; levelOne++) { - if (idxdata[levelOne] == null) - continue; - if (levelTwo < idxdata[levelOne].length) { - entry.offset = NB.decodeUInt32(idxdata[levelOne], levelTwo); - levelTwo += Constants.OBJECT_ID_LENGTH + 4; - returnedNumber++; - return entry; - } - levelTwo = 0; - } - throw new NoSuchElementException(); - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java deleted file mode 100644 index 128b2df8cb..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.NB; - -/** Support for the pack index v2 format. */ -class PackIndexV2 extends PackIndex { - private static final long IS_O64 = 1L << 31; - - private static final int FANOUT = 256; - - private static final int[] NO_INTS = {}; - - private static final byte[] NO_BYTES = {}; - - private long objectCnt; - - private final long[] fanoutTable; - - /** 256 arrays of contiguous object names. */ - private int[][] names; - - /** 256 arrays of the 32 bit offset data, matching {@link #names}. */ - private byte[][] offset32; - - /** 256 arrays of the CRC-32 of objects, matching {@link #names}. */ - private byte[][] crc32; - - /** 64 bit offset table. */ - private byte[] offset64; - - PackIndexV2(final InputStream fd) throws IOException { - final byte[] fanoutRaw = new byte[4 * FANOUT]; - IO.readFully(fd, fanoutRaw, 0, fanoutRaw.length); - fanoutTable = new long[FANOUT]; - for (int k = 0; k < FANOUT; k++) - fanoutTable[k] = NB.decodeUInt32(fanoutRaw, k * 4); - objectCnt = fanoutTable[FANOUT - 1]; - - names = new int[FANOUT][]; - offset32 = new byte[FANOUT][]; - crc32 = new byte[FANOUT][]; - - // Object name table. The size we can permit per fan-out bucket - // is limited to Java's 2 GB per byte array limitation. That is - // no more than 107,374,182 objects per fan-out. - // - for (int k = 0; k < FANOUT; k++) { - final long bucketCnt; - if (k == 0) - bucketCnt = fanoutTable[k]; - else - bucketCnt = fanoutTable[k] - fanoutTable[k - 1]; - - if (bucketCnt == 0) { - names[k] = NO_INTS; - offset32[k] = NO_BYTES; - crc32[k] = NO_BYTES; - continue; - } - - final long nameLen = bucketCnt * Constants.OBJECT_ID_LENGTH; - if (nameLen > Integer.MAX_VALUE) - throw new IOException(JGitText.get().indexFileIsTooLargeForJgit); - - final int intNameLen = (int) nameLen; - final byte[] raw = new byte[intNameLen]; - final int[] bin = new int[intNameLen >>> 2]; - IO.readFully(fd, raw, 0, raw.length); - for (int i = 0; i < bin.length; i++) - bin[i] = NB.decodeInt32(raw, i << 2); - - names[k] = bin; - offset32[k] = new byte[(int) (bucketCnt * 4)]; - crc32[k] = new byte[(int) (bucketCnt * 4)]; - } - - // CRC32 table. - for (int k = 0; k < FANOUT; k++) - IO.readFully(fd, crc32[k], 0, crc32[k].length); - - // 32 bit offset table. Any entries with the most significant bit - // set require a 64 bit offset entry in another table. - // - int o64cnt = 0; - for (int k = 0; k < FANOUT; k++) { - final byte[] ofs = offset32[k]; - IO.readFully(fd, ofs, 0, ofs.length); - for (int p = 0; p < ofs.length; p += 4) - if (ofs[p] < 0) - o64cnt++; - } - - // 64 bit offset table. Most objects should not require an entry. - // - if (o64cnt > 0) { - offset64 = new byte[o64cnt * 8]; - IO.readFully(fd, offset64, 0, offset64.length); - } else { - offset64 = NO_BYTES; - } - - packChecksum = new byte[20]; - IO.readFully(fd, packChecksum, 0, packChecksum.length); - } - - @Override - long getObjectCount() { - return objectCnt; - } - - @Override - long getOffset64Count() { - return offset64.length / 8; - } - - @Override - ObjectId getObjectId(final long nthPosition) { - int levelOne = Arrays.binarySearch(fanoutTable, nthPosition + 1); - long base; - if (levelOne >= 0) { - // If we hit the bucket exactly the item is in the bucket, or - // any bucket before it which has the same object count. - // - base = fanoutTable[levelOne]; - while (levelOne > 0 && base == fanoutTable[levelOne - 1]) - levelOne--; - } else { - // The item is in the bucket we would insert it into. - // - levelOne = -(levelOne + 1); - } - - base = levelOne > 0 ? fanoutTable[levelOne - 1] : 0; - final int p = (int) (nthPosition - base); - final int p4 = p << 2; - return ObjectId.fromRaw(names[levelOne], p4 + p); // p * 5 - } - - @Override - long findOffset(final AnyObjectId objId) { - final int levelOne = objId.getFirstByte(); - final int levelTwo = binarySearchLevelTwo(objId, levelOne); - if (levelTwo == -1) - return -1; - final long p = NB.decodeUInt32(offset32[levelOne], levelTwo << 2); - if ((p & IS_O64) != 0) - return NB.decodeUInt64(offset64, (8 * (int) (p & ~IS_O64))); - return p; - } - - @Override - long findCRC32(AnyObjectId objId) throws MissingObjectException { - final int levelOne = objId.getFirstByte(); - final int levelTwo = binarySearchLevelTwo(objId, levelOne); - if (levelTwo == -1) - throw new MissingObjectException(objId.copy(), "unknown"); - return NB.decodeUInt32(crc32[levelOne], levelTwo << 2); - } - - @Override - boolean hasCRC32Support() { - return true; - } - - public Iterator iterator() { - return new EntriesIteratorV2(); - } - - private int binarySearchLevelTwo(final AnyObjectId objId, final int levelOne) { - final int[] data = names[levelOne]; - int high = offset32[levelOne].length >>> 2; - if (high == 0) - return -1; - int low = 0; - do { - final int mid = (low + high) >>> 1; - final int mid4 = mid << 2; - final int cmp; - - cmp = objId.compareTo(data, mid4 + mid); // mid * 5 - if (cmp < 0) - high = mid; - else if (cmp == 0) { - return mid; - } else - low = mid + 1; - } while (low < high); - return -1; - } - - private class EntriesIteratorV2 extends EntriesIterator { - private int levelOne; - - private int levelTwo; - - @Override - protected MutableEntry initEntry() { - return new MutableEntry() { - protected void ensureId() { - idBuffer.fromRaw(names[levelOne], levelTwo - - Constants.OBJECT_ID_LENGTH / 4); - } - }; - } - - public MutableEntry next() { - for (; levelOne < names.length; levelOne++) { - if (levelTwo < names[levelOne].length) { - int idx = levelTwo / (Constants.OBJECT_ID_LENGTH / 4) * 4; - long offset = NB.decodeUInt32(offset32[levelOne], idx); - if ((offset & IS_O64) != 0) { - idx = (8 * (int) (offset & ~IS_O64)); - offset = NB.decodeUInt64(offset64, idx); - } - entry.offset = offset; - - levelTwo += Constants.OBJECT_ID_LENGTH / 4; - returnedNumber++; - return entry; - } - levelTwo = 0; - } - throw new NoSuchElementException(); - } - } - -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java deleted file mode 100644 index 4d2714bc55..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2008, Robin Rosenberg - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.security.DigestOutputStream; -import java.text.MessageFormat; -import java.util.List; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.transport.PackedObjectInfo; -import org.eclipse.jgit.util.NB; - -/** - * Creates a table of contents to support random access by {@link PackFile}. - *

- * Pack index files (the .idx suffix in a pack file pair) - * provides random access to any object in the pack by associating an ObjectId - * to the byte offset within the pack where the object's data can be read. - */ -public abstract class PackIndexWriter { - /** Magic constant indicating post-version 1 format. */ - protected static final byte[] TOC = { -1, 't', 'O', 'c' }; - - /** - * Create a new writer for the oldest (most widely understood) format. - *

- * This method selects an index format that can accurate describe the - * supplied objects and that will be the most compatible format with older - * Git implementations. - *

- * Index version 1 is widely recognized by all Git implementations, but - * index version 2 (and later) is not as well recognized as it was - * introduced more than a year later. Index version 1 can only be used if - * the resulting pack file is under 4 gigabytes in size; packs larger than - * that limit must use index version 2. - * - * @param dst - * the stream the index data will be written to. If not already - * buffered it will be automatically wrapped in a buffered - * stream. Callers are always responsible for closing the stream. - * @param objs - * the objects the caller needs to store in the index. Entries - * will be examined until a format can be conclusively selected. - * @return a new writer to output an index file of the requested format to - * the supplied stream. - * @throws IllegalArgumentException - * no recognized pack index version can support the supplied - * objects. This is likely a bug in the implementation. - */ - @SuppressWarnings("fallthrough") - public static PackIndexWriter createOldestPossible(final OutputStream dst, - final List objs) { - int version = 1; - LOOP: for (final PackedObjectInfo oe : objs) { - switch (version) { - case 1: - if (PackIndexWriterV1.canStore(oe)) - continue; - version = 2; - case 2: - break LOOP; - } - } - return createVersion(dst, version); - } - - /** - * Create a new writer instance for a specific index format version. - * - * @param dst - * the stream the index data will be written to. If not already - * buffered it will be automatically wrapped in a buffered - * stream. Callers are always responsible for closing the stream. - * @param version - * index format version number required by the caller. Exactly - * this formatted version will be written. - * @return a new writer to output an index file of the requested format to - * the supplied stream. - * @throws IllegalArgumentException - * the version requested is not supported by this - * implementation. - */ - public static PackIndexWriter createVersion(final OutputStream dst, - final int version) { - switch (version) { - case 1: - return new PackIndexWriterV1(dst); - case 2: - return new PackIndexWriterV2(dst); - default: - throw new IllegalArgumentException(MessageFormat.format( - JGitText.get().unsupportedPackIndexVersion, version)); - } - } - - /** The index data stream we are responsible for creating. */ - protected final DigestOutputStream out; - - /** A temporary buffer for use during IO to {link #out}. */ - protected final byte[] tmp; - - /** The entries this writer must pack. */ - protected List entries; - - /** SHA-1 checksum for the entire pack data. */ - protected byte[] packChecksum; - - /** - * Create a new writer instance. - * - * @param dst - * the stream this instance outputs to. If not already buffered - * it will be automatically wrapped in a buffered stream. - */ - protected PackIndexWriter(final OutputStream dst) { - out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst - : new BufferedOutputStream(dst), Constants.newMessageDigest()); - tmp = new byte[4 + Constants.OBJECT_ID_LENGTH]; - } - - /** - * Write all object entries to the index stream. - *

- * After writing the stream passed to the factory is flushed but remains - * open. Callers are always responsible for closing the output stream. - * - * @param toStore - * sorted list of objects to store in the index. The caller must - * have previously sorted the list using {@link PackedObjectInfo}'s - * native {@link Comparable} implementation. - * @param packDataChecksum - * checksum signature of the entire pack data content. This is - * traditionally the last 20 bytes of the pack file's own stream. - * @throws IOException - * an error occurred while writing to the output stream, or this - * index format cannot store the object data supplied. - */ - public void write(final List toStore, - final byte[] packDataChecksum) throws IOException { - entries = toStore; - packChecksum = packDataChecksum; - writeImpl(); - out.flush(); - } - - /** - * Writes the index file to {@link #out}. - *

- * Implementations should go something like: - * - *

-	 * writeFanOutTable();
-	 * for (final PackedObjectInfo po : entries)
-	 * 	writeOneEntry(po);
-	 * writeChecksumFooter();
-	 * 
- * - *

- * Where the logic for writeOneEntry is specific to the index - * format in use. Additional headers/footers may be used if necessary and - * the {@link #entries} collection may be iterated over more than once if - * necessary. Implementors therefore have complete control over the data. - * - * @throws IOException - * an error occurred while writing to the output stream, or this - * index format cannot store the object data supplied. - */ - protected abstract void writeImpl() throws IOException; - - /** - * Output the version 2 (and later) TOC header, with version number. - *

- * Post version 1 all index files start with a TOC header that makes the - * file an invalid version 1 file, and then includes the version number. - * This header is necessary to recognize a version 1 from a version 2 - * formatted index. - * - * @param version - * version number of this index format being written. - * @throws IOException - * an error occurred while writing to the output stream. - */ - protected void writeTOC(final int version) throws IOException { - out.write(TOC); - NB.encodeInt32(tmp, 0, version); - out.write(tmp, 0, 4); - } - - /** - * Output the standard 256 entry first-level fan-out table. - *

- * The fan-out table is 4 KB in size, holding 256 32-bit unsigned integer - * counts. Each count represents the number of objects within this index - * whose {@link ObjectId#getFirstByte()} matches the count's position in the - * fan-out table. - * - * @throws IOException - * an error occurred while writing to the output stream. - */ - protected void writeFanOutTable() throws IOException { - final int[] fanout = new int[256]; - for (final PackedObjectInfo po : entries) - fanout[po.getFirstByte() & 0xff]++; - for (int i = 1; i < 256; i++) - fanout[i] += fanout[i - 1]; - for (final int n : fanout) { - NB.encodeInt32(tmp, 0, n); - out.write(tmp, 0, 4); - } - } - - /** - * Output the standard two-checksum index footer. - *

- * The standard footer contains two checksums (20 byte SHA-1 values): - *

    - *
  1. Pack data checksum - taken from the last 20 bytes of the pack file.
  2. - *
  3. Index data checksum - checksum of all index bytes written, including - * the pack data checksum above.
  4. - *
- * - * @throws IOException - * an error occurred while writing to the output stream. - */ - protected void writeChecksumFooter() throws IOException { - out.write(packChecksum); - out.on(false); - out.write(out.getMessageDigest().digest()); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java deleted file mode 100644 index eb44b3a8c7..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2008, Robin Rosenberg - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.io.OutputStream; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.transport.PackedObjectInfo; -import org.eclipse.jgit.util.NB; - -/** - * Creates the version 1 (old style) pack table of contents files. - * - * @see PackIndexWriter - * @see PackIndexV1 - */ -class PackIndexWriterV1 extends PackIndexWriter { - static boolean canStore(final PackedObjectInfo oe) { - // We are limited to 4 GB per pack as offset is 32 bit unsigned int. - // - return oe.getOffset() >>> 1 < Integer.MAX_VALUE; - } - - PackIndexWriterV1(final OutputStream dst) { - super(dst); - } - - @Override - protected void writeImpl() throws IOException { - writeFanOutTable(); - - for (final PackedObjectInfo oe : entries) { - if (!canStore(oe)) - throw new IOException(JGitText.get().packTooLargeForIndexVersion1); - NB.encodeInt32(tmp, 0, (int) oe.getOffset()); - oe.copyRawTo(tmp, 4); - out.write(tmp); - } - - writeChecksumFooter(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java deleted file mode 100644 index b6ac7b89e3..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.io.OutputStream; - -import org.eclipse.jgit.transport.PackedObjectInfo; -import org.eclipse.jgit.util.NB; - -/** - * Creates the version 2 pack table of contents files. - * - * @see PackIndexWriter - * @see PackIndexV2 - */ -class PackIndexWriterV2 extends PackIndexWriter { - PackIndexWriterV2(final OutputStream dst) { - super(dst); - } - - @Override - protected void writeImpl() throws IOException { - writeTOC(2); - writeFanOutTable(); - writeObjectNames(); - writeCRCs(); - writeOffset32(); - writeOffset64(); - writeChecksumFooter(); - } - - private void writeObjectNames() throws IOException { - for (final PackedObjectInfo oe : entries) - oe.copyRawTo(out); - } - - private void writeCRCs() throws IOException { - for (final PackedObjectInfo oe : entries) { - NB.encodeInt32(tmp, 0, oe.getCRC()); - out.write(tmp, 0, 4); - } - } - - private void writeOffset32() throws IOException { - int o64 = 0; - for (final PackedObjectInfo oe : entries) { - final long o = oe.getOffset(); - if (o < Integer.MAX_VALUE) - NB.encodeInt32(tmp, 0, (int) o); - else - NB.encodeInt32(tmp, 0, (1 << 31) | o64++); - out.write(tmp, 0, 4); - } - } - - private void writeOffset64() throws IOException { - for (final PackedObjectInfo oe : entries) { - final long o = oe.getOffset(); - if (o > Integer.MAX_VALUE) { - NB.encodeInt64(tmp, 0, o); - out.write(tmp, 0, 8); - } - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java deleted file mode 100644 index de8e3fa637..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2009, 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.lib; - -import java.io.File; -import java.io.IOException; - -/** Keeps track of a {@link PackFile}'s associated .keep file. */ -public class PackLock { - private final File keepFile; - - /** - * Create a new lock for a pack file. - * - * @param packFile - * location of the pack-*.pack file. - */ - public PackLock(final File packFile) { - final File p = packFile.getParentFile(); - final String n = packFile.getName(); - keepFile = new File(p, n.substring(0, n.length() - 5) + ".keep"); - } - - /** - * Create the pack-*.keep file, with the given message. - * - * @param msg - * message to store in the file. - * @return true if the keep file was successfully written; false otherwise. - * @throws IOException - * the keep file could not be written. - */ - public boolean lock(String msg) throws IOException { - if (msg == null) - return false; - if (!msg.endsWith("\n")) - msg += "\n"; - final LockFile lf = new LockFile(keepFile); - if (!lf.lock()) - return false; - lf.write(Constants.encode(msg)); - return lf.commit(); - } - - /** Remove the .keep file that holds this pack in place. */ - public void unlock() { - keepFile.delete(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java deleted file mode 100644 index f4f57aed43..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2008, Marek Zawirski - * 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.lib; - -import java.text.MessageFormat; -import java.util.Arrays; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.lib.PackIndex.MutableEntry; - -/** - *

- * Reverse index for forward pack index. Provides operations based on offset - * instead of object id. Such offset-based reverse lookups are performed in - * O(log n) time. - *

- * - * @see PackIndex - * @see PackFile - */ -class PackReverseIndex { - /** Index we were created from, and that has our ObjectId data. */ - private final PackIndex index; - - /** - * (offset31, truly) Offsets accommodating in 31 bits. - */ - private final int offsets32[]; - - /** - * Offsets not accommodating in 31 bits. - */ - private final long offsets64[]; - - /** Position of the corresponding {@link #offsets32} in {@link #index}. */ - private final int nth32[]; - - /** Position of the corresponding {@link #offsets64} in {@link #index}. */ - private final int nth64[]; - - /** - * Create reverse index from straight/forward pack index, by indexing all - * its entries. - * - * @param packIndex - * forward index - entries to (reverse) index. - */ - PackReverseIndex(final PackIndex packIndex) { - index = packIndex; - - final long cnt = index.getObjectCount(); - final long n64 = index.getOffset64Count(); - final long n32 = cnt - n64; - if (n32 > Integer.MAX_VALUE || n64 > Integer.MAX_VALUE - || cnt > 0xffffffffL) - throw new IllegalArgumentException( - JGitText.get().hugeIndexesAreNotSupportedByJgitYet); - - offsets32 = new int[(int) n32]; - offsets64 = new long[(int) n64]; - nth32 = new int[offsets32.length]; - nth64 = new int[offsets64.length]; - - int i32 = 0; - int i64 = 0; - for (final MutableEntry me : index) { - final long o = me.getOffset(); - if (o < Integer.MAX_VALUE) - offsets32[i32++] = (int) o; - else - offsets64[i64++] = o; - } - - Arrays.sort(offsets32); - Arrays.sort(offsets64); - - int nth = 0; - for (final MutableEntry me : index) { - final long o = me.getOffset(); - if (o < Integer.MAX_VALUE) - nth32[Arrays.binarySearch(offsets32, (int) o)] = nth++; - else - nth64[Arrays.binarySearch(offsets64, o)] = nth++; - } - } - - /** - * Search for object id with the specified start offset in this pack - * (reverse) index. - * - * @param offset - * start offset of object to find. - * @return object id for this offset, or null if no object was found. - */ - ObjectId findObject(final long offset) { - if (offset <= Integer.MAX_VALUE) { - final int i32 = Arrays.binarySearch(offsets32, (int) offset); - if (i32 < 0) - return null; - return index.getObjectId(nth32[i32]); - } else { - final int i64 = Arrays.binarySearch(offsets64, offset); - if (i64 < 0) - return null; - return index.getObjectId(nth64[i64]); - } - } - - /** - * Search for the next offset to the specified offset in this pack (reverse) - * index. - * - * @param offset - * start offset of previous object (must be valid-existing - * offset). - * @param maxOffset - * maximum offset in a pack (returned when there is no next - * offset). - * @return offset of the next object in a pack or maxOffset if provided - * offset was the last one. - * @throws CorruptObjectException - * when there is no object with the provided offset. - */ - long findNextOffset(final long offset, final long maxOffset) - throws CorruptObjectException { - if (offset <= Integer.MAX_VALUE) { - final int i32 = Arrays.binarySearch(offsets32, (int) offset); - if (i32 < 0) - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset - , offset)); - - if (i32 + 1 == offsets32.length) { - if (offsets64.length > 0) - return offsets64[0]; - return maxOffset; - } - return offsets32[i32 + 1]; - } else { - final int i64 = Arrays.binarySearch(offsets64, offset); - if (i64 < 0) - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset - , offset)); - - if (i64 + 1 == offsets64.length) - return maxOffset; - return offsets64[i64 + 1]; - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java index 80d8fff536..a6f6b25078 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java @@ -66,6 +66,7 @@ import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevSort; +import org.eclipse.jgit.storage.file.PackIndexWriter; /** *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java deleted file mode 100644 index 47f5e67a73..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; - -/** - * Base class for a set of object loader classes for packed objects. - */ -abstract class PackedObjectLoader extends ObjectLoader { - protected final PackFile pack; - - /** Position of the first byte of the object's header. */ - protected final long objectOffset; - - /** Bytes used to express the object header, including delta reference. */ - protected final int headerSize; - - protected int objectType; - - protected int objectSize; - - protected byte[] cachedBytes; - - PackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSize) { - pack = pr; - this.objectOffset = objectOffset; - this.headerSize = headerSize; - } - - /** - * Force this object to be loaded into memory and pinned in this loader. - *

- * Once materialized, subsequent get operations for the following methods - * will always succeed without raising an exception, as all information is - * pinned in memory by this loader instance. - *

    - *
  • {@link #getType()}
  • - *
  • {@link #getSize()}
  • - *
  • {@link #getBytes()}, {@link #getCachedBytes}
  • - *
  • {@link #getRawSize()}
  • - *
  • {@link #getRawType()}
  • - *
- * - * @param curs - * temporary thread storage during data access. - * @throws IOException - * the object cannot be read. - */ - public abstract void materialize(WindowCursor curs) throws IOException; - - public final int getType() { - return objectType; - } - - public final long getSize() { - return objectSize; - } - - @Override - public final byte[] getCachedBytes() { - return cachedBytes; - } - - /** - * @return offset of object header within pack file - */ - public final long getObjectOffset() { - return objectOffset; - } - - /** - * @return id of delta base object for this object representation. null if - * object is not stored as delta. - * @throws IOException - * when delta base cannot read. - */ - public abstract ObjectId getDeltaBase() throws IOException; -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java deleted file mode 100644 index 13e9c22d98..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java +++ /dev/null @@ -1,1006 +0,0 @@ -/* - * Copyright (C) 2007, Dave Watson - * Copyright (C) 2009-2010, Google Inc. - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006, Shawn O. Pearce - * 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.lib; - -import static org.eclipse.jgit.lib.Constants.CHARSET; -import static org.eclipse.jgit.lib.Constants.HEAD; -import static org.eclipse.jgit.lib.Constants.LOGS; -import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; -import static org.eclipse.jgit.lib.Constants.PACKED_REFS; -import static org.eclipse.jgit.lib.Constants.R_HEADS; -import static org.eclipse.jgit.lib.Constants.R_REFS; -import static org.eclipse.jgit.lib.Constants.R_REMOTES; -import static org.eclipse.jgit.lib.Constants.R_TAGS; -import static org.eclipse.jgit.lib.Constants.encode; -import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; -import static org.eclipse.jgit.lib.Ref.Storage.NEW; -import static org.eclipse.jgit.lib.Ref.Storage.PACKED; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.ObjectWritingException; -import org.eclipse.jgit.events.RefsChangedEvent; -import org.eclipse.jgit.revwalk.RevObject; -import org.eclipse.jgit.revwalk.RevTag; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.util.FS; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.RawParseUtils; -import org.eclipse.jgit.util.RefList; -import org.eclipse.jgit.util.RefMap; - -/** - * Traditional file system based {@link RefDatabase}. - *

- * This is the classical reference database representation for a Git repository. - * References are stored in two formats: loose, and packed. - *

- * Loose references are stored as individual files within the {@code refs/} - * directory. The file name matches the reference name and the file contents is - * the current {@link ObjectId} in string form. - *

- * Packed references are stored in a single text file named {@code packed-refs}. - * In the packed format, each reference is stored on its own line. This file - * reduces the number of files needed for large reference spaces, reducing the - * overall size of a Git repository on disk. - */ -public class RefDirectory extends RefDatabase { - /** Magic string denoting the start of a symbolic reference file. */ - public static final String SYMREF = "ref: "; //$NON-NLS-1$ - - /** Magic string denoting the header of a packed-refs file. */ - public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$ - - /** If in the header, denotes the file has peeled data. */ - public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$ - - private final FileRepository parent; - - private final File gitDir; - - private final File refsDir; - - private final File logsDir; - - private final File logsRefsDir; - - private final File packedRefsFile; - - /** - * Immutable sorted list of loose references. - *

- * Symbolic references in this collection are stored unresolved, that is - * their target appears to be a new reference with no ObjectId. These are - * converted into resolved references during a get operation, ensuring the - * live value is always returned. - */ - private final AtomicReference> looseRefs = new AtomicReference>(); - - /** Immutable sorted list of packed references. */ - private final AtomicReference packedRefs = new AtomicReference(); - - /** - * Number of modifications made to this database. - *

- * This counter is incremented when a change is made, or detected from the - * filesystem during a read operation. - */ - private final AtomicInteger modCnt = new AtomicInteger(); - - /** - * Last {@link #modCnt} that we sent to listeners. - *

- * This value is compared to {@link #modCnt}, and a notification is sent to - * the listeners only when it differs. - */ - private final AtomicInteger lastNotifiedModCnt = new AtomicInteger(); - - RefDirectory(final FileRepository db) { - final FS fs = db.getFS(); - parent = db; - gitDir = db.getDirectory(); - refsDir = fs.resolve(gitDir, R_REFS); - logsDir = fs.resolve(gitDir, LOGS); - logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS); - packedRefsFile = fs.resolve(gitDir, PACKED_REFS); - - looseRefs.set(RefList. emptyList()); - packedRefs.set(PackedRefList.NO_PACKED_REFS); - } - - Repository getRepository() { - return parent; - } - - public void create() throws IOException { - refsDir.mkdir(); - logsDir.mkdir(); - logsRefsDir.mkdir(); - - new File(refsDir, R_HEADS.substring(R_REFS.length())).mkdir(); - new File(refsDir, R_TAGS.substring(R_REFS.length())).mkdir(); - new File(logsRefsDir, R_HEADS.substring(R_REFS.length())).mkdir(); - } - - @Override - public void close() { - // We have no resources to close. - } - - void rescan() { - looseRefs.set(RefList. emptyList()); - packedRefs.set(PackedRefList.NO_PACKED_REFS); - } - - @Override - public boolean isNameConflicting(String name) throws IOException { - RefList packed = getPackedRefs(); - RefList loose = getLooseRefs(); - - // Cannot be nested within an existing reference. - int lastSlash = name.lastIndexOf('/'); - while (0 < lastSlash) { - String needle = name.substring(0, lastSlash); - if (loose.contains(needle) || packed.contains(needle)) - return true; - lastSlash = name.lastIndexOf('/', lastSlash - 1); - } - - // Cannot be the container of an existing reference. - String prefix = name + '/'; - int idx; - - idx = -(packed.find(prefix) + 1); - if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix)) - return true; - - idx = -(loose.find(prefix) + 1); - if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix)) - return true; - - return false; - } - - private RefList getLooseRefs() { - final RefList oldLoose = looseRefs.get(); - - LooseScanner scan = new LooseScanner(oldLoose); - scan.scan(ALL); - - RefList loose; - if (scan.newLoose != null) { - loose = scan.newLoose.toRefList(); - if (looseRefs.compareAndSet(oldLoose, loose)) - modCnt.incrementAndGet(); - } else - loose = oldLoose; - return loose; - } - - @Override - public Ref getRef(final String needle) throws IOException { - final RefList packed = getPackedRefs(); - Ref ref = null; - for (String prefix : SEARCH_PATH) { - ref = readRef(prefix + needle, packed); - if (ref != null) { - ref = resolve(ref, 0, null, null, packed); - break; - } - } - fireRefsChanged(); - return ref; - } - - @Override - public Map getRefs(String prefix) throws IOException { - final RefList packed = getPackedRefs(); - final RefList oldLoose = looseRefs.get(); - - LooseScanner scan = new LooseScanner(oldLoose); - scan.scan(prefix); - - RefList loose; - if (scan.newLoose != null) { - loose = scan.newLoose.toRefList(); - if (looseRefs.compareAndSet(oldLoose, loose)) - modCnt.incrementAndGet(); - } else - loose = oldLoose; - fireRefsChanged(); - - RefList.Builder symbolic = scan.symbolic; - for (int idx = 0; idx < symbolic.size();) { - Ref ref = symbolic.get(idx); - ref = resolve(ref, 0, prefix, loose, packed); - if (ref != null && ref.getObjectId() != null) { - symbolic.set(idx, ref); - idx++; - } else { - // A broken symbolic reference, we have to drop it from the - // collections the client is about to receive. Should be a - // rare occurrence so pay a copy penalty. - loose = loose.remove(idx); - symbolic.remove(idx); - } - } - - return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList()); - } - - @SuppressWarnings("unchecked") - private RefList upcast(RefList loose) { - return (RefList) loose; - } - - private class LooseScanner { - private final RefList curLoose; - - private int curIdx; - - final RefList.Builder symbolic = new RefList.Builder(4); - - RefList.Builder newLoose; - - LooseScanner(final RefList curLoose) { - this.curLoose = curLoose; - } - - void scan(String prefix) { - if (ALL.equals(prefix)) { - scanOne(HEAD); - scanTree(R_REFS, refsDir); - - // If any entries remain, they are deleted, drop them. - if (newLoose == null && curIdx < curLoose.size()) - newLoose = curLoose.copy(curIdx); - - } else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) { - curIdx = -(curLoose.find(prefix) + 1); - File dir = new File(refsDir, prefix.substring(R_REFS.length())); - scanTree(prefix, dir); - - // Skip over entries still within the prefix; these have - // been removed from the directory. - while (curIdx < curLoose.size()) { - if (!curLoose.get(curIdx).getName().startsWith(prefix)) - break; - if (newLoose == null) - newLoose = curLoose.copy(curIdx); - curIdx++; - } - - // Keep any entries outside of the prefix space, we - // do not know anything about their status. - if (newLoose != null) { - while (curIdx < curLoose.size()) - newLoose.add(curLoose.get(curIdx++)); - } - } - } - - private boolean scanTree(String prefix, File dir) { - final String[] entries = dir.list(LockFile.FILTER); - if (entries == null) // not a directory or an I/O error - return false; - if (0 < entries.length) { - Arrays.sort(entries); - for (String name : entries) { - File e = new File(dir, name); - if (!scanTree(prefix + name + '/', e)) - scanOne(prefix + name); - } - } - return true; - } - - private void scanOne(String name) { - LooseRef cur; - - if (curIdx < curLoose.size()) { - do { - cur = curLoose.get(curIdx); - int cmp = RefComparator.compareTo(cur, name); - if (cmp < 0) { - // Reference is not loose anymore, its been deleted. - // Skip the name in the new result list. - if (newLoose == null) - newLoose = curLoose.copy(curIdx); - curIdx++; - cur = null; - continue; - } - - if (cmp > 0) // Newly discovered loose reference. - cur = null; - break; - } while (curIdx < curLoose.size()); - } else - cur = null; // Newly discovered loose reference. - - LooseRef n; - try { - n = scanRef(cur, name); - } catch (IOException notValid) { - n = null; - } - - if (n != null) { - if (cur != n && newLoose == null) - newLoose = curLoose.copy(curIdx); - if (newLoose != null) - newLoose.add(n); - if (n.isSymbolic()) - symbolic.add(n); - } else if (cur != null) { - // Tragically, this file is no longer a loose reference. - // Kill our cached entry of it. - if (newLoose == null) - newLoose = curLoose.copy(curIdx); - } - - if (cur != null) - curIdx++; - } - } - - @Override - public Ref peel(final Ref ref) throws IOException { - final Ref leaf = ref.getLeaf(); - if (leaf.isPeeled() || leaf.getObjectId() == null) - return ref; - - RevWalk rw = new RevWalk(getRepository()); - RevObject obj = rw.parseAny(leaf.getObjectId()); - ObjectIdRef newLeaf; - if (obj instanceof RevTag) { - newLeaf = new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf - .getName(), leaf.getObjectId(), rw.peel(obj).copy()); - } else { - newLeaf = new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf - .getName(), leaf.getObjectId()); - } - - // Try to remember this peeling in the cache, so we don't have to do - // it again in the future, but only if the reference is unchanged. - if (leaf.getStorage().isLoose()) { - RefList curList = looseRefs.get(); - int idx = curList.find(leaf.getName()); - if (0 <= idx && curList.get(idx) == leaf) { - LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf); - RefList newList = curList.set(idx, asPeeled); - looseRefs.compareAndSet(curList, newList); - } - } - - return recreate(ref, newLeaf); - } - - private static Ref recreate(final Ref old, final ObjectIdRef leaf) { - if (old.isSymbolic()) { - Ref dst = recreate(old.getTarget(), leaf); - return new SymbolicRef(old.getName(), dst); - } - return leaf; - } - - void storedSymbolicRef(RefDirectoryUpdate u, long modified, String target) { - putLooseRef(newSymbolicRef(modified, u.getRef().getName(), target)); - fireRefsChanged(); - } - - public RefDirectoryUpdate newUpdate(String name, boolean detach) - throws IOException { - final RefList packed = getPackedRefs(); - Ref ref = readRef(name, packed); - if (ref != null) - ref = resolve(ref, 0, null, null, packed); - if (ref == null) - ref = new ObjectIdRef.Unpeeled(NEW, name, null); - else if (detach && ref.isSymbolic()) - ref = new ObjectIdRef.Unpeeled(LOOSE, name, ref.getObjectId()); - return new RefDirectoryUpdate(this, ref); - } - - @Override - public RefDirectoryRename newRename(String fromName, String toName) - throws IOException { - RefDirectoryUpdate from = newUpdate(fromName, false); - RefDirectoryUpdate to = newUpdate(toName, false); - return new RefDirectoryRename(from, to); - } - - void stored(RefDirectoryUpdate update, long modified) { - final ObjectId target = update.getNewObjectId().copy(); - final Ref leaf = update.getRef().getLeaf(); - putLooseRef(new LooseUnpeeled(modified, leaf.getName(), target)); - } - - private void putLooseRef(LooseRef ref) { - RefList cList, nList; - do { - cList = looseRefs.get(); - nList = cList.put(ref); - } while (!looseRefs.compareAndSet(cList, nList)); - modCnt.incrementAndGet(); - fireRefsChanged(); - } - - void delete(RefDirectoryUpdate update) throws IOException { - Ref dst = update.getRef().getLeaf(); - String name = dst.getName(); - - // Write the packed-refs file using an atomic update. We might - // wind up reading it twice, before and after the lock, to ensure - // 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 IOException(MessageFormat.format( - JGitText.get().cannotLockFile, packedRefsFile)); - try { - PackedRefList cur = readPackedRefs(0, 0); - int idx = cur.find(name); - if (0 <= idx) - commitPackedRefs(lck, cur.remove(idx), packed); - } finally { - lck.unlock(); - } - } - - RefList curLoose, newLoose; - do { - curLoose = looseRefs.get(); - int idx = curLoose.find(name); - if (idx < 0) - break; - newLoose = curLoose.remove(idx); - } while (!looseRefs.compareAndSet(curLoose, newLoose)); - - int levels = levelsIn(name) - 2; - delete(logFor(name), levels); - if (dst.getStorage().isLoose()) { - update.unlock(); - delete(fileFor(name), levels); - } - - modCnt.incrementAndGet(); - fireRefsChanged(); - } - - void log(final RefUpdate update, final String msg, final boolean deref) - throws IOException { - final ObjectId oldId = update.getOldObjectId(); - final ObjectId newId = update.getNewObjectId(); - final Ref ref = update.getRef(); - - PersonIdent ident = update.getRefLogIdent(); - if (ident == null) - ident = new PersonIdent(parent); - else - ident = new PersonIdent(ident); - - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(oldId)); - r.append(' '); - r.append(ObjectId.toString(newId)); - r.append(' '); - r.append(ident.toExternalString()); - r.append('\t'); - r.append(msg); - r.append('\n'); - final byte[] rec = encode(r.toString()); - - if (deref && ref.isSymbolic()) { - log(ref.getName(), rec); - log(ref.getLeaf().getName(), rec); - } else { - log(ref.getName(), rec); - } - } - - private void log(final String refName, final byte[] rec) throws IOException { - final File log = logFor(refName); - final boolean write; - if (isLogAllRefUpdates() && shouldAutoCreateLog(refName)) - write = true; - else if (log.isFile()) - write = true; - else - write = false; - - if (write) { - FileOutputStream out; - try { - out = new FileOutputStream(log, true); - } catch (FileNotFoundException err) { - final File dir = log.getParentFile(); - if (dir.exists()) - throw err; - if (!dir.mkdirs() && !dir.isDirectory()) - throw new IOException(MessageFormat.format(JGitText.get().cannotCreateDirectory, dir)); - out = new FileOutputStream(log, true); - } - try { - out.write(rec); - } finally { - out.close(); - } - } - } - - private boolean isLogAllRefUpdates() { - return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates(); - } - - private boolean shouldAutoCreateLog(final String refName) { - return refName.equals(HEAD) // - || refName.startsWith(R_HEADS) // - || refName.startsWith(R_REMOTES); - } - - private Ref resolve(final Ref ref, int depth, String prefix, - RefList loose, RefList packed) throws IOException { - if (ref.isSymbolic()) { - Ref dst = ref.getTarget(); - - if (MAX_SYMBOLIC_REF_DEPTH <= depth) - return null; // claim it doesn't exist - - // If the cached value can be assumed to be current due to a - // recent scan of the loose directory, use it. - if (loose != null && dst.getName().startsWith(prefix)) { - int idx; - if (0 <= (idx = loose.find(dst.getName()))) - dst = loose.get(idx); - else if (0 <= (idx = packed.find(dst.getName()))) - dst = packed.get(idx); - else - return ref; - } else { - dst = readRef(dst.getName(), packed); - if (dst == null) - return ref; - } - - dst = resolve(dst, depth + 1, prefix, loose, packed); - if (dst == null) - return null; - return new SymbolicRef(ref.getName(), dst); - } - return ref; - } - - private PackedRefList getPackedRefs() throws IOException { - long size = packedRefsFile.length(); - long mtime = size != 0 ? packedRefsFile.lastModified() : 0; - - final PackedRefList curList = packedRefs.get(); - if (size == curList.lastSize && mtime == curList.lastModified) - return curList; - - final PackedRefList newList = readPackedRefs(size, mtime); - if (packedRefs.compareAndSet(curList, newList)) - modCnt.incrementAndGet(); - return newList; - } - - private PackedRefList readPackedRefs(long size, long mtime) - throws IOException { - final BufferedReader br; - try { - br = new BufferedReader(new InputStreamReader(new FileInputStream( - packedRefsFile), CHARSET)); - } catch (FileNotFoundException noPackedRefs) { - // Ignore it and leave the new list empty. - return PackedRefList.NO_PACKED_REFS; - } - try { - return new PackedRefList(parsePackedRefs(br), size, mtime); - } finally { - br.close(); - } - } - - private RefList parsePackedRefs(final BufferedReader br) - throws IOException { - RefList.Builder all = new RefList.Builder(); - Ref last = null; - boolean peeled = false; - boolean needSort = false; - - String p; - while ((p = br.readLine()) != null) { - if (p.charAt(0) == '#') { - if (p.startsWith(PACKED_REFS_HEADER)) { - p = p.substring(PACKED_REFS_HEADER.length()); - peeled = p.contains(PACKED_REFS_PEELED); - } - continue; - } - - if (p.charAt(0) == '^') { - if (last == null) - throw new IOException(JGitText.get().peeledLineBeforeRef); - - ObjectId id = ObjectId.fromString(p.substring(1)); - last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last - .getObjectId(), id); - all.set(all.size() - 1, last); - continue; - } - - int sp = p.indexOf(' '); - ObjectId id = ObjectId.fromString(p.substring(0, sp)); - String name = copy(p, sp + 1, p.length()); - ObjectIdRef cur; - if (peeled) - cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id); - else - cur = new ObjectIdRef.Unpeeled(PACKED, name, id); - if (last != null && RefComparator.compareTo(last, cur) > 0) - needSort = true; - all.add(cur); - last = cur; - } - - if (needSort) - all.sort(); - return all.toRefList(); - } - - private static String copy(final String src, final int off, final int end) { - // Don't use substring since it could leave a reference to the much - // larger existing string. Force construction of a full new object. - return new StringBuilder(end - off).append(src, off, end).toString(); - } - - private void commitPackedRefs(final LockFile lck, final RefList refs, - final PackedRefList oldPackedList) throws IOException { - new RefWriter(refs) { - @Override - protected void writeFile(String name, byte[] content) - throws IOException { - lck.setNeedStatInformation(true); - try { - lck.write(content); - } catch (IOException ioe) { - throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe); - } - try { - lck.waitForStatChange(); - } catch (InterruptedException e) { - lck.unlock(); - throw new ObjectWritingException(MessageFormat.format(JGitText.get().interruptedWriting, name)); - } - if (!lck.commit()) - throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name)); - - packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs, - content.length, lck.getCommitLastModified())); - } - }.writePackedRefs(); - } - - private Ref readRef(String name, RefList packed) throws IOException { - final RefList curList = looseRefs.get(); - final int idx = curList.find(name); - if (0 <= idx) { - final LooseRef o = curList.get(idx); - final LooseRef n = scanRef(o, name); - if (n == null) { - if (looseRefs.compareAndSet(curList, curList.remove(idx))) - modCnt.incrementAndGet(); - return packed.get(name); - } - - if (o == n) - return n; - if (looseRefs.compareAndSet(curList, curList.set(idx, n))) - modCnt.incrementAndGet(); - return n; - } - - final LooseRef n = scanRef(null, name); - if (n == null) - return packed.get(name); - if (looseRefs.compareAndSet(curList, curList.add(idx, n))) - modCnt.incrementAndGet(); - return n; - } - - private LooseRef scanRef(LooseRef ref, String name) throws IOException { - final File path = fileFor(name); - final long modified = path.lastModified(); - - if (ref != null) { - if (ref.getLastModified() == modified) - return ref; - name = ref.getName(); - } else if (modified == 0) - return null; - - final byte[] buf; - try { - buf = IO.readFully(path, 4096); - } catch (FileNotFoundException noFile) { - return null; // doesn't exist; not a reference. - } - - int n = buf.length; - if (n == 0) - return null; // empty file; not a reference. - - if (isSymRef(buf, n)) { - // trim trailing whitespace - while (0 < n && Character.isWhitespace(buf[n - 1])) - n--; - if (n < 6) { - String content = RawParseUtils.decode(buf, 0, n); - throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content)); - } - final String target = RawParseUtils.decode(buf, 5, n); - return newSymbolicRef(modified, name, target); - } - - if (n < OBJECT_ID_STRING_LENGTH) - return null; // impossibly short object identifier; not a reference. - - final ObjectId id; - try { - id = ObjectId.fromString(buf, 0); - } catch (IllegalArgumentException notRef) { - while (0 < n && Character.isWhitespace(buf[n - 1])) - n--; - String content = RawParseUtils.decode(buf, 0, n); - throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content)); - } - return new LooseUnpeeled(modified, name, id); - } - - private static boolean isSymRef(final byte[] buf, int n) { - if (n < 6) - return false; - return /**/buf[0] == 'r' // - && buf[1] == 'e' // - && buf[2] == 'f' // - && buf[3] == ':' // - && buf[4] == ' '; - } - - /** If the parent should fire listeners, fires them. */ - private void fireRefsChanged() { - final int last = lastNotifiedModCnt.get(); - final int curr = modCnt.get(); - if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr)) - parent.fireEvent(new RefsChangedEvent()); - } - - /** - * Create a reference update to write a temporary reference. - * - * @return an update for a new temporary reference. - * @throws IOException - * a temporary name cannot be allocated. - */ - RefDirectoryUpdate newTemporaryUpdate() throws IOException { - File tmp = File.createTempFile("renamed_", "_ref", refsDir); - String name = Constants.R_REFS + tmp.getName(); - Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null); - return new RefDirectoryUpdate(this, ref); - } - - /** - * Locate the file on disk for a single reference name. - * - * @param name - * name of the ref, relative to the Git repository top level - * directory (so typically starts with refs/). - * @return the loose file location. - */ - File fileFor(String name) { - if (name.startsWith(R_REFS)) { - name = name.substring(R_REFS.length()); - return new File(refsDir, name); - } - return new File(gitDir, name); - } - - /** - * Locate the log file on disk for a single reference name. - * - * @param name - * name of the ref, relative to the Git repository top level - * directory (so typically starts with refs/). - * @return the log file location. - */ - File logFor(String name) { - if (name.startsWith(R_REFS)) { - name = name.substring(R_REFS.length()); - return new File(logsRefsDir, name); - } - return new File(logsDir, name); - } - - static int levelsIn(final String name) { - int count = 0; - for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1)) - count++; - return count; - } - - static void delete(final File file, final int depth) throws IOException { - if (!file.delete() && file.isFile()) - throw new IOException(MessageFormat.format(JGitText.get().fileCannotBeDeleted, file)); - - File dir = file.getParentFile(); - for (int i = 0; i < depth; ++i) { - if (!dir.delete()) - break; // ignore problem here - dir = dir.getParentFile(); - } - } - - private static class PackedRefList extends RefList { - static final PackedRefList NO_PACKED_REFS = new PackedRefList(RefList - .emptyList(), 0, 0); - - /** Last length of the packed-refs file when we read it. */ - final long lastSize; - - /** Last modified time of the packed-refs file when we read it. */ - final long lastModified; - - PackedRefList(RefList src, long size, long mtime) { - super(src); - lastSize = size; - lastModified = mtime; - } - } - - private static LooseSymbolicRef newSymbolicRef(long lastModified, - String name, String target) { - Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null); - return new LooseSymbolicRef(lastModified, name, dst); - } - - private static interface LooseRef extends Ref { - long getLastModified(); - - LooseRef peel(ObjectIdRef newLeaf); - } - - private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag - implements LooseRef { - private final long lastModified; - - LoosePeeledTag(long mtime, String refName, ObjectId id, ObjectId p) { - super(LOOSE, refName, id, p); - this.lastModified = mtime; - } - - public long getLastModified() { - return lastModified; - } - - public LooseRef peel(ObjectIdRef newLeaf) { - return this; - } - } - - private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag - implements LooseRef { - private final long lastModified; - - LooseNonTag(long mtime, String refName, ObjectId id) { - super(LOOSE, refName, id); - this.lastModified = mtime; - } - - public long getLastModified() { - return lastModified; - } - - public LooseRef peel(ObjectIdRef newLeaf) { - return this; - } - } - - private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled - implements LooseRef { - private final long lastModified; - - LooseUnpeeled(long mtime, String refName, ObjectId id) { - super(LOOSE, refName, id); - this.lastModified = mtime; - } - - public long getLastModified() { - return lastModified; - } - - public LooseRef peel(ObjectIdRef newLeaf) { - if (newLeaf.getPeeledObjectId() != null) - return new LoosePeeledTag(lastModified, getName(), - getObjectId(), newLeaf.getPeeledObjectId()); - else - return new LooseNonTag(lastModified, getName(), getObjectId()); - } - } - - private final static class LooseSymbolicRef extends SymbolicRef implements - LooseRef { - private final long lastModified; - - LooseSymbolicRef(long mtime, String refName, Ref target) { - super(refName, target); - this.lastModified = mtime; - } - - public long getLastModified() { - return lastModified; - } - - public LooseRef peel(ObjectIdRef newLeaf) { - // We should never try to peel the symbolic references. - throw new UnsupportedOperationException(); - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java deleted file mode 100644 index fec00d9325..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2010, Google Inc. - * Copyright (C) 2009, Robin Rosenberg - * 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.lib; - -import java.io.File; -import java.io.IOException; - -import org.eclipse.jgit.lib.RefUpdate.Result; -import org.eclipse.jgit.revwalk.RevWalk; - -/** - * Rename any reference stored by {@link RefDirectory}. - *

- * This class works by first renaming the source reference to a temporary name, - * then renaming the temporary name to the final destination reference. - *

- * This strategy permits switching a reference like {@code refs/heads/foo}, - * which is a file, to {@code refs/heads/foo/bar}, which is stored inside a - * directory that happens to match the source name. - */ -class RefDirectoryRename extends RefRename { - private final RefDirectory refdb; - - /** - * The value of the source reference at the start of the rename. - *

- * At the end of the rename the destination reference must have this same - * value, otherwise we have a concurrent update and the rename must fail - * without making any changes. - */ - private ObjectId objId; - - /** True if HEAD must be moved to the destination reference. */ - private boolean updateHEAD; - - /** A reference we backup {@link #objId} into during the rename. */ - private RefDirectoryUpdate tmp; - - RefDirectoryRename(RefDirectoryUpdate src, RefDirectoryUpdate dst) { - super(src, dst); - refdb = src.getRefDatabase(); - } - - @Override - protected Result doRename() throws IOException { - if (source.getRef().isSymbolic()) - return Result.IO_FAILURE; // not supported - - final RevWalk rw = new RevWalk(refdb.getRepository()); - objId = source.getOldObjectId(); - updateHEAD = needToUpdateHEAD(); - tmp = refdb.newTemporaryUpdate(); - try { - // First backup the source so its never unreachable. - tmp.setNewObjectId(objId); - tmp.setForceUpdate(true); - tmp.disableRefLog(); - switch (tmp.update(rw)) { - case NEW: - case FORCED: - case NO_CHANGE: - break; - default: - return tmp.getResult(); - } - - // Save the source's log under the temporary name, we must do - // this before we delete the source, otherwise we lose the log. - if (!renameLog(source, tmp)) - return Result.IO_FAILURE; - - // If HEAD has to be updated, link it now to destination. - // We have to link before we delete, otherwise the delete - // fails because its the current branch. - RefUpdate dst = destination; - if (updateHEAD) { - if (!linkHEAD(destination)) { - renameLog(tmp, source); - return Result.LOCK_FAILURE; - } - - // Replace the update operation so HEAD will log the rename. - dst = refdb.newUpdate(Constants.HEAD, false); - dst.setRefLogIdent(destination.getRefLogIdent()); - dst.setRefLogMessage(destination.getRefLogMessage(), false); - } - - // Delete the source name so its path is free for replacement. - source.setExpectedOldObjectId(objId); - source.setForceUpdate(true); - source.disableRefLog(); - if (source.delete(rw) != Result.FORCED) { - renameLog(tmp, source); - if (updateHEAD) - linkHEAD(source); - return source.getResult(); - } - - // Move the log to the destination. - if (!renameLog(tmp, destination)) { - renameLog(tmp, source); - source.setExpectedOldObjectId(ObjectId.zeroId()); - source.setNewObjectId(objId); - source.update(rw); - if (updateHEAD) - linkHEAD(source); - return Result.IO_FAILURE; - } - - // Create the destination, logging the rename during the creation. - dst.setExpectedOldObjectId(ObjectId.zeroId()); - dst.setNewObjectId(objId); - if (dst.update(rw) != Result.NEW) { - // If we didn't create the destination we have to undo - // our work. Put the log back and restore source. - if (renameLog(destination, tmp)) - renameLog(tmp, source); - source.setExpectedOldObjectId(ObjectId.zeroId()); - source.setNewObjectId(objId); - source.update(rw); - if (updateHEAD) - linkHEAD(source); - return dst.getResult(); - } - - return Result.RENAMED; - } finally { - // Always try to free the temporary name. - try { - refdb.delete(tmp); - } catch (IOException err) { - refdb.fileFor(tmp.getName()).delete(); - } - } - } - - private boolean renameLog(RefUpdate src, RefUpdate dst) { - File srcLog = refdb.logFor(src.getName()); - File dstLog = refdb.logFor(dst.getName()); - - if (!srcLog.exists()) - return true; - - if (!rename(srcLog, dstLog)) - return false; - - try { - final int levels = RefDirectory.levelsIn(src.getName()) - 2; - RefDirectory.delete(srcLog, levels); - return true; - } catch (IOException e) { - rename(dstLog, srcLog); - return false; - } - } - - private static boolean rename(File src, File dst) { - if (src.renameTo(dst)) - return true; - - File dir = dst.getParentFile(); - if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory()) - return false; - return src.renameTo(dst); - } - - private boolean linkHEAD(RefUpdate target) { - try { - RefUpdate u = refdb.newUpdate(Constants.HEAD, false); - u.disableRefLog(); - switch (u.link(target.getName())) { - case NEW: - case FORCED: - case NO_CHANGE: - return true; - default: - return false; - } - } catch (IOException e) { - return false; - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java deleted file mode 100644 index 447be104ab..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2009-2010, Google Inc. - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import static org.eclipse.jgit.lib.Constants.encode; - -import java.io.IOException; - -/** Updates any reference stored by {@link RefDirectory}. */ -class RefDirectoryUpdate extends RefUpdate { - private final RefDirectory database; - - private LockFile lock; - - RefDirectoryUpdate(final RefDirectory r, final Ref ref) { - super(ref); - database = r; - } - - @Override - protected RefDirectory getRefDatabase() { - return database; - } - - @Override - protected Repository getRepository() { - return database.getRepository(); - } - - @Override - protected boolean tryLock(boolean deref) throws IOException { - Ref dst = getRef(); - if (deref) - dst = dst.getLeaf(); - String name = dst.getName(); - lock = new LockFile(database.fileFor(name)); - if (lock.lock()) { - dst = database.getRef(name); - setOldObjectId(dst != null ? dst.getObjectId() : null); - return true; - } else { - return false; - } - } - - @Override - protected void unlock() { - if (lock != null) { - lock.unlock(); - lock = null; - } - } - - @Override - protected Result doUpdate(final Result status) throws IOException { - lock.setNeedStatInformation(true); - lock.write(getNewObjectId()); - - String msg = getRefLogMessage(); - if (msg != null) { - if (isRefLogIncludingResult()) { - String strResult = toResultString(status); - if (strResult != null) { - if (msg.length() > 0) - msg = msg + ": " + strResult; - else - msg = strResult; - } - } - database.log(this, msg, true); - } - if (!lock.commit()) - return Result.LOCK_FAILURE; - database.stored(this, lock.getCommitLastModified()); - return status; - } - - private String toResultString(final Result status) { - switch (status) { - case FORCED: - return "forced-update"; - case FAST_FORWARD: - return "fast forward"; - case NEW: - return "created"; - default: - return null; - } - } - - @Override - protected Result doDelete(final Result status) throws IOException { - if (getRef().getLeaf().getStorage() != Ref.Storage.NEW) - database.delete(this); - return status; - } - - @Override - protected Result doLink(final String target) throws IOException { - lock.setNeedStatInformation(true); - lock.write(encode(RefDirectory.SYMREF + target + '\n')); - - String msg = getRefLogMessage(); - if (msg != null) - database.log(this, msg, false); - if (!lock.commit()) - return Result.LOCK_FAILURE; - database.storedSymbolicRef(this, lock.getCommitLastModified(), target); - - if (getRef().getStorage() == Ref.Storage.NEW) - return Result.NEW; - return Result.FORCED; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java index f2738245d4..4acb3ecd89 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java @@ -51,6 +51,7 @@ import java.io.StringWriter; import java.util.Collection; import java.util.Map; +import org.eclipse.jgit.storage.file.RefDirectory; import org.eclipse.jgit.util.RefList; import org.eclipse.jgit.util.RefMap; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java deleted file mode 100644 index 4c5503f321..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2009, Robin Rosenberg - * Copyright (C) 2009, Robin Rosenberg - * 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.lib; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * Utility for reading reflog entries - */ -public class ReflogReader { - /** - * Parsed reflog entry - */ - static public class Entry { - private ObjectId oldId; - - private ObjectId newId; - - private PersonIdent who; - - private String comment; - - Entry(byte[] raw, int pos) { - oldId = ObjectId.fromString(raw, pos); - pos += Constants.OBJECT_ID_STRING_LENGTH; - if (raw[pos++] != ' ') - throw new IllegalArgumentException( - JGitText.get().rawLogMessageDoesNotParseAsLogEntry); - newId = ObjectId.fromString(raw, pos); - pos += Constants.OBJECT_ID_STRING_LENGTH; - if (raw[pos++] != ' ') { - throw new IllegalArgumentException( - JGitText.get().rawLogMessageDoesNotParseAsLogEntry); - } - who = RawParseUtils.parsePersonIdentOnly(raw, pos); - int p0 = RawParseUtils.next(raw, pos, '\t'); // personident has no - // \t - if (p0 == -1) { - throw new IllegalArgumentException( - JGitText.get().rawLogMessageDoesNotParseAsLogEntry); - } - int p1 = RawParseUtils.nextLF(raw, p0); - if (p1 == -1) { - throw new IllegalArgumentException( - JGitText.get().rawLogMessageDoesNotParseAsLogEntry); - } - comment = RawParseUtils.decode(raw, p0, p1 - 1); - } - - /** - * @return the commit id before the change - */ - public ObjectId getOldId() { - return oldId; - } - - /** - * @return the commit id after the change - */ - public ObjectId getNewId() { - return newId; - } - - /** - * @return user performin the change - */ - public PersonIdent getWho() { - return who; - } - - /** - * @return textual description of the change - */ - public String getComment() { - return comment; - } - - @Override - public String toString() { - return "Entry[" + oldId.name() + ", " + newId.name() + ", " + getWho() + ", " - + getComment() + "]"; - } - } - - private File logName; - - ReflogReader(Repository db, String refname) { - logName = new File(db.getDirectory(), "logs/" + refname); - } - - /** - * Get the last entry in the reflog - * - * @return the latest reflog entry, or null if no log - * @throws IOException - */ - public Entry getLastEntry() throws IOException { - List entries = getReverseEntries(1); - return entries.size() > 0 ? entries.get(0) : null; - } - - /** - * @return all reflog entries in reverse order - * @throws IOException - */ - public List getReverseEntries() throws IOException { - return getReverseEntries(Integer.MAX_VALUE); - } - - /** - * @param max - * max numer of entries to read - * @return all reflog entries in reverse order - * @throws IOException - */ - public List getReverseEntries(int max) throws IOException { - final byte[] log; - try { - log = IO.readFully(logName); - } catch (FileNotFoundException e) { - return Collections.emptyList(); - } - - int rs = RawParseUtils.prevLF(log, log.length); - List ret = new ArrayList(); - while (rs >= 0 && max-- > 0) { - rs = RawParseUtils.prevLF(log, rs); - Entry entry = new Entry(log, rs < 0 ? 0 : rs + 2); - ret.add(entry); - } - return ret; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 6b91481d3e..65f75094ea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -69,6 +69,7 @@ import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.ReflogReader; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java index 4b0e1f6901..f9185e8f26 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java @@ -45,6 +45,8 @@ package org.eclipse.jgit.lib; import java.io.File; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; + /** * Base class to support constructing a {@link Repository}. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java index 39d734a32b..dc5eae5173 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java @@ -52,6 +52,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java deleted file mode 100644 index 3cef48242d..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.lang.ref.SoftReference; - -class UnpackedObjectCache { - private static final int CACHE_SZ = 1024; - - private static final SoftReference DEAD; - - private static int hash(final long position) { - return (((int) position) << 22) >>> 22; - } - - private static int maxByteCount; - - private static final Slot[] cache; - - private static Slot lruHead; - - private static Slot lruTail; - - private static int openByteCount; - - static { - DEAD = new SoftReference(null); - maxByteCount = new WindowCacheConfig().getDeltaBaseCacheLimit(); - - cache = new Slot[CACHE_SZ]; - for (int i = 0; i < CACHE_SZ; i++) - cache[i] = new Slot(); - } - - static synchronized void reconfigure(final WindowCacheConfig cfg) { - final int dbLimit = cfg.getDeltaBaseCacheLimit(); - if (maxByteCount != dbLimit) { - maxByteCount = dbLimit; - releaseMemory(); - } - } - - static synchronized Entry get(final PackFile pack, final long position) { - final Slot e = cache[hash(position)]; - if (e.provider == pack && e.position == position) { - final Entry buf = e.data.get(); - if (buf != null) { - moveToHead(e); - return buf; - } - } - return null; - } - - static synchronized void store(final PackFile pack, final long position, - final byte[] data, final int objectType) { - if (data.length > maxByteCount) - return; // Too large to cache. - - final Slot e = cache[hash(position)]; - clearEntry(e); - - openByteCount += data.length; - releaseMemory(); - - e.provider = pack; - e.position = position; - e.sz = data.length; - e.data = new SoftReference(new Entry(data, objectType)); - moveToHead(e); - } - - private static void releaseMemory() { - while (openByteCount > maxByteCount && lruTail != null) { - final Slot currOldest = lruTail; - final Slot nextOldest = currOldest.lruPrev; - - clearEntry(currOldest); - currOldest.lruPrev = null; - currOldest.lruNext = null; - - if (nextOldest == null) - lruHead = null; - else - nextOldest.lruNext = null; - lruTail = nextOldest; - } - } - - static synchronized void purge(final PackFile file) { - for (final Slot e : cache) { - if (e.provider == file) { - clearEntry(e); - unlink(e); - } - } - } - - private static void moveToHead(final Slot e) { - unlink(e); - e.lruPrev = null; - e.lruNext = lruHead; - if (lruHead != null) - lruHead.lruPrev = e; - else - lruTail = e; - lruHead = e; - } - - private static void unlink(final Slot e) { - final Slot prev = e.lruPrev; - final Slot next = e.lruNext; - if (prev != null) - prev.lruNext = next; - if (next != null) - next.lruPrev = prev; - } - - private static void clearEntry(final Slot e) { - openByteCount -= e.sz; - e.provider = null; - e.data = DEAD; - e.sz = 0; - } - - private UnpackedObjectCache() { - throw new UnsupportedOperationException(); - } - - static class Entry { - final byte[] data; - - final int type; - - Entry(final byte[] aData, final int aType) { - data = aData; - type = aType; - } - } - - private static class Slot { - Slot lruPrev; - - Slot lruNext; - - PackFile provider; - - long position; - - int sz; - - SoftReference data = DEAD; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java deleted file mode 100644 index cd2eb38ef1..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.MutableInteger; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * Loose object loader. This class loads an object not stored in a pack. - */ -public class UnpackedObjectLoader extends ObjectLoader { - private final int objectType; - - private final int objectSize; - - private final byte[] bytes; - - /** - * Construct an ObjectLoader to read from the file. - * - * @param path - * location of the loose object to read. - * @param id - * expected identity of the object being loaded, if known. - * @throws FileNotFoundException - * the loose object file does not exist. - * @throws IOException - * the loose object file exists, but is corrupt. - */ - public UnpackedObjectLoader(final File path, final AnyObjectId id) - throws IOException { - this(IO.readFully(path), id); - } - - /** - * Construct an ObjectLoader from a loose object's compressed form. - * - * @param compressed - * entire content of the loose object file. - * @throws CorruptObjectException - * The compressed data supplied does not match the format for a - * valid loose object. - */ - public UnpackedObjectLoader(final byte[] compressed) - throws CorruptObjectException { - this(compressed, null); - } - - private UnpackedObjectLoader(final byte[] compressed, final AnyObjectId id) - throws CorruptObjectException { - // Try to determine if this is a legacy format loose object or - // a new style loose object. The legacy format was completely - // compressed with zlib so the first byte must be 0x78 (15-bit - // window size, deflated) and the first 16 bit word must be - // evenly divisible by 31. Otherwise its a new style loose - // object. - // - final Inflater inflater = InflaterCache.get(); - try { - final int fb = compressed[0] & 0xff; - if (fb == 0x78 && (((fb << 8) | compressed[1] & 0xff) % 31) == 0) { - inflater.setInput(compressed); - final byte[] hdr = new byte[64]; - int avail = 0; - while (!inflater.finished() && avail < hdr.length) - try { - int uncompressed = inflater.inflate(hdr, avail, - hdr.length - avail); - if (uncompressed == 0) { - throw new CorruptObjectException(id, - JGitText.get().corruptObjectBadStreamCorruptHeader); - } - avail += uncompressed; - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(id, JGitText.get().corruptObjectBadStream); - coe.initCause(dfe); - throw coe; - } - if (avail < 5) - throw new CorruptObjectException(id, JGitText.get().corruptObjectNoHeader); - - final MutableInteger p = new MutableInteger(); - objectType = Constants.decodeTypeString(id, hdr, (byte) ' ', p); - objectSize = RawParseUtils.parseBase10(hdr, p.value, p); - if (objectSize < 0) - throw new CorruptObjectException(id, JGitText.get().corruptObjectNegativeSize); - if (hdr[p.value++] != 0) - throw new CorruptObjectException(id, JGitText.get().corruptObjectGarbageAfterSize); - bytes = new byte[objectSize]; - if (p.value < avail) - System.arraycopy(hdr, p.value, bytes, 0, avail - p.value); - decompress(id, inflater, avail - p.value); - } else { - int p = 0; - int c = compressed[p++] & 0xff; - final int typeCode = (c >> 4) & 7; - int size = c & 15; - int shift = 4; - while ((c & 0x80) != 0) { - c = compressed[p++] & 0xff; - size += (c & 0x7f) << shift; - shift += 7; - } - - switch (typeCode) { - case Constants.OBJ_COMMIT: - case Constants.OBJ_TREE: - case Constants.OBJ_BLOB: - case Constants.OBJ_TAG: - objectType = typeCode; - break; - default: - throw new CorruptObjectException(id, JGitText.get().corruptObjectInvalidType); - } - - objectSize = size; - bytes = new byte[objectSize]; - inflater.setInput(compressed, p, compressed.length - p); - decompress(id, inflater, 0); - } - } finally { - InflaterCache.release(inflater); - } - } - - private void decompress(final AnyObjectId id, final Inflater inf, int p) - throws CorruptObjectException { - try { - while (!inf.finished()) { - int uncompressed = inf.inflate(bytes, p, objectSize - p); - p += uncompressed; - if (uncompressed == 0 && !inf.finished()) { - throw new CorruptObjectException(id, - JGitText.get().corruptObjectBadStreamCorruptHeader); - } - } - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(id, JGitText.get().corruptObjectBadStream); - coe.initCause(dfe); - throw coe; - } - if (p != objectSize) - throw new CorruptObjectException(id, JGitText.get().corruptObjectIncorrectLength); - } - - @Override - public int getType() { - return objectType; - } - - @Override - public long getSize() { - return objectSize; - } - - @Override - public byte[] getCachedBytes() { - return bytes; - } - - @Override - public int getRawType() { - return objectType; - } - - @Override - public long getRawSize() { - return objectSize; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java deleted file mode 100644 index fcfa57339e..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.text.MessageFormat; -import java.util.zip.DataFormatException; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; - -/** Reader for a non-delta (just deflated) object in a pack file. */ -class WholePackedObjectLoader extends PackedObjectLoader { - private static final int OBJ_COMMIT = Constants.OBJ_COMMIT; - - WholePackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSize, final int type, final int size) { - super(pr, objectOffset, headerSize); - objectType = type; - objectSize = size; - } - - @Override - public void materialize(final WindowCursor curs) throws IOException { - if (cachedBytes != null) { - return; - } - - if (objectType != OBJ_COMMIT) { - UnpackedObjectCache.Entry cache = pack.readCache(objectOffset); - if (cache != null) { - curs.release(); - cachedBytes = cache.data; - return; - } - } - - try { - cachedBytes = pack.decompress(objectOffset + headerSize, - objectSize, curs); - curs.release(); - if (objectType != OBJ_COMMIT) - pack.saveCache(objectOffset, cachedBytes, objectType); - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream, - objectOffset, pack.getPackFile())); - coe.initCause(dfe); - throw coe; - } - } - - @Override - public int getRawType() { - return objectType; - } - - @Override - public long getRawSize() { - return objectSize; - } - - @Override - public ObjectId getDeltaBase() { - return null; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java deleted file mode 100644 index a44a30ee2d..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java +++ /dev/null @@ -1,624 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.SoftReference; -import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReferenceArray; -import java.util.concurrent.locks.ReentrantLock; - -import org.eclipse.jgit.JGitText; - -/** - * Caches slices of a {@link PackFile} in memory for faster read access. - *

- * The WindowCache serves as a Java based "buffer cache", loading segments of a - * PackFile into the JVM heap prior to use. As JGit often wants to do reads of - * only tiny slices of a file, the WindowCache tries to smooth out these tiny - * reads into larger block-sized IO operations. - *

- * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by - * exactly one thread for the given (PackFile,position) key tuple. - * This is ensured by an array of locks, with the tuple hashed to a lock - * instance. - *

- * During a miss, older entries are evicted from the cache so long as - * {@link #isFull()} returns true. - *

- * Its too expensive during object access to be 100% accurate with a least - * recently used (LRU) algorithm. Strictly ordering every read is a lot of - * overhead that typically doesn't yield a corresponding benefit to the - * application. - *

- * This cache implements a loose LRU policy by randomly picking a window - * comprised of roughly 10% of the cache, and evicting the oldest accessed entry - * within that window. - *

- * Entities created by the cache are held under SoftReferences, permitting the - * Java runtime's garbage collector to evict entries when heap memory gets low. - * Most JREs implement a loose least recently used algorithm for this eviction. - *

- * The internal hash table does not expand at runtime, instead it is fixed in - * size at cache creation time. The internal lock table used to gate load - * invocations is also fixed in size. - *

- * The key tuple is passed through to methods as a pair of parameters rather - * than as a single Object, thus reducing the transient memory allocations of - * callers. It is more efficient to avoid the allocation, as we can't be 100% - * sure that a JIT would be able to stack-allocate a key tuple. - *

- * This cache has an implementation rule such that: - *

    - *
  • {@link #load(PackFile, long)} is invoked by at most one thread at a time - * for a given (PackFile,position) tuple.
  • - *
  • For every load() invocation there is exactly one - * {@link #createRef(PackFile, long, ByteWindow)} invocation to wrap a - * SoftReference around the cached entity.
  • - *
  • For every Reference created by createRef() there will be - * exactly one call to {@link #clear(Ref)} to cleanup any resources associated - * with the (now expired) cached entity.
  • - *
- *

- * Therefore, it is safe to perform resource accounting increments during the - * {@link #load(PackFile, long)} or - * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching - * decrements during {@link #clear(Ref)}. Implementors may need to override - * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional - * accounting information into an implementation specific {@link Ref} subclass, - * as the cached entity may have already been evicted by the JRE's garbage - * collector. - *

- * To maintain higher concurrency workloads, during eviction only one thread - * performs the eviction work, while other threads can continue to insert new - * objects in parallel. This means that the cache can be temporarily over limit, - * especially if the nominated eviction thread is being starved relative to the - * other threads. - */ -public class WindowCache { - private static final int bits(int newSize) { - if (newSize < 4096) - throw new IllegalArgumentException(JGitText.get().invalidWindowSize); - if (Integer.bitCount(newSize) != 1) - throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2); - return Integer.numberOfTrailingZeros(newSize); - } - - private static final Random rng = new Random(); - - private static volatile WindowCache cache; - - static { - reconfigure(new WindowCacheConfig()); - } - - /** - * Modify the configuration of the window cache. - *

- * The new configuration is applied immediately. If the new limits are - * smaller than what what is currently cached, older entries will be purged - * as soon as possible to allow the cache to meet the new limit. - * - * @param packedGitLimit - * maximum number of bytes to hold within this instance. - * @param packedGitWindowSize - * number of bytes per window within the cache. - * @param packedGitMMAP - * true to enable use of mmap when creating windows. - * @param deltaBaseCacheLimit - * number of bytes to hold in the delta base cache. - * @deprecated Use {@link WindowCacheConfig} instead. - */ - public static void reconfigure(final int packedGitLimit, - final int packedGitWindowSize, final boolean packedGitMMAP, - final int deltaBaseCacheLimit) { - final WindowCacheConfig c = new WindowCacheConfig(); - c.setPackedGitLimit(packedGitLimit); - c.setPackedGitWindowSize(packedGitWindowSize); - c.setPackedGitMMAP(packedGitMMAP); - c.setDeltaBaseCacheLimit(deltaBaseCacheLimit); - reconfigure(c); - } - - /** - * Modify the configuration of the window cache. - *

- * The new configuration is applied immediately. If the new limits are - * smaller than what what is currently cached, older entries will be purged - * as soon as possible to allow the cache to meet the new limit. - * - * @param cfg - * the new window cache configuration. - * @throws IllegalArgumentException - * the cache configuration contains one or more invalid - * settings, usually too low of a limit. - */ - public static void reconfigure(final WindowCacheConfig cfg) { - final WindowCache nc = new WindowCache(cfg); - final WindowCache oc = cache; - if (oc != null) - oc.removeAll(); - cache = nc; - UnpackedObjectCache.reconfigure(cfg); - } - - static WindowCache getInstance() { - return cache; - } - - static final ByteWindow get(final PackFile pack, final long offset) - throws IOException { - final WindowCache c = cache; - final ByteWindow r = c.getOrLoad(pack, c.toStart(offset)); - if (c != cache) { - // The cache was reconfigured while we were using the old one - // to load this window. The window is still valid, but our - // cache may think its still live. Ensure the window is removed - // from the old cache so resources can be released. - // - c.removeAll(); - } - return r; - } - - static final void purge(final PackFile pack) { - cache.removeAll(pack); - } - - /** ReferenceQueue to cleanup released and garbage collected windows. */ - private final ReferenceQueue queue; - - /** Number of entries in {@link #table}. */ - private final int tableSize; - - /** Access clock for loose LRU. */ - private final AtomicLong clock; - - /** Hash bucket directory; entries are chained below. */ - private final AtomicReferenceArray table; - - /** Locks to prevent concurrent loads for same (PackFile,position). */ - private final Lock[] locks; - - /** Lock to elect the eviction thread after a load occurs. */ - private final ReentrantLock evictLock; - - /** Number of {@link #table} buckets to scan for an eviction window. */ - private final int evictBatch; - - private final int maxFiles; - - private final long maxBytes; - - private final boolean mmap; - - private final int windowSizeShift; - - private final int windowSize; - - private final AtomicInteger openFiles; - - private final AtomicLong openBytes; - - private WindowCache(final WindowCacheConfig cfg) { - tableSize = tableSize(cfg); - final int lockCount = lockCount(cfg); - if (tableSize < 1) - throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1); - if (lockCount < 1) - throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1); - - queue = new ReferenceQueue(); - clock = new AtomicLong(1); - table = new AtomicReferenceArray(tableSize); - locks = new Lock[lockCount]; - for (int i = 0; i < locks.length; i++) - locks[i] = new Lock(); - evictLock = new ReentrantLock(); - - int eb = (int) (tableSize * .1); - if (64 < eb) - eb = 64; - else if (eb < 4) - eb = 4; - if (tableSize < eb) - eb = tableSize; - evictBatch = eb; - - maxFiles = cfg.getPackedGitOpenFiles(); - maxBytes = cfg.getPackedGitLimit(); - mmap = cfg.isPackedGitMMAP(); - windowSizeShift = bits(cfg.getPackedGitWindowSize()); - windowSize = 1 << windowSizeShift; - - openFiles = new AtomicInteger(); - openBytes = new AtomicLong(); - - if (maxFiles < 1) - throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1); - if (maxBytes < windowSize) - throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); - } - - int getOpenFiles() { - return openFiles.get(); - } - - long getOpenBytes() { - return openBytes.get(); - } - - private int hash(final int packHash, final long off) { - return packHash + (int) (off >>> windowSizeShift); - } - - private ByteWindow load(final PackFile pack, final long offset) - throws IOException { - if (pack.beginWindowCache()) - openFiles.incrementAndGet(); - try { - if (mmap) - return pack.mmap(offset, windowSize); - return pack.read(offset, windowSize); - } catch (IOException e) { - close(pack); - throw e; - } catch (RuntimeException e) { - close(pack); - throw e; - } catch (Error e) { - close(pack); - throw e; - } - } - - private Ref createRef(final PackFile p, final long o, final ByteWindow v) { - final Ref ref = new Ref(p, o, v, queue); - openBytes.addAndGet(ref.size); - return ref; - } - - private void clear(final Ref ref) { - openBytes.addAndGet(-ref.size); - close(ref.pack); - } - - private void close(final PackFile pack) { - if (pack.endWindowCache()) - openFiles.decrementAndGet(); - } - - private boolean isFull() { - return maxFiles < openFiles.get() || maxBytes < openBytes.get(); - } - - private long toStart(final long offset) { - return (offset >>> windowSizeShift) << windowSizeShift; - } - - private static int tableSize(final WindowCacheConfig cfg) { - final int wsz = cfg.getPackedGitWindowSize(); - final long limit = cfg.getPackedGitLimit(); - if (wsz <= 0) - throw new IllegalArgumentException(JGitText.get().invalidWindowSize); - if (limit < wsz) - throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); - return (int) Math.min(5 * (limit / wsz) / 2, 2000000000); - } - - private static int lockCount(final WindowCacheConfig cfg) { - return Math.max(cfg.getPackedGitOpenFiles(), 32); - } - - /** - * Lookup a cached object, creating and loading it if it doesn't exist. - * - * @param pack - * the pack that "contains" the cached object. - * @param position - * offset within pack of the object. - * @return the object reference. - * @throws IOException - * the object reference was not in the cache and could not be - * obtained by {@link #load(PackFile, long)}. - */ - private ByteWindow getOrLoad(final PackFile pack, final long position) - throws IOException { - final int slot = slot(pack, position); - final Entry e1 = table.get(slot); - ByteWindow v = scan(e1, pack, position); - if (v != null) - return v; - - synchronized (lock(pack, position)) { - Entry e2 = table.get(slot); - if (e2 != e1) { - v = scan(e2, pack, position); - if (v != null) - return v; - } - - v = load(pack, position); - final Ref ref = createRef(pack, position, v); - hit(ref); - for (;;) { - final Entry n = new Entry(clean(e2), ref); - if (table.compareAndSet(slot, e2, n)) - break; - e2 = table.get(slot); - } - } - - if (evictLock.tryLock()) { - try { - gc(); - evict(); - } finally { - evictLock.unlock(); - } - } - - return v; - } - - private ByteWindow scan(Entry n, final PackFile pack, final long position) { - for (; n != null; n = n.next) { - final Ref r = n.ref; - if (r.pack == pack && r.position == position) { - final ByteWindow v = r.get(); - if (v != null) { - hit(r); - return v; - } - n.kill(); - break; - } - } - return null; - } - - private void hit(final Ref r) { - // We don't need to be 100% accurate here. Its sufficient that at least - // one thread performs the increment. Any other concurrent access at - // exactly the same time can simply use the same clock value. - // - // Consequently we attempt the set, but we don't try to recover should - // it fail. This is why we don't use getAndIncrement() here. - // - final long c = clock.get(); - clock.compareAndSet(c, c + 1); - r.lastAccess = c; - } - - private void evict() { - while (isFull()) { - int ptr = rng.nextInt(tableSize); - Entry old = null; - int slot = 0; - for (int b = evictBatch - 1; b >= 0; b--, ptr++) { - if (tableSize <= ptr) - ptr = 0; - for (Entry e = table.get(ptr); e != null; e = e.next) { - if (e.dead) - continue; - if (old == null || e.ref.lastAccess < old.ref.lastAccess) { - old = e; - slot = ptr; - } - } - } - if (old != null) { - old.kill(); - gc(); - final Entry e1 = table.get(slot); - table.compareAndSet(slot, e1, clean(e1)); - } - } - } - - /** - * Clear every entry from the cache. - *

- * This is a last-ditch effort to clear out the cache, such as before it - * gets replaced by another cache that is configured differently. This - * method tries to force every cached entry through {@link #clear(Ref)} to - * ensure that resources are correctly accounted for and cleaned up by the - * subclass. A concurrent reader loading entries while this method is - * running may cause resource accounting failures. - */ - private void removeAll() { - for (int s = 0; s < tableSize; s++) { - Entry e1; - do { - e1 = table.get(s); - for (Entry e = e1; e != null; e = e.next) - e.kill(); - } while (!table.compareAndSet(s, e1, null)); - } - gc(); - } - - /** - * Clear all entries related to a single file. - *

- * Typically this method is invoked during {@link PackFile#close()}, when we - * know the pack is never going to be useful to us again (for example, it no - * longer exists on disk). A concurrent reader loading an entry from this - * same pack may cause the pack to become stuck in the cache anyway. - * - * @param pack - * the file to purge all entries of. - */ - private void removeAll(final PackFile pack) { - for (int s = 0; s < tableSize; s++) { - final Entry e1 = table.get(s); - boolean hasDead = false; - for (Entry e = e1; e != null; e = e.next) { - if (e.ref.pack == pack) { - e.kill(); - hasDead = true; - } else if (e.dead) - hasDead = true; - } - if (hasDead) - table.compareAndSet(s, e1, clean(e1)); - } - gc(); - } - - @SuppressWarnings("unchecked") - private void gc() { - Ref r; - while ((r = (Ref) queue.poll()) != null) { - // Sun's Java 5 and 6 implementation have a bug where a Reference - // can be enqueued and dequeued twice on the same reference queue - // due to a race condition within ReferenceQueue.enqueue(Reference). - // - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6837858 - // - // We CANNOT permit a Reference to come through us twice, as it will - // skew the resource counters we maintain. Our canClear() check here - // provides a way to skip the redundant dequeues, if any. - // - if (r.canClear()) { - clear(r); - - boolean found = false; - final int s = slot(r.pack, r.position); - final Entry e1 = table.get(s); - for (Entry n = e1; n != null; n = n.next) { - if (n.ref == r) { - n.dead = true; - found = true; - break; - } - } - if (found) - table.compareAndSet(s, e1, clean(e1)); - } - } - } - - private int slot(final PackFile pack, final long position) { - return (hash(pack.hash, position) >>> 1) % tableSize; - } - - private Lock lock(final PackFile pack, final long position) { - return locks[(hash(pack.hash, position) >>> 1) % locks.length]; - } - - private static Entry clean(Entry top) { - while (top != null && top.dead) { - top.ref.enqueue(); - top = top.next; - } - if (top == null) - return null; - final Entry n = clean(top.next); - return n == top.next ? top : new Entry(n, top.ref); - } - - private static class Entry { - /** Next entry in the hash table's chain list. */ - final Entry next; - - /** The referenced object. */ - final Ref ref; - - /** - * Marked true when ref.get() returns null and the ref is dead. - *

- * A true here indicates that the ref is no longer accessible, and that - * we therefore need to eventually purge this Entry object out of the - * bucket's chain. - */ - volatile boolean dead; - - Entry(final Entry n, final Ref r) { - next = n; - ref = r; - } - - final void kill() { - dead = true; - ref.enqueue(); - } - } - - /** A soft reference wrapped around a cached object. */ - private static class Ref extends SoftReference { - final PackFile pack; - - final long position; - - final int size; - - long lastAccess; - - private boolean cleared; - - protected Ref(final PackFile pack, final long position, - final ByteWindow v, final ReferenceQueue queue) { - super(v, queue); - this.pack = pack; - this.position = position; - this.size = v.size(); - } - - final synchronized boolean canClear() { - if (cleared) - return false; - cleared = true; - return true; - } - } - - private static final class Lock { - // Used only for its implicit monitor. - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java deleted file mode 100644 index 2d8aef34ca..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2009, 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.lib; - -/** Configuration parameters for {@link WindowCache}. */ -public class WindowCacheConfig { - /** 1024 (number of bytes in one kibibyte/kilobyte) */ - public static final int KB = 1024; - - /** 1024 {@link #KB} (number of bytes in one mebibyte/megabyte) */ - public static final int MB = 1024 * KB; - - private int packedGitOpenFiles; - - private long packedGitLimit; - - private int packedGitWindowSize; - - private boolean packedGitMMAP; - - private int deltaBaseCacheLimit; - - /** Create a default configuration. */ - public WindowCacheConfig() { - packedGitOpenFiles = 128; - packedGitLimit = 10 * MB; - packedGitWindowSize = 8 * KB; - packedGitMMAP = false; - deltaBaseCacheLimit = 10 * MB; - } - - /** - * @return maximum number of streams to open at a time. Open packs count - * against the process limits. Default is 128. - */ - public int getPackedGitOpenFiles() { - return packedGitOpenFiles; - } - - /** - * @param fdLimit - * maximum number of streams to open at a time. Open packs count - * against the process limits - */ - public void setPackedGitOpenFiles(final int fdLimit) { - packedGitOpenFiles = fdLimit; - } - - /** - * @return maximum number bytes of heap memory to dedicate to caching pack - * file data. Default is 10 MB. - */ - public long getPackedGitLimit() { - return packedGitLimit; - } - - /** - * @param newLimit - * maximum number bytes of heap memory to dedicate to caching - * pack file data. - */ - public void setPackedGitLimit(final long newLimit) { - packedGitLimit = newLimit; - } - - /** - * @return size in bytes of a single window mapped or read in from the pack - * file. Default is 8 KB. - */ - public int getPackedGitWindowSize() { - return packedGitWindowSize; - } - - /** - * @param newSize - * size in bytes of a single window read in from the pack file. - */ - public void setPackedGitWindowSize(final int newSize) { - packedGitWindowSize = newSize; - } - - /** - * @return true enables use of Java NIO virtual memory mapping for windows; - * false reads entire window into a byte[] with standard read calls. - * Default false. - */ - public boolean isPackedGitMMAP() { - return packedGitMMAP; - } - - /** - * @param usemmap - * true enables use of Java NIO virtual memory mapping for - * windows; false reads entire window into a byte[] with standard - * read calls. - */ - public void setPackedGitMMAP(final boolean usemmap) { - packedGitMMAP = usemmap; - } - - /** - * @return maximum number of bytes to cache in {@link UnpackedObjectCache} - * for inflated, recently accessed objects, without delta chains. - * Default 10 MB. - */ - public int getDeltaBaseCacheLimit() { - return deltaBaseCacheLimit; - } - - /** - * @param newLimit - * maximum number of bytes to cache in - * {@link UnpackedObjectCache} for inflated, recently accessed - * objects, without delta chains. - */ - public void setDeltaBaseCacheLimit(final int newLimit) { - deltaBaseCacheLimit = newLimit; - } - - /** - * Update properties by setting fields from the configuration. - *

- * If a property is not defined in the configuration, then it is left - * unmodified. - * - * @param rc configuration to read properties from. - */ - public void fromConfig(final Config rc) { - setPackedGitOpenFiles(rc.getInt("core", null, "packedgitopenfiles", getPackedGitOpenFiles())); - setPackedGitLimit(rc.getLong("core", null, "packedgitlimit", getPackedGitLimit())); - setPackedGitWindowSize(rc.getInt("core", null, "packedgitwindowsize", getPackedGitWindowSize())); - setPackedGitMMAP(rc.getBoolean("core", null, "packedgitmmap", isPackedGitMMAP())); - setDeltaBaseCacheLimit(rc.getInt("core", null, "deltabasecachelimit", getDeltaBaseCacheLimit())); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java deleted file mode 100644 index 98916efcbd..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; -import org.eclipse.jgit.revwalk.RevObject; - -/** Active handle to a ByteWindow. */ -final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { - /** Temporary buffer large enough for at least one raw object id. */ - final byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH]; - - private Inflater inf; - - private ByteWindow window; - - private final FileObjectDatabase db; - - WindowCursor(FileObjectDatabase db) { - this.db = db; - } - - public boolean hasObject(AnyObjectId objectId) throws IOException { - return db.hasObject(objectId); - } - - public ObjectLoader openObject(AnyObjectId objectId, int typeHint) - throws MissingObjectException, IOException { - final ObjectLoader ldr = db.openObject(this, objectId); - if (ldr == null) { - if (typeHint == OBJ_ANY) - throw new MissingObjectException(objectId.copy(), "unknown"); - throw new MissingObjectException(objectId.copy(), typeHint); - } - return ldr; - } - - public LocalObjectToPack newObjectToPack(RevObject obj) { - return new LocalObjectToPack(obj); - } - - public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp) - throws IOException, MissingObjectException { - db.selectObjectRepresentation(packer, otp, this); - } - - public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp) - throws IOException, StoredObjectRepresentationNotAvailableException { - LocalObjectToPack src = (LocalObjectToPack) otp; - src.copyFromPack.copyAsIs(out, src, this); - } - - /** - * Copy bytes from the window to a caller supplied buffer. - * - * @param pack - * the file the desired window is stored within. - * @param position - * position within the file to read from. - * @param dstbuf - * destination buffer to copy into. - * @param dstoff - * offset within dstbuf to start copying into. - * @param cnt - * number of bytes to copy. This value may exceed the number of - * bytes remaining in the window starting at offset - * pos. - * @return number of bytes actually copied; this may be less than - * cnt if cnt exceeded the number of bytes - * available. - * @throws IOException - * this cursor does not match the provider or id and the proper - * window could not be acquired through the provider's cache. - */ - int copy(final PackFile pack, long position, final byte[] dstbuf, - int dstoff, final int cnt) throws IOException { - final long length = pack.length; - int need = cnt; - while (need > 0 && position < length) { - pin(pack, position); - final int r = window.copy(position, dstbuf, dstoff, need); - position += r; - dstoff += r; - need -= r; - } - return cnt - need; - } - - /** - * Pump bytes into the supplied inflater as input. - * - * @param pack - * the file the desired window is stored within. - * @param position - * position within the file to read from. - * @param dstbuf - * destination buffer the inflater should output decompressed - * data to. - * @param dstoff - * current offset within dstbuf to inflate into. - * @return updated dstoff based on the number of bytes - * successfully inflated into dstbuf. - * @throws IOException - * this cursor does not match the provider or id and the proper - * window could not be acquired through the provider's cache. - * @throws DataFormatException - * the inflater encountered an invalid chunk of data. Data - * stream corruption is likely. - */ - int inflate(final PackFile pack, long position, final byte[] dstbuf, - int dstoff) throws IOException, DataFormatException { - prepareInflater(); - for (;;) { - pin(pack, position); - dstoff = window.inflate(position, dstbuf, dstoff, inf); - if (inf.finished()) - return dstoff; - position = window.end; - } - } - - ByteArrayWindow quickCopy(PackFile p, long pos, long cnt) - throws IOException { - pin(p, pos); - if (window instanceof ByteArrayWindow - && window.contains(p, pos + (cnt - 1))) - return (ByteArrayWindow) window; - return null; - } - - Inflater inflater() { - prepareInflater(); - return inf; - } - - private void prepareInflater() { - if (inf == null) - inf = InflaterCache.get(); - else - inf.reset(); - } - - private void pin(final PackFile pack, final long position) - throws IOException { - final ByteWindow w = window; - if (w == null || !w.contains(pack, position)) { - // If memory is low, we may need what is in our window field to - // be cleaned up by the GC during the get for the next window. - // So we always clear it, even though we are just going to set - // it again. - // - window = null; - window = WindowCache.get(pack, position); - } - } - - /** Release the current window cursor. */ - public void release() { - window = null; - try { - InflaterCache.release(inf); - } finally { - inf = null; - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java new file mode 100644 index 0000000000..840244b77f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.CRC32; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * A {@link ByteWindow} with an underlying byte array for storage. + */ +final class ByteArrayWindow extends ByteWindow { + private final byte[] array; + + ByteArrayWindow(final PackFile pack, final long o, final byte[] b) { + super(pack, o, b.length); + array = b; + } + + @Override + protected int copy(final int p, final byte[] b, final int o, int n) { + n = Math.min(array.length - p, n); + System.arraycopy(array, p, b, o, n); + return n; + } + + @Override + protected int inflate(final int pos, final byte[] b, int o, + final Inflater inf) throws DataFormatException { + while (!inf.finished()) { + if (inf.needsInput()) { + inf.setInput(array, pos, array.length - pos); + break; + } + o += inf.inflate(b, o, b.length - o); + } + while (!inf.finished() && !inf.needsInput()) + o += inf.inflate(b, o, b.length - o); + return o; + } + + void crc32(CRC32 out, long pos, int cnt) { + out.update(array, (int) (pos - start), cnt); + } + + void write(OutputStream out, long pos, int cnt) throws IOException { + out.write(array, (int) (pos - start), cnt); + } + + void check(Inflater inf, byte[] tmp, long pos, int cnt) + throws DataFormatException { + inf.setInput(array, (int) (pos - start), cnt); + while (inf.inflate(tmp, 0, tmp.length) > 0) + continue; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java new file mode 100644 index 0000000000..52bc00f355 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.nio.ByteBuffer; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * A window for accessing git packs using a {@link ByteBuffer} for storage. + * + * @see ByteWindow + */ +final class ByteBufferWindow extends ByteWindow { + private final ByteBuffer buffer; + + ByteBufferWindow(final PackFile pack, final long o, final ByteBuffer b) { + super(pack, o, b.capacity()); + buffer = b; + } + + @Override + protected int copy(final int p, final byte[] b, final int o, int n) { + final ByteBuffer s = buffer.slice(); + s.position(p); + n = Math.min(s.remaining(), n); + s.get(b, o, n); + return n; + } + + @Override + protected int inflate(final int pos, final byte[] b, int o, + final Inflater inf) throws DataFormatException { + final byte[] tmp = new byte[512]; + final ByteBuffer s = buffer.slice(); + s.position(pos); + while (s.remaining() > 0 && !inf.finished()) { + if (inf.needsInput()) { + final int n = Math.min(s.remaining(), tmp.length); + s.get(tmp, 0, n); + inf.setInput(tmp, 0, n); + } + o += inf.inflate(b, o, b.length - o); + } + while (!inf.finished() && !inf.needsInput()) + o += inf.inflate(b, o, b.length - o); + return o; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java new file mode 100644 index 0000000000..5c77cff012 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * A window of data currently stored within a cache. + *

+ * All bytes in the window can be assumed to be "immediately available", that is + * they are very likely already in memory, unless the operating system's memory + * is very low and has paged part of this process out to disk. Therefore copying + * bytes from a window is very inexpensive. + *

+ */ +abstract class ByteWindow { + protected final PackFile pack; + + protected final long start; + + protected final long end; + + protected ByteWindow(final PackFile p, final long s, final int n) { + pack = p; + start = s; + end = start + n; + } + + final int size() { + return (int) (end - start); + } + + final boolean contains(final PackFile neededFile, final long neededPos) { + return pack == neededFile && start <= neededPos && neededPos < end; + } + + /** + * Copy bytes from the window to a caller supplied buffer. + * + * @param pos + * offset within the file to start copying from. + * @param dstbuf + * destination buffer to copy into. + * @param dstoff + * offset within dstbuf to start copying into. + * @param cnt + * number of bytes to copy. This value may exceed the number of + * bytes remaining in the window starting at offset + * pos. + * @return number of bytes actually copied; this may be less than + * cnt if cnt exceeded the number of + * bytes available. + */ + final int copy(long pos, byte[] dstbuf, int dstoff, int cnt) { + return copy((int) (pos - start), dstbuf, dstoff, cnt); + } + + /** + * Copy bytes from the window to a caller supplied buffer. + * + * @param pos + * offset within the window to start copying from. + * @param dstbuf + * destination buffer to copy into. + * @param dstoff + * offset within dstbuf to start copying into. + * @param cnt + * number of bytes to copy. This value may exceed the number of + * bytes remaining in the window starting at offset + * pos. + * @return number of bytes actually copied; this may be less than + * cnt if cnt exceeded the number of + * bytes available. + */ + protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt); + + /** + * Pump bytes into the supplied inflater as input. + * + * @param pos + * offset within the file to start supplying input from. + * @param dstbuf + * destination buffer the inflater should output decompressed + * data to. + * @param dstoff + * current offset within dstbuf to inflate into. + * @param inf + * the inflater to feed input to. The caller is responsible for + * initializing the inflater as multiple windows may need to + * supply data to the same inflater to completely decompress + * something. + * @return updated dstoff based on the number of bytes + * successfully copied into dstbuf by + * inf. If the inflater is not yet finished then + * another window's data must still be supplied as input to finish + * decompression. + * @throws DataFormatException + * the inflater encountered an invalid chunk of data. Data + * stream corruption is likely. + */ + final int inflate(long pos, byte[] dstbuf, int dstoff, Inflater inf) + throws DataFormatException { + return inflate((int) (pos - start), dstbuf, dstoff, inf); + } + + /** + * Pump bytes into the supplied inflater as input. + * + * @param pos + * offset within the window to start supplying input from. + * @param dstbuf + * destination buffer the inflater should output decompressed + * data to. + * @param dstoff + * current offset within dstbuf to inflate into. + * @param inf + * the inflater to feed input to. The caller is responsible for + * initializing the inflater as multiple windows may need to + * supply data to the same inflater to completely decompress + * something. + * @return updated dstoff based on the number of bytes + * successfully copied into dstbuf by + * inf. If the inflater is not yet finished then + * another window's data must still be supplied as input to finish + * decompression. + * @throws DataFormatException + * the inflater encountered an invalid chunk of data. Data + * stream corruption is likely. + */ + protected abstract int inflate(int pos, byte[] dstbuf, int dstoff, + Inflater inf) throws DataFormatException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java new file mode 100644 index 0000000000..79730d0551 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2010, Constantine Plotnikov + * Copyright (C) 2010, JetBrains s.r.o. + * 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.storage.file; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSubclassMap; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackWriter; + +/** + * The cached instance of an {@link ObjectDirectory}. + *

+ * This class caches the list of loose objects in memory, so the file system is + * not queried with stat calls. + */ +class CachedObjectDirectory extends FileObjectDatabase { + /** + * The set that contains unpacked objects identifiers, it is created when + * the cached instance is created. + */ + private final ObjectIdSubclassMap unpackedObjects = new ObjectIdSubclassMap(); + + private final ObjectDirectory wrapped; + + private AlternateHandle[] alts; + + /** + * The constructor + * + * @param wrapped + * the wrapped database + */ + CachedObjectDirectory(ObjectDirectory wrapped) { + this.wrapped = wrapped; + + File objects = wrapped.getDirectory(); + String[] fanout = objects.list(); + if (fanout == null) + fanout = new String[0]; + for (String d : fanout) { + if (d.length() != 2) + continue; + String[] entries = new File(objects, d).list(); + if (entries == null) + continue; + for (String e : entries) { + if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) + continue; + try { + unpackedObjects.add(ObjectId.fromString(d + e)); + } catch (IllegalArgumentException notAnObject) { + // ignoring the file that does not represent loose object + } + } + } + } + + @Override + public void close() { + // Don't close anything. + } + + @Override + public ObjectInserter newInserter() { + return wrapped.newInserter(); + } + + @Override + public ObjectDatabase newCachedDatabase() { + return this; + } + + @Override + FileObjectDatabase newCachedFileObjectDatabase() { + return this; + } + + @Override + File getDirectory() { + return wrapped.getDirectory(); + } + + @Override + AlternateHandle[] myAlternates() { + if (alts == null) { + AlternateHandle[] src = wrapped.myAlternates(); + alts = new AlternateHandle[src.length]; + for (int i = 0; i < alts.length; i++) { + FileObjectDatabase s = src[i].db; + alts[i] = new AlternateHandle(s.newCachedFileObjectDatabase()); + } + } + return alts; + } + + @Override + boolean tryAgain1() { + return wrapped.tryAgain1(); + } + + @Override + public boolean hasObject(final AnyObjectId objectId) { + return hasObjectImpl1(objectId); + } + + @Override + boolean hasObject1(AnyObjectId objectId) { + return unpackedObjects.contains(objectId) + || wrapped.hasObject1(objectId); + } + + @Override + ObjectLoader openObject(final WindowCursor curs, + final AnyObjectId objectId) throws IOException { + return openObjectImpl1(curs, objectId); + } + + @Override + ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) + throws IOException { + if (unpackedObjects.contains(objectId)) + return wrapped.openObject2(curs, objectId.name(), objectId); + return wrapped.openObject1(curs, objectId); + } + + @Override + boolean hasObject2(String objectId) { + // This method should never be invoked. + throw new UnsupportedOperationException(); + } + + @Override + ObjectLoader openObject2(WindowCursor curs, String objectName, + AnyObjectId objectId) throws IOException { + // This method should never be invoked. + throw new UnsupportedOperationException(); + } + + @Override + void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, + WindowCursor curs) throws IOException { + wrapped.selectObjectRepresentation(packer, otp, curs); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java new file mode 100644 index 0000000000..1a5fe8ea90 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; + +/** Reads a deltified object which uses an offset to find its base. */ +class DeltaOfsPackedObjectLoader extends DeltaPackedObjectLoader { + private final long deltaBase; + + DeltaOfsPackedObjectLoader(final PackFile pr, final long objectOffset, + final int headerSz, final int deltaSz, final long base) { + super(pr, objectOffset, headerSz, deltaSz); + deltaBase = base; + } + + protected PackedObjectLoader getBaseLoader(final WindowCursor curs) + throws IOException { + return pack.resolveBase(curs, deltaBase); + } + + @Override + public int getRawType() { + return Constants.OBJ_OFS_DELTA; + } + + @Override + ObjectId getDeltaBase() throws IOException { + final ObjectId id = pack.findObjectForOffset(deltaBase); + if (id == null) + throw new CorruptObjectException( + JGitText.get().offsetWrittenDeltaBaseForObjectNotFoundInAPack); + return id; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java new file mode 100644 index 0000000000..4a9323cccf --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.zip.DataFormatException; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.BinaryDelta; +import org.eclipse.jgit.lib.Constants; + +/** Reader for a deltified object stored in a pack file. */ +abstract class DeltaPackedObjectLoader extends PackedObjectLoader { + private static final int OBJ_COMMIT = Constants.OBJ_COMMIT; + + private final int deltaSize; + + DeltaPackedObjectLoader(final PackFile pr, final long objectOffset, + final int headerSize, final int deltaSz) { + super(pr, objectOffset, headerSize); + objectType = -1; + deltaSize = deltaSz; + } + + @Override + void materialize(final WindowCursor curs) throws IOException { + if (cachedBytes != null) { + return; + } + + if (objectType != OBJ_COMMIT) { + UnpackedObjectCache.Entry cache = pack.readCache(objectOffset); + if (cache != null) { + curs.release(); + objectType = cache.type; + objectSize = cache.data.length; + cachedBytes = cache.data; + return; + } + } + + try { + final PackedObjectLoader baseLoader = getBaseLoader(curs); + baseLoader.materialize(curs); + cachedBytes = BinaryDelta.apply(baseLoader.getCachedBytes(), pack + .decompress(objectOffset + headerSize, deltaSize, curs)); + curs.release(); + objectType = baseLoader.getType(); + objectSize = cachedBytes.length; + if (objectType != OBJ_COMMIT) + pack.saveCache(objectOffset, cachedBytes, objectType); + } catch (DataFormatException dfe) { + final CorruptObjectException coe; + coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream, + objectOffset, pack.getPackFile())); + coe.initCause(dfe); + throw coe; + } + } + + @Override + public long getRawSize() { + return deltaSize; + } + + /** + * @param curs + * temporary thread storage during data access. + * @return the object loader for the base object + * @throws IOException + */ + protected abstract PackedObjectLoader getBaseLoader(WindowCursor curs) + throws IOException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java new file mode 100644 index 0000000000..40e1975e94 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; + +/** Reads a deltified object which uses an {@link ObjectId} to find its base. */ +class DeltaRefPackedObjectLoader extends DeltaPackedObjectLoader { + private final ObjectId deltaBase; + + DeltaRefPackedObjectLoader(final PackFile pr, final long objectOffset, + final int headerSz, final int deltaSz, final ObjectId base) { + super(pr, objectOffset, headerSz, deltaSz); + deltaBase = base; + } + + protected PackedObjectLoader getBaseLoader(final WindowCursor curs) + throws IOException { + final PackedObjectLoader or = pack.get(curs, deltaBase); + if (or == null) + throw new MissingObjectException(deltaBase, "delta base"); + return or; + } + + @Override + public int getRawType() { + return Constants.OBJ_REF_DELTA; + } + + @Override + ObjectId getDeltaBase() throws IOException { + return deltaBase; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java new file mode 100644 index 0000000000..b39efb0e8b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2008-2009, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * Copyright (C) 2008, Thad Hughes + * 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.storage.file; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * The configuration file that is stored in the file of the file system. + */ +public class FileBasedConfig extends Config { + private final File configFile; + private volatile long lastModified; + + /** + * Create a configuration with no default fallback. + * + * @param cfgLocation + * the location of the configuration file on the file system + */ + public FileBasedConfig(File cfgLocation) { + this(null, cfgLocation); + } + + /** + * The constructor + * + * @param base + * the base configuration file + * @param cfgLocation + * the location of the configuration file on the file system + */ + public FileBasedConfig(Config base, File cfgLocation) { + super(base); + configFile = cfgLocation; + } + + /** @return location of the configuration file on disk */ + public final File getFile() { + return configFile; + } + + /** + * Load the configuration as a Git text style configuration file. + *

+ * If the file does not exist, this configuration is cleared, and thus + * behaves the same as though the file exists, but is empty. + * + * @throws IOException + * the file could not be read (but does exist). + * @throws ConfigInvalidException + * the file is not a properly formatted configuration file. + */ + public void load() throws IOException, ConfigInvalidException { + lastModified = getFile().lastModified(); + try { + fromText(RawParseUtils.decode(IO.readFully(getFile()))); + } catch (FileNotFoundException noFile) { + clear(); + } catch (IOException e) { + final IOException e2 = new IOException(MessageFormat.format(JGitText.get().cannotReadFile, getFile())); + e2.initCause(e); + throw e2; + } catch (ConfigInvalidException e) { + throw new ConfigInvalidException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()), e); + } + } + + /** + * Save the configuration as a Git text style configuration file. + *

+ * Warning: Although this method uses the traditional Git file + * locking approach to protect against concurrent writes of the + * configuration file, it does not ensure that the file has not been + * modified since the last read, which means updates performed by other + * objects accessing the same backing file may be lost. + * + * @throws IOException + * the file could not be written. + */ + public void save() throws IOException { + final byte[] out = Constants.encode(toText()); + final LockFile lf = new LockFile(getFile()); + if (!lf.lock()) + throw new IOException(MessageFormat.format(JGitText.get().cannotLockFile, getFile())); + try { + lf.setNeedStatInformation(true); + lf.write(out); + if (!lf.commit()) + throw new IOException(MessageFormat.format(JGitText.get().cannotCommitWriteTo, getFile())); + } finally { + lf.unlock(); + } + lastModified = lf.getCommitLastModified(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + getFile().getPath() + "]"; + } + + /** + * @return returns true if the currently loaded configuration file is older + * than the file on disk + */ + public boolean isOutdated() { + return getFile().lastModified() != lastModified; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java new file mode 100644 index 0000000000..6266a7c084 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2010, 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.storage.file; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackWriter; + +abstract class FileObjectDatabase extends ObjectDatabase { + @Override + public ObjectReader newReader() { + return new WindowCursor(this); + } + + /** + * Does the requested object exist in this database? + *

+ * Alternates (if present) are searched automatically. + * + * @param objectId + * identity of the object to test for existence of. + * @return true if the specified object is stored in this database, or any + * of the alternate databases. + */ + public boolean hasObject(final AnyObjectId objectId) { + return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name()); + } + + final boolean hasObjectImpl1(final AnyObjectId objectId) { + if (hasObject1(objectId)) + return true; + + for (final AlternateHandle alt : myAlternates()) { + if (alt.db.hasObjectImpl1(objectId)) + return true; + } + + return tryAgain1() && hasObject1(objectId); + } + + final boolean hasObjectImpl2(final String objectId) { + if (hasObject2(objectId)) + return true; + + for (final AlternateHandle alt : myAlternates()) { + if (alt.db.hasObjectImpl2(objectId)) + return true; + } + + return false; + } + + /** + * Open an object from this database. + *

+ * Alternates (if present) are searched automatically. + * + * @param curs + * temporary working space associated with the calling thread. + * @param objectId + * identity of the object to open. + * @return a {@link ObjectLoader} for accessing the data of the named + * object, or null if the object does not exist. + * @throws IOException + */ + ObjectLoader openObject(final WindowCursor curs, final AnyObjectId objectId) + throws IOException { + ObjectLoader ldr; + + ldr = openObjectImpl1(curs, objectId); + if (ldr != null) + return ldr; + + ldr = openObjectImpl2(curs, objectId.name(), objectId); + if (ldr != null) + return ldr; + + return null; + } + + final ObjectLoader openObjectImpl1(final WindowCursor curs, + final AnyObjectId objectId) throws IOException { + ObjectLoader ldr; + + ldr = openObject1(curs, objectId); + if (ldr != null) + return ldr; + + for (final AlternateHandle alt : myAlternates()) { + ldr = alt.db.openObjectImpl1(curs, objectId); + if (ldr != null) + return ldr; + } + + if (tryAgain1()) { + ldr = openObject1(curs, objectId); + if (ldr != null) + return ldr; + } + + return null; + } + + final ObjectLoader openObjectImpl2(final WindowCursor curs, + final String objectName, final AnyObjectId objectId) + throws IOException { + ObjectLoader ldr; + + ldr = openObject2(curs, objectName, objectId); + if (ldr != null) + return ldr; + + for (final AlternateHandle alt : myAlternates()) { + ldr = alt.db.openObjectImpl2(curs, objectName, objectId); + if (ldr != null) + return ldr; + } + + return null; + } + + abstract void selectObjectRepresentation(PackWriter packer, + ObjectToPack otp, WindowCursor curs) throws IOException; + + abstract File getDirectory(); + + abstract AlternateHandle[] myAlternates(); + + abstract boolean tryAgain1(); + + abstract boolean hasObject1(AnyObjectId objectId); + + abstract boolean hasObject2(String objectId); + + abstract ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) + throws IOException; + + abstract ObjectLoader openObject2(WindowCursor curs, String objectName, + AnyObjectId objectId) throws IOException; + + abstract FileObjectDatabase newCachedFileObjectDatabase(); + + static class AlternateHandle { + final FileObjectDatabase db; + + AlternateHandle(FileObjectDatabase db) { + this.db = db; + } + + void close() { + db.close(); + } + } + + static class AlternateRepository extends AlternateHandle { + final FileRepository repository; + + AlternateRepository(FileRepository r) { + super(r.getObjectDatabase()); + repository = r; + } + + void close() { + repository.close(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java new file mode 100644 index 0000000000..c86549b2e2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008-2010, Google Inc. + * Copyright (C) 2006-2010, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.BaseRepositoryBuilder; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileObjectDatabase.AlternateHandle; +import org.eclipse.jgit.storage.file.FileObjectDatabase.AlternateRepository; +import org.eclipse.jgit.util.SystemReader; + +/** + * Represents a Git repository. A repository holds all objects and refs used for + * managing source code (could by any type of file, but source code is what + * SCM's are typically used for). + * + * In Git terms all data is stored in GIT_DIR, typically a directory called + * .git. A work tree is maintained unless the repository is a bare repository. + * Typically the .git directory is located at the root of the work dir. + * + *

    + *
  • GIT_DIR + *
      + *
    • objects/ - objects
    • + *
    • refs/ - tags and heads
    • + *
    • config - configuration
    • + *
    • info/ - more configurations
    • + *
    + *
  • + *
+ *

+ * This class is thread-safe. + *

+ * This implementation only handles a subtly undocumented subset of git features. + * + */ +public class FileRepository extends Repository { + private final FileBasedConfig userConfig; + + private final FileBasedConfig repoConfig; + + private final RefDatabase refs; + + private final ObjectDirectory objectDatabase; + + /** + * Construct a representation of a Git repository. + *

+ * The work tree, object directory, alternate object directories and index + * file locations are deduced from the given git directory and the default + * rules by running {@link FileRepositoryBuilder}. This constructor is the + * same as saying: + * + *

+	 * new FileRepositoryBuilder().setGitDir(gitDir).build()
+	 * 
+ * + * @param gitDir + * GIT_DIR (the location of the repository metadata). + * @throws IOException + * the repository appears to already exist but cannot be + * accessed. + * @see FileRepositoryBuilder + */ + public FileRepository(final File gitDir) throws IOException { + this(new FileRepositoryBuilder().setGitDir(gitDir).setup()); + } + + /** + * Create a repository using the local file system. + * + * @param options + * description of the repository's important paths. + * @throws IOException + * the user configuration file or repository configuration file + * cannot be accessed. + */ + public FileRepository(final BaseRepositoryBuilder options) throws IOException { + super(options); + + userConfig = SystemReader.getInstance().openUserConfig(getFS()); + repoConfig = new FileBasedConfig(userConfig, getFS().resolve(getDirectory(), "config")); + + loadUserConfig(); + loadRepoConfig(); + + refs = new RefDirectory(this); + objectDatabase = new ObjectDirectory(repoConfig, // + options.getObjectDirectory(), // + options.getAlternateObjectDirectories(), // + getFS()); + + if (objectDatabase.exists()) { + final String repositoryFormatVersion = getConfig().getString( + ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION); + if (!"0".equals(repositoryFormatVersion)) { + throw new IOException(MessageFormat.format( + JGitText.get().unknownRepositoryFormat2, + repositoryFormatVersion)); + } + } + } + + private void loadUserConfig() throws IOException { + try { + userConfig.load(); + } catch (ConfigInvalidException e1) { + IOException e2 = new IOException(MessageFormat.format(JGitText + .get().userConfigFileInvalid, userConfig.getFile() + .getAbsolutePath(), e1)); + e2.initCause(e1); + throw e2; + } + } + + private void loadRepoConfig() throws IOException { + try { + repoConfig.load(); + } catch (ConfigInvalidException e1) { + IOException e2 = new IOException(JGitText.get().unknownRepositoryFormat); + e2.initCause(e1); + throw e2; + } + } + + /** + * Create a new Git repository initializing the necessary files and + * directories. + * + * @param bare + * if true, a bare repository is created. + * + * @throws IOException + * in case of IO problem + */ + public void create(boolean bare) throws IOException { + final FileBasedConfig cfg = getConfig(); + if (cfg.getFile().exists()) { + throw new IllegalStateException(MessageFormat.format( + JGitText.get().repositoryAlreadyExists, getDirectory())); + } + getDirectory().mkdirs(); + refs.create(); + objectDatabase.create(); + + new File(getDirectory(), "branches").mkdir(); + + RefUpdate head = updateRef(Constants.HEAD); + head.disableRefLog(); + head.link(Constants.R_HEADS + Constants.MASTER); + + cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0); + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_FILEMODE, true); + if (bare) + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_BARE, true); + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare); + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, false); + cfg.save(); + } + + /** + * @return the directory containing the objects owned by this repository. + */ + public File getObjectsDirectory() { + return objectDatabase.getDirectory(); + } + + /** + * @return the object database which stores this repository's data. + */ + public ObjectDirectory getObjectDatabase() { + return objectDatabase; + } + + /** @return the reference database which stores the reference namespace. */ + public RefDatabase getRefDatabase() { + return refs; + } + + /** + * @return the configuration of this repository + */ + public FileBasedConfig getConfig() { + if (userConfig.isOutdated()) { + try { + loadUserConfig(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if (repoConfig.isOutdated()) { + try { + loadRepoConfig(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return repoConfig; + } + + /** + * Objects known to exist but not expressed by {@link #getAllRefs()}. + *

+ * When a repository borrows objects from another repository, it can + * advertise that it safely has that other repository's references, without + * exposing any other details about the other repository. This may help + * a client trying to push changes avoid pushing more than it needs to. + * + * @return unmodifiable collection of other known objects. + */ + public Set getAdditionalHaves() { + HashSet r = new HashSet(); + for (AlternateHandle d : objectDatabase. myAlternates()) { + if (d instanceof AlternateRepository) { + Repository repo; + + repo = ((AlternateRepository) d).repository; + for (Ref ref : repo.getAllRefs().values()) + r.add(ref.getObjectId()); + r.addAll(repo.getAdditionalHaves()); + } + } + return r; + } + + /** + * Add a single existing pack to the list of available pack files. + * + * @param pack + * path of the pack file to open. + * @param idx + * path of the corresponding index file. + * @throws IOException + * index file could not be opened, read, or is not recognized as + * a Git pack file index. + */ + public void openPack(final File pack, final File idx) throws IOException { + objectDatabase.openPack(pack, idx); + } + + /** + * Force a scan for changed refs. + * + * @throws IOException + */ + public void scanForRepoChanges() throws IOException { + getAllRefs(); // This will look for changes to refs + if (!isBare()) + getIndex(); // This will detect changes in the index + } + + /** + * @param refName + * @return a {@link ReflogReader} for the supplied refname, or null if the + * named ref does not exist. + * @throws IOException the ref could not be accessed. + */ + public ReflogReader getReflogReader(String refName) throws IOException { + Ref ref = getRef(refName); + if (ref != null) + return new ReflogReader(this, ref.getName()); + return null; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java new file mode 100644 index 0000000000..31d3e99ad6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010, 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.storage.file; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.lib.BaseRepositoryBuilder; + +/** + * Constructs a {@link FileRepository}. + *

+ * Applications must set one of {@link #setGitDir(File)} or + * {@link #setWorkTree(File)}, or use {@link #readEnvironment()} or + * {@link #findGitDir()} in order to configure the minimum property set + * necessary to open a repository. + *

+ * Single repository applications trying to be compatible with other Git + * implementations are encouraged to use a model such as: + * + *

+ * new FileRepositoryBuilder() //
+ * 		.setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null
+ * 		.readEnviroment() // scan environment GIT_* variables
+ * 		.findGitDir() // scan up the file system tree
+ * 		.build()
+ * 
+ */ +public class FileRepositoryBuilder extends + BaseRepositoryBuilder { + /** + * Create a repository matching the configuration in this builder. + *

+ * If an option was not set, the build method will try to default the option + * based on other options. If insufficient information is available, an + * exception is thrown to the caller. + * + * @return a repository matching this configuration. + * @throws IllegalArgumentException + * insufficient parameters were set. + * @throws IOException + * the repository could not be accessed to configure the rest of + * the builder's parameters. + */ + @Override + public FileRepository build() throws IOException { + return new FileRepository(setup()); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java new file mode 100644 index 0000000000..4949b507eb --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2010, 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.storage.file; + +import java.io.IOException; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.StoredObjectRepresentation; + +class LocalObjectRepresentation extends StoredObjectRepresentation { + final PackedObjectLoader ldr; + + LocalObjectRepresentation(PackedObjectLoader ldr) { + this.ldr = ldr; + } + + @Override + public int getFormat() { + if (ldr instanceof DeltaPackedObjectLoader) + return PACK_DELTA; + if (ldr instanceof WholePackedObjectLoader) + return PACK_WHOLE; + return FORMAT_OTHER; + } + + @Override + public int getWeight() { + long sz = ldr.getRawSize(); + if (Integer.MAX_VALUE < sz) + return WEIGHT_UNKNOWN; + return (int) sz; + } + + @Override + public ObjectId getDeltaBase() { + try { + return ldr.getDeltaBase(); + } catch (IOException e) { + return null; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java new file mode 100644 index 0000000000..9ee43f398d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010, 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.storage.file; + +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.StoredObjectRepresentation; +import org.eclipse.jgit.revwalk.RevObject; + +/** {@link ObjectToPack} for {@link ObjectDirectory}. */ +class LocalObjectToPack extends ObjectToPack { + /** Pack to reuse compressed data from, otherwise null. */ + PackFile copyFromPack; + + /** Offset of the object's header in {@link #copyFromPack}. */ + long copyOffset; + + LocalObjectToPack(RevObject obj) { + super(obj); + } + + @Override + public void select(StoredObjectRepresentation ref) { + LocalObjectRepresentation ptr = (LocalObjectRepresentation)ref; + this.copyFromPack = ptr.ldr.pack; + this.copyOffset = ptr.ldr.objectOffset; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java new file mode 100644 index 0000000000..ad89a24847 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.text.MessageFormat; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; + +/** + * Git style file locking and replacement. + *

+ * To modify a ref file Git tries to use an atomic update approach: we write the + * new data into a brand new file, then rename it in place over the old name. + * This way we can just delete the temporary file if anything goes wrong, and + * nothing has been damaged. To coordinate access from multiple processes at + * once Git tries to atomically create the new temporary file under a well-known + * name. + */ +public class LockFile { + static final String SUFFIX = ".lock"; //$NON-NLS-1$ + + /** Filter to skip over active lock files when listing a directory. */ + static final FilenameFilter FILTER = new FilenameFilter() { + public boolean accept(File dir, String name) { + return !name.endsWith(SUFFIX); + } + }; + + private final File ref; + + private final File lck; + + private FileLock fLck; + + private boolean haveLck; + + private FileOutputStream os; + + private boolean needStatInformation; + + private long commitLastModified; + + /** + * Create a new lock for any file. + * + * @param f + * the file that will be locked. + */ + public LockFile(final File f) { + ref = f; + lck = new File(ref.getParentFile(), ref.getName() + SUFFIX); + } + + /** + * Try to establish the lock. + * + * @return true if the lock is now held by the caller; false if it is held + * by someone else. + * @throws IOException + * the temporary output file could not be created. The caller + * does not hold the lock. + */ + public boolean lock() throws IOException { + lck.getParentFile().mkdirs(); + if (lck.createNewFile()) { + haveLck = true; + try { + os = new FileOutputStream(lck); + try { + fLck = os.getChannel().tryLock(); + if (fLck == null) + throw new OverlappingFileLockException(); + } catch (OverlappingFileLockException ofle) { + // We cannot use unlock() here as this file is not + // held by us, but we thought we created it. We must + // not delete it, as it belongs to some other process. + // + haveLck = false; + try { + os.close(); + } catch (IOException ioe) { + // Fail by returning haveLck = false. + } + os = null; + } + } catch (IOException ioe) { + unlock(); + throw ioe; + } + } + return haveLck; + } + + /** + * Try to establish the lock for appending. + * + * @return true if the lock is now held by the caller; false if it is held + * by someone else. + * @throws IOException + * the temporary output file could not be created. The caller + * does not hold the lock. + */ + public boolean lockForAppend() throws IOException { + if (!lock()) + return false; + copyCurrentContent(); + return true; + } + + /** + * Copy the current file content into the temporary file. + *

+ * This method saves the current file content by inserting it into the + * temporary file, so that the caller can safely append rather than replace + * the primary file. + *

+ * This method does nothing if the current file does not exist, or exists + * but is empty. + * + * @throws IOException + * the temporary file could not be written, or a read error + * occurred while reading from the current file. The lock is + * released before throwing the underlying IO exception to the + * caller. + * @throws RuntimeException + * the temporary file could not be written. The lock is released + * before throwing the underlying exception to the caller. + */ + public void copyCurrentContent() throws IOException { + requireLock(); + try { + final FileInputStream fis = new FileInputStream(ref); + try { + final byte[] buf = new byte[2048]; + int r; + while ((r = fis.read(buf)) >= 0) + os.write(buf, 0, r); + } finally { + fis.close(); + } + } catch (FileNotFoundException fnfe) { + // Don't worry about a file that doesn't exist yet, it + // conceptually has no current content to copy. + // + } catch (IOException ioe) { + unlock(); + throw ioe; + } catch (RuntimeException ioe) { + unlock(); + throw ioe; + } catch (Error ioe) { + unlock(); + throw ioe; + } + } + + /** + * Write an ObjectId and LF to the temporary file. + * + * @param id + * the id to store in the file. The id will be written in hex, + * followed by a sole LF. + * @throws IOException + * the temporary file could not be written. The lock is released + * before throwing the underlying IO exception to the caller. + * @throws RuntimeException + * the temporary file could not be written. The lock is released + * before throwing the underlying exception to the caller. + */ + public void write(final ObjectId id) throws IOException { + requireLock(); + try { + final BufferedOutputStream b; + b = new BufferedOutputStream(os, Constants.OBJECT_ID_STRING_LENGTH + 1); + id.copyTo(b); + b.write('\n'); + b.flush(); + fLck.release(); + b.close(); + os = null; + } catch (IOException ioe) { + unlock(); + throw ioe; + } catch (RuntimeException ioe) { + unlock(); + throw ioe; + } catch (Error ioe) { + unlock(); + throw ioe; + } + } + + /** + * Write arbitrary data to the temporary file. + * + * @param content + * the bytes to store in the temporary file. No additional bytes + * are added, so if the file must end with an LF it must appear + * at the end of the byte array. + * @throws IOException + * the temporary file could not be written. The lock is released + * before throwing the underlying IO exception to the caller. + * @throws RuntimeException + * the temporary file could not be written. The lock is released + * before throwing the underlying exception to the caller. + */ + public void write(final byte[] content) throws IOException { + requireLock(); + try { + os.write(content); + os.flush(); + fLck.release(); + os.close(); + os = null; + } catch (IOException ioe) { + unlock(); + throw ioe; + } catch (RuntimeException ioe) { + unlock(); + throw ioe; + } catch (Error ioe) { + unlock(); + throw ioe; + } + } + + /** + * Obtain the direct output stream for this lock. + *

+ * The stream may only be accessed once, and only after {@link #lock()} has + * been successfully invoked and returned true. Callers must close the + * stream prior to calling {@link #commit()} to commit the change. + * + * @return a stream to write to the new file. The stream is unbuffered. + */ + public OutputStream getOutputStream() { + requireLock(); + return new OutputStream() { + @Override + public void write(final byte[] b, final int o, final int n) + throws IOException { + os.write(b, o, n); + } + + @Override + public void write(final byte[] b) throws IOException { + os.write(b); + } + + @Override + public void write(final int b) throws IOException { + os.write(b); + } + + @Override + public void flush() throws IOException { + os.flush(); + } + + @Override + public void close() throws IOException { + try { + os.flush(); + fLck.release(); + os.close(); + os = null; + } catch (IOException ioe) { + unlock(); + throw ioe; + } catch (RuntimeException ioe) { + unlock(); + throw ioe; + } catch (Error ioe) { + unlock(); + throw ioe; + } + } + }; + } + + private void requireLock() { + if (os == null) { + unlock(); + throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref)); + } + } + + /** + * Request that {@link #commit()} remember modification time. + * + * @param on + * true if the commit method must remember the modification time. + */ + public void setNeedStatInformation(final boolean on) { + needStatInformation = on; + } + + /** + * Wait until the lock file information differs from the old file. + *

+ * This method tests both the length and the last modification date. If both + * are the same, this method sleeps until it can force the new lock file's + * modification date to be later than the target file. + * + * @throws InterruptedException + * the thread was interrupted before the last modified date of + * the lock file was different from the last modified date of + * the target file. + */ + public void waitForStatChange() throws InterruptedException { + if (ref.length() == lck.length()) { + long otime = ref.lastModified(); + long ntime = lck.lastModified(); + while (otime == ntime) { + Thread.sleep(25 /* milliseconds */); + lck.setLastModified(System.currentTimeMillis()); + ntime = lck.lastModified(); + } + } + } + + /** + * Commit this change and release the lock. + *

+ * If this method fails (returns false) the lock is still released. + * + * @return true if the commit was successful and the file contains the new + * data; false if the commit failed and the file remains with the + * old data. + * @throws IllegalStateException + * the lock is not held. + */ + public boolean commit() { + if (os != null) { + unlock(); + throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotClosed, ref)); + } + + saveStatInformation(); + if (lck.renameTo(ref)) + return true; + if (!ref.exists() || ref.delete()) + if (lck.renameTo(ref)) + return true; + unlock(); + return false; + } + + private void saveStatInformation() { + if (needStatInformation) + commitLastModified = lck.lastModified(); + } + + /** + * Get the modification time of the output file when it was committed. + * + * @return modification time of the lock file right before we committed it. + */ + public long getCommitLastModified() { + return commitLastModified; + } + + /** + * Unlock this file and abort this change. + *

+ * The temporary file (if created) is deleted before returning. + */ + public void unlock() { + if (os != null) { + if (fLck != null) { + try { + fLck.release(); + } catch (IOException ioe) { + // Huh? + } + fLck = null; + } + try { + os.close(); + } catch (IOException ioe) { + // Ignore this + } + os = null; + } + + if (haveLck) { + haveLck = false; + lck.delete(); + } + } + + @Override + public String toString() { + return "LockFile[" + lck + ", haveLck=" + haveLck + "]"; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java new file mode 100644 index 0000000000..8a8055605a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2009, 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.storage.file; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.PackMismatchException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.lib.RepositoryCache; +import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.util.FS; + +/** + * Traditional file system based {@link ObjectDatabase}. + *

+ * This is the classical object database representation for a Git repository, + * where objects are stored loose by hashing them into directories by their + * {@link ObjectId}, or are stored in compressed containers known as + * {@link PackFile}s. + *

+ * Optionally an object database can reference one or more alternates; other + * ObjectDatabase instances that are searched in addition to the current + * database. + *

+ * Databases are divided into two halves: a half that is considered to be fast + * to search (the {@code PackFile}s), and a half that is considered to be slow + * to search (loose objects). When alternates are present the fast half is fully + * searched (recursively through all alternates) before the slow half is + * considered. + */ +public class ObjectDirectory extends FileObjectDatabase { + private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]); + + private final Config config; + + private final File objects; + + private final File infoDirectory; + + private final File packDirectory; + + private final File alternatesFile; + + private final AtomicReference packList; + + private final FS fs; + + private final AtomicReference alternates; + + /** + * Initialize a reference to an on-disk object directory. + * + * @param cfg + * configuration this directory consults for write settings. + * @param dir + * the location of the objects directory. + * @param alternatePaths + * a list of alternate object directories + * @param fs + * the file system abstraction which will be necessary to perform + * certain file system operations. + * @throws IOException + * an alternate object cannot be opened. + */ + public ObjectDirectory(final Config cfg, final File dir, + File[] alternatePaths, FS fs) throws IOException { + config = cfg; + objects = dir; + infoDirectory = new File(objects, "info"); + packDirectory = new File(objects, "pack"); + alternatesFile = new File(infoDirectory, "alternates"); + packList = new AtomicReference(NO_PACKS); + this.fs = fs; + + alternates = new AtomicReference(); + if (alternatePaths != null) { + AlternateHandle[] alt; + + alt = new AlternateHandle[alternatePaths.length]; + for (int i = 0; i < alternatePaths.length; i++) + alt[i] = openAlternate(alternatePaths[i]); + alternates.set(alt); + } + } + + /** + * @return the location of the objects directory. + */ + public final File getDirectory() { + return objects; + } + + @Override + public boolean exists() { + return objects.exists(); + } + + @Override + public void create() throws IOException { + objects.mkdirs(); + infoDirectory.mkdir(); + packDirectory.mkdir(); + } + + @Override + public ObjectInserter newInserter() { + return new ObjectDirectoryInserter(this, config); + } + + @Override + public void close() { + final PackList packs = packList.get(); + packList.set(NO_PACKS); + for (final PackFile p : packs.packs) + p.close(); + + // Fully close all loaded alternates and clear the alternate list. + AlternateHandle[] alt = alternates.get(); + if (alt != null) { + alternates.set(null); + for(final AlternateHandle od : alt) + od.close(); + } + } + + /** + * Compute the location of a loose object file. + * + * @param objectId + * identity of the loose object to map to the directory. + * @return location of the object, if it were to exist as a loose object. + */ + public File fileFor(final AnyObjectId objectId) { + return fileFor(objectId.name()); + } + + private File fileFor(final String objectName) { + final String d = objectName.substring(0, 2); + final String f = objectName.substring(2); + return new File(new File(objects, d), f); + } + + /** + * @return unmodifiable collection of all known pack files local to this + * directory. Most recent packs are presented first. Packs most + * likely to contain more recent objects appear before packs + * containing objects referenced by commits further back in the + * history of the repository. + */ + public Collection getPacks() { + final PackFile[] packs = packList.get().packs; + return Collections.unmodifiableCollection(Arrays.asList(packs)); + } + + /** + * Add a single existing pack to the list of available pack files. + * + * @param pack + * path of the pack file to open. + * @param idx + * path of the corresponding index file. + * @throws IOException + * index file could not be opened, read, or is not recognized as + * a Git pack file index. + */ + public void openPack(final File pack, final File idx) throws IOException { + final String p = pack.getName(); + final String i = idx.getName(); + + if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack")) + throw new IOException(MessageFormat.format(JGitText.get().notAValidPack, pack)); + + if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx")) + throw new IOException(MessageFormat.format(JGitText.get().notAValidPack, idx)); + + if (!p.substring(0, 45).equals(i.substring(0, 45))) + throw new IOException(MessageFormat.format(JGitText.get().packDoesNotMatchIndex, pack)); + + insertPack(new PackFile(idx, pack)); + } + + @Override + public String toString() { + return "ObjectDirectory[" + getDirectory() + "]"; + } + + boolean hasObject1(final AnyObjectId objectId) { + for (final PackFile p : packList.get().packs) { + try { + if (p.hasObject(objectId)) { + return true; + } + } catch (IOException e) { + // The hasObject call should have only touched the index, + // so any failure here indicates the index is unreadable + // by this process, and the pack is likewise not readable. + // + removePack(p); + continue; + } + } + return false; + } + + ObjectLoader openObject1(final WindowCursor curs, + final AnyObjectId objectId) throws IOException { + PackList pList = packList.get(); + SEARCH: for (;;) { + for (final PackFile p : pList.packs) { + try { + final PackedObjectLoader ldr = p.get(curs, objectId); + if (ldr != null) { + ldr.materialize(curs); + return ldr; + } + } catch (PackMismatchException e) { + // Pack was modified; refresh the entire pack list. + // + pList = scanPacks(pList); + continue SEARCH; + } catch (IOException e) { + // Assume the pack is corrupted. + // + removePack(p); + } + } + return null; + } + } + + @Override + void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, + WindowCursor curs) throws IOException { + PackList pList = packList.get(); + SEARCH: for (;;) { + for (final PackFile p : pList.packs) { + try { + PackedObjectLoader ldr = p.get(curs, otp); + if (ldr != null) + packer.select(otp, new LocalObjectRepresentation(ldr)); + } catch (PackMismatchException e) { + // Pack was modified; refresh the entire pack list. + // + pList = scanPacks(pList); + continue SEARCH; + } catch (IOException e) { + // Assume the pack is corrupted. + // + removePack(p); + } + } + break SEARCH; + } + + for (AlternateHandle h : myAlternates()) + h.db.selectObjectRepresentation(packer, otp, curs); + } + + boolean hasObject2(final String objectName) { + return fileFor(objectName).exists(); + } + + ObjectLoader openObject2(final WindowCursor curs, + final String objectName, final AnyObjectId objectId) + throws IOException { + try { + return new UnpackedObjectLoader(fileFor(objectName), objectId); + } catch (FileNotFoundException noFile) { + return null; + } + } + + boolean tryAgain1() { + final PackList old = packList.get(); + if (old.tryAgain(packDirectory.lastModified())) + return old != scanPacks(old); + return false; + } + + private void insertPack(final PackFile pf) { + PackList o, n; + do { + o = packList.get(); + + // If the pack in question is already present in the list + // (picked up by a concurrent thread that did a scan?) we + // do not want to insert it a second time. + // + final PackFile[] oldList = o.packs; + final String name = pf.getPackFile().getName(); + for (PackFile p : oldList) { + if (PackFile.SORT.compare(pf, p) < 0) + break; + if (name.equals(p.getPackFile().getName())) + return; + } + + final PackFile[] newList = new PackFile[1 + oldList.length]; + newList[0] = pf; + System.arraycopy(oldList, 0, newList, 1, oldList.length); + n = new PackList(o.lastRead, o.lastModified, newList); + } while (!packList.compareAndSet(o, n)); + } + + private void removePack(final PackFile deadPack) { + PackList o, n; + do { + o = packList.get(); + + final PackFile[] oldList = o.packs; + final int j = indexOf(oldList, deadPack); + if (j < 0) + break; + + final PackFile[] newList = new PackFile[oldList.length - 1]; + System.arraycopy(oldList, 0, newList, 0, j); + System.arraycopy(oldList, j + 1, newList, j, newList.length - j); + n = new PackList(o.lastRead, o.lastModified, newList); + } while (!packList.compareAndSet(o, n)); + deadPack.close(); + } + + private static int indexOf(final PackFile[] list, final PackFile pack) { + for (int i = 0; i < list.length; i++) { + if (list[i] == pack) + return i; + } + return -1; + } + + private PackList scanPacks(final PackList original) { + synchronized (packList) { + PackList o, n; + do { + o = packList.get(); + if (o != original) { + // Another thread did the scan for us, while we + // were blocked on the monitor above. + // + return o; + } + n = scanPacksImpl(o); + if (n == o) + return n; + } while (!packList.compareAndSet(o, n)); + return n; + } + } + + private PackList scanPacksImpl(final PackList old) { + final Map forReuse = reuseMap(old); + final long lastRead = System.currentTimeMillis(); + final long lastModified = packDirectory.lastModified(); + final Set names = listPackDirectory(); + final List list = new ArrayList(names.size() >> 2); + boolean foundNew = false; + for (final String indexName : names) { + // Must match "pack-[0-9a-f]{40}.idx" to be an index. + // + if (indexName.length() != 49 || !indexName.endsWith(".idx")) + continue; + + final String base = indexName.substring(0, indexName.length() - 4); + final String packName = base + ".pack"; + if (!names.contains(packName)) { + // Sometimes C Git's HTTP fetch transport leaves a + // .idx file behind and does not download the .pack. + // We have to skip over such useless indexes. + // + continue; + } + + final PackFile oldPack = forReuse.remove(packName); + if (oldPack != null) { + list.add(oldPack); + continue; + } + + final File packFile = new File(packDirectory, packName); + final File idxFile = new File(packDirectory, indexName); + list.add(new PackFile(idxFile, packFile)); + foundNew = true; + } + + // If we did not discover any new files, the modification time was not + // changed, and we did not remove any files, then the set of files is + // the same as the set we were given. Instead of building a new object + // return the same collection. + // + if (!foundNew && lastModified == old.lastModified && forReuse.isEmpty()) + return old.updateLastRead(lastRead); + + for (final PackFile p : forReuse.values()) { + p.close(); + } + + if (list.isEmpty()) + return new PackList(lastRead, lastModified, NO_PACKS.packs); + + final PackFile[] r = list.toArray(new PackFile[list.size()]); + Arrays.sort(r, PackFile.SORT); + return new PackList(lastRead, lastModified, r); + } + + private static Map reuseMap(final PackList old) { + final Map forReuse = new HashMap(); + for (final PackFile p : old.packs) { + if (p.invalid()) { + // The pack instance is corrupted, and cannot be safely used + // again. Do not include it in our reuse map. + // + p.close(); + continue; + } + + final PackFile prior = forReuse.put(p.getPackFile().getName(), p); + if (prior != null) { + // This should never occur. It should be impossible for us + // to have two pack files with the same name, as all of them + // came out of the same directory. If it does, we promised to + // close any PackFiles we did not reuse, so close the second, + // readers are likely to be actively using the first. + // + forReuse.put(prior.getPackFile().getName(), prior); + p.close(); + } + } + return forReuse; + } + + private Set listPackDirectory() { + final String[] nameList = packDirectory.list(); + if (nameList == null) + return Collections.emptySet(); + final Set nameSet = new HashSet(nameList.length << 1); + for (final String name : nameList) { + if (name.startsWith("pack-")) + nameSet.add(name); + } + return nameSet; + } + + AlternateHandle[] myAlternates() { + AlternateHandle[] alt = alternates.get(); + if (alt == null) { + synchronized (alternates) { + alt = alternates.get(); + if (alt == null) { + try { + alt = loadAlternates(); + } catch (IOException e) { + alt = new AlternateHandle[0]; + } + alternates.set(alt); + } + } + } + return alt; + } + + private AlternateHandle[] loadAlternates() throws IOException { + final List l = new ArrayList(4); + final BufferedReader br = open(alternatesFile); + try { + String line; + while ((line = br.readLine()) != null) { + l.add(openAlternate(line)); + } + } finally { + br.close(); + } + return l.toArray(new AlternateHandle[l.size()]); + } + + private static BufferedReader open(final File f) + throws FileNotFoundException { + return new BufferedReader(new FileReader(f)); + } + + private AlternateHandle openAlternate(final String location) + throws IOException { + final File objdir = fs.resolve(objects, location); + return openAlternate(objdir); + } + + private AlternateHandle openAlternate(File objdir) throws IOException { + final File parent = objdir.getParentFile(); + if (FileKey.isGitRepository(parent, fs)) { + FileKey key = FileKey.exact(parent, fs); + FileRepository db = (FileRepository) RepositoryCache.open(key); + return new AlternateRepository(db); + } + + ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs); + return new AlternateHandle(db); + } + + private static final class PackList { + /** Last wall-clock time the directory was read. */ + volatile long lastRead; + + /** Last modification time of {@link ObjectDirectory#packDirectory}. */ + final long lastModified; + + /** All known packs, sorted by {@link PackFile#SORT}. */ + final PackFile[] packs; + + private boolean cannotBeRacilyClean; + + PackList(final long lastRead, final long lastModified, + final PackFile[] packs) { + this.lastRead = lastRead; + this.lastModified = lastModified; + this.packs = packs; + this.cannotBeRacilyClean = notRacyClean(lastRead); + } + + private boolean notRacyClean(final long read) { + return read - lastModified > 2 * 60 * 1000L; + } + + PackList updateLastRead(final long now) { + if (notRacyClean(now)) + cannotBeRacilyClean = true; + lastRead = now; + return this; + } + + boolean tryAgain(final long currLastModified) { + // Any difference indicates the directory was modified. + // + if (lastModified != currLastModified) + return true; + + // We have already determined the last read was far enough + // after the last modification that any new modifications + // are certain to change the last modified time. + // + if (cannotBeRacilyClean) + return false; + + if (notRacyClean(lastRead)) { + // Our last read should have marked cannotBeRacilyClean, + // but this thread may not have seen the change. The read + // of the volatile field lastRead should have fixed that. + // + return false; + } + + // We last read this directory too close to its last observed + // modification time. We may have missed a modification. Scan + // the directory again, to ensure we still see the same state. + // + return true; + } + } + + @Override + public ObjectDatabase newCachedDatabase() { + return newCachedFileObjectDatabase(); + } + + FileObjectDatabase newCachedFileObjectDatabase() { + return new CachedObjectDirectory(this); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java new file mode 100644 index 0000000000..e6ed54022f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * Copyright (C) 2009, 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.storage.file; + +import java.io.EOFException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +import org.eclipse.jgit.errors.ObjectWritingException; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; + +/** Creates loose objects in a {@link ObjectDirectory}. */ +class ObjectDirectoryInserter extends ObjectInserter { + private final ObjectDirectory db; + + private final Config config; + + private Deflater deflate; + + ObjectDirectoryInserter(final ObjectDirectory dest, final Config cfg) { + db = dest; + config = cfg; + } + + @Override + public ObjectId insert(final int type, long len, final InputStream is) + throws IOException { + final MessageDigest md = digest(); + final File tmp = toTemp(md, type, len, is); + final ObjectId id = ObjectId.fromRaw(md.digest()); + if (db.hasObject(id)) { + // Object is already in the repository, remove temporary file. + // + tmp.delete(); + return id; + } + + final File dst = db.fileFor(id); + if (tmp.renameTo(dst)) + return id; + + // Maybe the directory doesn't exist yet as the object + // directories are always lazily created. Note that we + // try the rename first as the directory likely does exist. + // + dst.getParentFile().mkdir(); + if (tmp.renameTo(dst)) + return id; + + if (db.hasObject(id)) { + tmp.delete(); + return id; + } + + // The object failed to be renamed into its proper + // location and it doesn't exist in the repository + // either. We really don't know what went wrong, so + // fail. + // + tmp.delete(); + throw new ObjectWritingException("Unable to create new object: " + dst); + } + + @Override + public void flush() throws IOException { + // Do nothing. Objects are immediately visible. + } + + @Override + public void release() { + if (deflate != null) { + try { + deflate.end(); + } finally { + deflate = null; + } + } + } + + private File toTemp(final MessageDigest md, final int type, long len, + final InputStream is) throws IOException, FileNotFoundException, + Error { + boolean delete = true; + File tmp = File.createTempFile("noz", null, db.getDirectory()); + try { + DigestOutputStream dOut = new DigestOutputStream( + compress(new FileOutputStream(tmp)), md); + try { + dOut.write(Constants.encodedTypeString(type)); + dOut.write((byte) ' '); + dOut.write(Constants.encodeASCII(len)); + dOut.write((byte) 0); + + final byte[] buf = buffer(); + while (len > 0) { + int n = is.read(buf, 0, (int) Math.min(len, buf.length)); + if (n <= 0) + throw shortInput(len); + dOut.write(buf, 0, n); + len -= n; + } + } finally { + dOut.close(); + } + + tmp.setReadOnly(); + delete = false; + return tmp; + } finally { + if (delete) + tmp.delete(); + } + } + + private DeflaterOutputStream compress(final OutputStream out) { + if (deflate == null) + deflate = new Deflater(config.get(CoreConfig.KEY).getCompression()); + else + deflate.reset(); + return new DeflaterOutputStream(out, deflate); + } + + private static EOFException shortInput(long missing) { + return new EOFException("Input did not match supplied length. " + + missing + " bytes are missing."); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java new file mode 100644 index 0000000000..e5f6f03f40 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -0,0 +1,700 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel.MapMode; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.zip.CRC32; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.PackInvalidException; +import org.eclipse.jgit.errors.PackMismatchException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackOutputStream; +import org.eclipse.jgit.util.LongList; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * A Git version 2 pack file representation. A pack file contains Git objects in + * delta packed format yielding high compression of lots of object where some + * objects are similar. + */ +public class PackFile implements Iterable { + /** Sorts PackFiles to be most recently created to least recently created. */ + public static Comparator SORT = new Comparator() { + public int compare(final PackFile a, final PackFile b) { + return b.packLastModified - a.packLastModified; + } + }; + + private final File idxFile; + + private final File packFile; + + final int hash; + + private RandomAccessFile fd; + + /** Serializes reads performed against {@link #fd}. */ + private final Object readLock = new Object(); + + long length; + + private int activeWindows; + + private int activeCopyRawData; + + private int packLastModified; + + private volatile boolean invalid; + + private byte[] packChecksum; + + private PackIndex loadedIdx; + + private PackReverseIndex reverseIdx; + + /** + * Objects we have tried to read, and discovered to be corrupt. + *

+ * The list is allocated after the first corruption is found, and filled in + * as more entries are discovered. Typically this list is never used, as + * pack files do not usually contain corrupt objects. + */ + private volatile LongList corruptObjects; + + /** + * Construct a reader for an existing, pre-indexed packfile. + * + * @param idxFile + * path of the .idx file listing the contents. + * @param packFile + * path of the .pack file holding the data. + */ + public PackFile(final File idxFile, final File packFile) { + this.idxFile = idxFile; + this.packFile = packFile; + this.packLastModified = (int) (packFile.lastModified() >> 10); + + // Multiply by 31 here so we can more directly combine with another + // value in WindowCache.hash(), without doing the multiply there. + // + hash = System.identityHashCode(this) * 31; + length = Long.MAX_VALUE; + } + + private synchronized PackIndex idx() throws IOException { + if (loadedIdx == null) { + if (invalid) + throw new PackInvalidException(packFile); + + try { + final PackIndex idx = PackIndex.open(idxFile); + + if (packChecksum == null) + packChecksum = idx.packChecksum; + else if (!Arrays.equals(packChecksum, idx.packChecksum)) + throw new PackMismatchException(JGitText.get().packChecksumMismatch); + + loadedIdx = idx; + } catch (IOException e) { + invalid = true; + throw e; + } + } + return loadedIdx; + } + + final PackedObjectLoader resolveBase(final WindowCursor curs, final long ofs) + throws IOException { + if (isCorrupt(ofs)) { + throw new CorruptObjectException(MessageFormat.format(JGitText + .get().objectAtHasBadZlibStream, ofs, getPackFile())); + } + return reader(curs, ofs); + } + + /** @return the File object which locates this pack on disk. */ + public File getPackFile() { + return packFile; + } + + /** + * Determine if an object is contained within the pack file. + *

+ * For performance reasons only the index file is searched; the main pack + * content is ignored entirely. + *

+ * + * @param id + * the object to look for. Must not be null. + * @return true if the object is in this pack; false otherwise. + * @throws IOException + * the index file cannot be loaded into memory. + */ + public boolean hasObject(final AnyObjectId id) throws IOException { + final long offset = idx().findOffset(id); + return 0 < offset && !isCorrupt(offset); + } + + /** + * Get an object from this pack. + * + * @param curs + * temporary working space associated with the calling thread. + * @param id + * the object to obtain from the pack. Must not be null. + * @return the object loader for the requested object if it is contained in + * this pack; null if the object was not found. + * @throws IOException + * the pack file or the index could not be read. + */ + public PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id) + throws IOException { + final long offset = idx().findOffset(id); + return 0 < offset && !isCorrupt(offset) ? reader(curs, offset) : null; + } + + /** + * Close the resources utilized by this repository + */ + public void close() { + UnpackedObjectCache.purge(this); + WindowCache.purge(this); + synchronized (this) { + loadedIdx = null; + reverseIdx = null; + } + } + + /** + * Provide iterator over entries in associated pack index, that should also + * exist in this pack file. Objects returned by such iterator are mutable + * during iteration. + *

+ * Iterator returns objects in SHA-1 lexicographical order. + *

+ * + * @return iterator over entries of associated pack index + * + * @see PackIndex#iterator() + */ + public Iterator iterator() { + try { + return idx().iterator(); + } catch (IOException e) { + return Collections. emptyList().iterator(); + } + } + + /** + * Obtain the total number of objects available in this pack. This method + * relies on pack index, giving number of effectively available objects. + * + * @return number of objects in index of this pack, likewise in this pack + * @throws IOException + * the index file cannot be loaded into memory. + */ + long getObjectCount() throws IOException { + return idx().getObjectCount(); + } + + /** + * Search for object id with the specified start offset in associated pack + * (reverse) index. + * + * @param offset + * start offset of object to find + * @return object id for this offset, or null if no object was found + * @throws IOException + * the index file cannot be loaded into memory. + */ + ObjectId findObjectForOffset(final long offset) throws IOException { + return getReverseIdx().findObject(offset); + } + + final UnpackedObjectCache.Entry readCache(final long position) { + return UnpackedObjectCache.get(this, position); + } + + final void saveCache(final long position, final byte[] data, final int type) { + UnpackedObjectCache.store(this, position, data, type); + } + + final byte[] decompress(final long position, final int totalSize, + final WindowCursor curs) throws DataFormatException, IOException { + final byte[] dstbuf = new byte[totalSize]; + if (curs.inflate(this, position, dstbuf, 0) != totalSize) + throw new EOFException(MessageFormat.format(JGitText.get().shortCompressedStreamAt, position)); + return dstbuf; + } + + final void copyAsIs(PackOutputStream out, LocalObjectToPack src, + WindowCursor curs) throws IOException, + StoredObjectRepresentationNotAvailableException { + beginCopyAsIs(src); + try { + copyAsIs2(out, src, curs); + } finally { + endCopyAsIs(); + } + } + + private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, + WindowCursor curs) throws IOException, + StoredObjectRepresentationNotAvailableException { + final CRC32 crc1 = new CRC32(); + final CRC32 crc2 = new CRC32(); + final byte[] buf = out.getCopyBuffer(); + + // Rip apart the header so we can discover the size. + // + readFully(src.copyOffset, buf, 0, 20, curs); + int c = buf[0] & 0xff; + final int typeCode = (c >> 4) & 7; + long inflatedLength = c & 15; + int shift = 4; + int headerCnt = 1; + while ((c & 0x80) != 0) { + c = buf[headerCnt++] & 0xff; + inflatedLength += (c & 0x7f) << shift; + shift += 7; + } + + if (typeCode == Constants.OBJ_OFS_DELTA) { + do { + c = buf[headerCnt++] & 0xff; + } while ((c & 128) != 0); + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + } else if (typeCode == Constants.OBJ_REF_DELTA) { + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + + readFully(src.copyOffset + headerCnt, buf, 0, 20, curs); + crc1.update(buf, 0, 20); + crc2.update(buf, 0, headerCnt); + headerCnt += 20; + } else { + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + } + + final long dataOffset = src.copyOffset + headerCnt; + final long dataLength; + final long expectedCRC; + final ByteArrayWindow quickCopy; + + // Verify the object isn't corrupt before sending. If it is, + // we report it missing instead. + // + try { + dataLength = findEndOffset(src.copyOffset) - dataOffset; + quickCopy = curs.quickCopy(this, dataOffset, dataLength); + + if (idx().hasCRC32Support()) { + // Index has the CRC32 code cached, validate the object. + // + expectedCRC = idx().findCRC32(src); + if (quickCopy != null) { + quickCopy.crc32(crc1, dataOffset, (int) dataLength); + } else { + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc1.update(buf, 0, n); + pos += n; + cnt -= n; + } + } + if (crc1.getValue() != expectedCRC) { + setCorrupt(src.copyOffset); + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + src.copyOffset, getPackFile())); + } + } else { + // We don't have a CRC32 code in the index, so compute it + // now while inflating the raw data to get zlib to tell us + // whether or not the data is safe. + // + Inflater inf = curs.inflater(); + byte[] tmp = new byte[1024]; + if (quickCopy != null) { + quickCopy.check(inf, tmp, dataOffset, (int) dataLength); + } else { + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc1.update(buf, 0, n); + inf.setInput(buf, 0, n); + while (inf.inflate(tmp, 0, tmp.length) > 0) + continue; + pos += n; + cnt -= n; + } + } + if (!inf.finished() || inf.getBytesRead() != dataLength) { + setCorrupt(src.copyOffset); + throw new EOFException(MessageFormat.format( + JGitText.get().shortCompressedStreamAt, + src.copyOffset)); + } + expectedCRC = crc1.getValue(); + } + } catch (DataFormatException dataFormat) { + setCorrupt(src.copyOffset); + + CorruptObjectException corruptObject = new CorruptObjectException( + MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + src.copyOffset, getPackFile())); + corruptObject.initCause(dataFormat); + + StoredObjectRepresentationNotAvailableException gone; + gone = new StoredObjectRepresentationNotAvailableException(src); + gone.initCause(corruptObject); + throw gone; + + } catch (IOException ioError) { + StoredObjectRepresentationNotAvailableException gone; + gone = new StoredObjectRepresentationNotAvailableException(src); + gone.initCause(ioError); + throw gone; + } + + if (quickCopy != null) { + // The entire object fits into a single byte array window slice, + // and we have it pinned. Write this out without copying. + // + out.writeHeader(src, inflatedLength); + quickCopy.write(out, dataOffset, (int) dataLength); + + } else if (dataLength <= buf.length) { + // Tiny optimization: Lots of objects are very small deltas or + // deflated commits that are likely to fit in the copy buffer. + // + out.writeHeader(src, inflatedLength); + out.write(buf, 0, (int) dataLength); + } else { + // Now we are committed to sending the object. As we spool it out, + // check its CRC32 code to make sure there wasn't corruption between + // the verification we did above, and us actually outputting it. + // + out.writeHeader(src, inflatedLength); + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc2.update(buf, 0, n); + out.write(buf, 0, n); + pos += n; + cnt -= n; + } + if (crc2.getValue() != expectedCRC) { + throw new CorruptObjectException(MessageFormat.format(JGitText + .get().objectAtHasBadZlibStream, src.copyOffset, + getPackFile())); + } + } + } + + boolean invalid() { + return invalid; + } + + private void readFully(final long position, final byte[] dstbuf, + int dstoff, final int cnt, final WindowCursor curs) + throws IOException { + if (curs.copy(this, position, dstbuf, dstoff, cnt) != cnt) + throw new EOFException(); + } + + private synchronized void beginCopyAsIs(ObjectToPack otp) + throws StoredObjectRepresentationNotAvailableException { + if (++activeCopyRawData == 1 && activeWindows == 0) { + try { + doOpen(); + } catch (IOException thisPackNotValid) { + StoredObjectRepresentationNotAvailableException gone; + + gone = new StoredObjectRepresentationNotAvailableException(otp); + gone.initCause(thisPackNotValid); + throw gone; + } + } + } + + private synchronized void endCopyAsIs() { + if (--activeCopyRawData == 0 && activeWindows == 0) + doClose(); + } + + synchronized boolean beginWindowCache() throws IOException { + if (++activeWindows == 1) { + if (activeCopyRawData == 0) + doOpen(); + return true; + } + return false; + } + + synchronized boolean endWindowCache() { + final boolean r = --activeWindows == 0; + if (r && activeCopyRawData == 0) + doClose(); + return r; + } + + private void doOpen() throws IOException { + try { + if (invalid) + throw new PackInvalidException(packFile); + synchronized (readLock) { + fd = new RandomAccessFile(packFile, "r"); + length = fd.length(); + onOpenPack(); + } + } catch (IOException ioe) { + openFail(); + throw ioe; + } catch (RuntimeException re) { + openFail(); + throw re; + } catch (Error re) { + openFail(); + throw re; + } + } + + private void openFail() { + activeWindows = 0; + activeCopyRawData = 0; + invalid = true; + doClose(); + } + + private void doClose() { + synchronized (readLock) { + if (fd != null) { + try { + fd.close(); + } catch (IOException err) { + // Ignore a close event. We had it open only for reading. + // There should not be errors related to network buffers + // not flushed, etc. + } + fd = null; + } + } + } + + ByteArrayWindow read(final long pos, int size) throws IOException { + synchronized (readLock) { + if (length < pos + size) + size = (int) (length - pos); + final byte[] buf = new byte[size]; + fd.seek(pos); + fd.readFully(buf, 0, size); + return new ByteArrayWindow(this, pos, buf); + } + } + + ByteWindow mmap(final long pos, int size) throws IOException { + synchronized (readLock) { + if (length < pos + size) + size = (int) (length - pos); + + MappedByteBuffer map; + try { + map = fd.getChannel().map(MapMode.READ_ONLY, pos, size); + } catch (IOException ioe1) { + // The most likely reason this failed is the JVM has run out + // of virtual memory. We need to discard quickly, and try to + // force the GC to finalize and release any existing mappings. + // + System.gc(); + System.runFinalization(); + map = fd.getChannel().map(MapMode.READ_ONLY, pos, size); + } + + if (map.hasArray()) + return new ByteArrayWindow(this, pos, map.array()); + return new ByteBufferWindow(this, pos, map); + } + } + + private void onOpenPack() throws IOException { + final PackIndex idx = idx(); + final byte[] buf = new byte[20]; + + fd.seek(0); + fd.readFully(buf, 0, 12); + if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) + throw new IOException(JGitText.get().notAPACKFile); + final long vers = NB.decodeUInt32(buf, 4); + final long packCnt = NB.decodeUInt32(buf, 8); + if (vers != 2 && vers != 3) + throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackVersion, vers)); + + if (packCnt != idx.getObjectCount()) + throw new PackMismatchException(MessageFormat.format( + JGitText.get().packObjectCountMismatch, packCnt, idx.getObjectCount(), getPackFile())); + + fd.seek(length - 20); + fd.read(buf, 0, 20); + if (!Arrays.equals(buf, packChecksum)) + throw new PackMismatchException(MessageFormat.format( + JGitText.get().packObjectCountMismatch + , ObjectId.fromRaw(buf).name() + , ObjectId.fromRaw(idx.packChecksum).name() + , getPackFile())); + } + + private PackedObjectLoader reader(final WindowCursor curs, + final long objOffset) throws IOException { + int p = 0; + final byte[] ib = curs.tempId; + readFully(objOffset, ib, 0, 20, curs); + int c = ib[p++] & 0xff; + final int typeCode = (c >> 4) & 7; + long dataSize = c & 15; + int shift = 4; + while ((c & 0x80) != 0) { + c = ib[p++] & 0xff; + dataSize += (c & 0x7f) << shift; + shift += 7; + } + + switch (typeCode) { + case Constants.OBJ_COMMIT: + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + case Constants.OBJ_TAG: + return new WholePackedObjectLoader(this, objOffset, p, typeCode, + (int) dataSize); + + case Constants.OBJ_OFS_DELTA: { + c = ib[p++] & 0xff; + long ofs = c & 127; + while ((c & 128) != 0) { + ofs += 1; + c = ib[p++] & 0xff; + ofs <<= 7; + ofs += (c & 127); + } + return new DeltaOfsPackedObjectLoader(this, objOffset, p, + (int) dataSize, objOffset - ofs); + } + case Constants.OBJ_REF_DELTA: { + readFully(objOffset + p, ib, 0, 20, curs); + return new DeltaRefPackedObjectLoader(this, objOffset, p + 20, + (int) dataSize, ObjectId.fromRaw(ib)); + } + default: + throw new IOException(MessageFormat.format(JGitText.get().unknownObjectType, typeCode)); + } + } + + private long findEndOffset(final long startOffset) + throws IOException, CorruptObjectException { + final long maxOffset = length - 20; + return getReverseIdx().findNextOffset(startOffset, maxOffset); + } + + private synchronized PackReverseIndex getReverseIdx() throws IOException { + if (reverseIdx == null) + reverseIdx = new PackReverseIndex(idx()); + return reverseIdx; + } + + private boolean isCorrupt(long offset) { + LongList list = corruptObjects; + if (list == null) + return false; + synchronized (list) { + return list.contains(offset); + } + } + + private void setCorrupt(long offset) { + LongList list = corruptObjects; + if (list == null) { + synchronized (readLock) { + list = corruptObjects; + if (list == null) { + list = new LongList(); + corruptObjects = list; + } + } + } + synchronized (list) { + list.add(offset); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java new file mode 100644 index 0000000000..62d1c9d8f8 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Iterator; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.MutableObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.NB; + +/** + * Access path to locate objects by {@link ObjectId} in a {@link PackFile}. + *

+ * Indexes are strictly redundant information in that we can rebuild all of the + * data held in the index file from the on disk representation of the pack file + * itself, but it is faster to access for random requests because data is stored + * by ObjectId. + *

+ */ +public abstract class PackIndex implements Iterable { + /** + * Open an existing pack .idx file for reading. + *

+ * The format of the file will be automatically detected and a proper access + * implementation for that format will be constructed and returned to the + * caller. The file may or may not be held open by the returned instance. + *

+ * + * @param idxFile + * existing pack .idx to read. + * @return access implementation for the requested file. + * @throws FileNotFoundException + * the file does not exist. + * @throws IOException + * the file exists but could not be read due to security errors, + * unrecognized data version, or unexpected data corruption. + */ + public static PackIndex open(final File idxFile) throws IOException { + final FileInputStream fd = new FileInputStream(idxFile); + try { + final byte[] hdr = new byte[8]; + IO.readFully(fd, hdr, 0, hdr.length); + if (isTOC(hdr)) { + final int v = NB.decodeInt32(hdr, 4); + switch (v) { + case 2: + return new PackIndexV2(fd); + default: + throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackIndexVersion, v)); + } + } + return new PackIndexV1(fd, hdr); + } catch (IOException ioe) { + final String path = idxFile.getAbsolutePath(); + final IOException err; + err = new IOException(MessageFormat.format(JGitText.get().unreadablePackIndex, path)); + err.initCause(ioe); + throw err; + } finally { + try { + fd.close(); + } catch (IOException err2) { + // ignore + } + } + } + + private static boolean isTOC(final byte[] h) { + final byte[] toc = PackIndexWriter.TOC; + for (int i = 0; i < toc.length; i++) + if (h[i] != toc[i]) + return false; + return true; + } + + /** Footer checksum applied on the bottom of the pack file. */ + protected byte[] packChecksum; + + /** + * Determine if an object is contained within the pack file. + * + * @param id + * the object to look for. Must not be null. + * @return true if the object is listed in this index; false otherwise. + */ + public boolean hasObject(final AnyObjectId id) { + return findOffset(id) != -1; + } + + /** + * Provide iterator that gives access to index entries. Note, that iterator + * returns reference to mutable object, the same reference in each call - + * for performance reason. If client needs immutable objects, it must copy + * returned object on its own. + *

+ * Iterator returns objects in SHA-1 lexicographical order. + *

+ * + * @return iterator over pack index entries + */ + public abstract Iterator iterator(); + + /** + * Obtain the total number of objects described by this index. + * + * @return number of objects in this index, and likewise in the associated + * pack that this index was generated from. + */ + abstract long getObjectCount(); + + /** + * Obtain the total number of objects needing 64 bit offsets. + * + * @return number of objects in this index using a 64 bit offset; that is an + * object positioned after the 2 GB position within the file. + */ + abstract long getOffset64Count(); + + /** + * Get ObjectId for the n-th object entry returned by {@link #iterator()}. + *

+ * This method is a constant-time replacement for the following loop: + * + *

+	 * Iterator<MutableEntry> eItr = index.iterator();
+	 * int curPosition = 0;
+	 * while (eItr.hasNext() && curPosition++ < nthPosition)
+	 * 	eItr.next();
+	 * ObjectId result = eItr.next().toObjectId();
+	 * 
+ * + * @param nthPosition + * position within the traversal of {@link #iterator()} that the + * caller needs the object for. The first returned + * {@link MutableEntry} is 0, the second is 1, etc. + * @return the ObjectId for the corresponding entry. + */ + abstract ObjectId getObjectId(long nthPosition); + + /** + * Get ObjectId for the n-th object entry returned by {@link #iterator()}. + *

+ * This method is a constant-time replacement for the following loop: + * + *

+	 * Iterator<MutableEntry> eItr = index.iterator();
+	 * int curPosition = 0;
+	 * while (eItr.hasNext() && curPosition++ < nthPosition)
+	 * 	eItr.next();
+	 * ObjectId result = eItr.next().toObjectId();
+	 * 
+ * + * @param nthPosition + * unsigned 32 bit position within the traversal of + * {@link #iterator()} that the caller needs the object for. The + * first returned {@link MutableEntry} is 0, the second is 1, + * etc. Positions past 2**31-1 are negative, but still valid. + * @return the ObjectId for the corresponding entry. + */ + final ObjectId getObjectId(final int nthPosition) { + if (nthPosition >= 0) + return getObjectId((long) nthPosition); + final int u31 = nthPosition >>> 1; + final int one = nthPosition & 1; + return getObjectId(((long) u31) << 1 | one); + } + + /** + * Locate the file offset position for the requested object. + * + * @param objId + * name of the object to locate within the pack. + * @return offset of the object's header and compressed content; -1 if the + * object does not exist in this index and is thus not stored in the + * associated pack. + */ + abstract long findOffset(AnyObjectId objId); + + /** + * Retrieve stored CRC32 checksum of the requested object raw-data + * (including header). + * + * @param objId + * id of object to look for + * @return CRC32 checksum of specified object (at 32 less significant bits) + * @throws MissingObjectException + * when requested ObjectId was not found in this index + * @throws UnsupportedOperationException + * when this index doesn't support CRC32 checksum + */ + abstract long findCRC32(AnyObjectId objId) throws MissingObjectException, + UnsupportedOperationException; + + /** + * Check whether this index supports (has) CRC32 checksums for objects. + * + * @return true if CRC32 is stored, false otherwise + */ + abstract boolean hasCRC32Support(); + + /** + * Represent mutable entry of pack index consisting of object id and offset + * in pack (both mutable). + * + */ + public static class MutableEntry { + final MutableObjectId idBuffer = new MutableObjectId(); + + long offset; + + /** + * Returns offset for this index object entry + * + * @return offset of this object in a pack file + */ + public long getOffset() { + return offset; + } + + /** @return hex string describing the object id of this entry. */ + public String name() { + ensureId(); + return idBuffer.name(); + } + + /** @return a copy of the object id. */ + public ObjectId toObjectId() { + ensureId(); + return idBuffer.toObjectId(); + } + + /** @return a complete copy of this entry, that won't modify */ + public MutableEntry cloneEntry() { + final MutableEntry r = new MutableEntry(); + ensureId(); + r.idBuffer.fromObjectId(idBuffer); + r.offset = offset; + return r; + } + + void ensureId() { + // Override in implementations. + } + } + + abstract class EntriesIterator implements Iterator { + protected final MutableEntry entry = initEntry(); + + protected long returnedNumber = 0; + + protected abstract MutableEntry initEntry(); + + public boolean hasNext() { + return returnedNumber < getObjectCount(); + } + + /** + * Implementation must update {@link #returnedNumber} before returning + * element. + */ + public abstract MutableEntry next(); + + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java new file mode 100644 index 0000000000..3b68edc191 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007-2009, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.NB; + +class PackIndexV1 extends PackIndex { + private static final int IDX_HDR_LEN = 256 * 4; + + private final long[] idxHeader; + + private byte[][] idxdata; + + private long objectCnt; + + PackIndexV1(final InputStream fd, final byte[] hdr) + throws CorruptObjectException, IOException { + final byte[] fanoutTable = new byte[IDX_HDR_LEN]; + System.arraycopy(hdr, 0, fanoutTable, 0, hdr.length); + IO.readFully(fd, fanoutTable, hdr.length, IDX_HDR_LEN - hdr.length); + + idxHeader = new long[256]; // really unsigned 32-bit... + for (int k = 0; k < idxHeader.length; k++) + idxHeader[k] = NB.decodeUInt32(fanoutTable, k * 4); + idxdata = new byte[idxHeader.length][]; + for (int k = 0; k < idxHeader.length; k++) { + int n; + if (k == 0) { + n = (int) (idxHeader[k]); + } else { + n = (int) (idxHeader[k] - idxHeader[k - 1]); + } + if (n > 0) { + idxdata[k] = new byte[n * (Constants.OBJECT_ID_LENGTH + 4)]; + IO.readFully(fd, idxdata[k], 0, idxdata[k].length); + } + } + objectCnt = idxHeader[255]; + + packChecksum = new byte[20]; + IO.readFully(fd, packChecksum, 0, packChecksum.length); + } + + long getObjectCount() { + return objectCnt; + } + + @Override + long getOffset64Count() { + long n64 = 0; + for (final MutableEntry e : this) { + if (e.getOffset() >= Integer.MAX_VALUE) + n64++; + } + return n64; + } + + @Override + ObjectId getObjectId(final long nthPosition) { + int levelOne = Arrays.binarySearch(idxHeader, nthPosition + 1); + long base; + if (levelOne >= 0) { + // If we hit the bucket exactly the item is in the bucket, or + // any bucket before it which has the same object count. + // + base = idxHeader[levelOne]; + while (levelOne > 0 && base == idxHeader[levelOne - 1]) + levelOne--; + } else { + // The item is in the bucket we would insert it into. + // + levelOne = -(levelOne + 1); + } + + base = levelOne > 0 ? idxHeader[levelOne - 1] : 0; + final int p = (int) (nthPosition - base); + final int dataIdx = ((4 + Constants.OBJECT_ID_LENGTH) * p) + 4; + return ObjectId.fromRaw(idxdata[levelOne], dataIdx); + } + + long findOffset(final AnyObjectId objId) { + final int levelOne = objId.getFirstByte(); + byte[] data = idxdata[levelOne]; + if (data == null) + return -1; + int high = data.length / (4 + Constants.OBJECT_ID_LENGTH); + int low = 0; + do { + final int mid = (low + high) >>> 1; + final int pos = ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4; + final int cmp = objId.compareTo(data, pos); + if (cmp < 0) + high = mid; + else if (cmp == 0) { + int b0 = data[pos - 4] & 0xff; + int b1 = data[pos - 3] & 0xff; + int b2 = data[pos - 2] & 0xff; + int b3 = data[pos - 1] & 0xff; + return (((long) b0) << 24) | (b1 << 16) | (b2 << 8) | (b3); + } else + low = mid + 1; + } while (low < high); + return -1; + } + + @Override + long findCRC32(AnyObjectId objId) { + throw new UnsupportedOperationException(); + } + + @Override + boolean hasCRC32Support() { + return false; + } + + public Iterator iterator() { + return new IndexV1Iterator(); + } + + private class IndexV1Iterator extends EntriesIterator { + private int levelOne; + + private int levelTwo; + + @Override + protected MutableEntry initEntry() { + return new MutableEntry() { + protected void ensureId() { + idBuffer.fromRaw(idxdata[levelOne], levelTwo + - Constants.OBJECT_ID_LENGTH); + } + }; + } + + public MutableEntry next() { + for (; levelOne < idxdata.length; levelOne++) { + if (idxdata[levelOne] == null) + continue; + if (levelTwo < idxdata[levelOne].length) { + entry.offset = NB.decodeUInt32(idxdata[levelOne], levelTwo); + levelTwo += Constants.OBJECT_ID_LENGTH + 4; + returnedNumber++; + return entry; + } + levelTwo = 0; + } + throw new NoSuchElementException(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java new file mode 100644 index 0000000000..cef7cc429e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.NB; + +/** Support for the pack index v2 format. */ +class PackIndexV2 extends PackIndex { + private static final long IS_O64 = 1L << 31; + + private static final int FANOUT = 256; + + private static final int[] NO_INTS = {}; + + private static final byte[] NO_BYTES = {}; + + private long objectCnt; + + private final long[] fanoutTable; + + /** 256 arrays of contiguous object names. */ + private int[][] names; + + /** 256 arrays of the 32 bit offset data, matching {@link #names}. */ + private byte[][] offset32; + + /** 256 arrays of the CRC-32 of objects, matching {@link #names}. */ + private byte[][] crc32; + + /** 64 bit offset table. */ + private byte[] offset64; + + PackIndexV2(final InputStream fd) throws IOException { + final byte[] fanoutRaw = new byte[4 * FANOUT]; + IO.readFully(fd, fanoutRaw, 0, fanoutRaw.length); + fanoutTable = new long[FANOUT]; + for (int k = 0; k < FANOUT; k++) + fanoutTable[k] = NB.decodeUInt32(fanoutRaw, k * 4); + objectCnt = fanoutTable[FANOUT - 1]; + + names = new int[FANOUT][]; + offset32 = new byte[FANOUT][]; + crc32 = new byte[FANOUT][]; + + // Object name table. The size we can permit per fan-out bucket + // is limited to Java's 2 GB per byte array limitation. That is + // no more than 107,374,182 objects per fan-out. + // + for (int k = 0; k < FANOUT; k++) { + final long bucketCnt; + if (k == 0) + bucketCnt = fanoutTable[k]; + else + bucketCnt = fanoutTable[k] - fanoutTable[k - 1]; + + if (bucketCnt == 0) { + names[k] = NO_INTS; + offset32[k] = NO_BYTES; + crc32[k] = NO_BYTES; + continue; + } + + final long nameLen = bucketCnt * Constants.OBJECT_ID_LENGTH; + if (nameLen > Integer.MAX_VALUE) + throw new IOException(JGitText.get().indexFileIsTooLargeForJgit); + + final int intNameLen = (int) nameLen; + final byte[] raw = new byte[intNameLen]; + final int[] bin = new int[intNameLen >>> 2]; + IO.readFully(fd, raw, 0, raw.length); + for (int i = 0; i < bin.length; i++) + bin[i] = NB.decodeInt32(raw, i << 2); + + names[k] = bin; + offset32[k] = new byte[(int) (bucketCnt * 4)]; + crc32[k] = new byte[(int) (bucketCnt * 4)]; + } + + // CRC32 table. + for (int k = 0; k < FANOUT; k++) + IO.readFully(fd, crc32[k], 0, crc32[k].length); + + // 32 bit offset table. Any entries with the most significant bit + // set require a 64 bit offset entry in another table. + // + int o64cnt = 0; + for (int k = 0; k < FANOUT; k++) { + final byte[] ofs = offset32[k]; + IO.readFully(fd, ofs, 0, ofs.length); + for (int p = 0; p < ofs.length; p += 4) + if (ofs[p] < 0) + o64cnt++; + } + + // 64 bit offset table. Most objects should not require an entry. + // + if (o64cnt > 0) { + offset64 = new byte[o64cnt * 8]; + IO.readFully(fd, offset64, 0, offset64.length); + } else { + offset64 = NO_BYTES; + } + + packChecksum = new byte[20]; + IO.readFully(fd, packChecksum, 0, packChecksum.length); + } + + @Override + long getObjectCount() { + return objectCnt; + } + + @Override + long getOffset64Count() { + return offset64.length / 8; + } + + @Override + ObjectId getObjectId(final long nthPosition) { + int levelOne = Arrays.binarySearch(fanoutTable, nthPosition + 1); + long base; + if (levelOne >= 0) { + // If we hit the bucket exactly the item is in the bucket, or + // any bucket before it which has the same object count. + // + base = fanoutTable[levelOne]; + while (levelOne > 0 && base == fanoutTable[levelOne - 1]) + levelOne--; + } else { + // The item is in the bucket we would insert it into. + // + levelOne = -(levelOne + 1); + } + + base = levelOne > 0 ? fanoutTable[levelOne - 1] : 0; + final int p = (int) (nthPosition - base); + final int p4 = p << 2; + return ObjectId.fromRaw(names[levelOne], p4 + p); // p * 5 + } + + @Override + long findOffset(final AnyObjectId objId) { + final int levelOne = objId.getFirstByte(); + final int levelTwo = binarySearchLevelTwo(objId, levelOne); + if (levelTwo == -1) + return -1; + final long p = NB.decodeUInt32(offset32[levelOne], levelTwo << 2); + if ((p & IS_O64) != 0) + return NB.decodeUInt64(offset64, (8 * (int) (p & ~IS_O64))); + return p; + } + + @Override + long findCRC32(AnyObjectId objId) throws MissingObjectException { + final int levelOne = objId.getFirstByte(); + final int levelTwo = binarySearchLevelTwo(objId, levelOne); + if (levelTwo == -1) + throw new MissingObjectException(objId.copy(), "unknown"); + return NB.decodeUInt32(crc32[levelOne], levelTwo << 2); + } + + @Override + boolean hasCRC32Support() { + return true; + } + + public Iterator iterator() { + return new EntriesIteratorV2(); + } + + private int binarySearchLevelTwo(final AnyObjectId objId, final int levelOne) { + final int[] data = names[levelOne]; + int high = offset32[levelOne].length >>> 2; + if (high == 0) + return -1; + int low = 0; + do { + final int mid = (low + high) >>> 1; + final int mid4 = mid << 2; + final int cmp; + + cmp = objId.compareTo(data, mid4 + mid); // mid * 5 + if (cmp < 0) + high = mid; + else if (cmp == 0) { + return mid; + } else + low = mid + 1; + } while (low < high); + return -1; + } + + private class EntriesIteratorV2 extends EntriesIterator { + private int levelOne; + + private int levelTwo; + + @Override + protected MutableEntry initEntry() { + return new MutableEntry() { + protected void ensureId() { + idBuffer.fromRaw(names[levelOne], levelTwo + - Constants.OBJECT_ID_LENGTH / 4); + } + }; + } + + public MutableEntry next() { + for (; levelOne < names.length; levelOne++) { + if (levelTwo < names[levelOne].length) { + int idx = levelTwo / (Constants.OBJECT_ID_LENGTH / 4) * 4; + long offset = NB.decodeUInt32(offset32[levelOne], idx); + if ((offset & IS_O64) != 0) { + idx = (8 * (int) (offset & ~IS_O64)); + offset = NB.decodeUInt64(offset64, idx); + } + entry.offset = offset; + + levelTwo += Constants.OBJECT_ID_LENGTH / 4; + returnedNumber++; + return entry; + } + levelTwo = 0; + } + throw new NoSuchElementException(); + } + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java new file mode 100644 index 0000000000..6bd73adcb1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.DigestOutputStream; +import java.text.MessageFormat; +import java.util.List; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.transport.PackedObjectInfo; +import org.eclipse.jgit.util.NB; + +/** + * Creates a table of contents to support random access by {@link PackFile}. + *

+ * Pack index files (the .idx suffix in a pack file pair) + * provides random access to any object in the pack by associating an ObjectId + * to the byte offset within the pack where the object's data can be read. + */ +public abstract class PackIndexWriter { + /** Magic constant indicating post-version 1 format. */ + protected static final byte[] TOC = { -1, 't', 'O', 'c' }; + + /** + * Create a new writer for the oldest (most widely understood) format. + *

+ * This method selects an index format that can accurate describe the + * supplied objects and that will be the most compatible format with older + * Git implementations. + *

+ * Index version 1 is widely recognized by all Git implementations, but + * index version 2 (and later) is not as well recognized as it was + * introduced more than a year later. Index version 1 can only be used if + * the resulting pack file is under 4 gigabytes in size; packs larger than + * that limit must use index version 2. + * + * @param dst + * the stream the index data will be written to. If not already + * buffered it will be automatically wrapped in a buffered + * stream. Callers are always responsible for closing the stream. + * @param objs + * the objects the caller needs to store in the index. Entries + * will be examined until a format can be conclusively selected. + * @return a new writer to output an index file of the requested format to + * the supplied stream. + * @throws IllegalArgumentException + * no recognized pack index version can support the supplied + * objects. This is likely a bug in the implementation. + */ + @SuppressWarnings("fallthrough") + public static PackIndexWriter createOldestPossible(final OutputStream dst, + final List objs) { + int version = 1; + LOOP: for (final PackedObjectInfo oe : objs) { + switch (version) { + case 1: + if (PackIndexWriterV1.canStore(oe)) + continue; + version = 2; + case 2: + break LOOP; + } + } + return createVersion(dst, version); + } + + /** + * Create a new writer instance for a specific index format version. + * + * @param dst + * the stream the index data will be written to. If not already + * buffered it will be automatically wrapped in a buffered + * stream. Callers are always responsible for closing the stream. + * @param version + * index format version number required by the caller. Exactly + * this formatted version will be written. + * @return a new writer to output an index file of the requested format to + * the supplied stream. + * @throws IllegalArgumentException + * the version requested is not supported by this + * implementation. + */ + public static PackIndexWriter createVersion(final OutputStream dst, + final int version) { + switch (version) { + case 1: + return new PackIndexWriterV1(dst); + case 2: + return new PackIndexWriterV2(dst); + default: + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().unsupportedPackIndexVersion, version)); + } + } + + /** The index data stream we are responsible for creating. */ + protected final DigestOutputStream out; + + /** A temporary buffer for use during IO to {link #out}. */ + protected final byte[] tmp; + + /** The entries this writer must pack. */ + protected List entries; + + /** SHA-1 checksum for the entire pack data. */ + protected byte[] packChecksum; + + /** + * Create a new writer instance. + * + * @param dst + * the stream this instance outputs to. If not already buffered + * it will be automatically wrapped in a buffered stream. + */ + protected PackIndexWriter(final OutputStream dst) { + out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst + : new BufferedOutputStream(dst), Constants.newMessageDigest()); + tmp = new byte[4 + Constants.OBJECT_ID_LENGTH]; + } + + /** + * Write all object entries to the index stream. + *

+ * After writing the stream passed to the factory is flushed but remains + * open. Callers are always responsible for closing the output stream. + * + * @param toStore + * sorted list of objects to store in the index. The caller must + * have previously sorted the list using {@link PackedObjectInfo}'s + * native {@link Comparable} implementation. + * @param packDataChecksum + * checksum signature of the entire pack data content. This is + * traditionally the last 20 bytes of the pack file's own stream. + * @throws IOException + * an error occurred while writing to the output stream, or this + * index format cannot store the object data supplied. + */ + public void write(final List toStore, + final byte[] packDataChecksum) throws IOException { + entries = toStore; + packChecksum = packDataChecksum; + writeImpl(); + out.flush(); + } + + /** + * Writes the index file to {@link #out}. + *

+ * Implementations should go something like: + * + *

+	 * writeFanOutTable();
+	 * for (final PackedObjectInfo po : entries)
+	 * 	writeOneEntry(po);
+	 * writeChecksumFooter();
+	 * 
+ * + *

+ * Where the logic for writeOneEntry is specific to the index + * format in use. Additional headers/footers may be used if necessary and + * the {@link #entries} collection may be iterated over more than once if + * necessary. Implementors therefore have complete control over the data. + * + * @throws IOException + * an error occurred while writing to the output stream, or this + * index format cannot store the object data supplied. + */ + protected abstract void writeImpl() throws IOException; + + /** + * Output the version 2 (and later) TOC header, with version number. + *

+ * Post version 1 all index files start with a TOC header that makes the + * file an invalid version 1 file, and then includes the version number. + * This header is necessary to recognize a version 1 from a version 2 + * formatted index. + * + * @param version + * version number of this index format being written. + * @throws IOException + * an error occurred while writing to the output stream. + */ + protected void writeTOC(final int version) throws IOException { + out.write(TOC); + NB.encodeInt32(tmp, 0, version); + out.write(tmp, 0, 4); + } + + /** + * Output the standard 256 entry first-level fan-out table. + *

+ * The fan-out table is 4 KB in size, holding 256 32-bit unsigned integer + * counts. Each count represents the number of objects within this index + * whose {@link ObjectId#getFirstByte()} matches the count's position in the + * fan-out table. + * + * @throws IOException + * an error occurred while writing to the output stream. + */ + protected void writeFanOutTable() throws IOException { + final int[] fanout = new int[256]; + for (final PackedObjectInfo po : entries) + fanout[po.getFirstByte() & 0xff]++; + for (int i = 1; i < 256; i++) + fanout[i] += fanout[i - 1]; + for (final int n : fanout) { + NB.encodeInt32(tmp, 0, n); + out.write(tmp, 0, 4); + } + } + + /** + * Output the standard two-checksum index footer. + *

+ * The standard footer contains two checksums (20 byte SHA-1 values): + *

    + *
  1. Pack data checksum - taken from the last 20 bytes of the pack file.
  2. + *
  3. Index data checksum - checksum of all index bytes written, including + * the pack data checksum above.
  4. + *
+ * + * @throws IOException + * an error occurred while writing to the output stream. + */ + protected void writeChecksumFooter() throws IOException { + out.write(packChecksum); + out.on(false); + out.write(out.getMessageDigest().digest()); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java new file mode 100644 index 0000000000..722ab0e06b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.transport.PackedObjectInfo; +import org.eclipse.jgit.util.NB; + +/** + * Creates the version 1 (old style) pack table of contents files. + * + * @see PackIndexWriter + * @see PackIndexV1 + */ +class PackIndexWriterV1 extends PackIndexWriter { + static boolean canStore(final PackedObjectInfo oe) { + // We are limited to 4 GB per pack as offset is 32 bit unsigned int. + // + return oe.getOffset() >>> 1 < Integer.MAX_VALUE; + } + + PackIndexWriterV1(final OutputStream dst) { + super(dst); + } + + @Override + protected void writeImpl() throws IOException { + writeFanOutTable(); + + for (final PackedObjectInfo oe : entries) { + if (!canStore(oe)) + throw new IOException(JGitText.get().packTooLargeForIndexVersion1); + NB.encodeInt32(tmp, 0, (int) oe.getOffset()); + oe.copyRawTo(tmp, 4); + out.write(tmp); + } + + writeChecksumFooter(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java new file mode 100644 index 0000000000..21ebd1ca9c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.jgit.transport.PackedObjectInfo; +import org.eclipse.jgit.util.NB; + +/** + * Creates the version 2 pack table of contents files. + * + * @see PackIndexWriter + * @see PackIndexV2 + */ +class PackIndexWriterV2 extends PackIndexWriter { + PackIndexWriterV2(final OutputStream dst) { + super(dst); + } + + @Override + protected void writeImpl() throws IOException { + writeTOC(2); + writeFanOutTable(); + writeObjectNames(); + writeCRCs(); + writeOffset32(); + writeOffset64(); + writeChecksumFooter(); + } + + private void writeObjectNames() throws IOException { + for (final PackedObjectInfo oe : entries) + oe.copyRawTo(out); + } + + private void writeCRCs() throws IOException { + for (final PackedObjectInfo oe : entries) { + NB.encodeInt32(tmp, 0, oe.getCRC()); + out.write(tmp, 0, 4); + } + } + + private void writeOffset32() throws IOException { + int o64 = 0; + for (final PackedObjectInfo oe : entries) { + final long o = oe.getOffset(); + if (o < Integer.MAX_VALUE) + NB.encodeInt32(tmp, 0, (int) o); + else + NB.encodeInt32(tmp, 0, (1 << 31) | o64++); + out.write(tmp, 0, 4); + } + } + + private void writeOffset64() throws IOException { + for (final PackedObjectInfo oe : entries) { + final long o = oe.getOffset(); + if (o > Integer.MAX_VALUE) { + NB.encodeInt64(tmp, 0, o); + out.write(tmp, 0, 8); + } + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java new file mode 100644 index 0000000000..be250114c2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009, 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.storage.file; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.lib.Constants; + +/** Keeps track of a {@link PackFile}'s associated .keep file. */ +public class PackLock { + private final File keepFile; + + /** + * Create a new lock for a pack file. + * + * @param packFile + * location of the pack-*.pack file. + */ + public PackLock(final File packFile) { + final File p = packFile.getParentFile(); + final String n = packFile.getName(); + keepFile = new File(p, n.substring(0, n.length() - 5) + ".keep"); + } + + /** + * Create the pack-*.keep file, with the given message. + * + * @param msg + * message to store in the file. + * @return true if the keep file was successfully written; false otherwise. + * @throws IOException + * the keep file could not be written. + */ + public boolean lock(String msg) throws IOException { + if (msg == null) + return false; + if (!msg.endsWith("\n")) + msg += "\n"; + final LockFile lf = new LockFile(keepFile); + if (!lf.lock()) + return false; + lf.write(Constants.encode(msg)); + return lf.commit(); + } + + /** Remove the .keep file that holds this pack in place. */ + public void unlock() { + keepFile.delete(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java new file mode 100644 index 0000000000..96abaeefd3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * 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.storage.file; + +import java.text.MessageFormat; +import java.util.Arrays; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; + +/** + *

+ * Reverse index for forward pack index. Provides operations based on offset + * instead of object id. Such offset-based reverse lookups are performed in + * O(log n) time. + *

+ * + * @see PackIndex + * @see PackFile + */ +class PackReverseIndex { + /** Index we were created from, and that has our ObjectId data. */ + private final PackIndex index; + + /** + * (offset31, truly) Offsets accommodating in 31 bits. + */ + private final int offsets32[]; + + /** + * Offsets not accommodating in 31 bits. + */ + private final long offsets64[]; + + /** Position of the corresponding {@link #offsets32} in {@link #index}. */ + private final int nth32[]; + + /** Position of the corresponding {@link #offsets64} in {@link #index}. */ + private final int nth64[]; + + /** + * Create reverse index from straight/forward pack index, by indexing all + * its entries. + * + * @param packIndex + * forward index - entries to (reverse) index. + */ + PackReverseIndex(final PackIndex packIndex) { + index = packIndex; + + final long cnt = index.getObjectCount(); + final long n64 = index.getOffset64Count(); + final long n32 = cnt - n64; + if (n32 > Integer.MAX_VALUE || n64 > Integer.MAX_VALUE + || cnt > 0xffffffffL) + throw new IllegalArgumentException( + JGitText.get().hugeIndexesAreNotSupportedByJgitYet); + + offsets32 = new int[(int) n32]; + offsets64 = new long[(int) n64]; + nth32 = new int[offsets32.length]; + nth64 = new int[offsets64.length]; + + int i32 = 0; + int i64 = 0; + for (final MutableEntry me : index) { + final long o = me.getOffset(); + if (o < Integer.MAX_VALUE) + offsets32[i32++] = (int) o; + else + offsets64[i64++] = o; + } + + Arrays.sort(offsets32); + Arrays.sort(offsets64); + + int nth = 0; + for (final MutableEntry me : index) { + final long o = me.getOffset(); + if (o < Integer.MAX_VALUE) + nth32[Arrays.binarySearch(offsets32, (int) o)] = nth++; + else + nth64[Arrays.binarySearch(offsets64, o)] = nth++; + } + } + + /** + * Search for object id with the specified start offset in this pack + * (reverse) index. + * + * @param offset + * start offset of object to find. + * @return object id for this offset, or null if no object was found. + */ + ObjectId findObject(final long offset) { + if (offset <= Integer.MAX_VALUE) { + final int i32 = Arrays.binarySearch(offsets32, (int) offset); + if (i32 < 0) + return null; + return index.getObjectId(nth32[i32]); + } else { + final int i64 = Arrays.binarySearch(offsets64, offset); + if (i64 < 0) + return null; + return index.getObjectId(nth64[i64]); + } + } + + /** + * Search for the next offset to the specified offset in this pack (reverse) + * index. + * + * @param offset + * start offset of previous object (must be valid-existing + * offset). + * @param maxOffset + * maximum offset in a pack (returned when there is no next + * offset). + * @return offset of the next object in a pack or maxOffset if provided + * offset was the last one. + * @throws CorruptObjectException + * when there is no object with the provided offset. + */ + long findNextOffset(final long offset, final long maxOffset) + throws CorruptObjectException { + if (offset <= Integer.MAX_VALUE) { + final int i32 = Arrays.binarySearch(offsets32, (int) offset); + if (i32 < 0) + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset + , offset)); + + if (i32 + 1 == offsets32.length) { + if (offsets64.length > 0) + return offsets64[0]; + return maxOffset; + } + return offsets32[i32 + 1]; + } else { + final int i64 = Arrays.binarySearch(offsets64, offset); + if (i64 < 0) + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset + , offset)); + + if (i64 + 1 == offsets64.length) + return maxOffset; + return offsets64[i64 + 1]; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java new file mode 100644 index 0000000000..e784f3d181 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; + +/** + * Base class for a set of object loader classes for packed objects. + */ +abstract class PackedObjectLoader extends ObjectLoader { + protected final PackFile pack; + + /** Position of the first byte of the object's header. */ + protected final long objectOffset; + + /** Bytes used to express the object header, including delta reference. */ + protected final int headerSize; + + protected int objectType; + + protected int objectSize; + + protected byte[] cachedBytes; + + PackedObjectLoader(final PackFile pr, final long objectOffset, + final int headerSize) { + pack = pr; + this.objectOffset = objectOffset; + this.headerSize = headerSize; + } + + /** + * Force this object to be loaded into memory and pinned in this loader. + *

+ * Once materialized, subsequent get operations for the following methods + * will always succeed without raising an exception, as all information is + * pinned in memory by this loader instance. + *

    + *
  • {@link #getType()}
  • + *
  • {@link #getSize()}
  • + *
  • {@link #getBytes()}, {@link #getCachedBytes}
  • + *
  • {@link #getRawSize()}
  • + *
  • {@link #getRawType()}
  • + *
+ * + * @param curs + * temporary thread storage during data access. + * @throws IOException + * the object cannot be read. + */ + abstract void materialize(WindowCursor curs) throws IOException; + + public final int getType() { + return objectType; + } + + public final long getSize() { + return objectSize; + } + + @Override + public final byte[] getCachedBytes() { + return cachedBytes; + } + + /** + * @return offset of object header within pack file + */ + final long getObjectOffset() { + return objectOffset; + } + + /** + * @return id of delta base object for this object representation. null if + * object is not stored as delta. + * @throws IOException + * when delta base cannot read. + */ + abstract ObjectId getDeltaBase() throws IOException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java new file mode 100644 index 0000000000..f7ffa3e39a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java @@ -0,0 +1,1018 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2009-2010, Google Inc. + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * 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.storage.file; + +import static org.eclipse.jgit.lib.Constants.CHARSET; +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.LOGS; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; +import static org.eclipse.jgit.lib.Constants.PACKED_REFS; +import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.Constants.R_REFS; +import static org.eclipse.jgit.lib.Constants.R_REMOTES; +import static org.eclipse.jgit.lib.Constants.R_TAGS; +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.ObjectWritingException; +import org.eclipse.jgit.events.RefsChangedEvent; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefComparator; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefWriter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.RefList; +import org.eclipse.jgit.util.RefMap; + +/** + * Traditional file system based {@link RefDatabase}. + *

+ * This is the classical reference database representation for a Git repository. + * References are stored in two formats: loose, and packed. + *

+ * Loose references are stored as individual files within the {@code refs/} + * directory. The file name matches the reference name and the file contents is + * the current {@link ObjectId} in string form. + *

+ * Packed references are stored in a single text file named {@code packed-refs}. + * In the packed format, each reference is stored on its own line. This file + * reduces the number of files needed for large reference spaces, reducing the + * overall size of a Git repository on disk. + */ +public class RefDirectory extends RefDatabase { + /** Magic string denoting the start of a symbolic reference file. */ + public static final String SYMREF = "ref: "; //$NON-NLS-1$ + + /** Magic string denoting the header of a packed-refs file. */ + public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$ + + /** If in the header, denotes the file has peeled data. */ + public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$ + + private final FileRepository parent; + + private final File gitDir; + + private final File refsDir; + + private final File logsDir; + + private final File logsRefsDir; + + private final File packedRefsFile; + + /** + * Immutable sorted list of loose references. + *

+ * Symbolic references in this collection are stored unresolved, that is + * their target appears to be a new reference with no ObjectId. These are + * converted into resolved references during a get operation, ensuring the + * live value is always returned. + */ + private final AtomicReference> looseRefs = new AtomicReference>(); + + /** Immutable sorted list of packed references. */ + private final AtomicReference packedRefs = new AtomicReference(); + + /** + * Number of modifications made to this database. + *

+ * This counter is incremented when a change is made, or detected from the + * filesystem during a read operation. + */ + private final AtomicInteger modCnt = new AtomicInteger(); + + /** + * Last {@link #modCnt} that we sent to listeners. + *

+ * This value is compared to {@link #modCnt}, and a notification is sent to + * the listeners only when it differs. + */ + private final AtomicInteger lastNotifiedModCnt = new AtomicInteger(); + + RefDirectory(final FileRepository db) { + final FS fs = db.getFS(); + parent = db; + gitDir = db.getDirectory(); + refsDir = fs.resolve(gitDir, R_REFS); + logsDir = fs.resolve(gitDir, LOGS); + logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS); + packedRefsFile = fs.resolve(gitDir, PACKED_REFS); + + looseRefs.set(RefList. emptyList()); + packedRefs.set(PackedRefList.NO_PACKED_REFS); + } + + Repository getRepository() { + return parent; + } + + public void create() throws IOException { + refsDir.mkdir(); + logsDir.mkdir(); + logsRefsDir.mkdir(); + + new File(refsDir, R_HEADS.substring(R_REFS.length())).mkdir(); + new File(refsDir, R_TAGS.substring(R_REFS.length())).mkdir(); + new File(logsRefsDir, R_HEADS.substring(R_REFS.length())).mkdir(); + } + + @Override + public void close() { + // We have no resources to close. + } + + void rescan() { + looseRefs.set(RefList. emptyList()); + packedRefs.set(PackedRefList.NO_PACKED_REFS); + } + + @Override + public boolean isNameConflicting(String name) throws IOException { + RefList packed = getPackedRefs(); + RefList loose = getLooseRefs(); + + // Cannot be nested within an existing reference. + int lastSlash = name.lastIndexOf('/'); + while (0 < lastSlash) { + String needle = name.substring(0, lastSlash); + if (loose.contains(needle) || packed.contains(needle)) + return true; + lastSlash = name.lastIndexOf('/', lastSlash - 1); + } + + // Cannot be the container of an existing reference. + String prefix = name + '/'; + int idx; + + idx = -(packed.find(prefix) + 1); + if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix)) + return true; + + idx = -(loose.find(prefix) + 1); + if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix)) + return true; + + return false; + } + + private RefList getLooseRefs() { + final RefList oldLoose = looseRefs.get(); + + LooseScanner scan = new LooseScanner(oldLoose); + scan.scan(ALL); + + RefList loose; + if (scan.newLoose != null) { + loose = scan.newLoose.toRefList(); + if (looseRefs.compareAndSet(oldLoose, loose)) + modCnt.incrementAndGet(); + } else + loose = oldLoose; + return loose; + } + + @Override + public Ref getRef(final String needle) throws IOException { + final RefList packed = getPackedRefs(); + Ref ref = null; + for (String prefix : SEARCH_PATH) { + ref = readRef(prefix + needle, packed); + if (ref != null) { + ref = resolve(ref, 0, null, null, packed); + break; + } + } + fireRefsChanged(); + return ref; + } + + @Override + public Map getRefs(String prefix) throws IOException { + final RefList packed = getPackedRefs(); + final RefList oldLoose = looseRefs.get(); + + LooseScanner scan = new LooseScanner(oldLoose); + scan.scan(prefix); + + RefList loose; + if (scan.newLoose != null) { + loose = scan.newLoose.toRefList(); + if (looseRefs.compareAndSet(oldLoose, loose)) + modCnt.incrementAndGet(); + } else + loose = oldLoose; + fireRefsChanged(); + + RefList.Builder symbolic = scan.symbolic; + for (int idx = 0; idx < symbolic.size();) { + Ref ref = symbolic.get(idx); + ref = resolve(ref, 0, prefix, loose, packed); + if (ref != null && ref.getObjectId() != null) { + symbolic.set(idx, ref); + idx++; + } else { + // A broken symbolic reference, we have to drop it from the + // collections the client is about to receive. Should be a + // rare occurrence so pay a copy penalty. + loose = loose.remove(idx); + symbolic.remove(idx); + } + } + + return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList()); + } + + @SuppressWarnings("unchecked") + private RefList upcast(RefList loose) { + return (RefList) loose; + } + + private class LooseScanner { + private final RefList curLoose; + + private int curIdx; + + final RefList.Builder symbolic = new RefList.Builder(4); + + RefList.Builder newLoose; + + LooseScanner(final RefList curLoose) { + this.curLoose = curLoose; + } + + void scan(String prefix) { + if (ALL.equals(prefix)) { + scanOne(HEAD); + scanTree(R_REFS, refsDir); + + // If any entries remain, they are deleted, drop them. + if (newLoose == null && curIdx < curLoose.size()) + newLoose = curLoose.copy(curIdx); + + } else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) { + curIdx = -(curLoose.find(prefix) + 1); + File dir = new File(refsDir, prefix.substring(R_REFS.length())); + scanTree(prefix, dir); + + // Skip over entries still within the prefix; these have + // been removed from the directory. + while (curIdx < curLoose.size()) { + if (!curLoose.get(curIdx).getName().startsWith(prefix)) + break; + if (newLoose == null) + newLoose = curLoose.copy(curIdx); + curIdx++; + } + + // Keep any entries outside of the prefix space, we + // do not know anything about their status. + if (newLoose != null) { + while (curIdx < curLoose.size()) + newLoose.add(curLoose.get(curIdx++)); + } + } + } + + private boolean scanTree(String prefix, File dir) { + final String[] entries = dir.list(LockFile.FILTER); + if (entries == null) // not a directory or an I/O error + return false; + if (0 < entries.length) { + Arrays.sort(entries); + for (String name : entries) { + File e = new File(dir, name); + if (!scanTree(prefix + name + '/', e)) + scanOne(prefix + name); + } + } + return true; + } + + private void scanOne(String name) { + LooseRef cur; + + if (curIdx < curLoose.size()) { + do { + cur = curLoose.get(curIdx); + int cmp = RefComparator.compareTo(cur, name); + if (cmp < 0) { + // Reference is not loose anymore, its been deleted. + // Skip the name in the new result list. + if (newLoose == null) + newLoose = curLoose.copy(curIdx); + curIdx++; + cur = null; + continue; + } + + if (cmp > 0) // Newly discovered loose reference. + cur = null; + break; + } while (curIdx < curLoose.size()); + } else + cur = null; // Newly discovered loose reference. + + LooseRef n; + try { + n = scanRef(cur, name); + } catch (IOException notValid) { + n = null; + } + + if (n != null) { + if (cur != n && newLoose == null) + newLoose = curLoose.copy(curIdx); + if (newLoose != null) + newLoose.add(n); + if (n.isSymbolic()) + symbolic.add(n); + } else if (cur != null) { + // Tragically, this file is no longer a loose reference. + // Kill our cached entry of it. + if (newLoose == null) + newLoose = curLoose.copy(curIdx); + } + + if (cur != null) + curIdx++; + } + } + + @Override + public Ref peel(final Ref ref) throws IOException { + final Ref leaf = ref.getLeaf(); + if (leaf.isPeeled() || leaf.getObjectId() == null) + return ref; + + RevWalk rw = new RevWalk(getRepository()); + RevObject obj = rw.parseAny(leaf.getObjectId()); + ObjectIdRef newLeaf; + if (obj instanceof RevTag) { + newLeaf = new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf + .getName(), leaf.getObjectId(), rw.peel(obj).copy()); + } else { + newLeaf = new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf + .getName(), leaf.getObjectId()); + } + + // Try to remember this peeling in the cache, so we don't have to do + // it again in the future, but only if the reference is unchanged. + if (leaf.getStorage().isLoose()) { + RefList curList = looseRefs.get(); + int idx = curList.find(leaf.getName()); + if (0 <= idx && curList.get(idx) == leaf) { + LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf); + RefList newList = curList.set(idx, asPeeled); + looseRefs.compareAndSet(curList, newList); + } + } + + return recreate(ref, newLeaf); + } + + private static Ref recreate(final Ref old, final ObjectIdRef leaf) { + if (old.isSymbolic()) { + Ref dst = recreate(old.getTarget(), leaf); + return new SymbolicRef(old.getName(), dst); + } + return leaf; + } + + void storedSymbolicRef(RefDirectoryUpdate u, long modified, String target) { + putLooseRef(newSymbolicRef(modified, u.getRef().getName(), target)); + fireRefsChanged(); + } + + public RefDirectoryUpdate newUpdate(String name, boolean detach) + throws IOException { + final RefList packed = getPackedRefs(); + Ref ref = readRef(name, packed); + if (ref != null) + ref = resolve(ref, 0, null, null, packed); + if (ref == null) + ref = new ObjectIdRef.Unpeeled(NEW, name, null); + else if (detach && ref.isSymbolic()) + ref = new ObjectIdRef.Unpeeled(LOOSE, name, ref.getObjectId()); + return new RefDirectoryUpdate(this, ref); + } + + @Override + public RefDirectoryRename newRename(String fromName, String toName) + throws IOException { + RefDirectoryUpdate from = newUpdate(fromName, false); + RefDirectoryUpdate to = newUpdate(toName, false); + return new RefDirectoryRename(from, to); + } + + void stored(RefDirectoryUpdate update, long modified) { + final ObjectId target = update.getNewObjectId().copy(); + final Ref leaf = update.getRef().getLeaf(); + putLooseRef(new LooseUnpeeled(modified, leaf.getName(), target)); + } + + private void putLooseRef(LooseRef ref) { + RefList cList, nList; + do { + cList = looseRefs.get(); + nList = cList.put(ref); + } while (!looseRefs.compareAndSet(cList, nList)); + modCnt.incrementAndGet(); + fireRefsChanged(); + } + + void delete(RefDirectoryUpdate update) throws IOException { + Ref dst = update.getRef().getLeaf(); + String name = dst.getName(); + + // Write the packed-refs file using an atomic update. We might + // wind up reading it twice, before and after the lock, to ensure + // 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 IOException(MessageFormat.format( + JGitText.get().cannotLockFile, packedRefsFile)); + try { + PackedRefList cur = readPackedRefs(0, 0); + int idx = cur.find(name); + if (0 <= idx) + commitPackedRefs(lck, cur.remove(idx), packed); + } finally { + lck.unlock(); + } + } + + RefList curLoose, newLoose; + do { + curLoose = looseRefs.get(); + int idx = curLoose.find(name); + if (idx < 0) + break; + newLoose = curLoose.remove(idx); + } while (!looseRefs.compareAndSet(curLoose, newLoose)); + + int levels = levelsIn(name) - 2; + delete(logFor(name), levels); + if (dst.getStorage().isLoose()) { + update.unlock(); + delete(fileFor(name), levels); + } + + modCnt.incrementAndGet(); + fireRefsChanged(); + } + + void log(final RefUpdate update, final String msg, final boolean deref) + throws IOException { + final ObjectId oldId = update.getOldObjectId(); + final ObjectId newId = update.getNewObjectId(); + final Ref ref = update.getRef(); + + PersonIdent ident = update.getRefLogIdent(); + if (ident == null) + ident = new PersonIdent(parent); + else + ident = new PersonIdent(ident); + + final StringBuilder r = new StringBuilder(); + r.append(ObjectId.toString(oldId)); + r.append(' '); + r.append(ObjectId.toString(newId)); + r.append(' '); + r.append(ident.toExternalString()); + r.append('\t'); + r.append(msg); + r.append('\n'); + final byte[] rec = encode(r.toString()); + + if (deref && ref.isSymbolic()) { + log(ref.getName(), rec); + log(ref.getLeaf().getName(), rec); + } else { + log(ref.getName(), rec); + } + } + + private void log(final String refName, final byte[] rec) throws IOException { + final File log = logFor(refName); + final boolean write; + if (isLogAllRefUpdates() && shouldAutoCreateLog(refName)) + write = true; + else if (log.isFile()) + write = true; + else + write = false; + + if (write) { + FileOutputStream out; + try { + out = new FileOutputStream(log, true); + } catch (FileNotFoundException err) { + final File dir = log.getParentFile(); + if (dir.exists()) + throw err; + if (!dir.mkdirs() && !dir.isDirectory()) + throw new IOException(MessageFormat.format(JGitText.get().cannotCreateDirectory, dir)); + out = new FileOutputStream(log, true); + } + try { + out.write(rec); + } finally { + out.close(); + } + } + } + + private boolean isLogAllRefUpdates() { + return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates(); + } + + private boolean shouldAutoCreateLog(final String refName) { + return refName.equals(HEAD) // + || refName.startsWith(R_HEADS) // + || refName.startsWith(R_REMOTES); + } + + private Ref resolve(final Ref ref, int depth, String prefix, + RefList loose, RefList packed) throws IOException { + if (ref.isSymbolic()) { + Ref dst = ref.getTarget(); + + if (MAX_SYMBOLIC_REF_DEPTH <= depth) + return null; // claim it doesn't exist + + // If the cached value can be assumed to be current due to a + // recent scan of the loose directory, use it. + if (loose != null && dst.getName().startsWith(prefix)) { + int idx; + if (0 <= (idx = loose.find(dst.getName()))) + dst = loose.get(idx); + else if (0 <= (idx = packed.find(dst.getName()))) + dst = packed.get(idx); + else + return ref; + } else { + dst = readRef(dst.getName(), packed); + if (dst == null) + return ref; + } + + dst = resolve(dst, depth + 1, prefix, loose, packed); + if (dst == null) + return null; + return new SymbolicRef(ref.getName(), dst); + } + return ref; + } + + private PackedRefList getPackedRefs() throws IOException { + long size = packedRefsFile.length(); + long mtime = size != 0 ? packedRefsFile.lastModified() : 0; + + final PackedRefList curList = packedRefs.get(); + if (size == curList.lastSize && mtime == curList.lastModified) + return curList; + + final PackedRefList newList = readPackedRefs(size, mtime); + if (packedRefs.compareAndSet(curList, newList)) + modCnt.incrementAndGet(); + return newList; + } + + private PackedRefList readPackedRefs(long size, long mtime) + throws IOException { + final BufferedReader br; + try { + br = new BufferedReader(new InputStreamReader(new FileInputStream( + packedRefsFile), CHARSET)); + } catch (FileNotFoundException noPackedRefs) { + // Ignore it and leave the new list empty. + return PackedRefList.NO_PACKED_REFS; + } + try { + return new PackedRefList(parsePackedRefs(br), size, mtime); + } finally { + br.close(); + } + } + + private RefList parsePackedRefs(final BufferedReader br) + throws IOException { + RefList.Builder all = new RefList.Builder(); + Ref last = null; + boolean peeled = false; + boolean needSort = false; + + String p; + while ((p = br.readLine()) != null) { + if (p.charAt(0) == '#') { + if (p.startsWith(PACKED_REFS_HEADER)) { + p = p.substring(PACKED_REFS_HEADER.length()); + peeled = p.contains(PACKED_REFS_PEELED); + } + continue; + } + + if (p.charAt(0) == '^') { + if (last == null) + throw new IOException(JGitText.get().peeledLineBeforeRef); + + ObjectId id = ObjectId.fromString(p.substring(1)); + last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last + .getObjectId(), id); + all.set(all.size() - 1, last); + continue; + } + + int sp = p.indexOf(' '); + ObjectId id = ObjectId.fromString(p.substring(0, sp)); + String name = copy(p, sp + 1, p.length()); + ObjectIdRef cur; + if (peeled) + cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id); + else + cur = new ObjectIdRef.Unpeeled(PACKED, name, id); + if (last != null && RefComparator.compareTo(last, cur) > 0) + needSort = true; + all.add(cur); + last = cur; + } + + if (needSort) + all.sort(); + return all.toRefList(); + } + + private static String copy(final String src, final int off, final int end) { + // Don't use substring since it could leave a reference to the much + // larger existing string. Force construction of a full new object. + return new StringBuilder(end - off).append(src, off, end).toString(); + } + + private void commitPackedRefs(final LockFile lck, final RefList refs, + final PackedRefList oldPackedList) throws IOException { + new RefWriter(refs) { + @Override + protected void writeFile(String name, byte[] content) + throws IOException { + lck.setNeedStatInformation(true); + try { + lck.write(content); + } catch (IOException ioe) { + throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe); + } + try { + lck.waitForStatChange(); + } catch (InterruptedException e) { + lck.unlock(); + throw new ObjectWritingException(MessageFormat.format(JGitText.get().interruptedWriting, name)); + } + if (!lck.commit()) + throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name)); + + packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs, + content.length, lck.getCommitLastModified())); + } + }.writePackedRefs(); + } + + private Ref readRef(String name, RefList packed) throws IOException { + final RefList curList = looseRefs.get(); + final int idx = curList.find(name); + if (0 <= idx) { + final LooseRef o = curList.get(idx); + final LooseRef n = scanRef(o, name); + if (n == null) { + if (looseRefs.compareAndSet(curList, curList.remove(idx))) + modCnt.incrementAndGet(); + return packed.get(name); + } + + if (o == n) + return n; + if (looseRefs.compareAndSet(curList, curList.set(idx, n))) + modCnt.incrementAndGet(); + return n; + } + + final LooseRef n = scanRef(null, name); + if (n == null) + return packed.get(name); + if (looseRefs.compareAndSet(curList, curList.add(idx, n))) + modCnt.incrementAndGet(); + return n; + } + + private LooseRef scanRef(LooseRef ref, String name) throws IOException { + final File path = fileFor(name); + final long modified = path.lastModified(); + + if (ref != null) { + if (ref.getLastModified() == modified) + return ref; + name = ref.getName(); + } else if (modified == 0) + return null; + + final byte[] buf; + try { + buf = IO.readFully(path, 4096); + } catch (FileNotFoundException noFile) { + return null; // doesn't exist; not a reference. + } + + int n = buf.length; + if (n == 0) + return null; // empty file; not a reference. + + if (isSymRef(buf, n)) { + // trim trailing whitespace + while (0 < n && Character.isWhitespace(buf[n - 1])) + n--; + if (n < 6) { + String content = RawParseUtils.decode(buf, 0, n); + throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content)); + } + final String target = RawParseUtils.decode(buf, 5, n); + return newSymbolicRef(modified, name, target); + } + + if (n < OBJECT_ID_STRING_LENGTH) + return null; // impossibly short object identifier; not a reference. + + final ObjectId id; + try { + id = ObjectId.fromString(buf, 0); + } catch (IllegalArgumentException notRef) { + while (0 < n && Character.isWhitespace(buf[n - 1])) + n--; + String content = RawParseUtils.decode(buf, 0, n); + throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content)); + } + return new LooseUnpeeled(modified, name, id); + } + + private static boolean isSymRef(final byte[] buf, int n) { + if (n < 6) + return false; + return /**/buf[0] == 'r' // + && buf[1] == 'e' // + && buf[2] == 'f' // + && buf[3] == ':' // + && buf[4] == ' '; + } + + /** If the parent should fire listeners, fires them. */ + private void fireRefsChanged() { + final int last = lastNotifiedModCnt.get(); + final int curr = modCnt.get(); + if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr)) + parent.fireEvent(new RefsChangedEvent()); + } + + /** + * Create a reference update to write a temporary reference. + * + * @return an update for a new temporary reference. + * @throws IOException + * a temporary name cannot be allocated. + */ + RefDirectoryUpdate newTemporaryUpdate() throws IOException { + File tmp = File.createTempFile("renamed_", "_ref", refsDir); + String name = Constants.R_REFS + tmp.getName(); + Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null); + return new RefDirectoryUpdate(this, ref); + } + + /** + * Locate the file on disk for a single reference name. + * + * @param name + * name of the ref, relative to the Git repository top level + * directory (so typically starts with refs/). + * @return the loose file location. + */ + File fileFor(String name) { + if (name.startsWith(R_REFS)) { + name = name.substring(R_REFS.length()); + return new File(refsDir, name); + } + return new File(gitDir, name); + } + + /** + * Locate the log file on disk for a single reference name. + * + * @param name + * name of the ref, relative to the Git repository top level + * directory (so typically starts with refs/). + * @return the log file location. + */ + File logFor(String name) { + if (name.startsWith(R_REFS)) { + name = name.substring(R_REFS.length()); + return new File(logsRefsDir, name); + } + return new File(logsDir, name); + } + + static int levelsIn(final String name) { + int count = 0; + for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1)) + count++; + return count; + } + + static void delete(final File file, final int depth) throws IOException { + if (!file.delete() && file.isFile()) + throw new IOException(MessageFormat.format(JGitText.get().fileCannotBeDeleted, file)); + + File dir = file.getParentFile(); + for (int i = 0; i < depth; ++i) { + if (!dir.delete()) + break; // ignore problem here + dir = dir.getParentFile(); + } + } + + private static class PackedRefList extends RefList { + static final PackedRefList NO_PACKED_REFS = new PackedRefList(RefList + .emptyList(), 0, 0); + + /** Last length of the packed-refs file when we read it. */ + final long lastSize; + + /** Last modified time of the packed-refs file when we read it. */ + final long lastModified; + + PackedRefList(RefList src, long size, long mtime) { + super(src); + lastSize = size; + lastModified = mtime; + } + } + + private static LooseSymbolicRef newSymbolicRef(long lastModified, + String name, String target) { + Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null); + return new LooseSymbolicRef(lastModified, name, dst); + } + + private static interface LooseRef extends Ref { + long getLastModified(); + + LooseRef peel(ObjectIdRef newLeaf); + } + + private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag + implements LooseRef { + private final long lastModified; + + LoosePeeledTag(long mtime, String refName, ObjectId id, ObjectId p) { + super(LOOSE, refName, id, p); + this.lastModified = mtime; + } + + public long getLastModified() { + return lastModified; + } + + public LooseRef peel(ObjectIdRef newLeaf) { + return this; + } + } + + private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag + implements LooseRef { + private final long lastModified; + + LooseNonTag(long mtime, String refName, ObjectId id) { + super(LOOSE, refName, id); + this.lastModified = mtime; + } + + public long getLastModified() { + return lastModified; + } + + public LooseRef peel(ObjectIdRef newLeaf) { + return this; + } + } + + private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled + implements LooseRef { + private final long lastModified; + + LooseUnpeeled(long mtime, String refName, ObjectId id) { + super(LOOSE, refName, id); + this.lastModified = mtime; + } + + public long getLastModified() { + return lastModified; + } + + public LooseRef peel(ObjectIdRef newLeaf) { + if (newLeaf.getPeeledObjectId() != null) + return new LoosePeeledTag(lastModified, getName(), + getObjectId(), newLeaf.getPeeledObjectId()); + else + return new LooseNonTag(lastModified, getName(), getObjectId()); + } + } + + private final static class LooseSymbolicRef extends SymbolicRef implements + LooseRef { + private final long lastModified; + + LooseSymbolicRef(long mtime, String refName, Ref target) { + super(refName, target); + this.lastModified = mtime; + } + + public long getLastModified() { + return lastModified; + } + + public LooseRef peel(ObjectIdRef newLeaf) { + // We should never try to peel the symbolic references. + throw new UnsupportedOperationException(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java new file mode 100644 index 0000000000..b43b70f1e4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2010, Google Inc. + * Copyright (C) 2009, Robin Rosenberg + * 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.storage.file; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RefRename; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.revwalk.RevWalk; + +/** + * Rename any reference stored by {@link RefDirectory}. + *

+ * This class works by first renaming the source reference to a temporary name, + * then renaming the temporary name to the final destination reference. + *

+ * This strategy permits switching a reference like {@code refs/heads/foo}, + * which is a file, to {@code refs/heads/foo/bar}, which is stored inside a + * directory that happens to match the source name. + */ +class RefDirectoryRename extends RefRename { + private final RefDirectory refdb; + + /** + * The value of the source reference at the start of the rename. + *

+ * At the end of the rename the destination reference must have this same + * value, otherwise we have a concurrent update and the rename must fail + * without making any changes. + */ + private ObjectId objId; + + /** True if HEAD must be moved to the destination reference. */ + private boolean updateHEAD; + + /** A reference we backup {@link #objId} into during the rename. */ + private RefDirectoryUpdate tmp; + + RefDirectoryRename(RefDirectoryUpdate src, RefDirectoryUpdate dst) { + super(src, dst); + refdb = src.getRefDatabase(); + } + + @Override + protected Result doRename() throws IOException { + if (source.getRef().isSymbolic()) + return Result.IO_FAILURE; // not supported + + final RevWalk rw = new RevWalk(refdb.getRepository()); + objId = source.getOldObjectId(); + updateHEAD = needToUpdateHEAD(); + tmp = refdb.newTemporaryUpdate(); + try { + // First backup the source so its never unreachable. + tmp.setNewObjectId(objId); + tmp.setForceUpdate(true); + tmp.disableRefLog(); + switch (tmp.update(rw)) { + case NEW: + case FORCED: + case NO_CHANGE: + break; + default: + return tmp.getResult(); + } + + // Save the source's log under the temporary name, we must do + // this before we delete the source, otherwise we lose the log. + if (!renameLog(source, tmp)) + return Result.IO_FAILURE; + + // If HEAD has to be updated, link it now to destination. + // We have to link before we delete, otherwise the delete + // fails because its the current branch. + RefUpdate dst = destination; + if (updateHEAD) { + if (!linkHEAD(destination)) { + renameLog(tmp, source); + return Result.LOCK_FAILURE; + } + + // Replace the update operation so HEAD will log the rename. + dst = refdb.newUpdate(Constants.HEAD, false); + dst.setRefLogIdent(destination.getRefLogIdent()); + dst.setRefLogMessage(destination.getRefLogMessage(), false); + } + + // Delete the source name so its path is free for replacement. + source.setExpectedOldObjectId(objId); + source.setForceUpdate(true); + source.disableRefLog(); + if (source.delete(rw) != Result.FORCED) { + renameLog(tmp, source); + if (updateHEAD) + linkHEAD(source); + return source.getResult(); + } + + // Move the log to the destination. + if (!renameLog(tmp, destination)) { + renameLog(tmp, source); + source.setExpectedOldObjectId(ObjectId.zeroId()); + source.setNewObjectId(objId); + source.update(rw); + if (updateHEAD) + linkHEAD(source); + return Result.IO_FAILURE; + } + + // Create the destination, logging the rename during the creation. + dst.setExpectedOldObjectId(ObjectId.zeroId()); + dst.setNewObjectId(objId); + if (dst.update(rw) != Result.NEW) { + // If we didn't create the destination we have to undo + // our work. Put the log back and restore source. + if (renameLog(destination, tmp)) + renameLog(tmp, source); + source.setExpectedOldObjectId(ObjectId.zeroId()); + source.setNewObjectId(objId); + source.update(rw); + if (updateHEAD) + linkHEAD(source); + return dst.getResult(); + } + + return Result.RENAMED; + } finally { + // Always try to free the temporary name. + try { + refdb.delete(tmp); + } catch (IOException err) { + refdb.fileFor(tmp.getName()).delete(); + } + } + } + + private boolean renameLog(RefUpdate src, RefUpdate dst) { + File srcLog = refdb.logFor(src.getName()); + File dstLog = refdb.logFor(dst.getName()); + + if (!srcLog.exists()) + return true; + + if (!rename(srcLog, dstLog)) + return false; + + try { + final int levels = RefDirectory.levelsIn(src.getName()) - 2; + RefDirectory.delete(srcLog, levels); + return true; + } catch (IOException e) { + rename(dstLog, srcLog); + return false; + } + } + + private static boolean rename(File src, File dst) { + if (src.renameTo(dst)) + return true; + + File dir = dst.getParentFile(); + if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory()) + return false; + return src.renameTo(dst); + } + + private boolean linkHEAD(RefUpdate target) { + try { + RefUpdate u = refdb.newUpdate(Constants.HEAD, false); + u.disableRefLog(); + switch (u.link(target.getName())) { + case NEW: + case FORCED: + case NO_CHANGE: + return true; + default: + return false; + } + } catch (IOException e) { + return false; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java new file mode 100644 index 0000000000..8d35ec34f6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2009-2010, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * 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.storage.file; + +import static org.eclipse.jgit.lib.Constants.encode; + +import java.io.IOException; + +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; + +/** Updates any reference stored by {@link RefDirectory}. */ +class RefDirectoryUpdate extends RefUpdate { + private final RefDirectory database; + + private LockFile lock; + + RefDirectoryUpdate(final RefDirectory r, final Ref ref) { + super(ref); + database = r; + } + + @Override + protected RefDirectory getRefDatabase() { + return database; + } + + @Override + protected Repository getRepository() { + return database.getRepository(); + } + + @Override + protected boolean tryLock(boolean deref) throws IOException { + Ref dst = getRef(); + if (deref) + dst = dst.getLeaf(); + String name = dst.getName(); + lock = new LockFile(database.fileFor(name)); + if (lock.lock()) { + dst = database.getRef(name); + setOldObjectId(dst != null ? dst.getObjectId() : null); + return true; + } else { + return false; + } + } + + @Override + protected void unlock() { + if (lock != null) { + lock.unlock(); + lock = null; + } + } + + @Override + protected Result doUpdate(final Result status) throws IOException { + lock.setNeedStatInformation(true); + lock.write(getNewObjectId()); + + String msg = getRefLogMessage(); + if (msg != null) { + if (isRefLogIncludingResult()) { + String strResult = toResultString(status); + if (strResult != null) { + if (msg.length() > 0) + msg = msg + ": " + strResult; + else + msg = strResult; + } + } + database.log(this, msg, true); + } + if (!lock.commit()) + return Result.LOCK_FAILURE; + database.stored(this, lock.getCommitLastModified()); + return status; + } + + private String toResultString(final Result status) { + switch (status) { + case FORCED: + return "forced-update"; + case FAST_FORWARD: + return "fast forward"; + case NEW: + return "created"; + default: + return null; + } + } + + @Override + protected Result doDelete(final Result status) throws IOException { + if (getRef().getLeaf().getStorage() != Ref.Storage.NEW) + database.delete(this); + return status; + } + + @Override + protected Result doLink(final String target) throws IOException { + lock.setNeedStatInformation(true); + lock.write(encode(RefDirectory.SYMREF + target + '\n')); + + String msg = getRefLogMessage(); + if (msg != null) + database.log(this, msg, false); + if (!lock.commit()) + return Result.LOCK_FAILURE; + database.storedSymbolicRef(this, lock.getCommitLastModified(), target); + + if (getRef().getStorage() == Ref.Storage.NEW) + return Result.NEW; + return Result.FORCED; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java new file mode 100644 index 0000000000..75214308d6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2009, Robin Rosenberg + * Copyright (C) 2009, Robin Rosenberg + * 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.storage.file; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * Utility for reading reflog entries + */ +public class ReflogReader { + /** + * Parsed reflog entry + */ + static public class Entry { + private ObjectId oldId; + + private ObjectId newId; + + private PersonIdent who; + + private String comment; + + Entry(byte[] raw, int pos) { + oldId = ObjectId.fromString(raw, pos); + pos += Constants.OBJECT_ID_STRING_LENGTH; + if (raw[pos++] != ' ') + throw new IllegalArgumentException( + JGitText.get().rawLogMessageDoesNotParseAsLogEntry); + newId = ObjectId.fromString(raw, pos); + pos += Constants.OBJECT_ID_STRING_LENGTH; + if (raw[pos++] != ' ') { + throw new IllegalArgumentException( + JGitText.get().rawLogMessageDoesNotParseAsLogEntry); + } + who = RawParseUtils.parsePersonIdentOnly(raw, pos); + int p0 = RawParseUtils.next(raw, pos, '\t'); // personident has no + // \t + if (p0 == -1) { + throw new IllegalArgumentException( + JGitText.get().rawLogMessageDoesNotParseAsLogEntry); + } + int p1 = RawParseUtils.nextLF(raw, p0); + if (p1 == -1) { + throw new IllegalArgumentException( + JGitText.get().rawLogMessageDoesNotParseAsLogEntry); + } + comment = RawParseUtils.decode(raw, p0, p1 - 1); + } + + /** + * @return the commit id before the change + */ + public ObjectId getOldId() { + return oldId; + } + + /** + * @return the commit id after the change + */ + public ObjectId getNewId() { + return newId; + } + + /** + * @return user performin the change + */ + public PersonIdent getWho() { + return who; + } + + /** + * @return textual description of the change + */ + public String getComment() { + return comment; + } + + @Override + public String toString() { + return "Entry[" + oldId.name() + ", " + newId.name() + ", " + getWho() + ", " + + getComment() + "]"; + } + } + + private File logName; + + ReflogReader(Repository db, String refname) { + logName = new File(db.getDirectory(), "logs/" + refname); + } + + /** + * Get the last entry in the reflog + * + * @return the latest reflog entry, or null if no log + * @throws IOException + */ + public Entry getLastEntry() throws IOException { + List entries = getReverseEntries(1); + return entries.size() > 0 ? entries.get(0) : null; + } + + /** + * @return all reflog entries in reverse order + * @throws IOException + */ + public List getReverseEntries() throws IOException { + return getReverseEntries(Integer.MAX_VALUE); + } + + /** + * @param max + * max numer of entries to read + * @return all reflog entries in reverse order + * @throws IOException + */ + public List getReverseEntries(int max) throws IOException { + final byte[] log; + try { + log = IO.readFully(logName); + } catch (FileNotFoundException e) { + return Collections.emptyList(); + } + + int rs = RawParseUtils.prevLF(log, log.length); + List ret = new ArrayList(); + while (rs >= 0 && max-- > 0) { + rs = RawParseUtils.prevLF(log, rs); + Entry entry = new Entry(log, rs < 0 ? 0 : rs + 2); + ret.add(entry); + } + return ret; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java new file mode 100644 index 0000000000..92f4824254 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * 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.storage.file; + +import java.lang.ref.SoftReference; + +class UnpackedObjectCache { + private static final int CACHE_SZ = 1024; + + private static final SoftReference DEAD; + + private static int hash(final long position) { + return (((int) position) << 22) >>> 22; + } + + private static int maxByteCount; + + private static final Slot[] cache; + + private static Slot lruHead; + + private static Slot lruTail; + + private static int openByteCount; + + static { + DEAD = new SoftReference(null); + maxByteCount = new WindowCacheConfig().getDeltaBaseCacheLimit(); + + cache = new Slot[CACHE_SZ]; + for (int i = 0; i < CACHE_SZ; i++) + cache[i] = new Slot(); + } + + static synchronized void reconfigure(final WindowCacheConfig cfg) { + final int dbLimit = cfg.getDeltaBaseCacheLimit(); + if (maxByteCount != dbLimit) { + maxByteCount = dbLimit; + releaseMemory(); + } + } + + static synchronized Entry get(final PackFile pack, final long position) { + final Slot e = cache[hash(position)]; + if (e.provider == pack && e.position == position) { + final Entry buf = e.data.get(); + if (buf != null) { + moveToHead(e); + return buf; + } + } + return null; + } + + static synchronized void store(final PackFile pack, final long position, + final byte[] data, final int objectType) { + if (data.length > maxByteCount) + return; // Too large to cache. + + final Slot e = cache[hash(position)]; + clearEntry(e); + + openByteCount += data.length; + releaseMemory(); + + e.provider = pack; + e.position = position; + e.sz = data.length; + e.data = new SoftReference(new Entry(data, objectType)); + moveToHead(e); + } + + private static void releaseMemory() { + while (openByteCount > maxByteCount && lruTail != null) { + final Slot currOldest = lruTail; + final Slot nextOldest = currOldest.lruPrev; + + clearEntry(currOldest); + currOldest.lruPrev = null; + currOldest.lruNext = null; + + if (nextOldest == null) + lruHead = null; + else + nextOldest.lruNext = null; + lruTail = nextOldest; + } + } + + static synchronized void purge(final PackFile file) { + for (final Slot e : cache) { + if (e.provider == file) { + clearEntry(e); + unlink(e); + } + } + } + + private static void moveToHead(final Slot e) { + unlink(e); + e.lruPrev = null; + e.lruNext = lruHead; + if (lruHead != null) + lruHead.lruPrev = e; + else + lruTail = e; + lruHead = e; + } + + private static void unlink(final Slot e) { + final Slot prev = e.lruPrev; + final Slot next = e.lruNext; + if (prev != null) + prev.lruNext = next; + if (next != null) + next.lruPrev = prev; + } + + private static void clearEntry(final Slot e) { + openByteCount -= e.sz; + e.provider = null; + e.data = DEAD; + e.sz = 0; + } + + private UnpackedObjectCache() { + throw new UnsupportedOperationException(); + } + + static class Entry { + final byte[] data; + + final int type; + + Entry(final byte[] aData, final int aType) { + data = aData; + type = aType; + } + } + + private static class Slot { + Slot lruPrev; + + Slot lruNext; + + PackFile provider; + + long position; + + int sz; + + SoftReference data = DEAD; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java new file mode 100644 index 0000000000..054a4ae37e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.InflaterCache; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.MutableInteger; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * Loose object loader. This class loads an object not stored in a pack. + */ +public class UnpackedObjectLoader extends ObjectLoader { + private final int objectType; + + private final int objectSize; + + private final byte[] bytes; + + /** + * Construct an ObjectLoader to read from the file. + * + * @param path + * location of the loose object to read. + * @param id + * expected identity of the object being loaded, if known. + * @throws FileNotFoundException + * the loose object file does not exist. + * @throws IOException + * the loose object file exists, but is corrupt. + */ + public UnpackedObjectLoader(final File path, final AnyObjectId id) + throws IOException { + this(IO.readFully(path), id); + } + + /** + * Construct an ObjectLoader from a loose object's compressed form. + * + * @param compressed + * entire content of the loose object file. + * @throws CorruptObjectException + * The compressed data supplied does not match the format for a + * valid loose object. + */ + public UnpackedObjectLoader(final byte[] compressed) + throws CorruptObjectException { + this(compressed, null); + } + + private UnpackedObjectLoader(final byte[] compressed, final AnyObjectId id) + throws CorruptObjectException { + // Try to determine if this is a legacy format loose object or + // a new style loose object. The legacy format was completely + // compressed with zlib so the first byte must be 0x78 (15-bit + // window size, deflated) and the first 16 bit word must be + // evenly divisible by 31. Otherwise its a new style loose + // object. + // + final Inflater inflater = InflaterCache.get(); + try { + final int fb = compressed[0] & 0xff; + if (fb == 0x78 && (((fb << 8) | compressed[1] & 0xff) % 31) == 0) { + inflater.setInput(compressed); + final byte[] hdr = new byte[64]; + int avail = 0; + while (!inflater.finished() && avail < hdr.length) + try { + int uncompressed = inflater.inflate(hdr, avail, + hdr.length - avail); + if (uncompressed == 0) { + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStreamCorruptHeader); + } + avail += uncompressed; + } catch (DataFormatException dfe) { + final CorruptObjectException coe; + coe = new CorruptObjectException(id, JGitText.get().corruptObjectBadStream); + coe.initCause(dfe); + throw coe; + } + if (avail < 5) + throw new CorruptObjectException(id, JGitText.get().corruptObjectNoHeader); + + final MutableInteger p = new MutableInteger(); + objectType = Constants.decodeTypeString(id, hdr, (byte) ' ', p); + objectSize = RawParseUtils.parseBase10(hdr, p.value, p); + if (objectSize < 0) + throw new CorruptObjectException(id, JGitText.get().corruptObjectNegativeSize); + if (hdr[p.value++] != 0) + throw new CorruptObjectException(id, JGitText.get().corruptObjectGarbageAfterSize); + bytes = new byte[objectSize]; + if (p.value < avail) + System.arraycopy(hdr, p.value, bytes, 0, avail - p.value); + decompress(id, inflater, avail - p.value); + } else { + int p = 0; + int c = compressed[p++] & 0xff; + final int typeCode = (c >> 4) & 7; + int size = c & 15; + int shift = 4; + while ((c & 0x80) != 0) { + c = compressed[p++] & 0xff; + size += (c & 0x7f) << shift; + shift += 7; + } + + switch (typeCode) { + case Constants.OBJ_COMMIT: + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + case Constants.OBJ_TAG: + objectType = typeCode; + break; + default: + throw new CorruptObjectException(id, JGitText.get().corruptObjectInvalidType); + } + + objectSize = size; + bytes = new byte[objectSize]; + inflater.setInput(compressed, p, compressed.length - p); + decompress(id, inflater, 0); + } + } finally { + InflaterCache.release(inflater); + } + } + + private void decompress(final AnyObjectId id, final Inflater inf, int p) + throws CorruptObjectException { + try { + while (!inf.finished()) { + int uncompressed = inf.inflate(bytes, p, objectSize - p); + p += uncompressed; + if (uncompressed == 0 && !inf.finished()) { + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStreamCorruptHeader); + } + } + } catch (DataFormatException dfe) { + final CorruptObjectException coe; + coe = new CorruptObjectException(id, JGitText.get().corruptObjectBadStream); + coe.initCause(dfe); + throw coe; + } + if (p != objectSize) + throw new CorruptObjectException(id, JGitText.get().corruptObjectIncorrectLength); + } + + @Override + public int getType() { + return objectType; + } + + @Override + public long getSize() { + return objectSize; + } + + @Override + public byte[] getCachedBytes() { + return bytes; + } + + @Override + public int getRawType() { + return objectType; + } + + @Override + public long getRawSize() { + return objectSize; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java new file mode 100644 index 0000000000..ba7781a0f1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.zip.DataFormatException; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; + +/** Reader for a non-delta (just deflated) object in a pack file. */ +class WholePackedObjectLoader extends PackedObjectLoader { + private static final int OBJ_COMMIT = Constants.OBJ_COMMIT; + + WholePackedObjectLoader(final PackFile pr, final long objectOffset, + final int headerSize, final int type, final int size) { + super(pr, objectOffset, headerSize); + objectType = type; + objectSize = size; + } + + @Override + void materialize(final WindowCursor curs) throws IOException { + if (cachedBytes != null) { + return; + } + + if (objectType != OBJ_COMMIT) { + UnpackedObjectCache.Entry cache = pack.readCache(objectOffset); + if (cache != null) { + curs.release(); + cachedBytes = cache.data; + return; + } + } + + try { + cachedBytes = pack.decompress(objectOffset + headerSize, + objectSize, curs); + curs.release(); + if (objectType != OBJ_COMMIT) + pack.saveCache(objectOffset, cachedBytes, objectType); + } catch (DataFormatException dfe) { + final CorruptObjectException coe; + coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream, + objectOffset, pack.getPackFile())); + coe.initCause(dfe); + throw coe; + } + } + + @Override + public int getRawType() { + return objectType; + } + + @Override + public long getRawSize() { + return objectSize; + } + + @Override + ObjectId getDeltaBase() { + return null; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java new file mode 100644 index 0000000000..39633ee5ef --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java @@ -0,0 +1,624 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.jgit.JGitText; + +/** + * Caches slices of a {@link PackFile} in memory for faster read access. + *

+ * The WindowCache serves as a Java based "buffer cache", loading segments of a + * PackFile into the JVM heap prior to use. As JGit often wants to do reads of + * only tiny slices of a file, the WindowCache tries to smooth out these tiny + * reads into larger block-sized IO operations. + *

+ * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by + * exactly one thread for the given (PackFile,position) key tuple. + * This is ensured by an array of locks, with the tuple hashed to a lock + * instance. + *

+ * During a miss, older entries are evicted from the cache so long as + * {@link #isFull()} returns true. + *

+ * Its too expensive during object access to be 100% accurate with a least + * recently used (LRU) algorithm. Strictly ordering every read is a lot of + * overhead that typically doesn't yield a corresponding benefit to the + * application. + *

+ * This cache implements a loose LRU policy by randomly picking a window + * comprised of roughly 10% of the cache, and evicting the oldest accessed entry + * within that window. + *

+ * Entities created by the cache are held under SoftReferences, permitting the + * Java runtime's garbage collector to evict entries when heap memory gets low. + * Most JREs implement a loose least recently used algorithm for this eviction. + *

+ * The internal hash table does not expand at runtime, instead it is fixed in + * size at cache creation time. The internal lock table used to gate load + * invocations is also fixed in size. + *

+ * The key tuple is passed through to methods as a pair of parameters rather + * than as a single Object, thus reducing the transient memory allocations of + * callers. It is more efficient to avoid the allocation, as we can't be 100% + * sure that a JIT would be able to stack-allocate a key tuple. + *

+ * This cache has an implementation rule such that: + *

    + *
  • {@link #load(PackFile, long)} is invoked by at most one thread at a time + * for a given (PackFile,position) tuple.
  • + *
  • For every load() invocation there is exactly one + * {@link #createRef(PackFile, long, ByteWindow)} invocation to wrap a + * SoftReference around the cached entity.
  • + *
  • For every Reference created by createRef() there will be + * exactly one call to {@link #clear(Ref)} to cleanup any resources associated + * with the (now expired) cached entity.
  • + *
+ *

+ * Therefore, it is safe to perform resource accounting increments during the + * {@link #load(PackFile, long)} or + * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching + * decrements during {@link #clear(Ref)}. Implementors may need to override + * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional + * accounting information into an implementation specific {@link Ref} subclass, + * as the cached entity may have already been evicted by the JRE's garbage + * collector. + *

+ * To maintain higher concurrency workloads, during eviction only one thread + * performs the eviction work, while other threads can continue to insert new + * objects in parallel. This means that the cache can be temporarily over limit, + * especially if the nominated eviction thread is being starved relative to the + * other threads. + */ +public class WindowCache { + private static final int bits(int newSize) { + if (newSize < 4096) + throw new IllegalArgumentException(JGitText.get().invalidWindowSize); + if (Integer.bitCount(newSize) != 1) + throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2); + return Integer.numberOfTrailingZeros(newSize); + } + + private static final Random rng = new Random(); + + private static volatile WindowCache cache; + + static { + reconfigure(new WindowCacheConfig()); + } + + /** + * Modify the configuration of the window cache. + *

+ * The new configuration is applied immediately. If the new limits are + * smaller than what what is currently cached, older entries will be purged + * as soon as possible to allow the cache to meet the new limit. + * + * @param packedGitLimit + * maximum number of bytes to hold within this instance. + * @param packedGitWindowSize + * number of bytes per window within the cache. + * @param packedGitMMAP + * true to enable use of mmap when creating windows. + * @param deltaBaseCacheLimit + * number of bytes to hold in the delta base cache. + * @deprecated Use {@link WindowCacheConfig} instead. + */ + public static void reconfigure(final int packedGitLimit, + final int packedGitWindowSize, final boolean packedGitMMAP, + final int deltaBaseCacheLimit) { + final WindowCacheConfig c = new WindowCacheConfig(); + c.setPackedGitLimit(packedGitLimit); + c.setPackedGitWindowSize(packedGitWindowSize); + c.setPackedGitMMAP(packedGitMMAP); + c.setDeltaBaseCacheLimit(deltaBaseCacheLimit); + reconfigure(c); + } + + /** + * Modify the configuration of the window cache. + *

+ * The new configuration is applied immediately. If the new limits are + * smaller than what what is currently cached, older entries will be purged + * as soon as possible to allow the cache to meet the new limit. + * + * @param cfg + * the new window cache configuration. + * @throws IllegalArgumentException + * the cache configuration contains one or more invalid + * settings, usually too low of a limit. + */ + public static void reconfigure(final WindowCacheConfig cfg) { + final WindowCache nc = new WindowCache(cfg); + final WindowCache oc = cache; + if (oc != null) + oc.removeAll(); + cache = nc; + UnpackedObjectCache.reconfigure(cfg); + } + + static WindowCache getInstance() { + return cache; + } + + static final ByteWindow get(final PackFile pack, final long offset) + throws IOException { + final WindowCache c = cache; + final ByteWindow r = c.getOrLoad(pack, c.toStart(offset)); + if (c != cache) { + // The cache was reconfigured while we were using the old one + // to load this window. The window is still valid, but our + // cache may think its still live. Ensure the window is removed + // from the old cache so resources can be released. + // + c.removeAll(); + } + return r; + } + + static final void purge(final PackFile pack) { + cache.removeAll(pack); + } + + /** ReferenceQueue to cleanup released and garbage collected windows. */ + private final ReferenceQueue queue; + + /** Number of entries in {@link #table}. */ + private final int tableSize; + + /** Access clock for loose LRU. */ + private final AtomicLong clock; + + /** Hash bucket directory; entries are chained below. */ + private final AtomicReferenceArray table; + + /** Locks to prevent concurrent loads for same (PackFile,position). */ + private final Lock[] locks; + + /** Lock to elect the eviction thread after a load occurs. */ + private final ReentrantLock evictLock; + + /** Number of {@link #table} buckets to scan for an eviction window. */ + private final int evictBatch; + + private final int maxFiles; + + private final long maxBytes; + + private final boolean mmap; + + private final int windowSizeShift; + + private final int windowSize; + + private final AtomicInteger openFiles; + + private final AtomicLong openBytes; + + private WindowCache(final WindowCacheConfig cfg) { + tableSize = tableSize(cfg); + final int lockCount = lockCount(cfg); + if (tableSize < 1) + throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1); + if (lockCount < 1) + throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1); + + queue = new ReferenceQueue(); + clock = new AtomicLong(1); + table = new AtomicReferenceArray(tableSize); + locks = new Lock[lockCount]; + for (int i = 0; i < locks.length; i++) + locks[i] = new Lock(); + evictLock = new ReentrantLock(); + + int eb = (int) (tableSize * .1); + if (64 < eb) + eb = 64; + else if (eb < 4) + eb = 4; + if (tableSize < eb) + eb = tableSize; + evictBatch = eb; + + maxFiles = cfg.getPackedGitOpenFiles(); + maxBytes = cfg.getPackedGitLimit(); + mmap = cfg.isPackedGitMMAP(); + windowSizeShift = bits(cfg.getPackedGitWindowSize()); + windowSize = 1 << windowSizeShift; + + openFiles = new AtomicInteger(); + openBytes = new AtomicLong(); + + if (maxFiles < 1) + throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1); + if (maxBytes < windowSize) + throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); + } + + int getOpenFiles() { + return openFiles.get(); + } + + long getOpenBytes() { + return openBytes.get(); + } + + private int hash(final int packHash, final long off) { + return packHash + (int) (off >>> windowSizeShift); + } + + private ByteWindow load(final PackFile pack, final long offset) + throws IOException { + if (pack.beginWindowCache()) + openFiles.incrementAndGet(); + try { + if (mmap) + return pack.mmap(offset, windowSize); + return pack.read(offset, windowSize); + } catch (IOException e) { + close(pack); + throw e; + } catch (RuntimeException e) { + close(pack); + throw e; + } catch (Error e) { + close(pack); + throw e; + } + } + + private Ref createRef(final PackFile p, final long o, final ByteWindow v) { + final Ref ref = new Ref(p, o, v, queue); + openBytes.addAndGet(ref.size); + return ref; + } + + private void clear(final Ref ref) { + openBytes.addAndGet(-ref.size); + close(ref.pack); + } + + private void close(final PackFile pack) { + if (pack.endWindowCache()) + openFiles.decrementAndGet(); + } + + private boolean isFull() { + return maxFiles < openFiles.get() || maxBytes < openBytes.get(); + } + + private long toStart(final long offset) { + return (offset >>> windowSizeShift) << windowSizeShift; + } + + private static int tableSize(final WindowCacheConfig cfg) { + final int wsz = cfg.getPackedGitWindowSize(); + final long limit = cfg.getPackedGitLimit(); + if (wsz <= 0) + throw new IllegalArgumentException(JGitText.get().invalidWindowSize); + if (limit < wsz) + throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); + return (int) Math.min(5 * (limit / wsz) / 2, 2000000000); + } + + private static int lockCount(final WindowCacheConfig cfg) { + return Math.max(cfg.getPackedGitOpenFiles(), 32); + } + + /** + * Lookup a cached object, creating and loading it if it doesn't exist. + * + * @param pack + * the pack that "contains" the cached object. + * @param position + * offset within pack of the object. + * @return the object reference. + * @throws IOException + * the object reference was not in the cache and could not be + * obtained by {@link #load(PackFile, long)}. + */ + private ByteWindow getOrLoad(final PackFile pack, final long position) + throws IOException { + final int slot = slot(pack, position); + final Entry e1 = table.get(slot); + ByteWindow v = scan(e1, pack, position); + if (v != null) + return v; + + synchronized (lock(pack, position)) { + Entry e2 = table.get(slot); + if (e2 != e1) { + v = scan(e2, pack, position); + if (v != null) + return v; + } + + v = load(pack, position); + final Ref ref = createRef(pack, position, v); + hit(ref); + for (;;) { + final Entry n = new Entry(clean(e2), ref); + if (table.compareAndSet(slot, e2, n)) + break; + e2 = table.get(slot); + } + } + + if (evictLock.tryLock()) { + try { + gc(); + evict(); + } finally { + evictLock.unlock(); + } + } + + return v; + } + + private ByteWindow scan(Entry n, final PackFile pack, final long position) { + for (; n != null; n = n.next) { + final Ref r = n.ref; + if (r.pack == pack && r.position == position) { + final ByteWindow v = r.get(); + if (v != null) { + hit(r); + return v; + } + n.kill(); + break; + } + } + return null; + } + + private void hit(final Ref r) { + // We don't need to be 100% accurate here. Its sufficient that at least + // one thread performs the increment. Any other concurrent access at + // exactly the same time can simply use the same clock value. + // + // Consequently we attempt the set, but we don't try to recover should + // it fail. This is why we don't use getAndIncrement() here. + // + final long c = clock.get(); + clock.compareAndSet(c, c + 1); + r.lastAccess = c; + } + + private void evict() { + while (isFull()) { + int ptr = rng.nextInt(tableSize); + Entry old = null; + int slot = 0; + for (int b = evictBatch - 1; b >= 0; b--, ptr++) { + if (tableSize <= ptr) + ptr = 0; + for (Entry e = table.get(ptr); e != null; e = e.next) { + if (e.dead) + continue; + if (old == null || e.ref.lastAccess < old.ref.lastAccess) { + old = e; + slot = ptr; + } + } + } + if (old != null) { + old.kill(); + gc(); + final Entry e1 = table.get(slot); + table.compareAndSet(slot, e1, clean(e1)); + } + } + } + + /** + * Clear every entry from the cache. + *

+ * This is a last-ditch effort to clear out the cache, such as before it + * gets replaced by another cache that is configured differently. This + * method tries to force every cached entry through {@link #clear(Ref)} to + * ensure that resources are correctly accounted for and cleaned up by the + * subclass. A concurrent reader loading entries while this method is + * running may cause resource accounting failures. + */ + private void removeAll() { + for (int s = 0; s < tableSize; s++) { + Entry e1; + do { + e1 = table.get(s); + for (Entry e = e1; e != null; e = e.next) + e.kill(); + } while (!table.compareAndSet(s, e1, null)); + } + gc(); + } + + /** + * Clear all entries related to a single file. + *

+ * Typically this method is invoked during {@link PackFile#close()}, when we + * know the pack is never going to be useful to us again (for example, it no + * longer exists on disk). A concurrent reader loading an entry from this + * same pack may cause the pack to become stuck in the cache anyway. + * + * @param pack + * the file to purge all entries of. + */ + private void removeAll(final PackFile pack) { + for (int s = 0; s < tableSize; s++) { + final Entry e1 = table.get(s); + boolean hasDead = false; + for (Entry e = e1; e != null; e = e.next) { + if (e.ref.pack == pack) { + e.kill(); + hasDead = true; + } else if (e.dead) + hasDead = true; + } + if (hasDead) + table.compareAndSet(s, e1, clean(e1)); + } + gc(); + } + + @SuppressWarnings("unchecked") + private void gc() { + Ref r; + while ((r = (Ref) queue.poll()) != null) { + // Sun's Java 5 and 6 implementation have a bug where a Reference + // can be enqueued and dequeued twice on the same reference queue + // due to a race condition within ReferenceQueue.enqueue(Reference). + // + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6837858 + // + // We CANNOT permit a Reference to come through us twice, as it will + // skew the resource counters we maintain. Our canClear() check here + // provides a way to skip the redundant dequeues, if any. + // + if (r.canClear()) { + clear(r); + + boolean found = false; + final int s = slot(r.pack, r.position); + final Entry e1 = table.get(s); + for (Entry n = e1; n != null; n = n.next) { + if (n.ref == r) { + n.dead = true; + found = true; + break; + } + } + if (found) + table.compareAndSet(s, e1, clean(e1)); + } + } + } + + private int slot(final PackFile pack, final long position) { + return (hash(pack.hash, position) >>> 1) % tableSize; + } + + private Lock lock(final PackFile pack, final long position) { + return locks[(hash(pack.hash, position) >>> 1) % locks.length]; + } + + private static Entry clean(Entry top) { + while (top != null && top.dead) { + top.ref.enqueue(); + top = top.next; + } + if (top == null) + return null; + final Entry n = clean(top.next); + return n == top.next ? top : new Entry(n, top.ref); + } + + private static class Entry { + /** Next entry in the hash table's chain list. */ + final Entry next; + + /** The referenced object. */ + final Ref ref; + + /** + * Marked true when ref.get() returns null and the ref is dead. + *

+ * A true here indicates that the ref is no longer accessible, and that + * we therefore need to eventually purge this Entry object out of the + * bucket's chain. + */ + volatile boolean dead; + + Entry(final Entry n, final Ref r) { + next = n; + ref = r; + } + + final void kill() { + dead = true; + ref.enqueue(); + } + } + + /** A soft reference wrapped around a cached object. */ + private static class Ref extends SoftReference { + final PackFile pack; + + final long position; + + final int size; + + long lastAccess; + + private boolean cleared; + + protected Ref(final PackFile pack, final long position, + final ByteWindow v, final ReferenceQueue queue) { + super(v, queue); + this.pack = pack; + this.position = position; + this.size = v.size(); + } + + final synchronized boolean canClear() { + if (cleared) + return false; + cleared = true; + return true; + } + } + + private static final class Lock { + // Used only for its implicit monitor. + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java new file mode 100644 index 0000000000..48d7018e42 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2009, 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.storage.file; + +import org.eclipse.jgit.lib.Config; + +/** Configuration parameters for {@link WindowCache}. */ +public class WindowCacheConfig { + /** 1024 (number of bytes in one kibibyte/kilobyte) */ + public static final int KB = 1024; + + /** 1024 {@link #KB} (number of bytes in one mebibyte/megabyte) */ + public static final int MB = 1024 * KB; + + private int packedGitOpenFiles; + + private long packedGitLimit; + + private int packedGitWindowSize; + + private boolean packedGitMMAP; + + private int deltaBaseCacheLimit; + + /** Create a default configuration. */ + public WindowCacheConfig() { + packedGitOpenFiles = 128; + packedGitLimit = 10 * MB; + packedGitWindowSize = 8 * KB; + packedGitMMAP = false; + deltaBaseCacheLimit = 10 * MB; + } + + /** + * @return maximum number of streams to open at a time. Open packs count + * against the process limits. Default is 128. + */ + public int getPackedGitOpenFiles() { + return packedGitOpenFiles; + } + + /** + * @param fdLimit + * maximum number of streams to open at a time. Open packs count + * against the process limits + */ + public void setPackedGitOpenFiles(final int fdLimit) { + packedGitOpenFiles = fdLimit; + } + + /** + * @return maximum number bytes of heap memory to dedicate to caching pack + * file data. Default is 10 MB. + */ + public long getPackedGitLimit() { + return packedGitLimit; + } + + /** + * @param newLimit + * maximum number bytes of heap memory to dedicate to caching + * pack file data. + */ + public void setPackedGitLimit(final long newLimit) { + packedGitLimit = newLimit; + } + + /** + * @return size in bytes of a single window mapped or read in from the pack + * file. Default is 8 KB. + */ + public int getPackedGitWindowSize() { + return packedGitWindowSize; + } + + /** + * @param newSize + * size in bytes of a single window read in from the pack file. + */ + public void setPackedGitWindowSize(final int newSize) { + packedGitWindowSize = newSize; + } + + /** + * @return true enables use of Java NIO virtual memory mapping for windows; + * false reads entire window into a byte[] with standard read calls. + * Default false. + */ + public boolean isPackedGitMMAP() { + return packedGitMMAP; + } + + /** + * @param usemmap + * true enables use of Java NIO virtual memory mapping for + * windows; false reads entire window into a byte[] with standard + * read calls. + */ + public void setPackedGitMMAP(final boolean usemmap) { + packedGitMMAP = usemmap; + } + + /** + * @return maximum number of bytes to cache in {@link UnpackedObjectCache} + * for inflated, recently accessed objects, without delta chains. + * Default 10 MB. + */ + public int getDeltaBaseCacheLimit() { + return deltaBaseCacheLimit; + } + + /** + * @param newLimit + * maximum number of bytes to cache in + * {@link UnpackedObjectCache} for inflated, recently accessed + * objects, without delta chains. + */ + public void setDeltaBaseCacheLimit(final int newLimit) { + deltaBaseCacheLimit = newLimit; + } + + /** + * Update properties by setting fields from the configuration. + *

+ * If a property is not defined in the configuration, then it is left + * unmodified. + * + * @param rc configuration to read properties from. + */ + public void fromConfig(final Config rc) { + setPackedGitOpenFiles(rc.getInt("core", null, "packedgitopenfiles", getPackedGitOpenFiles())); + setPackedGitLimit(rc.getLong("core", null, "packedgitlimit", getPackedGitLimit())); + setPackedGitWindowSize(rc.getInt("core", null, "packedgitwindowsize", getPackedGitWindowSize())); + setPackedGitMMAP(rc.getBoolean("core", null, "packedgitmmap", isPackedGitMMAP())); + setDeltaBaseCacheLimit(rc.getInt("core", null, "deltabasecachelimit", getDeltaBaseCacheLimit())); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java new file mode 100644 index 0000000000..a88261162d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.storage.file; + +import java.io.IOException; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.InflaterCache; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ObjectReuseAsIs; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackOutputStream; +import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.revwalk.RevObject; + +/** Active handle to a ByteWindow. */ +final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { + /** Temporary buffer large enough for at least one raw object id. */ + final byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH]; + + private Inflater inf; + + private ByteWindow window; + + private final FileObjectDatabase db; + + WindowCursor(FileObjectDatabase db) { + this.db = db; + } + + public boolean hasObject(AnyObjectId objectId) throws IOException { + return db.hasObject(objectId); + } + + public ObjectLoader openObject(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IOException { + final ObjectLoader ldr = db.openObject(this, objectId); + if (ldr == null) { + if (typeHint == OBJ_ANY) + throw new MissingObjectException(objectId.copy(), "unknown"); + throw new MissingObjectException(objectId.copy(), typeHint); + } + return ldr; + } + + public LocalObjectToPack newObjectToPack(RevObject obj) { + return new LocalObjectToPack(obj); + } + + public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp) + throws IOException, MissingObjectException { + db.selectObjectRepresentation(packer, otp, this); + } + + public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp) + throws IOException, StoredObjectRepresentationNotAvailableException { + LocalObjectToPack src = (LocalObjectToPack) otp; + src.copyFromPack.copyAsIs(out, src, this); + } + + /** + * Copy bytes from the window to a caller supplied buffer. + * + * @param pack + * the file the desired window is stored within. + * @param position + * position within the file to read from. + * @param dstbuf + * destination buffer to copy into. + * @param dstoff + * offset within dstbuf to start copying into. + * @param cnt + * number of bytes to copy. This value may exceed the number of + * bytes remaining in the window starting at offset + * pos. + * @return number of bytes actually copied; this may be less than + * cnt if cnt exceeded the number of bytes + * available. + * @throws IOException + * this cursor does not match the provider or id and the proper + * window could not be acquired through the provider's cache. + */ + int copy(final PackFile pack, long position, final byte[] dstbuf, + int dstoff, final int cnt) throws IOException { + final long length = pack.length; + int need = cnt; + while (need > 0 && position < length) { + pin(pack, position); + final int r = window.copy(position, dstbuf, dstoff, need); + position += r; + dstoff += r; + need -= r; + } + return cnt - need; + } + + /** + * Pump bytes into the supplied inflater as input. + * + * @param pack + * the file the desired window is stored within. + * @param position + * position within the file to read from. + * @param dstbuf + * destination buffer the inflater should output decompressed + * data to. + * @param dstoff + * current offset within dstbuf to inflate into. + * @return updated dstoff based on the number of bytes + * successfully inflated into dstbuf. + * @throws IOException + * this cursor does not match the provider or id and the proper + * window could not be acquired through the provider's cache. + * @throws DataFormatException + * the inflater encountered an invalid chunk of data. Data + * stream corruption is likely. + */ + int inflate(final PackFile pack, long position, final byte[] dstbuf, + int dstoff) throws IOException, DataFormatException { + prepareInflater(); + for (;;) { + pin(pack, position); + dstoff = window.inflate(position, dstbuf, dstoff, inf); + if (inf.finished()) + return dstoff; + position = window.end; + } + } + + ByteArrayWindow quickCopy(PackFile p, long pos, long cnt) + throws IOException { + pin(p, pos); + if (window instanceof ByteArrayWindow + && window.contains(p, pos + (cnt - 1))) + return (ByteArrayWindow) window; + return null; + } + + Inflater inflater() { + prepareInflater(); + return inf; + } + + private void prepareInflater() { + if (inf == null) + inf = InflaterCache.get(); + else + inf.reset(); + } + + private void pin(final PackFile pack, final long position) + throws IOException { + final ByteWindow w = window; + if (w == null || !w.contains(pack, position)) { + // If memory is low, we may need what is in our window field to + // be cleaned up by the GC during the get for the next window. + // So we always clear it, even though we are just going to set + // it again. + // + window = null; + window = WindowCache.get(pack, position); + } + } + + /** Release the current window cursor. */ + public void release() { + window = null; + try { + InflaterCache.release(inf); + } finally { + inf = null; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java index 2819ae26de..8c336c5255 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java @@ -61,7 +61,6 @@ import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Config.SectionParser; @@ -73,6 +72,7 @@ import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter; import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.transport.PacketLineIn.AckNackResult; import org.eclipse.jgit.util.TemporaryBuffer; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java index 3b97dfc0d6..98ecc5540f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java @@ -68,13 +68,13 @@ import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java index 50c0866f23..9dc54da00b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java @@ -51,9 +51,9 @@ import java.util.Set; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.storage.file.PackLock; /** * Lists known refs from the remote and copies objects of selected refs. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java index 27505bef65..72d73eb59b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -63,14 +63,14 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.LockFile; +import org.eclipse.jgit.storage.file.PackLock; class FetchProcess { /** Transport we will fetch over. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java index 0cd6733695..5a37825cea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java @@ -75,11 +75,11 @@ import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.PackIndexWriter; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.storage.file.PackIndexWriter; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.util.NB; /** Indexes Git pack files for local use. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java index 91105cc8a3..1597a35fe9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -73,7 +73,6 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; @@ -86,6 +85,7 @@ import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.transport.ReceiveCommand.Result; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.util.io.InterruptTimer; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java index 71e7bf285e..0f4c1314a3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java @@ -85,10 +85,10 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.RefDirectory; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.lib.Config.SectionParser; +import org.eclipse.jgit.storage.file.RefDirectory; import org.eclipse.jgit.util.HttpSupport; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java index ae54a155a1..c9b18be1f8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java @@ -60,8 +60,8 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.io.MessageWriter; import org.eclipse.jgit.util.io.StreamCopyThread; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java index c9d2e9e193..89dd6b4904 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -68,15 +68,11 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectChecker; -import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.PackIndex; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.UnpackedObjectLoader; import org.eclipse.jgit.revwalk.DateRevQueue; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; @@ -84,6 +80,10 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.ObjectDirectory; +import org.eclipse.jgit.storage.file.PackIndex; +import org.eclipse.jgit.storage.file.PackLock; +import org.eclipse.jgit.storage.file.UnpackedObjectLoader; import org.eclipse.jgit.treewalk.TreeWalk; /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java index f1743b378d..0e2adae503 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java @@ -62,7 +62,7 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.RefDirectory; +import org.eclipse.jgit.storage.file.RefDirectory; import org.eclipse.jgit.util.IO; /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java index 9d7feb08f2..f4382eb186 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java @@ -50,7 +50,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.TimeZone; -import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; /** * Interface to read values from the system. -- cgit v1.2.3