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 16KB

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