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

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