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.

PackedBatchRefUpdate.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. /*
  2. * Copyright (C) 2017, Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.internal.storage.file;
  44. import static java.util.stream.Collectors.toList;
  45. import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
  46. import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
  47. import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
  48. import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
  49. import java.io.IOException;
  50. import java.text.MessageFormat;
  51. import java.util.Collections;
  52. import java.util.Comparator;
  53. import java.util.HashMap;
  54. import java.util.HashSet;
  55. import java.util.List;
  56. import java.util.Map;
  57. import java.util.Set;
  58. import org.eclipse.jgit.annotations.Nullable;
  59. import org.eclipse.jgit.errors.LockFailedException;
  60. import org.eclipse.jgit.errors.MissingObjectException;
  61. import org.eclipse.jgit.internal.JGitText;
  62. import org.eclipse.jgit.internal.storage.file.RefDirectory.PackedRefList;
  63. import org.eclipse.jgit.lib.BatchRefUpdate;
  64. import org.eclipse.jgit.lib.ObjectId;
  65. import org.eclipse.jgit.lib.ObjectIdRef;
  66. import org.eclipse.jgit.lib.PersonIdent;
  67. import org.eclipse.jgit.lib.ProgressMonitor;
  68. import org.eclipse.jgit.lib.Ref;
  69. import org.eclipse.jgit.lib.RefDatabase;
  70. import org.eclipse.jgit.lib.ReflogEntry;
  71. import org.eclipse.jgit.revwalk.RevObject;
  72. import org.eclipse.jgit.revwalk.RevTag;
  73. import org.eclipse.jgit.revwalk.RevWalk;
  74. import org.eclipse.jgit.transport.ReceiveCommand;
  75. import org.eclipse.jgit.util.RefList;
  76. /**
  77. * Implementation of {@link BatchRefUpdate} that uses the {@code packed-refs}
  78. * file to support atomically updating multiple refs.
  79. * <p>
  80. * The algorithm is designed to be compatible with traditional single ref
  81. * updates operating on single refs only. Regardless of success or failure, the
  82. * results are atomic: from the perspective of any reader, either all updates in
  83. * the batch will be visible, or none will. In the case of process failure
  84. * during any of the following steps, removal of stale lock files is always
  85. * safe, and will never result in an inconsistent state, although the update may
  86. * or may not have been applied.
  87. * <p>
  88. * The algorithm is:
  89. * <ol>
  90. * <li>Pack loose refs involved in the transaction using the normal pack-refs
  91. * operation. This ensures that creating lock files in the following step
  92. * succeeds even if a batch contains both a delete of {@code refs/x} (loose) and
  93. * a create of {@code refs/x/y}.</li>
  94. * <li>Create locks for all loose refs involved in the transaction, even if they
  95. * are not currently loose.</li>
  96. * <li>Pack loose refs again, this time while holding all lock files (see {@link
  97. * RefDirectory#pack(Map)}), without deleting them afterwards. This covers a
  98. * potential race where new loose refs were created after the initial packing
  99. * step. If no new loose refs were created during this race, this step does not
  100. * modify any files on disk. Keep the merged state in memory.</li>
  101. * <li>Update the in-memory packed refs with the commands in the batch, possibly
  102. * failing the whole batch if any old ref values do not match.</li>
  103. * <li>If the update succeeds, lock {@code packed-refs} and commit by atomically
  104. * renaming the lock file.</li>
  105. * <li>Delete loose ref lock files.</li>
  106. * </ol>
  107. *
  108. * Because the packed-refs file format is a sorted list, this algorithm is
  109. * linear in the total number of refs, regardless of the batch size. This can be
  110. * a significant slowdown on repositories with large numbers of refs; callers
  111. * that prefer speed over atomicity should use {@code setAtomic(false)}. As an
  112. * optimization, an update containing a single ref update does not use the
  113. * packed-refs protocol.
  114. */
  115. class PackedBatchRefUpdate extends BatchRefUpdate {
  116. private RefDirectory refdb;
  117. PackedBatchRefUpdate(RefDirectory refdb) {
  118. super(refdb);
  119. this.refdb = refdb;
  120. }
  121. /** {@inheritDoc} */
  122. @Override
  123. public void execute(RevWalk walk, ProgressMonitor monitor,
  124. List<String> options) throws IOException {
  125. if (!isAtomic()) {
  126. // Use default one-by-one implementation.
  127. super.execute(walk, monitor, options);
  128. return;
  129. }
  130. List<ReceiveCommand> pending =
  131. ReceiveCommand.filter(getCommands(), NOT_ATTEMPTED);
  132. if (pending.isEmpty()) {
  133. return;
  134. }
  135. if (pending.size() == 1) {
  136. // Single-ref updates are always atomic, no need for packed-refs.
  137. super.execute(walk, monitor, options);
  138. return;
  139. }
  140. if (containsSymrefs(pending)) {
  141. // packed-refs file cannot store symrefs
  142. reject(pending.get(0), REJECTED_OTHER_REASON,
  143. JGitText.get().atomicSymRefNotSupported, pending);
  144. return;
  145. }
  146. // Required implementation details copied from super.execute.
  147. if (!blockUntilTimestamps(MAX_WAIT)) {
  148. return;
  149. }
  150. if (options != null) {
  151. setPushOptions(options);
  152. }
  153. // End required implementation details.
  154. // Check for conflicting names before attempting to acquire locks, since
  155. // lockfile creation may fail on file/directory conflicts.
  156. if (!checkConflictingNames(pending)) {
  157. return;
  158. }
  159. if (!checkObjectExistence(walk, pending)) {
  160. return;
  161. }
  162. if (!checkNonFastForwards(walk, pending)) {
  163. return;
  164. }
  165. // Pack refs normally, so we can create lock files even in the case where
  166. // refs/x is deleted and refs/x/y is created in this batch.
  167. try {
  168. refdb.pack(
  169. pending.stream().map(ReceiveCommand::getRefName).collect(toList()));
  170. } catch (LockFailedException e) {
  171. lockFailure(pending.get(0), pending);
  172. return;
  173. }
  174. Map<String, LockFile> locks = null;
  175. refdb.inProcessPackedRefsLock.lock();
  176. try {
  177. PackedRefList oldPackedList;
  178. if (!refdb.isInClone()) {
  179. locks = lockLooseRefs(pending);
  180. if (locks == null) {
  181. return;
  182. }
  183. oldPackedList = refdb.pack(locks);
  184. } else {
  185. // During clone locking isn't needed since no refs exist yet.
  186. // This also helps to avoid problems with refs only differing in
  187. // case on a case insensitive filesystem (bug 528497)
  188. oldPackedList = refdb.getPackedRefs();
  189. }
  190. RefList<Ref> newRefs = applyUpdates(walk, oldPackedList, pending);
  191. if (newRefs == null) {
  192. return;
  193. }
  194. LockFile packedRefsLock = refdb.lockPackedRefs();
  195. if (packedRefsLock == null) {
  196. lockFailure(pending.get(0), pending);
  197. return;
  198. }
  199. // commitPackedRefs removes lock file (by renaming over real file).
  200. refdb.commitPackedRefs(packedRefsLock, newRefs, oldPackedList,
  201. true);
  202. } finally {
  203. try {
  204. unlockAll(locks);
  205. } finally {
  206. refdb.inProcessPackedRefsLock.unlock();
  207. }
  208. }
  209. refdb.fireRefsChanged();
  210. pending.forEach(c -> c.setResult(ReceiveCommand.Result.OK));
  211. writeReflog(pending);
  212. }
  213. private static boolean containsSymrefs(List<ReceiveCommand> commands) {
  214. for (ReceiveCommand cmd : commands) {
  215. if (cmd.getOldSymref() != null || cmd.getNewSymref() != null) {
  216. return true;
  217. }
  218. }
  219. return false;
  220. }
  221. private boolean checkConflictingNames(List<ReceiveCommand> commands)
  222. throws IOException {
  223. Set<String> takenNames = new HashSet<>();
  224. Set<String> takenPrefixes = new HashSet<>();
  225. Set<String> deletes = new HashSet<>();
  226. for (ReceiveCommand cmd : commands) {
  227. if (cmd.getType() != ReceiveCommand.Type.DELETE) {
  228. takenNames.add(cmd.getRefName());
  229. addPrefixesTo(cmd.getRefName(), takenPrefixes);
  230. } else {
  231. deletes.add(cmd.getRefName());
  232. }
  233. }
  234. Set<String> initialRefs = refdb.getRefs(RefDatabase.ALL).keySet();
  235. for (String name : initialRefs) {
  236. if (!deletes.contains(name)) {
  237. takenNames.add(name);
  238. addPrefixesTo(name, takenPrefixes);
  239. }
  240. }
  241. for (ReceiveCommand cmd : commands) {
  242. if (cmd.getType() != ReceiveCommand.Type.DELETE &&
  243. takenPrefixes.contains(cmd.getRefName())) {
  244. // This ref is a prefix of some other ref. This check doesn't apply when
  245. // this command is a delete, because if the ref is deleted nobody will
  246. // ever be creating a loose ref with that name.
  247. lockFailure(cmd, commands);
  248. return false;
  249. }
  250. for (String prefix : getPrefixes(cmd.getRefName())) {
  251. if (takenNames.contains(prefix)) {
  252. // A prefix of this ref is already a refname. This check does apply
  253. // when this command is a delete, because we would need to create the
  254. // refname as a directory in order to create a lockfile for the
  255. // to-be-deleted ref.
  256. lockFailure(cmd, commands);
  257. return false;
  258. }
  259. }
  260. }
  261. return true;
  262. }
  263. private boolean checkObjectExistence(RevWalk walk,
  264. List<ReceiveCommand> commands) throws IOException {
  265. for (ReceiveCommand cmd : commands) {
  266. try {
  267. if (!cmd.getNewId().equals(ObjectId.zeroId())) {
  268. walk.parseAny(cmd.getNewId());
  269. }
  270. } catch (MissingObjectException e) {
  271. // ReceiveCommand#setResult(Result) converts REJECTED to
  272. // REJECTED_NONFASTFORWARD, even though that result is also used for a
  273. // missing object. Eagerly handle this case so we can set the right
  274. // result.
  275. reject(cmd, ReceiveCommand.Result.REJECTED_MISSING_OBJECT, commands);
  276. return false;
  277. }
  278. }
  279. return true;
  280. }
  281. private boolean checkNonFastForwards(RevWalk walk,
  282. List<ReceiveCommand> commands) throws IOException {
  283. if (isAllowNonFastForwards()) {
  284. return true;
  285. }
  286. for (ReceiveCommand cmd : commands) {
  287. cmd.updateType(walk);
  288. if (cmd.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD) {
  289. reject(cmd, REJECTED_NONFASTFORWARD, commands);
  290. return false;
  291. }
  292. }
  293. return true;
  294. }
  295. /**
  296. * Lock loose refs corresponding to a list of commands.
  297. *
  298. * @param commands
  299. * commands that we intend to execute.
  300. * @return map of ref name in the input commands to lock file. Always contains
  301. * one entry for each ref in the input list. All locks are acquired
  302. * before returning. If any lock was not able to be acquired: the
  303. * return value is null; no locks are held; and all commands that were
  304. * pending are set to fail with {@code LOCK_FAILURE}.
  305. * @throws IOException
  306. * an error occurred other than a failure to acquire; no locks are
  307. * held if this exception is thrown.
  308. */
  309. @Nullable
  310. private Map<String, LockFile> lockLooseRefs(List<ReceiveCommand> commands)
  311. throws IOException {
  312. ReceiveCommand failed = null;
  313. Map<String, LockFile> locks = new HashMap<>();
  314. try {
  315. RETRY: for (int ms : refdb.getRetrySleepMs()) {
  316. failed = null;
  317. // Release all locks before trying again, to prevent deadlock.
  318. unlockAll(locks);
  319. locks.clear();
  320. RefDirectory.sleep(ms);
  321. for (ReceiveCommand c : commands) {
  322. String name = c.getRefName();
  323. LockFile lock = new LockFile(refdb.fileFor(name));
  324. if (locks.put(name, lock) != null) {
  325. throw new IOException(
  326. MessageFormat.format(JGitText.get().duplicateRef, name));
  327. }
  328. if (!lock.lock()) {
  329. failed = c;
  330. continue RETRY;
  331. }
  332. }
  333. Map<String, LockFile> result = locks;
  334. locks = null;
  335. return result;
  336. }
  337. } finally {
  338. unlockAll(locks);
  339. }
  340. lockFailure(failed != null ? failed : commands.get(0), commands);
  341. return null;
  342. }
  343. private static RefList<Ref> applyUpdates(RevWalk walk, RefList<Ref> refs,
  344. List<ReceiveCommand> commands) throws IOException {
  345. // Construct a new RefList by merging the old list with the updates.
  346. // This assumes that each ref occurs at most once as a ReceiveCommand.
  347. Collections.sort(commands, new Comparator<ReceiveCommand>() {
  348. @Override
  349. public int compare(ReceiveCommand a, ReceiveCommand b) {
  350. return a.getRefName().compareTo(b.getRefName());
  351. }
  352. });
  353. int delta = 0;
  354. for (ReceiveCommand c : commands) {
  355. switch (c.getType()) {
  356. case DELETE:
  357. delta--;
  358. break;
  359. case CREATE:
  360. delta++;
  361. break;
  362. default:
  363. }
  364. }
  365. RefList.Builder<Ref> b = new RefList.Builder<>(refs.size() + delta);
  366. int refIdx = 0;
  367. int cmdIdx = 0;
  368. while (refIdx < refs.size() || cmdIdx < commands.size()) {
  369. Ref ref = (refIdx < refs.size()) ? refs.get(refIdx) : null;
  370. ReceiveCommand cmd = (cmdIdx < commands.size())
  371. ? commands.get(cmdIdx)
  372. : null;
  373. int cmp = 0;
  374. if (ref != null && cmd != null) {
  375. cmp = ref.getName().compareTo(cmd.getRefName());
  376. } else if (ref == null) {
  377. cmp = 1;
  378. } else if (cmd == null) {
  379. cmp = -1;
  380. }
  381. if (cmp < 0) {
  382. b.add(ref);
  383. refIdx++;
  384. } else if (cmp > 0) {
  385. assert cmd != null;
  386. if (cmd.getType() != ReceiveCommand.Type.CREATE) {
  387. lockFailure(cmd, commands);
  388. return null;
  389. }
  390. b.add(peeledRef(walk, cmd));
  391. cmdIdx++;
  392. } else {
  393. assert cmd != null;
  394. assert ref != null;
  395. if (!cmd.getOldId().equals(ref.getObjectId())) {
  396. lockFailure(cmd, commands);
  397. return null;
  398. }
  399. if (cmd.getType() != ReceiveCommand.Type.DELETE) {
  400. b.add(peeledRef(walk, cmd));
  401. }
  402. cmdIdx++;
  403. refIdx++;
  404. }
  405. }
  406. return b.toRefList();
  407. }
  408. private void writeReflog(List<ReceiveCommand> commands) {
  409. PersonIdent ident = getRefLogIdent();
  410. if (ident == null) {
  411. ident = new PersonIdent(refdb.getRepository());
  412. }
  413. for (ReceiveCommand cmd : commands) {
  414. // Assume any pending commands have already been executed atomically.
  415. if (cmd.getResult() != ReceiveCommand.Result.OK) {
  416. continue;
  417. }
  418. String name = cmd.getRefName();
  419. if (cmd.getType() == ReceiveCommand.Type.DELETE) {
  420. try {
  421. RefDirectory.delete(refdb.logFor(name), RefDirectory.levelsIn(name));
  422. } catch (IOException e) {
  423. // Ignore failures, see below.
  424. }
  425. continue;
  426. }
  427. if (isRefLogDisabled(cmd)) {
  428. continue;
  429. }
  430. String msg = getRefLogMessage(cmd);
  431. if (isRefLogIncludingResult(cmd)) {
  432. String strResult = toResultString(cmd);
  433. if (strResult != null) {
  434. msg = msg.isEmpty()
  435. ? strResult : msg + ": " + strResult; //$NON-NLS-1$
  436. }
  437. }
  438. try {
  439. new ReflogWriter(refdb, isForceRefLog(cmd))
  440. .log(name, cmd.getOldId(), cmd.getNewId(), ident, msg);
  441. } catch (IOException e) {
  442. // Ignore failures, but continue attempting to write more reflogs.
  443. //
  444. // In this storage format, it is impossible to atomically write the
  445. // reflog with the ref updates, so we have to choose between:
  446. // a. Propagating this exception and claiming failure, even though the
  447. // actual ref updates succeeded.
  448. // b. Ignoring failures writing the reflog, so we claim success if and
  449. // only if the ref updates succeeded.
  450. // We choose (b) in order to surprise callers the least.
  451. //
  452. // Possible future improvements:
  453. // * Log a warning to a logger.
  454. // * Retry a fixed number of times in case the error was transient.
  455. }
  456. }
  457. }
  458. private String toResultString(ReceiveCommand cmd) {
  459. switch (cmd.getType()) {
  460. case CREATE:
  461. return ReflogEntry.PREFIX_CREATED;
  462. case UPDATE:
  463. // Match the behavior of a single RefUpdate. In that case, setting the
  464. // force bit completely bypasses the potentially expensive isMergedInto
  465. // check, by design, so the reflog message may be inaccurate.
  466. //
  467. // Similarly, this class bypasses the isMergedInto checks when the force
  468. // bit is set, meaning we can't actually distinguish between UPDATE and
  469. // UPDATE_NONFASTFORWARD when isAllowNonFastForwards() returns true.
  470. return isAllowNonFastForwards()
  471. ? ReflogEntry.PREFIX_FORCED_UPDATE : ReflogEntry.PREFIX_FAST_FORWARD;
  472. case UPDATE_NONFASTFORWARD:
  473. return ReflogEntry.PREFIX_FORCED_UPDATE;
  474. default:
  475. return null;
  476. }
  477. }
  478. private static Ref peeledRef(RevWalk walk, ReceiveCommand cmd)
  479. throws IOException {
  480. ObjectId newId = cmd.getNewId().copy();
  481. RevObject obj = walk.parseAny(newId);
  482. if (obj instanceof RevTag) {
  483. return new ObjectIdRef.PeeledTag(
  484. Ref.Storage.PACKED, cmd.getRefName(), newId, walk.peel(obj).copy());
  485. }
  486. return new ObjectIdRef.PeeledNonTag(
  487. Ref.Storage.PACKED, cmd.getRefName(), newId);
  488. }
  489. private static void unlockAll(@Nullable Map<?, LockFile> locks) {
  490. if (locks != null) {
  491. locks.values().forEach(LockFile::unlock);
  492. }
  493. }
  494. private static void lockFailure(ReceiveCommand cmd,
  495. List<ReceiveCommand> commands) {
  496. reject(cmd, LOCK_FAILURE, commands);
  497. }
  498. private static void reject(ReceiveCommand cmd, ReceiveCommand.Result result,
  499. List<ReceiveCommand> commands) {
  500. reject(cmd, result, null, commands);
  501. }
  502. private static void reject(ReceiveCommand cmd, ReceiveCommand.Result result,
  503. String why, List<ReceiveCommand> commands) {
  504. cmd.setResult(result, why);
  505. for (ReceiveCommand c2 : commands) {
  506. if (c2.getResult() == ReceiveCommand.Result.OK) {
  507. // Undo OK status so ReceiveCommand#abort aborts it. Assumes this method
  508. // is always called before committing any updates to disk.
  509. c2.setResult(ReceiveCommand.Result.NOT_ATTEMPTED);
  510. }
  511. }
  512. ReceiveCommand.abort(commands);
  513. }
  514. }