You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PackDirectory.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  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. package org.eclipse.jgit.internal.storage.file;
  11. import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
  12. import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
  13. import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
  14. import java.io.File;
  15. import java.io.FileNotFoundException;
  16. import java.io.IOException;
  17. import java.text.MessageFormat;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import java.util.concurrent.atomic.AtomicReference;
  27. import org.eclipse.jgit.errors.CorruptObjectException;
  28. import org.eclipse.jgit.errors.PackInvalidException;
  29. import org.eclipse.jgit.errors.PackMismatchException;
  30. import org.eclipse.jgit.internal.JGitText;
  31. import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
  32. import org.eclipse.jgit.internal.storage.pack.PackExt;
  33. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  34. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  35. import org.eclipse.jgit.lib.AnyObjectId;
  36. import org.eclipse.jgit.lib.Config;
  37. import org.eclipse.jgit.lib.ConfigConstants;
  38. import org.eclipse.jgit.lib.ObjectId;
  39. import org.eclipse.jgit.lib.ObjectLoader;
  40. import org.eclipse.jgit.util.FileUtils;
  41. import org.slf4j.Logger;
  42. import org.slf4j.LoggerFactory;
  43. /**
  44. * Traditional file system packed objects directory handler.
  45. * <p>
  46. * This is the {@link org.eclipse.jgit.internal.storage.file.Pack}s object
  47. * representation for a Git object database, where objects are stored in
  48. * compressed containers known as
  49. * {@link org.eclipse.jgit.internal.storage.file.Pack}s.
  50. */
  51. class PackDirectory {
  52. private final static Logger LOG = LoggerFactory
  53. .getLogger(PackDirectory.class);
  54. private static final PackList NO_PACKS = new PackList(FileSnapshot.DIRTY,
  55. new Pack[0]);
  56. private final Config config;
  57. private final File directory;
  58. private final AtomicReference<PackList> packList;
  59. /**
  60. * Initialize a reference to an on-disk 'pack' directory.
  61. *
  62. * @param config
  63. * configuration this directory consults for write settings.
  64. * @param directory
  65. * the location of the {@code pack} directory.
  66. */
  67. PackDirectory(Config config, File directory) {
  68. this.config = config;
  69. this.directory = directory;
  70. packList = new AtomicReference<>(NO_PACKS);
  71. }
  72. /**
  73. * Getter for the field {@code directory}.
  74. *
  75. * @return the location of the {@code pack} directory.
  76. */
  77. File getDirectory() {
  78. return directory;
  79. }
  80. void create() throws IOException {
  81. FileUtils.mkdir(directory);
  82. }
  83. void close() {
  84. PackList packs = packList.get();
  85. if (packs != NO_PACKS && packList.compareAndSet(packs, NO_PACKS)) {
  86. for (Pack p : packs.packs) {
  87. p.close();
  88. }
  89. }
  90. }
  91. Collection<Pack> getPacks() {
  92. PackList list = packList.get();
  93. if (list == NO_PACKS) {
  94. list = scanPacks(list);
  95. }
  96. Pack[] packs = list.packs;
  97. return Collections.unmodifiableCollection(Arrays.asList(packs));
  98. }
  99. /** {@inheritDoc} */
  100. @Override
  101. public String toString() {
  102. return "PackDirectory[" + getDirectory() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
  103. }
  104. /**
  105. * Does the requested object exist in this PackDirectory?
  106. *
  107. * @param objectId
  108. * identity of the object to test for existence of.
  109. * @return true if the specified object is stored in this PackDirectory.
  110. */
  111. boolean has(AnyObjectId objectId) {
  112. PackList pList;
  113. do {
  114. pList = packList.get();
  115. for (Pack p : pList.packs) {
  116. try {
  117. if (p.hasObject(objectId)) {
  118. return true;
  119. }
  120. } catch (IOException e) {
  121. // The hasObject call should have only touched the index,
  122. // so any failure here indicates the index is unreadable
  123. // by this process, and the pack is likewise not readable.
  124. LOG.warn(MessageFormat.format(
  125. JGitText.get().unableToReadPackfile,
  126. p.getPackFile().getAbsolutePath()), e);
  127. remove(p);
  128. }
  129. }
  130. } while (searchPacksAgain(pList));
  131. return false;
  132. }
  133. /**
  134. * Find objects matching the prefix abbreviation.
  135. *
  136. * @param matches
  137. * set to add any located ObjectIds to. This is an output
  138. * parameter.
  139. * @param id
  140. * prefix to search for.
  141. * @param matchLimit
  142. * maximum number of results to return. At most this many
  143. * ObjectIds should be added to matches before returning.
  144. * @return {@code true} if the matches were exhausted before reaching
  145. * {@code maxLimit}.
  146. */
  147. boolean resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
  148. int matchLimit) {
  149. // Go through the packs once. If we didn't find any resolutions
  150. // scan for new packs and check once more.
  151. int oldSize = matches.size();
  152. PackList pList;
  153. do {
  154. pList = packList.get();
  155. for (Pack p : pList.packs) {
  156. try {
  157. p.resolve(matches, id, matchLimit);
  158. p.resetTransientErrorCount();
  159. } catch (IOException e) {
  160. handlePackError(e, p);
  161. }
  162. if (matches.size() > matchLimit) {
  163. return false;
  164. }
  165. }
  166. } while (matches.size() == oldSize && searchPacksAgain(pList));
  167. return true;
  168. }
  169. ObjectLoader open(WindowCursor curs, AnyObjectId objectId) {
  170. PackList pList;
  171. do {
  172. SEARCH: for (;;) {
  173. pList = packList.get();
  174. for (Pack p : pList.packs) {
  175. try {
  176. ObjectLoader ldr = p.get(curs, objectId);
  177. p.resetTransientErrorCount();
  178. if (ldr != null)
  179. return ldr;
  180. } catch (PackMismatchException e) {
  181. // Pack was modified; refresh the entire pack list.
  182. if (searchPacksAgain(pList)) {
  183. continue SEARCH;
  184. }
  185. } catch (IOException e) {
  186. handlePackError(e, p);
  187. }
  188. }
  189. break SEARCH;
  190. }
  191. } while (searchPacksAgain(pList));
  192. return null;
  193. }
  194. long getSize(WindowCursor curs, AnyObjectId id) {
  195. PackList pList;
  196. do {
  197. SEARCH: for (;;) {
  198. pList = packList.get();
  199. for (Pack p : pList.packs) {
  200. try {
  201. long len = p.getObjectSize(curs, id);
  202. p.resetTransientErrorCount();
  203. if (0 <= len) {
  204. return len;
  205. }
  206. } catch (PackMismatchException e) {
  207. // Pack was modified; refresh the entire pack list.
  208. if (searchPacksAgain(pList)) {
  209. continue SEARCH;
  210. }
  211. } catch (IOException e) {
  212. handlePackError(e, p);
  213. }
  214. }
  215. break SEARCH;
  216. }
  217. } while (searchPacksAgain(pList));
  218. return -1;
  219. }
  220. void selectRepresentation(PackWriter packer, ObjectToPack otp,
  221. WindowCursor curs) {
  222. PackList pList = packList.get();
  223. SEARCH: for (;;) {
  224. for (Pack p : pList.packs) {
  225. try {
  226. LocalObjectRepresentation rep = p.representation(curs, otp);
  227. p.resetTransientErrorCount();
  228. if (rep != null) {
  229. packer.select(otp, rep);
  230. }
  231. } catch (PackMismatchException e) {
  232. // Pack was modified; refresh the entire pack list.
  233. //
  234. pList = scanPacks(pList);
  235. continue SEARCH;
  236. } catch (IOException e) {
  237. handlePackError(e, p);
  238. }
  239. }
  240. break SEARCH;
  241. }
  242. }
  243. private void handlePackError(IOException e, Pack p) {
  244. String warnTmpl = null;
  245. int transientErrorCount = 0;
  246. String errTmpl = JGitText.get().exceptionWhileReadingPack;
  247. if ((e instanceof CorruptObjectException)
  248. || (e instanceof PackInvalidException)) {
  249. warnTmpl = JGitText.get().corruptPack;
  250. LOG.warn(MessageFormat.format(warnTmpl,
  251. p.getPackFile().getAbsolutePath()), e);
  252. // Assume the pack is corrupted, and remove it from the list.
  253. remove(p);
  254. } else if (e instanceof FileNotFoundException) {
  255. if (p.getPackFile().exists()) {
  256. errTmpl = JGitText.get().packInaccessible;
  257. transientErrorCount = p.incrementTransientErrorCount();
  258. } else {
  259. warnTmpl = JGitText.get().packWasDeleted;
  260. remove(p);
  261. }
  262. } else if (FileUtils.isStaleFileHandleInCausalChain(e)) {
  263. warnTmpl = JGitText.get().packHandleIsStale;
  264. remove(p);
  265. } else {
  266. transientErrorCount = p.incrementTransientErrorCount();
  267. }
  268. if (warnTmpl != null) {
  269. LOG.warn(MessageFormat.format(warnTmpl,
  270. p.getPackFile().getAbsolutePath()), e);
  271. } else {
  272. if (doLogExponentialBackoff(transientErrorCount)) {
  273. // Don't remove the pack from the list, as the error may be
  274. // transient.
  275. LOG.error(MessageFormat.format(errTmpl,
  276. p.getPackFile().getAbsolutePath(),
  277. Integer.valueOf(transientErrorCount)), e);
  278. }
  279. }
  280. }
  281. /**
  282. * @param n
  283. * count of consecutive failures
  284. * @return @{code true} if i is a power of 2
  285. */
  286. private boolean doLogExponentialBackoff(int n) {
  287. return (n & (n - 1)) == 0;
  288. }
  289. boolean searchPacksAgain(PackList old) {
  290. // Whether to trust the pack folder's modification time. If set
  291. // to false we will always scan the .git/objects/pack folder to
  292. // check for new pack files. If set to true (default) we use the
  293. // lastmodified attribute of the folder and assume that no new
  294. // pack files can be in this folder if his modification time has
  295. // not changed.
  296. boolean trustFolderStat = config.getBoolean(
  297. ConfigConstants.CONFIG_CORE_SECTION,
  298. ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
  299. return ((!trustFolderStat) || old.snapshot.isModified(directory))
  300. && old != scanPacks(old);
  301. }
  302. void insert(Pack pack) {
  303. PackList o, n;
  304. do {
  305. o = packList.get();
  306. // If the pack in question is already present in the list
  307. // (picked up by a concurrent thread that did a scan?) we
  308. // do not want to insert it a second time.
  309. //
  310. final Pack[] oldList = o.packs;
  311. final String name = pack.getPackFile().getName();
  312. for (Pack p : oldList) {
  313. if (name.equals(p.getPackFile().getName())) {
  314. return;
  315. }
  316. }
  317. final Pack[] newList = new Pack[1 + oldList.length];
  318. newList[0] = pack;
  319. System.arraycopy(oldList, 0, newList, 1, oldList.length);
  320. n = new PackList(o.snapshot, newList);
  321. } while (!packList.compareAndSet(o, n));
  322. }
  323. private void remove(Pack deadPack) {
  324. PackList o, n;
  325. do {
  326. o = packList.get();
  327. final Pack[] oldList = o.packs;
  328. final int j = indexOf(oldList, deadPack);
  329. if (j < 0) {
  330. break;
  331. }
  332. final Pack[] newList = new Pack[oldList.length - 1];
  333. System.arraycopy(oldList, 0, newList, 0, j);
  334. System.arraycopy(oldList, j + 1, newList, j, newList.length - j);
  335. n = new PackList(o.snapshot, newList);
  336. } while (!packList.compareAndSet(o, n));
  337. deadPack.close();
  338. }
  339. private static int indexOf(Pack[] list, Pack pack) {
  340. for (int i = 0; i < list.length; i++) {
  341. if (list[i] == pack) {
  342. return i;
  343. }
  344. }
  345. return -1;
  346. }
  347. private PackList scanPacks(PackList original) {
  348. synchronized (packList) {
  349. PackList o, n;
  350. do {
  351. o = packList.get();
  352. if (o != original) {
  353. // Another thread did the scan for us, while we
  354. // were blocked on the monitor above.
  355. //
  356. return o;
  357. }
  358. n = scanPacksImpl(o);
  359. if (n == o) {
  360. return n;
  361. }
  362. } while (!packList.compareAndSet(o, n));
  363. return n;
  364. }
  365. }
  366. private PackList scanPacksImpl(PackList old) {
  367. final Map<String, Pack> forReuse = reuseMap(old);
  368. final FileSnapshot snapshot = FileSnapshot.save(directory);
  369. Map<String, Map<PackExt, PackFile>> packFilesByExtById = getPackFilesByExtById();
  370. List<Pack> list = new ArrayList<>(packFilesByExtById.size());
  371. boolean foundNew = false;
  372. for (Map<PackExt, PackFile> packFilesByExt : packFilesByExtById
  373. .values()) {
  374. PackFile packFile = packFilesByExt.get(PACK);
  375. if (packFile == null || !packFilesByExt.containsKey(INDEX)) {
  376. // Sometimes C Git's HTTP fetch transport leaves a
  377. // .idx file behind and does not download the .pack.
  378. // We have to skip over such useless indexes.
  379. // Also skip if we don't have any index for this id
  380. continue;
  381. }
  382. Pack oldPack = forReuse.get(packFile.getName());
  383. if (oldPack != null
  384. && !oldPack.getFileSnapshot().isModified(packFile)) {
  385. forReuse.remove(packFile.getName());
  386. list.add(oldPack);
  387. continue;
  388. }
  389. list.add(new Pack(packFile,
  390. packFilesByExt.containsKey(BITMAP_INDEX)
  391. ? BITMAP_INDEX.getBit()
  392. : 0));
  393. foundNew = true;
  394. }
  395. // If we did not discover any new files, the modification time was not
  396. // changed, and we did not remove any files, then the set of files is
  397. // the same as the set we were given. Instead of building a new object
  398. // return the same collection.
  399. //
  400. if (!foundNew && forReuse.isEmpty() && snapshot.equals(old.snapshot)) {
  401. old.snapshot.setClean(snapshot);
  402. return old;
  403. }
  404. for (Pack p : forReuse.values()) {
  405. p.close();
  406. }
  407. if (list.isEmpty()) {
  408. return new PackList(snapshot, NO_PACKS.packs);
  409. }
  410. final Pack[] r = list.toArray(new Pack[0]);
  411. Arrays.sort(r, Pack.SORT);
  412. return new PackList(snapshot, r);
  413. }
  414. private static Map<String, Pack> reuseMap(PackList old) {
  415. final Map<String, Pack> forReuse = new HashMap<>();
  416. for (Pack p : old.packs) {
  417. if (p.invalid()) {
  418. // The pack instance is corrupted, and cannot be safely used
  419. // again. Do not include it in our reuse map.
  420. //
  421. p.close();
  422. continue;
  423. }
  424. final Pack prior = forReuse.put(p.getPackFile().getName(), p);
  425. if (prior != null) {
  426. // This should never occur. It should be impossible for us
  427. // to have two pack files with the same name, as all of them
  428. // came out of the same directory. If it does, we promised to
  429. // close any PackFiles we did not reuse, so close the second,
  430. // readers are likely to be actively using the first.
  431. //
  432. forReuse.put(prior.getPackFile().getName(), prior);
  433. p.close();
  434. }
  435. }
  436. return forReuse;
  437. }
  438. /**
  439. * Scans the pack directory for
  440. * {@link org.eclipse.jgit.internal.storage.file.PackFile}s and returns them
  441. * organized by their extensions and their pack ids
  442. *
  443. * Skips files in the directory that we cannot create a
  444. * {@link org.eclipse.jgit.internal.storage.file.PackFile} for.
  445. *
  446. * @return a map of {@link org.eclipse.jgit.internal.storage.file.PackFile}s
  447. * and {@link org.eclipse.jgit.internal.storage.pack.PackExt}s keyed
  448. * by pack ids
  449. */
  450. private Map<String, Map<PackExt, PackFile>> getPackFilesByExtById() {
  451. final String[] nameList = directory.list();
  452. if (nameList == null) {
  453. return Collections.emptyMap();
  454. }
  455. Map<String, Map<PackExt, PackFile>> packFilesByExtById = new HashMap<>(
  456. nameList.length / 2); // assume roughly 2 files per id
  457. for (String name : nameList) {
  458. try {
  459. PackFile pack = new PackFile(directory, name);
  460. Map<PackExt, PackFile> packByExt = packFilesByExtById
  461. .get(pack.getId());
  462. if (packByExt == null) {
  463. packByExt = new HashMap<>(PackExt.values().length);
  464. packFilesByExtById.put(pack.getId(), packByExt);
  465. }
  466. packByExt.put(pack.getPackExt(), pack);
  467. } catch (IllegalArgumentException e) {
  468. continue;
  469. }
  470. }
  471. return packFilesByExtById;
  472. }
  473. static final class PackList {
  474. /** State just before reading the pack directory. */
  475. final FileSnapshot snapshot;
  476. /** All known packs, sorted by {@link Pack#SORT}. */
  477. final Pack[] packs;
  478. PackList(FileSnapshot monitor, Pack[] packs) {
  479. this.snapshot = monitor;
  480. this.packs = packs;
  481. }
  482. }
  483. }