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

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