View Javadoc
1   /*
2    * Copyright (C) 2009, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.storage.file;
12  
13  import java.io.File;
14  import java.io.FileInputStream;
15  import java.io.FileNotFoundException;
16  import java.io.IOException;
17  import java.nio.file.Files;
18  import java.nio.file.NoSuchFileException;
19  import java.nio.file.StandardCopyOption;
20  import java.util.Set;
21  
22  import org.eclipse.jgit.internal.storage.file.FileObjectDatabase.InsertLooseObjectResult;
23  import org.eclipse.jgit.lib.AbbreviatedObjectId;
24  import org.eclipse.jgit.lib.AnyObjectId;
25  import org.eclipse.jgit.lib.Constants;
26  import org.eclipse.jgit.lib.ObjectId;
27  import org.eclipse.jgit.lib.ObjectLoader;
28  import org.eclipse.jgit.util.FileUtils;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  /**
33   * Traditional file system based loose objects handler.
34   * <p>
35   * This is the loose object representation for a Git object database, where
36   * objects are stored loose by hashing them into directories by their
37   * {@link org.eclipse.jgit.lib.ObjectId}.
38   */
39  class LooseObjects {
40  	private static final Logger LOG = LoggerFactory
41  			.getLogger(LooseObjects.class);
42  
43  	private final File directory;
44  
45  	private final UnpackedObjectCache unpackedObjectCache;
46  
47  	/**
48  	 * Initialize a reference to an on-disk object directory.
49  	 *
50  	 * @param dir
51  	 *            the location of the <code>objects</code> directory.
52  	 */
53  	LooseObjects(File dir) {
54  		directory = dir;
55  		unpackedObjectCache = new UnpackedObjectCache();
56  	}
57  
58  	/**
59  	 * Getter for the field <code>directory</code>.
60  	 *
61  	 * @return the location of the <code>objects</code> directory.
62  	 */
63  	File getDirectory() {
64  		return directory;
65  	}
66  
67  	void create() throws IOException {
68  		FileUtils.mkdirs(directory);
69  	}
70  
71  	void close() {
72  		unpackedObjectCache.clear();
73  	}
74  
75  	/** {@inheritDoc} */
76  	@Override
77  	public String toString() {
78  		return "LooseObjects[" + directory + "]"; //$NON-NLS-1$ //$NON-NLS-2$
79  	}
80  
81  	boolean hasCached(AnyObjectId id) {
82  		return unpackedObjectCache.isUnpacked(id);
83  	}
84  
85  	/**
86  	 * Does the requested object exist as a loose object?
87  	 *
88  	 * @param objectId
89  	 *            identity of the object to test for existence of.
90  	 * @return {@code true} if the specified object is stored as a loose object.
91  	 */
92  	boolean has(AnyObjectId objectId) {
93  		return fileFor(objectId).exists();
94  	}
95  
96  	/**
97  	 * Find objects matching the prefix abbreviation.
98  	 *
99  	 * @param matches
100 	 *            set to add any located ObjectIds to. This is an output
101 	 *            parameter.
102 	 * @param id
103 	 *            prefix to search for.
104 	 * @param matchLimit
105 	 *            maximum number of results to return. At most this many
106 	 *            ObjectIds should be added to matches before returning.
107 	 * @return {@code true} if the matches were exhausted before reaching
108 	 *         {@code maxLimit}.
109 	 */
110 	boolean resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
111 			int matchLimit) {
112 		String fanOut = id.name().substring(0, 2);
113 		String[] entries = new File(directory, fanOut).list();
114 		if (entries != null) {
115 			for (String e : entries) {
116 				if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) {
117 					continue;
118 				}
119 				try {
120 					ObjectId entId = ObjectId.fromString(fanOut + e);
121 					if (id.prefixCompare(entId) == 0) {
122 						matches.add(entId);
123 					}
124 				} catch (IllegalArgumentException notId) {
125 					continue;
126 				}
127 				if (matches.size() > matchLimit) {
128 					return false;
129 				}
130 			}
131 		}
132 		return true;
133 	}
134 
135 	ObjectLoader open(WindowCursor curs, AnyObjectId id) throws IOException {
136 		File path = fileFor(id);
137 		try (FileInputStream in = new FileInputStream(path)) {
138 			unpackedObjectCache.add(id);
139 			return UnpackedObject.open(in, path, id, curs);
140 		} catch (FileNotFoundException noFile) {
141 			if (path.exists()) {
142 				throw noFile;
143 			}
144 			unpackedObjectCache.remove(id);
145 			return null;
146 		}
147 	}
148 
149 	long getSize(WindowCursor curs, AnyObjectId id) throws IOException {
150 		File f = fileFor(id);
151 		try (FileInputStream in = new FileInputStream(f)) {
152 			unpackedObjectCache.add(id);
153 			return UnpackedObject.getSize(in, id, curs);
154 		} catch (FileNotFoundException noFile) {
155 			if (f.exists()) {
156 				throw noFile;
157 			}
158 			unpackedObjectCache.remove(id);
159 			return -1;
160 		}
161 	}
162 
163 	InsertLooseObjectResult insert(File tmp, ObjectId id) throws IOException {
164 		final File dst = fileFor(id);
165 		if (dst.exists()) {
166 			// We want to be extra careful and avoid replacing an object
167 			// that already exists. We can't be sure renameTo() would
168 			// fail on all platforms if dst exists, so we check first.
169 			//
170 			FileUtils.delete(tmp, FileUtils.RETRY);
171 			return InsertLooseObjectResult.EXISTS_LOOSE;
172 		}
173 
174 		try {
175 			return tryMove(tmp, dst, id);
176 		} catch (NoSuchFileException e) {
177 			// It's possible the directory doesn't exist yet as the object
178 			// directories are always lazily created. Note that we try the
179 			// rename/move first as the directory likely does exist.
180 			//
181 			// Create the directory.
182 			//
183 			FileUtils.mkdir(dst.getParentFile(), true);
184 		} catch (IOException e) {
185 			// Any other IO error is considered a failure.
186 			//
187 			LOG.error(e.getMessage(), e);
188 			FileUtils.delete(tmp, FileUtils.RETRY);
189 			return InsertLooseObjectResult.FAILURE;
190 		}
191 
192 		try {
193 			return tryMove(tmp, dst, id);
194 		} catch (IOException e) {
195 			// The object failed to be renamed into its proper location and
196 			// it doesn't exist in the repository either. We really don't
197 			// know what went wrong, so fail.
198 			//
199 			LOG.error(e.getMessage(), e);
200 			FileUtils.delete(tmp, FileUtils.RETRY);
201 			return InsertLooseObjectResult.FAILURE;
202 		}
203 	}
204 
205 	private InsertLooseObjectResult tryMove(File tmp, File dst, ObjectId id)
206 			throws IOException {
207 		Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst),
208 				StandardCopyOption.ATOMIC_MOVE);
209 		dst.setReadOnly();
210 		unpackedObjectCache.add(id);
211 		return InsertLooseObjectResult.INSERTED;
212 	}
213 
214 	/**
215 	 * Compute the location of a loose object file.
216 	 *
217 	 * @param objectId
218 	 *            identity of the object to get the File location for.
219 	 * @return {@link java.io.File} location of the specified loose object.
220 	 */
221 	File fileFor(AnyObjectId objectId) {
222 		String n = objectId.name();
223 		String d = n.substring(0, 2);
224 		String f = n.substring(2);
225 		return new File(new File(getDirectory(), d), f);
226 	}
227 }