123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- /*
- * 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.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.errors.PackMismatchException;
- import org.eclipse.jgit.lib.RepositoryCache.FileKey;
- import org.eclipse.jgit.util.FS;
-
- /**
- * Traditional file system based {@link ObjectDatabase}.
- * <p>
- * 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.
- */
- public class ObjectDirectory extends ObjectDatabase {
- private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]);
-
- private final File objects;
-
- private final File infoDirectory;
-
- private final File packDirectory;
-
- private final File alternatesFile;
-
- private final AtomicReference<PackList> packList;
-
- private final File[] alternateObjectDir;
-
- /**
- * Initialize a reference to an on-disk object directory.
- *
- * @param dir
- * the location of the <code>objects</code> directory.
- * @param alternateObjectDir
- * a list of alternate object directories
- */
- public ObjectDirectory(final File dir, File[] alternateObjectDir) {
- objects = dir;
- this.alternateObjectDir = alternateObjectDir;
- infoDirectory = new File(objects, "info");
- packDirectory = new File(objects, "pack");
- alternatesFile = new File(infoDirectory, "alternates");
- packList = new AtomicReference<PackList>(NO_PACKS);
- }
-
- /**
- * @return the location of the <code>objects</code> 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 void closeSelf() {
- final PackList packs = packList.get();
- packList.set(NO_PACKS);
- for (final PackFile p : packs.packs)
- p.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<PackFile> 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("Not a valid pack " + pack);
-
- if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx"))
- throw new IOException("Not a valid pack " + idx);
-
- if (!p.substring(0, 45).equals(i.substring(0, 45)))
- throw new IOException("Pack " + pack + "does not match index");
-
- insertPack(new PackFile(idx, pack));
- }
-
- @Override
- public String toString() {
- return "ObjectDirectory[" + getDirectory() + "]";
- }
-
- @Override
- protected 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;
- }
-
- @Override
- protected 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 openObjectInAllPacks1(final Collection<PackedObjectLoader> out,
- 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) {
- out.add(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;
- }
- }
-
- @Override
- protected boolean hasObject2(final String objectName) {
- return fileFor(objectName).exists();
- }
-
- @Override
- protected 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;
- }
- }
-
- @Override
- protected 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();
- final PackFile[] oldList = o.packs;
- 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<String, PackFile> forReuse = reuseMap(old);
- final long lastRead = System.currentTimeMillis();
- final long lastModified = packDirectory.lastModified();
- final Set<String> names = listPackDirectory();
- final List<PackFile> list = new ArrayList<PackFile>(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<String, PackFile> reuseMap(final PackList old) {
- final Map<String, PackFile> forReuse = new HashMap<String, PackFile>();
- 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<String> listPackDirectory() {
- final String[] nameList = packDirectory.list();
- if (nameList == null)
- return Collections.emptySet();
- final Set<String> nameSet = new HashSet<String>(nameList.length << 1);
- for (final String name : nameList) {
- if (name.startsWith("pack-"))
- nameSet.add(name);
- }
- return nameSet;
- }
-
- @Override
- protected ObjectDatabase[] loadAlternates() throws IOException {
- final List<ObjectDatabase> l = new ArrayList<ObjectDatabase>(4);
- if (alternateObjectDir != null) {
- for (File d : alternateObjectDir) {
- l.add(openAlternate(d));
- }
- } else {
- final BufferedReader br = open(alternatesFile);
- try {
- String line;
- while ((line = br.readLine()) != null) {
- l.add(openAlternate(line));
- }
- } finally {
- br.close();
- }
- }
-
- if (l.isEmpty()) {
- return NO_ALTERNATES;
- }
- return l.toArray(new ObjectDatabase[l.size()]);
- }
-
- private static BufferedReader open(final File f)
- throws FileNotFoundException {
- return new BufferedReader(new FileReader(f));
- }
-
- private ObjectDatabase openAlternate(final String location)
- throws IOException {
- final File objdir = FS.resolve(objects, location);
- return openAlternate(objdir);
- }
-
- private ObjectDatabase openAlternate(File objdir) throws IOException {
- final File parent = objdir.getParentFile();
- if (FileKey.isGitRepository(parent)) {
- final Repository db = RepositoryCache.open(FileKey.exact(parent));
- return new AlternateRepositoryDatabase(db);
- }
- return new ObjectDirectory(objdir, null);
- }
-
- 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 new CachedObjectDirectory(this);
- }
- }
|