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.

PackWriter.java 39KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231
  1. /*
  2. * Copyright (C) 2008-2010, Google Inc.
  3. * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  4. * and other copyright owners as documented in the project's IP log.
  5. *
  6. * This program and the accompanying materials are made available
  7. * under the terms of the Eclipse Distribution License v1.0 which
  8. * accompanies this distribution, is reproduced below, and is
  9. * available at http://www.eclipse.org/org/documents/edl-v10.php
  10. *
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or
  14. * without modification, are permitted provided that the following
  15. * conditions are met:
  16. *
  17. * - Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. *
  20. * - Redistributions in binary form must reproduce the above
  21. * copyright notice, this list of conditions and the following
  22. * disclaimer in the documentation and/or other materials provided
  23. * with the distribution.
  24. *
  25. * - Neither the name of the Eclipse Foundation, Inc. nor the
  26. * names of its contributors may be used to endorse or promote
  27. * products derived from this software without specific prior
  28. * written permission.
  29. *
  30. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  31. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  32. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  33. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  34. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  35. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  36. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  37. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  38. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  39. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  41. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  42. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  43. */
  44. package org.eclipse.jgit.storage.pack;
  45. import static org.eclipse.jgit.storage.pack.StoredObjectRepresentation.PACK_DELTA;
  46. import static org.eclipse.jgit.storage.pack.StoredObjectRepresentation.PACK_WHOLE;
  47. import java.io.IOException;
  48. import java.io.OutputStream;
  49. import java.security.MessageDigest;
  50. import java.util.ArrayList;
  51. import java.util.Arrays;
  52. import java.util.Collection;
  53. import java.util.Collections;
  54. import java.util.Comparator;
  55. import java.util.HashSet;
  56. import java.util.Iterator;
  57. import java.util.List;
  58. import java.util.Set;
  59. import java.util.concurrent.ExecutionException;
  60. import java.util.concurrent.Executor;
  61. import java.util.concurrent.ExecutorService;
  62. import java.util.concurrent.Executors;
  63. import java.util.concurrent.Future;
  64. import java.util.concurrent.TimeUnit;
  65. import java.util.zip.Deflater;
  66. import java.util.zip.DeflaterOutputStream;
  67. import org.eclipse.jgit.JGitText;
  68. import org.eclipse.jgit.errors.CorruptObjectException;
  69. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  70. import org.eclipse.jgit.errors.LargeObjectException;
  71. import org.eclipse.jgit.errors.MissingObjectException;
  72. import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
  73. import org.eclipse.jgit.lib.AnyObjectId;
  74. import org.eclipse.jgit.lib.AsyncObjectSizeQueue;
  75. import org.eclipse.jgit.lib.Constants;
  76. import org.eclipse.jgit.lib.NullProgressMonitor;
  77. import org.eclipse.jgit.lib.ObjectId;
  78. import org.eclipse.jgit.lib.ObjectIdSubclassMap;
  79. import org.eclipse.jgit.lib.ObjectLoader;
  80. import org.eclipse.jgit.lib.ObjectReader;
  81. import org.eclipse.jgit.lib.ProgressMonitor;
  82. import org.eclipse.jgit.lib.Repository;
  83. import org.eclipse.jgit.lib.ThreadSafeProgressMonitor;
  84. import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
  85. import org.eclipse.jgit.revwalk.ObjectListIterator;
  86. import org.eclipse.jgit.revwalk.ObjectWalk;
  87. import org.eclipse.jgit.revwalk.RevFlag;
  88. import org.eclipse.jgit.revwalk.RevObject;
  89. import org.eclipse.jgit.revwalk.RevSort;
  90. import org.eclipse.jgit.storage.file.PackIndexWriter;
  91. import org.eclipse.jgit.util.TemporaryBuffer;
  92. /**
  93. * <p>
  94. * PackWriter class is responsible for generating pack files from specified set
  95. * of objects from repository. This implementation produce pack files in format
  96. * version 2.
  97. * </p>
  98. * <p>
  99. * Source of objects may be specified in two ways:
  100. * <ul>
  101. * <li>(usually) by providing sets of interesting and uninteresting objects in
  102. * repository - all interesting objects and their ancestors except uninteresting
  103. * objects and their ancestors will be included in pack, or</li>
  104. * <li>by providing iterator of {@link RevObject} specifying exact list and
  105. * order of objects in pack</li>
  106. * </ul>
  107. * Typical usage consists of creating instance intended for some pack,
  108. * configuring options, preparing the list of objects by calling
  109. * {@link #preparePack(Iterator)} or
  110. * {@link #preparePack(ProgressMonitor, Collection, Collection)}, and finally
  111. * producing the stream with {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}.
  112. * </p>
  113. * <p>
  114. * Class provide set of configurable options and {@link ProgressMonitor}
  115. * support, as operations may take a long time for big repositories. Deltas
  116. * searching algorithm is <b>NOT IMPLEMENTED</b> yet - this implementation
  117. * relies only on deltas and objects reuse.
  118. * </p>
  119. * <p>
  120. * This class is not thread safe, it is intended to be used in one thread, with
  121. * one instance per created pack. Subsequent calls to writePack result in
  122. * undefined behavior.
  123. * </p>
  124. */
  125. public class PackWriter {
  126. private static final int PACK_VERSION_GENERATED = 2;
  127. @SuppressWarnings("unchecked")
  128. private final List<ObjectToPack> objectsLists[] = new List[Constants.OBJ_TAG + 1];
  129. {
  130. objectsLists[0] = Collections.<ObjectToPack> emptyList();
  131. objectsLists[Constants.OBJ_COMMIT] = new ArrayList<ObjectToPack>();
  132. objectsLists[Constants.OBJ_TREE] = new ArrayList<ObjectToPack>();
  133. objectsLists[Constants.OBJ_BLOB] = new ArrayList<ObjectToPack>();
  134. objectsLists[Constants.OBJ_TAG] = new ArrayList<ObjectToPack>();
  135. }
  136. private final ObjectIdSubclassMap<ObjectToPack> objectsMap = new ObjectIdSubclassMap<ObjectToPack>();
  137. // edge objects for thin packs
  138. private final ObjectIdSubclassMap<ObjectToPack> edgeObjects = new ObjectIdSubclassMap<ObjectToPack>();
  139. private Deflater myDeflater;
  140. private final ObjectReader reader;
  141. /** {@link #reader} recast to the reuse interface, if it supports it. */
  142. private final ObjectReuseAsIs reuseSupport;
  143. private final PackConfig config;
  144. private List<ObjectToPack> sortedByName;
  145. private byte packcsum[];
  146. private boolean deltaBaseAsOffset;
  147. private boolean reuseDeltas;
  148. private boolean thin;
  149. private boolean ignoreMissingUninteresting = true;
  150. /**
  151. * Create writer for specified repository.
  152. * <p>
  153. * Objects for packing are specified in {@link #preparePack(Iterator)} or
  154. * {@link #preparePack(ProgressMonitor, Collection, Collection)}.
  155. *
  156. * @param repo
  157. * repository where objects are stored.
  158. */
  159. public PackWriter(final Repository repo) {
  160. this(repo, repo.newObjectReader());
  161. }
  162. /**
  163. * Create a writer to load objects from the specified reader.
  164. * <p>
  165. * Objects for packing are specified in {@link #preparePack(Iterator)} or
  166. * {@link #preparePack(ProgressMonitor, Collection, Collection)}.
  167. *
  168. * @param reader
  169. * reader to read from the repository with.
  170. */
  171. public PackWriter(final ObjectReader reader) {
  172. this(new PackConfig(), reader);
  173. }
  174. /**
  175. * Create writer for specified repository.
  176. * <p>
  177. * Objects for packing are specified in {@link #preparePack(Iterator)} or
  178. * {@link #preparePack(ProgressMonitor, Collection, Collection)}.
  179. *
  180. * @param repo
  181. * repository where objects are stored.
  182. * @param reader
  183. * reader to read from the repository with.
  184. */
  185. public PackWriter(final Repository repo, final ObjectReader reader) {
  186. this(new PackConfig(repo), reader);
  187. }
  188. /**
  189. * Create writer with a specified configuration.
  190. * <p>
  191. * Objects for packing are specified in {@link #preparePack(Iterator)} or
  192. * {@link #preparePack(ProgressMonitor, Collection, Collection)}.
  193. *
  194. * @param config
  195. * configuration for the pack writer.
  196. * @param reader
  197. * reader to read from the repository with.
  198. */
  199. public PackWriter(final PackConfig config, final ObjectReader reader) {
  200. this.config = config;
  201. this.reader = reader;
  202. if (reader instanceof ObjectReuseAsIs)
  203. reuseSupport = ((ObjectReuseAsIs) reader);
  204. else
  205. reuseSupport = null;
  206. deltaBaseAsOffset = config.isDeltaBaseAsOffset();
  207. reuseDeltas = config.isReuseDeltas();
  208. }
  209. /**
  210. * Check whether writer can store delta base as an offset (new style
  211. * reducing pack size) or should store it as an object id (legacy style,
  212. * compatible with old readers).
  213. *
  214. * Default setting: {@value PackConfig#DEFAULT_DELTA_BASE_AS_OFFSET}
  215. *
  216. * @return true if delta base is stored as an offset; false if it is stored
  217. * as an object id.
  218. */
  219. public boolean isDeltaBaseAsOffset() {
  220. return deltaBaseAsOffset;
  221. }
  222. /**
  223. * Set writer delta base format. Delta base can be written as an offset in a
  224. * pack file (new approach reducing file size) or as an object id (legacy
  225. * approach, compatible with old readers).
  226. *
  227. * Default setting: {@value PackConfig#DEFAULT_DELTA_BASE_AS_OFFSET}
  228. *
  229. * @param deltaBaseAsOffset
  230. * boolean indicating whether delta base can be stored as an
  231. * offset.
  232. */
  233. public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) {
  234. this.deltaBaseAsOffset = deltaBaseAsOffset;
  235. }
  236. /** @return true if this writer is producing a thin pack. */
  237. public boolean isThin() {
  238. return thin;
  239. }
  240. /**
  241. * @param packthin
  242. * a boolean indicating whether writer may pack objects with
  243. * delta base object not within set of objects to pack, but
  244. * belonging to party repository (uninteresting/boundary) as
  245. * determined by set; this kind of pack is used only for
  246. * transport; true - to produce thin pack, false - otherwise.
  247. */
  248. public void setThin(final boolean packthin) {
  249. thin = packthin;
  250. }
  251. /**
  252. * @return true to ignore objects that are uninteresting and also not found
  253. * on local disk; false to throw a {@link MissingObjectException}
  254. * out of {@link #preparePack(ProgressMonitor, Collection, Collection)} if an
  255. * uninteresting object is not in the source repository. By default,
  256. * true, permitting gracefully ignoring of uninteresting objects.
  257. */
  258. public boolean isIgnoreMissingUninteresting() {
  259. return ignoreMissingUninteresting;
  260. }
  261. /**
  262. * @param ignore
  263. * true if writer should ignore non existing uninteresting
  264. * objects during construction set of objects to pack; false
  265. * otherwise - non existing uninteresting objects may cause
  266. * {@link MissingObjectException}
  267. */
  268. public void setIgnoreMissingUninteresting(final boolean ignore) {
  269. ignoreMissingUninteresting = ignore;
  270. }
  271. /**
  272. * Returns objects number in a pack file that was created by this writer.
  273. *
  274. * @return number of objects in pack.
  275. */
  276. public int getObjectsNumber() {
  277. return objectsMap.size();
  278. }
  279. /**
  280. * Prepare the list of objects to be written to the pack stream.
  281. * <p>
  282. * Iterator <b>exactly</b> determines which objects are included in a pack
  283. * and order they appear in pack (except that objects order by type is not
  284. * needed at input). This order should conform general rules of ordering
  285. * objects in git - by recency and path (type and delta-base first is
  286. * internally secured) and responsibility for guaranteeing this order is on
  287. * a caller side. Iterator must return each id of object to write exactly
  288. * once.
  289. * </p>
  290. * <p>
  291. * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag,
  292. * this object won't be included in an output pack. Instead, it is recorded
  293. * as edge-object (known to remote repository) for thin-pack. In such a case
  294. * writer may pack objects with delta base object not within set of objects
  295. * to pack, but belonging to party repository - those marked with
  296. * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for
  297. * transport.
  298. * </p>
  299. *
  300. * @param objectsSource
  301. * iterator of object to store in a pack; order of objects within
  302. * each type is important, ordering by type is not needed;
  303. * allowed types for objects are {@link Constants#OBJ_COMMIT},
  304. * {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and
  305. * {@link Constants#OBJ_TAG}; objects returned by iterator may
  306. * be later reused by caller as object id and type are internally
  307. * copied in each iteration; if object returned by iterator has
  308. * {@link RevFlag#UNINTERESTING} flag set, it won't be included
  309. * in a pack, but is considered as edge-object for thin-pack.
  310. * @throws IOException
  311. * when some I/O problem occur during reading objects.
  312. */
  313. public void preparePack(final Iterator<RevObject> objectsSource)
  314. throws IOException {
  315. while (objectsSource.hasNext()) {
  316. addObject(objectsSource.next());
  317. }
  318. }
  319. /**
  320. * Prepare the list of objects to be written to the pack stream.
  321. * <p>
  322. * Basing on these 2 sets, another set of objects to put in a pack file is
  323. * created: this set consists of all objects reachable (ancestors) from
  324. * interesting objects, except uninteresting objects and their ancestors.
  325. * This method uses class {@link ObjectWalk} extensively to find out that
  326. * appropriate set of output objects and their optimal order in output pack.
  327. * Order is consistent with general git in-pack rules: sort by object type,
  328. * recency, path and delta-base first.
  329. * </p>
  330. *
  331. * @param countingMonitor
  332. * progress during object enumeration.
  333. * @param interestingObjects
  334. * collection of objects to be marked as interesting (start
  335. * points of graph traversal).
  336. * @param uninterestingObjects
  337. * collection of objects to be marked as uninteresting (end
  338. * points of graph traversal).
  339. * @throws IOException
  340. * when some I/O problem occur during reading objects.
  341. */
  342. public void preparePack(ProgressMonitor countingMonitor,
  343. final Collection<? extends ObjectId> interestingObjects,
  344. final Collection<? extends ObjectId> uninterestingObjects)
  345. throws IOException {
  346. if (countingMonitor == null)
  347. countingMonitor = NullProgressMonitor.INSTANCE;
  348. findObjectsToPack(countingMonitor, interestingObjects,
  349. uninterestingObjects);
  350. }
  351. /**
  352. * Determine if the pack file will contain the requested object.
  353. *
  354. * @param id
  355. * the object to test the existence of.
  356. * @return true if the object will appear in the output pack file.
  357. */
  358. public boolean willInclude(final AnyObjectId id) {
  359. return get(id) != null;
  360. }
  361. /**
  362. * Lookup the ObjectToPack object for a given ObjectId.
  363. *
  364. * @param id
  365. * the object to find in the pack.
  366. * @return the object we are packing, or null.
  367. */
  368. public ObjectToPack get(AnyObjectId id) {
  369. return objectsMap.get(id);
  370. }
  371. /**
  372. * Computes SHA-1 of lexicographically sorted objects ids written in this
  373. * pack, as used to name a pack file in repository.
  374. *
  375. * @return ObjectId representing SHA-1 name of a pack that was created.
  376. */
  377. public ObjectId computeName() {
  378. final byte[] buf = new byte[Constants.OBJECT_ID_LENGTH];
  379. final MessageDigest md = Constants.newMessageDigest();
  380. for (ObjectToPack otp : sortByName()) {
  381. otp.copyRawTo(buf, 0);
  382. md.update(buf, 0, Constants.OBJECT_ID_LENGTH);
  383. }
  384. return ObjectId.fromRaw(md.digest());
  385. }
  386. /**
  387. * Create an index file to match the pack file just written.
  388. * <p>
  389. * This method can only be invoked after {@link #preparePack(Iterator)} or
  390. * {@link #preparePack(ProgressMonitor, Collection, Collection)} has been
  391. * invoked and completed successfully. Writing a corresponding index is an
  392. * optional feature that not all pack users may require.
  393. *
  394. * @param indexStream
  395. * output for the index data. Caller is responsible for closing
  396. * this stream.
  397. * @throws IOException
  398. * the index data could not be written to the supplied stream.
  399. */
  400. public void writeIndex(final OutputStream indexStream) throws IOException {
  401. final List<ObjectToPack> list = sortByName();
  402. final PackIndexWriter iw;
  403. int indexVersion = config.getIndexVersion();
  404. if (indexVersion <= 0)
  405. iw = PackIndexWriter.createOldestPossible(indexStream, list);
  406. else
  407. iw = PackIndexWriter.createVersion(indexStream, indexVersion);
  408. iw.write(list, packcsum);
  409. }
  410. private List<ObjectToPack> sortByName() {
  411. if (sortedByName == null) {
  412. sortedByName = new ArrayList<ObjectToPack>(objectsMap.size());
  413. for (List<ObjectToPack> list : objectsLists) {
  414. for (ObjectToPack otp : list)
  415. sortedByName.add(otp);
  416. }
  417. Collections.sort(sortedByName);
  418. }
  419. return sortedByName;
  420. }
  421. /**
  422. * Write the prepared pack to the supplied stream.
  423. * <p>
  424. * At first, this method collects and sorts objects to pack, then deltas
  425. * search is performed if set up accordingly, finally pack stream is
  426. * written.
  427. * </p>
  428. * <p>
  429. * All reused objects data checksum (Adler32/CRC32) is computed and
  430. * validated against existing checksum.
  431. * </p>
  432. *
  433. * @param compressMonitor
  434. * progress monitor to report object compression work.
  435. * @param writeMonitor
  436. * progress monitor to report the number of objects written.
  437. * @param packStream
  438. * output stream of pack data. The stream should be buffered by
  439. * the caller. The caller is responsible for closing the stream.
  440. * @throws IOException
  441. * an error occurred reading a local object's data to include in
  442. * the pack, or writing compressed object data to the output
  443. * stream.
  444. */
  445. public void writePack(ProgressMonitor compressMonitor,
  446. ProgressMonitor writeMonitor, OutputStream packStream)
  447. throws IOException {
  448. if (compressMonitor == null)
  449. compressMonitor = NullProgressMonitor.INSTANCE;
  450. if (writeMonitor == null)
  451. writeMonitor = NullProgressMonitor.INSTANCE;
  452. if ((reuseDeltas || config.isReuseObjects()) && reuseSupport != null)
  453. searchForReuse(compressMonitor);
  454. if (config.isDeltaCompress())
  455. searchForDeltas(compressMonitor);
  456. final PackOutputStream out = new PackOutputStream(writeMonitor,
  457. packStream, this);
  458. int objCnt = getObjectsNumber();
  459. writeMonitor.beginTask(JGitText.get().writingObjects, objCnt);
  460. out.writeFileHeader(PACK_VERSION_GENERATED, objCnt);
  461. out.flush();
  462. writeObjects(out);
  463. writeChecksum(out);
  464. reader.release();
  465. writeMonitor.endTask();
  466. }
  467. /** Release all resources used by this writer. */
  468. public void release() {
  469. reader.release();
  470. if (myDeflater != null) {
  471. myDeflater.end();
  472. myDeflater = null;
  473. }
  474. }
  475. private void searchForReuse(ProgressMonitor monitor) throws IOException {
  476. monitor.beginTask(JGitText.get().searchForReuse, getObjectsNumber());
  477. for (List<ObjectToPack> list : objectsLists)
  478. reuseSupport.selectObjectRepresentation(this, monitor, list);
  479. monitor.endTask();
  480. }
  481. private void searchForDeltas(ProgressMonitor monitor)
  482. throws MissingObjectException, IncorrectObjectTypeException,
  483. IOException {
  484. // Commits and annotated tags tend to have too many differences to
  485. // really benefit from delta compression. Consequently just don't
  486. // bother examining those types here.
  487. //
  488. ObjectToPack[] list = new ObjectToPack[
  489. objectsLists[Constants.OBJ_TREE].size()
  490. + objectsLists[Constants.OBJ_BLOB].size()
  491. + edgeObjects.size()];
  492. int cnt = 0;
  493. cnt = findObjectsNeedingDelta(list, cnt, Constants.OBJ_TREE);
  494. cnt = findObjectsNeedingDelta(list, cnt, Constants.OBJ_BLOB);
  495. if (cnt == 0)
  496. return;
  497. // Queue up any edge objects that we might delta against. We won't
  498. // be sending these as we assume the other side has them, but we need
  499. // them in the search phase below.
  500. //
  501. for (ObjectToPack eo : edgeObjects) {
  502. eo.setWeight(0);
  503. list[cnt++] = eo;
  504. }
  505. // Compute the sizes of the objects so we can do a proper sort.
  506. // We let the reader skip missing objects if it chooses. For
  507. // some readers this can be a huge win. We detect missing objects
  508. // by having set the weights above to 0 and allowing the delta
  509. // search code to discover the missing object and skip over it, or
  510. // abort with an exception if we actually had to have it.
  511. //
  512. monitor.beginTask(JGitText.get().compressingObjects, cnt);
  513. AsyncObjectSizeQueue<ObjectToPack> sizeQueue = reader.getObjectSize(
  514. Arrays.<ObjectToPack> asList(list).subList(0, cnt), false);
  515. try {
  516. final long limit = config.getBigFileThreshold();
  517. for (;;) {
  518. monitor.update(1);
  519. try {
  520. if (!sizeQueue.next())
  521. break;
  522. } catch (MissingObjectException notFound) {
  523. if (ignoreMissingUninteresting) {
  524. ObjectToPack otp = sizeQueue.getCurrent();
  525. if (otp != null && otp.isEdge()) {
  526. otp.setDoNotDelta(true);
  527. continue;
  528. }
  529. otp = edgeObjects.get(notFound.getObjectId());
  530. if (otp != null) {
  531. otp.setDoNotDelta(true);
  532. continue;
  533. }
  534. }
  535. throw notFound;
  536. }
  537. ObjectToPack otp = sizeQueue.getCurrent();
  538. if (otp == null) {
  539. otp = objectsMap.get(sizeQueue.getObjectId());
  540. if (otp == null)
  541. otp = edgeObjects.get(sizeQueue.getObjectId());
  542. }
  543. long sz = sizeQueue.getSize();
  544. if (limit <= sz || Integer.MAX_VALUE <= sz)
  545. otp.setDoNotDelta(true); // too big, avoid costly files
  546. else if (sz <= DeltaIndex.BLKSZ)
  547. otp.setDoNotDelta(true); // too small, won't work
  548. else
  549. otp.setWeight((int) sz);
  550. }
  551. } finally {
  552. sizeQueue.release();
  553. }
  554. monitor.endTask();
  555. // Sort the objects by path hash so like files are near each other,
  556. // and then by size descending so that bigger files are first. This
  557. // applies "Linus' Law" which states that newer files tend to be the
  558. // bigger ones, because source files grow and hardly ever shrink.
  559. //
  560. Arrays.sort(list, 0, cnt, new Comparator<ObjectToPack>() {
  561. public int compare(ObjectToPack a, ObjectToPack b) {
  562. int cmp = (a.isDoNotDelta() ? 1 : 0)
  563. - (b.isDoNotDelta() ? 1 : 0);
  564. if (cmp != 0)
  565. return cmp;
  566. cmp = a.getType() - b.getType();
  567. if (cmp != 0)
  568. return cmp;
  569. cmp = (a.getPathHash() >>> 1) - (b.getPathHash() >>> 1);
  570. if (cmp != 0)
  571. return cmp;
  572. cmp = (a.getPathHash() & 1) - (b.getPathHash() & 1);
  573. if (cmp != 0)
  574. return cmp;
  575. return b.getWeight() - a.getWeight();
  576. }
  577. });
  578. // Above we stored the objects we cannot delta onto the end.
  579. // Remove them from the list so we don't waste time on them.
  580. while (0 < cnt && list[cnt - 1].isDoNotDelta())
  581. cnt--;
  582. if (cnt == 0)
  583. return;
  584. monitor.beginTask(JGitText.get().compressingObjects, cnt);
  585. searchForDeltas(monitor, list, cnt);
  586. monitor.endTask();
  587. }
  588. private int findObjectsNeedingDelta(ObjectToPack[] list, int cnt, int type) {
  589. for (ObjectToPack otp : objectsLists[type]) {
  590. if (otp.isDoNotDelta()) // delta is disabled for this path
  591. continue;
  592. if (otp.isDeltaRepresentation()) // already reusing a delta
  593. continue;
  594. otp.setWeight(0);
  595. list[cnt++] = otp;
  596. }
  597. return cnt;
  598. }
  599. private void searchForDeltas(final ProgressMonitor monitor,
  600. final ObjectToPack[] list, final int cnt)
  601. throws MissingObjectException, IncorrectObjectTypeException,
  602. LargeObjectException, IOException {
  603. int threads = config.getThreads();
  604. if (threads == 0)
  605. threads = Runtime.getRuntime().availableProcessors();
  606. if (threads <= 1 || cnt <= 2 * config.getDeltaSearchWindowSize()) {
  607. DeltaCache dc = new DeltaCache(config);
  608. DeltaWindow dw = new DeltaWindow(config, dc, reader);
  609. dw.search(monitor, list, 0, cnt);
  610. return;
  611. }
  612. final DeltaCache dc = new ThreadSafeDeltaCache(config);
  613. final ThreadSafeProgressMonitor pm = new ThreadSafeProgressMonitor(monitor);
  614. // Guess at the size of batch we want. Because we don't really
  615. // have a way for a thread to steal work from another thread if
  616. // it ends early, we over partition slightly so the work units
  617. // are a bit smaller.
  618. //
  619. int estSize = cnt / (threads * 2);
  620. if (estSize < 2 * config.getDeltaSearchWindowSize())
  621. estSize = 2 * config.getDeltaSearchWindowSize();
  622. final List<DeltaTask> myTasks = new ArrayList<DeltaTask>(threads * 2);
  623. for (int i = 0; i < cnt;) {
  624. final int start = i;
  625. final int batchSize;
  626. if (cnt - i < estSize) {
  627. // If we don't have enough to fill the remaining block,
  628. // schedule what is left over as a single block.
  629. //
  630. batchSize = cnt - i;
  631. } else {
  632. // Try to split the block at the end of a path.
  633. //
  634. int end = start + estSize;
  635. while (end < cnt) {
  636. ObjectToPack a = list[end - 1];
  637. ObjectToPack b = list[end];
  638. if (a.getPathHash() == b.getPathHash())
  639. end++;
  640. else
  641. break;
  642. }
  643. batchSize = end - start;
  644. }
  645. i += batchSize;
  646. myTasks.add(new DeltaTask(config, reader, dc, pm, batchSize, start, list));
  647. }
  648. pm.startWorkers(myTasks.size());
  649. final Executor executor = config.getExecutor();
  650. final List<Throwable> errors = Collections
  651. .synchronizedList(new ArrayList<Throwable>());
  652. if (executor instanceof ExecutorService) {
  653. // Caller supplied us a service, use it directly.
  654. //
  655. runTasks((ExecutorService) executor, pm, myTasks, errors);
  656. } else if (executor == null) {
  657. // Caller didn't give us a way to run the tasks, spawn up a
  658. // temporary thread pool and make sure it tears down cleanly.
  659. //
  660. ExecutorService pool = Executors.newFixedThreadPool(threads);
  661. try {
  662. runTasks(pool, pm, myTasks, errors);
  663. } finally {
  664. pool.shutdown();
  665. for (;;) {
  666. try {
  667. if (pool.awaitTermination(60, TimeUnit.SECONDS))
  668. break;
  669. } catch (InterruptedException e) {
  670. throw new IOException(
  671. JGitText.get().packingCancelledDuringObjectsWriting);
  672. }
  673. }
  674. }
  675. } else {
  676. // The caller gave us an executor, but it might not do
  677. // asynchronous execution. Wrap everything and hope it
  678. // can schedule these for us.
  679. //
  680. for (final DeltaTask task : myTasks) {
  681. executor.execute(new Runnable() {
  682. public void run() {
  683. try {
  684. task.call();
  685. } catch (Throwable failure) {
  686. errors.add(failure);
  687. }
  688. }
  689. });
  690. }
  691. try {
  692. pm.waitForCompletion();
  693. } catch (InterruptedException ie) {
  694. // We can't abort the other tasks as we have no handle.
  695. // Cross our fingers and just break out anyway.
  696. //
  697. throw new IOException(
  698. JGitText.get().packingCancelledDuringObjectsWriting);
  699. }
  700. }
  701. // If any task threw an error, try to report it back as
  702. // though we weren't using a threaded search algorithm.
  703. //
  704. if (!errors.isEmpty()) {
  705. Throwable err = errors.get(0);
  706. if (err instanceof Error)
  707. throw (Error) err;
  708. if (err instanceof RuntimeException)
  709. throw (RuntimeException) err;
  710. if (err instanceof IOException)
  711. throw (IOException) err;
  712. IOException fail = new IOException(err.getMessage());
  713. fail.initCause(err);
  714. throw fail;
  715. }
  716. }
  717. private void runTasks(ExecutorService pool, ThreadSafeProgressMonitor pm,
  718. List<DeltaTask> tasks, List<Throwable> errors) throws IOException {
  719. List<Future<?>> futures = new ArrayList<Future<?>>(tasks.size());
  720. for (DeltaTask task : tasks)
  721. futures.add(pool.submit(task));
  722. try {
  723. pm.waitForCompletion();
  724. for (Future<?> f : futures) {
  725. try {
  726. f.get();
  727. } catch (ExecutionException failed) {
  728. errors.add(failed.getCause());
  729. }
  730. }
  731. } catch (InterruptedException ie) {
  732. for (Future<?> f : futures)
  733. f.cancel(true);
  734. throw new IOException(
  735. JGitText.get().packingCancelledDuringObjectsWriting);
  736. }
  737. }
  738. private void writeObjects(PackOutputStream out) throws IOException {
  739. if (reuseSupport != null) {
  740. for (List<ObjectToPack> list : objectsLists)
  741. reuseSupport.writeObjects(out, list);
  742. } else {
  743. for (List<ObjectToPack> list : objectsLists) {
  744. for (ObjectToPack otp : list)
  745. out.writeObject(otp);
  746. }
  747. }
  748. }
  749. void writeObject(PackOutputStream out, ObjectToPack otp) throws IOException {
  750. if (otp.isWritten())
  751. return; // We shouldn't be here.
  752. otp.markWantWrite();
  753. if (otp.isDeltaRepresentation())
  754. writeBaseFirst(out, otp);
  755. out.resetCRC32();
  756. otp.setOffset(out.length());
  757. while (otp.isReuseAsIs()) {
  758. try {
  759. reuseSupport.copyObjectAsIs(out, otp);
  760. out.endObject();
  761. otp.setCRC(out.getCRC32());
  762. return;
  763. } catch (StoredObjectRepresentationNotAvailableException gone) {
  764. if (otp.getOffset() == out.length()) {
  765. redoSearchForReuse(otp);
  766. continue;
  767. } else {
  768. // Object writing already started, we cannot recover.
  769. //
  770. CorruptObjectException coe;
  771. coe = new CorruptObjectException(otp, "");
  772. coe.initCause(gone);
  773. throw coe;
  774. }
  775. }
  776. }
  777. // If we reached here, reuse wasn't possible.
  778. //
  779. if (otp.isDeltaRepresentation())
  780. writeDeltaObjectDeflate(out, otp);
  781. else
  782. writeWholeObjectDeflate(out, otp);
  783. out.endObject();
  784. otp.setCRC(out.getCRC32());
  785. }
  786. private void writeBaseFirst(PackOutputStream out, final ObjectToPack otp)
  787. throws IOException {
  788. ObjectToPack baseInPack = otp.getDeltaBase();
  789. if (baseInPack != null) {
  790. if (!baseInPack.isWritten()) {
  791. if (baseInPack.wantWrite()) {
  792. // There is a cycle. Our caller is trying to write the
  793. // object we want as a base, and called us. Turn off
  794. // delta reuse so we can find another form.
  795. //
  796. reuseDeltas = false;
  797. redoSearchForReuse(otp);
  798. reuseDeltas = true;
  799. } else {
  800. writeObject(out, baseInPack);
  801. }
  802. }
  803. } else if (!thin) {
  804. // This should never occur, the base isn't in the pack and
  805. // the pack isn't allowed to reference base outside objects.
  806. // Write the object as a whole form, even if that is slow.
  807. //
  808. otp.clearDeltaBase();
  809. otp.clearReuseAsIs();
  810. }
  811. }
  812. private void redoSearchForReuse(final ObjectToPack otp) throws IOException,
  813. MissingObjectException {
  814. otp.clearDeltaBase();
  815. otp.clearReuseAsIs();
  816. reuseSupport.selectObjectRepresentation(this,
  817. NullProgressMonitor.INSTANCE, Collections.singleton(otp));
  818. }
  819. private void writeWholeObjectDeflate(PackOutputStream out,
  820. final ObjectToPack otp) throws IOException {
  821. final Deflater deflater = deflater();
  822. final ObjectLoader ldr = reader.open(otp, otp.getType());
  823. out.writeHeader(otp, ldr.getSize());
  824. deflater.reset();
  825. DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater);
  826. ldr.copyTo(dst);
  827. dst.finish();
  828. }
  829. private void writeDeltaObjectDeflate(PackOutputStream out,
  830. final ObjectToPack otp) throws IOException {
  831. DeltaCache.Ref ref = otp.popCachedDelta();
  832. if (ref != null) {
  833. byte[] zbuf = ref.get();
  834. if (zbuf != null) {
  835. out.writeHeader(otp, otp.getCachedSize());
  836. out.write(zbuf);
  837. return;
  838. }
  839. }
  840. TemporaryBuffer.Heap delta = delta(otp);
  841. out.writeHeader(otp, delta.length());
  842. Deflater deflater = deflater();
  843. deflater.reset();
  844. DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater);
  845. delta.writeTo(dst, null);
  846. dst.finish();
  847. }
  848. private TemporaryBuffer.Heap delta(final ObjectToPack otp)
  849. throws IOException {
  850. DeltaIndex index = new DeltaIndex(buffer(otp.getDeltaBaseId()));
  851. byte[] res = buffer(otp);
  852. // We never would have proposed this pair if the delta would be
  853. // larger than the unpacked version of the object. So using it
  854. // as our buffer limit is valid: we will never reach it.
  855. //
  856. TemporaryBuffer.Heap delta = new TemporaryBuffer.Heap(res.length);
  857. index.encode(delta, res);
  858. return delta;
  859. }
  860. private byte[] buffer(AnyObjectId objId) throws IOException {
  861. return buffer(config, reader, objId);
  862. }
  863. static byte[] buffer(PackConfig config, ObjectReader or, AnyObjectId objId)
  864. throws IOException {
  865. // PackWriter should have already pruned objects that
  866. // are above the big file threshold, so our chances of
  867. // the object being below it are very good. We really
  868. // shouldn't be here, unless the implementation is odd.
  869. return or.open(objId).getCachedBytes(config.getBigFileThreshold());
  870. }
  871. private Deflater deflater() {
  872. if (myDeflater == null)
  873. myDeflater = new Deflater(config.getCompressionLevel());
  874. return myDeflater;
  875. }
  876. private void writeChecksum(PackOutputStream out) throws IOException {
  877. packcsum = out.getDigest();
  878. out.write(packcsum);
  879. }
  880. private void findObjectsToPack(final ProgressMonitor countingMonitor,
  881. final Collection<? extends ObjectId> interestingObjects,
  882. final Collection<? extends ObjectId> uninterestingObjects)
  883. throws MissingObjectException, IOException,
  884. IncorrectObjectTypeException {
  885. countingMonitor.beginTask(JGitText.get().countingObjects,
  886. ProgressMonitor.UNKNOWN);
  887. List<ObjectId> all = new ArrayList<ObjectId>(interestingObjects.size());
  888. for (ObjectId id : interestingObjects)
  889. all.add(id.copy());
  890. final Set<ObjectId> not;
  891. if (uninterestingObjects != null && !uninterestingObjects.isEmpty()) {
  892. not = new HashSet<ObjectId>();
  893. for (ObjectId id : uninterestingObjects)
  894. not.add(id.copy());
  895. all.addAll(not);
  896. } else
  897. not = Collections.emptySet();
  898. final ObjectWalk walker = new ObjectWalk(reader);
  899. final RevFlag hasObjectList = walker.newFlag("hasObjectList");
  900. walker.setRetainBody(false);
  901. if (not.isEmpty()) {
  902. walker.sort(RevSort.COMMIT_TIME_DESC);
  903. for (ObjectId listName : reader.getAvailableObjectLists())
  904. walker.lookupCommit(listName).add(hasObjectList);
  905. } else {
  906. walker.sort(RevSort.TOPO);
  907. if (thin)
  908. walker.sort(RevSort.BOUNDARY, true);
  909. }
  910. AsyncRevObjectQueue q = walker.parseAny(all, true);
  911. try {
  912. for (;;) {
  913. try {
  914. RevObject o = q.next();
  915. if (o == null)
  916. break;
  917. if (not.contains(o.copy()))
  918. walker.markUninteresting(o);
  919. else
  920. walker.markStart(o);
  921. } catch (MissingObjectException e) {
  922. if (ignoreMissingUninteresting
  923. && not.contains(e.getObjectId()))
  924. continue;
  925. throw e;
  926. }
  927. }
  928. } finally {
  929. q.release();
  930. }
  931. RevObject listName = null;
  932. RevObject o;
  933. while ((o = walker.next()) != null) {
  934. if (o.has(hasObjectList)) {
  935. listName = o;
  936. break;
  937. }
  938. addResultOrBase(o, 0);
  939. countingMonitor.update(1);
  940. }
  941. if (listName != null) {
  942. addByObjectList(listName, countingMonitor, walker,
  943. interestingObjects);
  944. } else {
  945. while ((o = walker.nextObject()) != null) {
  946. addResultOrBase(o, walker.getPathHashCode());
  947. countingMonitor.update(1);
  948. }
  949. }
  950. countingMonitor.endTask();
  951. }
  952. private void addByObjectList(RevObject listName,
  953. final ProgressMonitor countingMonitor, final ObjectWalk walker,
  954. final Collection<? extends ObjectId> interestingObjects)
  955. throws MissingObjectException, IncorrectObjectTypeException,
  956. IOException {
  957. int restartedProgress = objectsMap.size();
  958. for (List<ObjectToPack> list : objectsLists)
  959. list.clear();
  960. objectsMap.clear();
  961. walker.reset();
  962. walker.markUninteresting(listName);
  963. for (ObjectId id : interestingObjects)
  964. walker.markStart(walker.lookupOrNull(id));
  965. RevFlag added = walker.newFlag("added");
  966. RevObject o;
  967. while ((o = walker.next()) != null) {
  968. addResult(o, 0);
  969. o.add(added);
  970. if (restartedProgress != 0)
  971. restartedProgress--;
  972. else
  973. countingMonitor.update(1);
  974. }
  975. while ((o = walker.nextObject()) != null) {
  976. addResult(o, walker.getPathHashCode());
  977. o.add(added);
  978. if (restartedProgress != 0)
  979. restartedProgress--;
  980. else
  981. countingMonitor.update(1);
  982. }
  983. ObjectListIterator itr = reader.openObjectList(listName, walker);
  984. try {
  985. while ((o = itr.next()) != null) {
  986. if (o.has(added))
  987. continue;
  988. addResult(o, 0);
  989. o.add(added);
  990. if (restartedProgress != 0)
  991. restartedProgress--;
  992. else
  993. countingMonitor.update(1);
  994. }
  995. while ((o = itr.nextObject()) != null) {
  996. if (o.has(added))
  997. continue;
  998. addResult(o, itr.getPathHashCode());
  999. o.add(added);
  1000. if (restartedProgress != 0)
  1001. restartedProgress--;
  1002. else
  1003. countingMonitor.update(1);
  1004. }
  1005. } finally {
  1006. itr.release();
  1007. }
  1008. }
  1009. /**
  1010. * Include one object to the output file.
  1011. * <p>
  1012. * Objects are written in the order they are added. If the same object is
  1013. * added twice, it may be written twice, creating a larger than necessary
  1014. * file.
  1015. *
  1016. * @param object
  1017. * the object to add.
  1018. * @throws IncorrectObjectTypeException
  1019. * the object is an unsupported type.
  1020. */
  1021. public void addObject(final RevObject object)
  1022. throws IncorrectObjectTypeException {
  1023. addResultOrBase(object, 0);
  1024. }
  1025. private void addResultOrBase(final RevObject object, final int pathHashCode)
  1026. throws IncorrectObjectTypeException {
  1027. if (object.has(RevFlag.UNINTERESTING)) {
  1028. switch (object.getType()) {
  1029. case Constants.OBJ_TREE:
  1030. case Constants.OBJ_BLOB:
  1031. ObjectToPack otp = new ObjectToPack(object);
  1032. otp.setPathHash(pathHashCode);
  1033. otp.setEdge();
  1034. edgeObjects.add(otp);
  1035. thin = true;
  1036. break;
  1037. }
  1038. } else {
  1039. addResult(object, pathHashCode);
  1040. }
  1041. }
  1042. private void addResult(final RevObject object, final int pathHashCode)
  1043. throws IncorrectObjectTypeException {
  1044. final ObjectToPack otp;
  1045. if (reuseSupport != null)
  1046. otp = reuseSupport.newObjectToPack(object);
  1047. else
  1048. otp = new ObjectToPack(object);
  1049. otp.setPathHash(pathHashCode);
  1050. try {
  1051. objectsLists[object.getType()].add(otp);
  1052. } catch (ArrayIndexOutOfBoundsException x) {
  1053. throw new IncorrectObjectTypeException(object,
  1054. JGitText.get().incorrectObjectType_COMMITnorTREEnorBLOBnorTAG);
  1055. } catch (UnsupportedOperationException x) {
  1056. // index pointing to "dummy" empty list
  1057. throw new IncorrectObjectTypeException(object,
  1058. JGitText.get().incorrectObjectType_COMMITnorTREEnorBLOBnorTAG);
  1059. }
  1060. objectsMap.add(otp);
  1061. }
  1062. /**
  1063. * Select an object representation for this writer.
  1064. * <p>
  1065. * An {@link ObjectReader} implementation should invoke this method once for
  1066. * each representation available for an object, to allow the writer to find
  1067. * the most suitable one for the output.
  1068. *
  1069. * @param otp
  1070. * the object being packed.
  1071. * @param next
  1072. * the next available representation from the repository.
  1073. */
  1074. public void select(ObjectToPack otp, StoredObjectRepresentation next) {
  1075. int nFmt = next.getFormat();
  1076. int nWeight;
  1077. if (otp.isReuseAsIs()) {
  1078. // We've already chosen to reuse a packed form, if next
  1079. // cannot beat that break out early.
  1080. //
  1081. if (PACK_WHOLE < nFmt)
  1082. return; // next isn't packed
  1083. else if (PACK_DELTA < nFmt && otp.isDeltaRepresentation())
  1084. return; // next isn't a delta, but we are
  1085. nWeight = next.getWeight();
  1086. if (otp.getWeight() <= nWeight)
  1087. return; // next would be bigger
  1088. } else
  1089. nWeight = next.getWeight();
  1090. if (nFmt == PACK_DELTA && reuseDeltas) {
  1091. ObjectId baseId = next.getDeltaBase();
  1092. ObjectToPack ptr = objectsMap.get(baseId);
  1093. if (ptr != null) {
  1094. otp.setDeltaBase(ptr);
  1095. otp.setReuseAsIs();
  1096. otp.setWeight(nWeight);
  1097. } else if (thin && edgeObjects.contains(baseId)) {
  1098. otp.setDeltaBase(baseId);
  1099. otp.setReuseAsIs();
  1100. otp.setWeight(nWeight);
  1101. } else {
  1102. otp.clearDeltaBase();
  1103. otp.clearReuseAsIs();
  1104. }
  1105. } else if (nFmt == PACK_WHOLE && config.isReuseObjects()) {
  1106. otp.clearDeltaBase();
  1107. otp.setReuseAsIs();
  1108. otp.setWeight(nWeight);
  1109. } else {
  1110. otp.clearDeltaBase();
  1111. otp.clearReuseAsIs();
  1112. }
  1113. otp.select(next);
  1114. }
  1115. }