2 * Copyright (C) 2009, Google Inc.
3 * and other copyright owners as documented in the project's IP log.
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44 package org
.eclipse
.jgit
.lib
;
46 import java
.io
.BufferedReader
;
48 import java
.io
.FileNotFoundException
;
49 import java
.io
.FileReader
;
50 import java
.io
.IOException
;
51 import java
.util
.ArrayList
;
52 import java
.util
.Arrays
;
53 import java
.util
.Collection
;
54 import java
.util
.Collections
;
55 import java
.util
.HashMap
;
56 import java
.util
.HashSet
;
57 import java
.util
.List
;
60 import java
.util
.concurrent
.atomic
.AtomicReference
;
62 import org
.eclipse
.jgit
.errors
.PackMismatchException
;
63 import org
.eclipse
.jgit
.lib
.RepositoryCache
.FileKey
;
64 import org
.eclipse
.jgit
.util
.FS
;
67 * Traditional file system based {@link ObjectDatabase}.
69 * This is the classical object database representation for a Git repository,
70 * where objects are stored loose by hashing them into directories by their
71 * {@link ObjectId}, or are stored in compressed containers known as
74 public class ObjectDirectory
extends ObjectDatabase
{
75 private static final PackList NO_PACKS
= new PackList(-1, -1, new PackFile
[0]);
77 private final File objects
;
79 private final File infoDirectory
;
81 private final File packDirectory
;
83 private final File alternatesFile
;
85 private final AtomicReference
<PackList
> packList
;
87 private final File
[] alternateObjectDir
;
90 * Initialize a reference to an on-disk object directory.
93 * the location of the <code>objects</code> directory.
94 * @param alternateObjectDir
95 * a list of alternate object directories
97 public ObjectDirectory(final File dir
, File
[] alternateObjectDir
) {
99 this.alternateObjectDir
= alternateObjectDir
;
100 infoDirectory
= new File(objects
, "info");
101 packDirectory
= new File(objects
, "pack");
102 alternatesFile
= new File(infoDirectory
, "alternates");
103 packList
= new AtomicReference
<PackList
>(NO_PACKS
);
107 * @return the location of the <code>objects</code> directory.
109 public final File
getDirectory() {
114 public boolean exists() {
115 return objects
.exists();
119 public void create() throws IOException
{
121 infoDirectory
.mkdir();
122 packDirectory
.mkdir();
126 public void closeSelf() {
127 final PackList packs
= packList
.get();
128 packList
.set(NO_PACKS
);
129 for (final PackFile p
: packs
.packs
)
134 * Compute the location of a loose object file.
137 * identity of the loose object to map to the directory.
138 * @return location of the object, if it were to exist as a loose object.
140 public File
fileFor(final AnyObjectId objectId
) {
141 return fileFor(objectId
.name());
144 private File
fileFor(final String objectName
) {
145 final String d
= objectName
.substring(0, 2);
146 final String f
= objectName
.substring(2);
147 return new File(new File(objects
, d
), f
);
151 * @return unmodifiable collection of all known pack files local to this
152 * directory. Most recent packs are presented first. Packs most
153 * likely to contain more recent objects appear before packs
154 * containing objects referenced by commits further back in the
155 * history of the repository.
157 public Collection
<PackFile
> getPacks() {
158 final PackFile
[] packs
= packList
.get().packs
;
159 return Collections
.unmodifiableCollection(Arrays
.asList(packs
));
163 * Add a single existing pack to the list of available pack files.
166 * path of the pack file to open.
168 * path of the corresponding index file.
169 * @throws IOException
170 * index file could not be opened, read, or is not recognized as
171 * a Git pack file index.
173 public void openPack(final File pack
, final File idx
) throws IOException
{
174 final String p
= pack
.getName();
175 final String i
= idx
.getName();
177 if (p
.length() != 50 || !p
.startsWith("pack-") || !p
.endsWith(".pack"))
178 throw new IOException("Not a valid pack " + pack
);
180 if (i
.length() != 49 || !i
.startsWith("pack-") || !i
.endsWith(".idx"))
181 throw new IOException("Not a valid pack " + idx
);
183 if (!p
.substring(0, 45).equals(i
.substring(0, 45)))
184 throw new IOException("Pack " + pack
+ "does not match index");
186 insertPack(new PackFile(idx
, pack
));
190 public String
toString() {
191 return "ObjectDirectory[" + getDirectory() + "]";
195 protected boolean hasObject1(final AnyObjectId objectId
) {
196 for (final PackFile p
: packList
.get().packs
) {
198 if (p
.hasObject(objectId
)) {
201 } catch (IOException e
) {
202 // The hasObject call should have only touched the index,
203 // so any failure here indicates the index is unreadable
204 // by this process, and the pack is likewise not readable.
214 protected ObjectLoader
openObject1(final WindowCursor curs
,
215 final AnyObjectId objectId
) throws IOException
{
216 PackList pList
= packList
.get();
218 for (final PackFile p
: pList
.packs
) {
220 final PackedObjectLoader ldr
= p
.get(curs
, objectId
);
222 ldr
.materialize(curs
);
225 } catch (PackMismatchException e
) {
226 // Pack was modified; refresh the entire pack list.
228 pList
= scanPacks(pList
);
230 } catch (IOException e
) {
231 // Assume the pack is corrupted.
241 void openObjectInAllPacks1(final Collection
<PackedObjectLoader
> out
,
242 final WindowCursor curs
, final AnyObjectId objectId
)
244 PackList pList
= packList
.get();
246 for (final PackFile p
: pList
.packs
) {
248 final PackedObjectLoader ldr
= p
.get(curs
, objectId
);
252 } catch (PackMismatchException e
) {
253 // Pack was modified; refresh the entire pack list.
255 pList
= scanPacks(pList
);
257 } catch (IOException e
) {
258 // Assume the pack is corrupted.
268 protected boolean hasObject2(final String objectName
) {
269 return fileFor(objectName
).exists();
273 protected ObjectLoader
openObject2(final WindowCursor curs
,
274 final String objectName
, final AnyObjectId objectId
)
277 return new UnpackedObjectLoader(fileFor(objectName
), objectId
);
278 } catch (FileNotFoundException noFile
) {
284 protected boolean tryAgain1() {
285 final PackList old
= packList
.get();
286 if (old
.tryAgain(packDirectory
.lastModified()))
287 return old
!= scanPacks(old
);
291 private void insertPack(final PackFile pf
) {
295 final PackFile
[] oldList
= o
.packs
;
296 final PackFile
[] newList
= new PackFile
[1 + oldList
.length
];
298 System
.arraycopy(oldList
, 0, newList
, 1, oldList
.length
);
299 n
= new PackList(o
.lastRead
, o
.lastModified
, newList
);
300 } while (!packList
.compareAndSet(o
, n
));
303 private void removePack(final PackFile deadPack
) {
308 final PackFile
[] oldList
= o
.packs
;
309 final int j
= indexOf(oldList
, deadPack
);
313 final PackFile
[] newList
= new PackFile
[oldList
.length
- 1];
314 System
.arraycopy(oldList
, 0, newList
, 0, j
);
315 System
.arraycopy(oldList
, j
+ 1, newList
, j
, newList
.length
- j
);
316 n
= new PackList(o
.lastRead
, o
.lastModified
, newList
);
317 } while (!packList
.compareAndSet(o
, n
));
321 private static int indexOf(final PackFile
[] list
, final PackFile pack
) {
322 for (int i
= 0; i
< list
.length
; i
++) {
329 private PackList
scanPacks(final PackList original
) {
330 synchronized (packList
) {
335 // Another thread did the scan for us, while we
336 // were blocked on the monitor above.
340 n
= scanPacksImpl(o
);
343 } while (!packList
.compareAndSet(o
, n
));
348 private PackList
scanPacksImpl(final PackList old
) {
349 final Map
<String
, PackFile
> forReuse
= reuseMap(old
);
350 final long lastRead
= System
.currentTimeMillis();
351 final long lastModified
= packDirectory
.lastModified();
352 final Set
<String
> names
= listPackDirectory();
353 final List
<PackFile
> list
= new ArrayList
<PackFile
>(names
.size() >> 2);
354 boolean foundNew
= false;
355 for (final String indexName
: names
) {
356 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
358 if (indexName
.length() != 49 || !indexName
.endsWith(".idx"))
361 final String base
= indexName
.substring(0, indexName
.length() - 4);
362 final String packName
= base
+ ".pack";
363 if (!names
.contains(packName
)) {
364 // Sometimes C Git's HTTP fetch transport leaves a
365 // .idx file behind and does not download the .pack.
366 // We have to skip over such useless indexes.
371 final PackFile oldPack
= forReuse
.remove(packName
);
372 if (oldPack
!= null) {
377 final File packFile
= new File(packDirectory
, packName
);
378 final File idxFile
= new File(packDirectory
, indexName
);
379 list
.add(new PackFile(idxFile
, packFile
));
383 // If we did not discover any new files, the modification time was not
384 // changed, and we did not remove any files, then the set of files is
385 // the same as the set we were given. Instead of building a new object
386 // return the same collection.
388 if (!foundNew
&& lastModified
== old
.lastModified
&& forReuse
.isEmpty())
389 return old
.updateLastRead(lastRead
);
391 for (final PackFile p
: forReuse
.values()) {
396 return new PackList(lastRead
, lastModified
, NO_PACKS
.packs
);
398 final PackFile
[] r
= list
.toArray(new PackFile
[list
.size()]);
399 Arrays
.sort(r
, PackFile
.SORT
);
400 return new PackList(lastRead
, lastModified
, r
);
403 private static Map
<String
, PackFile
> reuseMap(final PackList old
) {
404 final Map
<String
, PackFile
> forReuse
= new HashMap
<String
, PackFile
>();
405 for (final PackFile p
: old
.packs
) {
407 // The pack instance is corrupted, and cannot be safely used
408 // again. Do not include it in our reuse map.
414 final PackFile prior
= forReuse
.put(p
.getPackFile().getName(), p
);
416 // This should never occur. It should be impossible for us
417 // to have two pack files with the same name, as all of them
418 // came out of the same directory. If it does, we promised to
419 // close any PackFiles we did not reuse, so close the second,
420 // readers are likely to be actively using the first.
422 forReuse
.put(prior
.getPackFile().getName(), prior
);
429 private Set
<String
> listPackDirectory() {
430 final String
[] nameList
= packDirectory
.list();
431 if (nameList
== null)
432 return Collections
.emptySet();
433 final Set
<String
> nameSet
= new HashSet
<String
>(nameList
.length
<< 1);
434 for (final String name
: nameList
) {
435 if (name
.startsWith("pack-"))
442 protected ObjectDatabase
[] loadAlternates() throws IOException
{
443 final List
<ObjectDatabase
> l
= new ArrayList
<ObjectDatabase
>(4);
444 if (alternateObjectDir
!= null) {
445 for (File d
: alternateObjectDir
) {
446 l
.add(openAlternate(d
));
449 final BufferedReader br
= open(alternatesFile
);
452 while ((line
= br
.readLine()) != null) {
453 l
.add(openAlternate(line
));
461 return NO_ALTERNATES
;
463 return l
.toArray(new ObjectDatabase
[l
.size()]);
466 private static BufferedReader
open(final File f
)
467 throws FileNotFoundException
{
468 return new BufferedReader(new FileReader(f
));
471 private ObjectDatabase
openAlternate(final String location
)
473 final File objdir
= FS
.resolve(objects
, location
);
474 return openAlternate(objdir
);
477 private ObjectDatabase
openAlternate(File objdir
) throws IOException
{
478 final File parent
= objdir
.getParentFile();
479 if (FileKey
.isGitRepository(parent
)) {
480 final Repository db
= RepositoryCache
.open(FileKey
.exact(parent
));
481 return new AlternateRepositoryDatabase(db
);
483 return new ObjectDirectory(objdir
, null);
486 private static final class PackList
{
487 /** Last wall-clock time the directory was read. */
488 volatile long lastRead
;
490 /** Last modification time of {@link ObjectDirectory#packDirectory}. */
491 final long lastModified
;
493 /** All known packs, sorted by {@link PackFile#SORT}. */
494 final PackFile
[] packs
;
496 private boolean cannotBeRacilyClean
;
498 PackList(final long lastRead
, final long lastModified
,
499 final PackFile
[] packs
) {
500 this.lastRead
= lastRead
;
501 this.lastModified
= lastModified
;
503 this.cannotBeRacilyClean
= notRacyClean(lastRead
);
506 private boolean notRacyClean(final long read
) {
507 return read
- lastModified
> 2 * 60 * 1000L;
510 PackList
updateLastRead(final long now
) {
511 if (notRacyClean(now
))
512 cannotBeRacilyClean
= true;
517 boolean tryAgain(final long currLastModified
) {
518 // Any difference indicates the directory was modified.
520 if (lastModified
!= currLastModified
)
523 // We have already determined the last read was far enough
524 // after the last modification that any new modifications
525 // are certain to change the last modified time.
527 if (cannotBeRacilyClean
)
530 if (notRacyClean(lastRead
)) {
531 // Our last read should have marked cannotBeRacilyClean,
532 // but this thread may not have seen the change. The read
533 // of the volatile field lastRead should have fixed that.
538 // We last read this directory too close to its last observed
539 // modification time. We may have missed a modification. Scan
540 // the directory again, to ensure we still see the same state.
547 public ObjectDatabase
newCachedDatabase() {
548 return new CachedObjectDirectory(this);