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

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