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.

BatchRefUpdate.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /*
  2. * Copyright (C) 2008-2012, Google Inc.
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  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.lib;
  45. import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
  46. import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
  47. import java.io.IOException;
  48. import java.text.MessageFormat;
  49. import java.time.Duration;
  50. import java.util.ArrayList;
  51. import java.util.Arrays;
  52. import java.util.Collection;
  53. import java.util.Collections;
  54. import java.util.HashSet;
  55. import java.util.List;
  56. import java.util.concurrent.TimeoutException;
  57. import org.eclipse.jgit.internal.JGitText;
  58. import org.eclipse.jgit.lib.RefUpdate.Result;
  59. import org.eclipse.jgit.revwalk.RevWalk;
  60. import org.eclipse.jgit.transport.PushCertificate;
  61. import org.eclipse.jgit.transport.ReceiveCommand;
  62. import org.eclipse.jgit.util.time.ProposedTimestamp;
  63. /**
  64. * Batch of reference updates to be applied to a repository.
  65. * <p>
  66. * The batch update is primarily useful in the transport code, where a client or
  67. * server is making changes to more than one reference at a time.
  68. */
  69. public class BatchRefUpdate {
  70. /**
  71. * Maximum delay the calling thread will tolerate while waiting for a
  72. * {@code MonotonicClock} to resolve associated {@link ProposedTimestamp}s.
  73. * <p>
  74. * A default of 5 seconds was chosen by guessing. A common assumption is
  75. * clock skew between machines on the same LAN using an NTP server also on
  76. * the same LAN should be under 5 seconds. 5 seconds is also not that long
  77. * for a large `git push` operation to complete.
  78. */
  79. private static final Duration MAX_WAIT = Duration.ofSeconds(5);
  80. private final RefDatabase refdb;
  81. /** Commands to apply during this batch. */
  82. private final List<ReceiveCommand> commands;
  83. /** Does the caller permit a forced update on a reference? */
  84. private boolean allowNonFastForwards;
  85. /** Identity to record action as within the reflog. */
  86. private PersonIdent refLogIdent;
  87. /** Message the caller wants included in the reflog. */
  88. private String refLogMessage;
  89. /** Should the result value be appended to {@link #refLogMessage}. */
  90. private boolean refLogIncludeResult;
  91. /** Push certificate associated with this update. */
  92. private PushCertificate pushCert;
  93. /** Whether updates should be atomic. */
  94. private boolean atomic;
  95. /** Push options associated with this update. */
  96. private List<String> pushOptions;
  97. /** Associated timestamps that should be blocked on before update. */
  98. private List<ProposedTimestamp> timestamps;
  99. /**
  100. * Initialize a new batch update.
  101. *
  102. * @param refdb
  103. * the reference database of the repository to be updated.
  104. */
  105. protected BatchRefUpdate(RefDatabase refdb) {
  106. this.refdb = refdb;
  107. this.commands = new ArrayList<ReceiveCommand>();
  108. this.atomic = refdb.performsAtomicTransactions();
  109. }
  110. /**
  111. * @return true if the batch update will permit a non-fast-forward update to
  112. * an existing reference.
  113. */
  114. public boolean isAllowNonFastForwards() {
  115. return allowNonFastForwards;
  116. }
  117. /**
  118. * Set if this update wants to permit a forced update.
  119. *
  120. * @param allow
  121. * true if this update batch should ignore merge tests.
  122. * @return {@code this}.
  123. */
  124. public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
  125. allowNonFastForwards = allow;
  126. return this;
  127. }
  128. /** @return identity of the user making the change in the reflog. */
  129. public PersonIdent getRefLogIdent() {
  130. return refLogIdent;
  131. }
  132. /**
  133. * Set the identity of the user appearing in the reflog.
  134. * <p>
  135. * The timestamp portion of the identity is ignored. A new identity with the
  136. * current timestamp will be created automatically when the update occurs
  137. * and the log record is written.
  138. *
  139. * @param pi
  140. * identity of the user. If null the identity will be
  141. * automatically determined based on the repository
  142. * configuration.
  143. * @return {@code this}.
  144. */
  145. public BatchRefUpdate setRefLogIdent(final PersonIdent pi) {
  146. refLogIdent = pi;
  147. return this;
  148. }
  149. /**
  150. * Get the message to include in the reflog.
  151. *
  152. * @return message the caller wants to include in the reflog; null if the
  153. * update should not be logged.
  154. */
  155. public String getRefLogMessage() {
  156. return refLogMessage;
  157. }
  158. /** @return {@code true} if the ref log message should show the result. */
  159. public boolean isRefLogIncludingResult() {
  160. return refLogIncludeResult;
  161. }
  162. /**
  163. * Set the message to include in the reflog.
  164. *
  165. * @param msg
  166. * the message to describe this change. It may be null if
  167. * appendStatus is null in order not to append to the reflog
  168. * @param appendStatus
  169. * true if the status of the ref change (fast-forward or
  170. * forced-update) should be appended to the user supplied
  171. * message.
  172. * @return {@code this}.
  173. */
  174. public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
  175. if (msg == null && !appendStatus)
  176. disableRefLog();
  177. else if (msg == null && appendStatus) {
  178. refLogMessage = ""; //$NON-NLS-1$
  179. refLogIncludeResult = true;
  180. } else {
  181. refLogMessage = msg;
  182. refLogIncludeResult = appendStatus;
  183. }
  184. return this;
  185. }
  186. /**
  187. * Don't record this update in the ref's associated reflog.
  188. *
  189. * @return {@code this}.
  190. */
  191. public BatchRefUpdate disableRefLog() {
  192. refLogMessage = null;
  193. refLogIncludeResult = false;
  194. return this;
  195. }
  196. /** @return true if log has been disabled by {@link #disableRefLog()}. */
  197. public boolean isRefLogDisabled() {
  198. return refLogMessage == null;
  199. }
  200. /**
  201. * Request that all updates in this batch be performed atomically.
  202. * <p>
  203. * When atomic updates are used, either all commands apply successfully, or
  204. * none do. Commands that might have otherwise succeeded are rejected with
  205. * {@code REJECTED_OTHER_REASON}.
  206. * <p>
  207. * This method only works if the underlying ref database supports atomic
  208. * transactions, i.e. {@link RefDatabase#performsAtomicTransactions()} returns
  209. * true. Calling this method with true if the underlying ref database does not
  210. * support atomic transactions will cause all commands to fail with {@code
  211. * REJECTED_OTHER_REASON}.
  212. *
  213. * @param atomic whether updates should be atomic.
  214. * @return {@code this}
  215. * @since 4.4
  216. */
  217. public BatchRefUpdate setAtomic(boolean atomic) {
  218. this.atomic = atomic;
  219. return this;
  220. }
  221. /**
  222. * @return atomic whether updates should be atomic.
  223. * @since 4.4
  224. */
  225. public boolean isAtomic() {
  226. return atomic;
  227. }
  228. /**
  229. * Set a push certificate associated with this update.
  230. * <p>
  231. * This usually includes commands to update the refs in this batch, but is not
  232. * required to.
  233. *
  234. * @param cert
  235. * push certificate, may be null.
  236. * @since 4.1
  237. */
  238. public void setPushCertificate(PushCertificate cert) {
  239. pushCert = cert;
  240. }
  241. /**
  242. * Set the push certificate associated with this update.
  243. * <p>
  244. * This usually includes commands to update the refs in this batch, but is not
  245. * required to.
  246. *
  247. * @return push certificate, may be null.
  248. * @since 4.1
  249. */
  250. protected PushCertificate getPushCertificate() {
  251. return pushCert;
  252. }
  253. /** @return commands this update will process. */
  254. public List<ReceiveCommand> getCommands() {
  255. return Collections.unmodifiableList(commands);
  256. }
  257. /**
  258. * Add a single command to this batch update.
  259. *
  260. * @param cmd
  261. * the command to add, must not be null.
  262. * @return {@code this}.
  263. */
  264. public BatchRefUpdate addCommand(ReceiveCommand cmd) {
  265. commands.add(cmd);
  266. return this;
  267. }
  268. /**
  269. * Add commands to this batch update.
  270. *
  271. * @param cmd
  272. * the commands to add, must not be null.
  273. * @return {@code this}.
  274. */
  275. public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
  276. return addCommand(Arrays.asList(cmd));
  277. }
  278. /**
  279. * Add commands to this batch update.
  280. *
  281. * @param cmd
  282. * the commands to add, must not be null.
  283. * @return {@code this}.
  284. */
  285. public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
  286. commands.addAll(cmd);
  287. return this;
  288. }
  289. /**
  290. * Gets the list of option strings associated with this update.
  291. *
  292. * @return pushOptions
  293. * @since 4.5
  294. */
  295. public List<String> getPushOptions() {
  296. return pushOptions;
  297. }
  298. /**
  299. * @return list of timestamps the batch must wait for.
  300. * @since 4.6
  301. */
  302. public List<ProposedTimestamp> getProposedTimestamps() {
  303. if (timestamps != null) {
  304. return Collections.unmodifiableList(timestamps);
  305. }
  306. return Collections.emptyList();
  307. }
  308. /**
  309. * Request the batch to wait for the affected timestamps to resolve.
  310. *
  311. * @param ts
  312. * @return {@code this}.
  313. * @since 4.6
  314. */
  315. public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) {
  316. if (timestamps == null) {
  317. timestamps = new ArrayList<>(4);
  318. }
  319. timestamps.add(ts);
  320. return this;
  321. }
  322. /**
  323. * Execute this batch update.
  324. * <p>
  325. * The default implementation of this method performs a sequential reference
  326. * update over each reference.
  327. * <p>
  328. * Implementations must respect the atomicity requirements of the underlying
  329. * database as described in {@link #setAtomic(boolean)} and
  330. * {@link RefDatabase#performsAtomicTransactions()}.
  331. *
  332. * @param walk
  333. * a RevWalk to parse tags in case the storage system wants to
  334. * store them pre-peeled, a common performance optimization.
  335. * @param monitor
  336. * progress monitor to receive update status on.
  337. * @param options
  338. * a list of option strings; set null to execute without
  339. * @throws IOException
  340. * the database is unable to accept the update. Individual
  341. * command status must be tested to determine if there is a
  342. * partial failure, or a total failure.
  343. * @since 4.5
  344. */
  345. public void execute(RevWalk walk, ProgressMonitor monitor,
  346. List<String> options) throws IOException {
  347. if (atomic && !refdb.performsAtomicTransactions()) {
  348. for (ReceiveCommand c : commands) {
  349. if (c.getResult() == NOT_ATTEMPTED) {
  350. c.setResult(REJECTED_OTHER_REASON,
  351. JGitText.get().atomicRefUpdatesNotSupported);
  352. }
  353. }
  354. return;
  355. }
  356. if (!blockUntilTimestamps(MAX_WAIT)) {
  357. return;
  358. }
  359. if (options != null) {
  360. pushOptions = options;
  361. }
  362. monitor.beginTask(JGitText.get().updatingReferences, commands.size());
  363. List<ReceiveCommand> commands2 = new ArrayList<ReceiveCommand>(
  364. commands.size());
  365. // First delete refs. This may free the name space for some of the
  366. // updates.
  367. for (ReceiveCommand cmd : commands) {
  368. try {
  369. if (cmd.getResult() == NOT_ATTEMPTED) {
  370. cmd.updateType(walk);
  371. switch (cmd.getType()) {
  372. case CREATE:
  373. commands2.add(cmd);
  374. break;
  375. case UPDATE:
  376. case UPDATE_NONFASTFORWARD:
  377. commands2.add(cmd);
  378. break;
  379. case DELETE:
  380. RefUpdate rud = newUpdate(cmd);
  381. monitor.update(1);
  382. cmd.setResult(rud.delete(walk));
  383. }
  384. }
  385. } catch (IOException err) {
  386. cmd.setResult(
  387. REJECTED_OTHER_REASON,
  388. MessageFormat.format(JGitText.get().lockError,
  389. err.getMessage()));
  390. }
  391. }
  392. if (!commands2.isEmpty()) {
  393. // What part of the name space is already taken
  394. Collection<String> takenNames = new HashSet<String>(refdb.getRefs(
  395. RefDatabase.ALL).keySet());
  396. Collection<String> takenPrefixes = getTakenPrefixes(takenNames);
  397. // Now to the update that may require more room in the name space
  398. for (ReceiveCommand cmd : commands2) {
  399. try {
  400. if (cmd.getResult() == NOT_ATTEMPTED) {
  401. cmd.updateType(walk);
  402. RefUpdate ru = newUpdate(cmd);
  403. SWITCH: switch (cmd.getType()) {
  404. case DELETE:
  405. // Performed in the first phase
  406. break;
  407. case UPDATE:
  408. case UPDATE_NONFASTFORWARD:
  409. RefUpdate ruu = newUpdate(cmd);
  410. cmd.setResult(ruu.update(walk));
  411. break;
  412. case CREATE:
  413. for (String prefix : getPrefixes(cmd.getRefName())) {
  414. if (takenNames.contains(prefix)) {
  415. cmd.setResult(Result.LOCK_FAILURE);
  416. break SWITCH;
  417. }
  418. }
  419. if (takenPrefixes.contains(cmd.getRefName())) {
  420. cmd.setResult(Result.LOCK_FAILURE);
  421. break SWITCH;
  422. }
  423. ru.setCheckConflicting(false);
  424. addRefToPrefixes(takenPrefixes, cmd.getRefName());
  425. takenNames.add(cmd.getRefName());
  426. cmd.setResult(ru.update(walk));
  427. }
  428. }
  429. } catch (IOException err) {
  430. cmd.setResult(REJECTED_OTHER_REASON, MessageFormat.format(
  431. JGitText.get().lockError, err.getMessage()));
  432. } finally {
  433. monitor.update(1);
  434. }
  435. }
  436. }
  437. monitor.endTask();
  438. }
  439. /**
  440. * Wait for timestamps to be in the past, aborting commands on timeout.
  441. *
  442. * @param maxWait
  443. * maximum amount of time to wait for timestamps to resolve.
  444. * @return true if timestamps were successfully waited for; false if
  445. * commands were aborted.
  446. * @since 4.6
  447. */
  448. protected boolean blockUntilTimestamps(Duration maxWait) {
  449. if (timestamps == null) {
  450. return true;
  451. }
  452. try {
  453. ProposedTimestamp.blockUntil(timestamps, maxWait);
  454. return true;
  455. } catch (TimeoutException | InterruptedException e) {
  456. String msg = JGitText.get().timeIsUncertain;
  457. for (ReceiveCommand c : commands) {
  458. if (c.getResult() == NOT_ATTEMPTED) {
  459. c.setResult(REJECTED_OTHER_REASON, msg);
  460. }
  461. }
  462. return false;
  463. }
  464. }
  465. /**
  466. * Execute this batch update without option strings.
  467. *
  468. * @param walk
  469. * a RevWalk to parse tags in case the storage system wants to
  470. * store them pre-peeled, a common performance optimization.
  471. * @param monitor
  472. * progress monitor to receive update status on.
  473. * @throws IOException
  474. * the database is unable to accept the update. Individual
  475. * command status must be tested to determine if there is a
  476. * partial failure, or a total failure.
  477. */
  478. public void execute(RevWalk walk, ProgressMonitor monitor)
  479. throws IOException {
  480. execute(walk, monitor, null);
  481. }
  482. private static Collection<String> getTakenPrefixes(
  483. final Collection<String> names) {
  484. Collection<String> ref = new HashSet<String>();
  485. for (String name : names)
  486. ref.addAll(getPrefixes(name));
  487. return ref;
  488. }
  489. private static void addRefToPrefixes(Collection<String> prefixes,
  490. String name) {
  491. for (String prefix : getPrefixes(name)) {
  492. prefixes.add(prefix);
  493. }
  494. }
  495. static Collection<String> getPrefixes(String s) {
  496. Collection<String> ret = new HashSet<String>();
  497. int p1 = s.indexOf('/');
  498. while (p1 > 0) {
  499. ret.add(s.substring(0, p1));
  500. p1 = s.indexOf('/', p1 + 1);
  501. }
  502. return ret;
  503. }
  504. /**
  505. * Create a new RefUpdate copying the batch settings.
  506. *
  507. * @param cmd
  508. * specific command the update should be created to copy.
  509. * @return a single reference update command.
  510. * @throws IOException
  511. * the reference database cannot make a new update object for
  512. * the given reference.
  513. */
  514. protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
  515. RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false);
  516. if (isRefLogDisabled())
  517. ru.disableRefLog();
  518. else {
  519. ru.setRefLogIdent(refLogIdent);
  520. ru.setRefLogMessage(refLogMessage, refLogIncludeResult);
  521. }
  522. ru.setPushCertificate(pushCert);
  523. switch (cmd.getType()) {
  524. case DELETE:
  525. if (!ObjectId.zeroId().equals(cmd.getOldId()))
  526. ru.setExpectedOldObjectId(cmd.getOldId());
  527. ru.setForceUpdate(true);
  528. return ru;
  529. case CREATE:
  530. case UPDATE:
  531. case UPDATE_NONFASTFORWARD:
  532. default:
  533. ru.setForceUpdate(isAllowNonFastForwards());
  534. ru.setExpectedOldObjectId(cmd.getOldId());
  535. ru.setNewObjectId(cmd.getNewId());
  536. return ru;
  537. }
  538. }
  539. @Override
  540. public String toString() {
  541. StringBuilder r = new StringBuilder();
  542. r.append(getClass().getSimpleName()).append('[');
  543. if (commands.isEmpty())
  544. return r.append(']').toString();
  545. r.append('\n');
  546. for (ReceiveCommand cmd : commands) {
  547. r.append(" "); //$NON-NLS-1$
  548. r.append(cmd);
  549. r.append(" (").append(cmd.getResult()); //$NON-NLS-1$
  550. if (cmd.getMessage() != null) {
  551. r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$
  552. }
  553. r.append(")\n"); //$NON-NLS-1$
  554. }
  555. return r.append(']').toString();
  556. }
  557. }