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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. /*
  2. * Copyright (C) 2008-2012, Google Inc.
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  4. *
  5. * This program and the accompanying materials are made available under the
  6. * terms of the Eclipse Distribution License v. 1.0 which is available at
  7. * https://www.eclipse.org/org/documents/edl-v10.php.
  8. *
  9. * SPDX-License-Identifier: BSD-3-Clause
  10. */
  11. package org.eclipse.jgit.lib;
  12. import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
  13. import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
  14. import java.io.IOException;
  15. import java.text.MessageFormat;
  16. import java.time.Duration;
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashSet;
  22. import java.util.List;
  23. import java.util.concurrent.TimeoutException;
  24. import org.eclipse.jgit.annotations.Nullable;
  25. import org.eclipse.jgit.errors.MissingObjectException;
  26. import org.eclipse.jgit.internal.JGitText;
  27. import org.eclipse.jgit.revwalk.RevWalk;
  28. import org.eclipse.jgit.transport.PushCertificate;
  29. import org.eclipse.jgit.transport.ReceiveCommand;
  30. import org.eclipse.jgit.util.time.ProposedTimestamp;
  31. /**
  32. * Batch of reference updates to be applied to a repository.
  33. * <p>
  34. * The batch update is primarily useful in the transport code, where a client or
  35. * server is making changes to more than one reference at a time.
  36. */
  37. public class BatchRefUpdate {
  38. /**
  39. * Maximum delay the calling thread will tolerate while waiting for a
  40. * {@code MonotonicClock} to resolve associated {@link ProposedTimestamp}s.
  41. * <p>
  42. * A default of 5 seconds was chosen by guessing. A common assumption is
  43. * clock skew between machines on the same LAN using an NTP server also on
  44. * the same LAN should be under 5 seconds. 5 seconds is also not that long
  45. * for a large `git push` operation to complete.
  46. *
  47. * @since 4.9
  48. */
  49. protected static final Duration MAX_WAIT = Duration.ofSeconds(5);
  50. private final RefDatabase refdb;
  51. /** Commands to apply during this batch. */
  52. private final List<ReceiveCommand> commands;
  53. /** Does the caller permit a forced update on a reference? */
  54. private boolean allowNonFastForwards;
  55. /** Identity to record action as within the reflog. */
  56. private PersonIdent refLogIdent;
  57. /** Message the caller wants included in the reflog. */
  58. private String refLogMessage;
  59. /** Should the result value be appended to {@link #refLogMessage}. */
  60. private boolean refLogIncludeResult;
  61. /**
  62. * Should reflogs be written even if the configured default for this ref is
  63. * not to write it.
  64. */
  65. private boolean forceRefLog;
  66. /** Push certificate associated with this update. */
  67. private PushCertificate pushCert;
  68. /** Whether updates should be atomic. */
  69. private boolean atomic;
  70. /** Push options associated with this update. */
  71. private List<String> pushOptions;
  72. /** Associated timestamps that should be blocked on before update. */
  73. private List<ProposedTimestamp> timestamps;
  74. /**
  75. * Initialize a new batch update.
  76. *
  77. * @param refdb
  78. * the reference database of the repository to be updated.
  79. */
  80. protected BatchRefUpdate(RefDatabase refdb) {
  81. this.refdb = refdb;
  82. this.commands = new ArrayList<>();
  83. this.atomic = refdb.performsAtomicTransactions();
  84. }
  85. /**
  86. * Whether the batch update will permit a non-fast-forward update to an
  87. * existing reference.
  88. *
  89. * @return true if the batch update will permit a non-fast-forward update to
  90. * an existing reference.
  91. */
  92. public boolean isAllowNonFastForwards() {
  93. return allowNonFastForwards;
  94. }
  95. /**
  96. * Set if this update wants to permit a forced update.
  97. *
  98. * @param allow
  99. * true if this update batch should ignore merge tests.
  100. * @return {@code this}.
  101. */
  102. public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
  103. allowNonFastForwards = allow;
  104. return this;
  105. }
  106. /**
  107. * Get identity of the user making the change in the reflog.
  108. *
  109. * @return identity of the user making the change in the reflog.
  110. */
  111. public PersonIdent getRefLogIdent() {
  112. return refLogIdent;
  113. }
  114. /**
  115. * Set the identity of the user appearing in the reflog.
  116. * <p>
  117. * The timestamp portion of the identity is ignored. A new identity with the
  118. * current timestamp will be created automatically when the update occurs
  119. * and the log record is written.
  120. *
  121. * @param pi
  122. * identity of the user. If null the identity will be
  123. * automatically determined based on the repository
  124. * configuration.
  125. * @return {@code this}.
  126. */
  127. public BatchRefUpdate setRefLogIdent(PersonIdent pi) {
  128. refLogIdent = pi;
  129. return this;
  130. }
  131. /**
  132. * Get the message to include in the reflog.
  133. *
  134. * @return message the caller wants to include in the reflog; null if the
  135. * update should not be logged.
  136. */
  137. @Nullable
  138. public String getRefLogMessage() {
  139. return refLogMessage;
  140. }
  141. /**
  142. * Check whether the reflog message should include the result of the update,
  143. * such as fast-forward or force-update.
  144. * <p>
  145. * Describes the default for commands in this batch that do not override it
  146. * with
  147. * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
  148. *
  149. * @return true if the message should include the result.
  150. */
  151. public boolean isRefLogIncludingResult() {
  152. return refLogIncludeResult;
  153. }
  154. /**
  155. * Set the message to include in the reflog.
  156. * <p>
  157. * Repository implementations may limit which reflogs are written by
  158. * default, based on the project configuration. If a repo is not configured
  159. * to write logs for this ref by default, setting the message alone may have
  160. * no effect. To indicate that the repo should write logs for this update in
  161. * spite of configured defaults, use {@link #setForceRefLog(boolean)}.
  162. * <p>
  163. * Describes the default for commands in this batch that do not override it
  164. * with
  165. * {@link org.eclipse.jgit.transport.ReceiveCommand#setRefLogMessage(String, boolean)}.
  166. *
  167. * @param msg
  168. * the message to describe this change. If null and appendStatus
  169. * is false, the reflog will not be updated.
  170. * @param appendStatus
  171. * true if the status of the ref change (fast-forward or
  172. * forced-update) should be appended to the user supplied
  173. * message.
  174. * @return {@code this}.
  175. */
  176. public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
  177. if (msg == null && !appendStatus)
  178. disableRefLog();
  179. else if (msg == null && appendStatus) {
  180. refLogMessage = ""; //$NON-NLS-1$
  181. refLogIncludeResult = true;
  182. } else {
  183. refLogMessage = msg;
  184. refLogIncludeResult = appendStatus;
  185. }
  186. return this;
  187. }
  188. /**
  189. * Don't record this update in the ref's associated reflog.
  190. * <p>
  191. * Equivalent to {@code setRefLogMessage(null, false)}.
  192. *
  193. * @return {@code this}.
  194. */
  195. public BatchRefUpdate disableRefLog() {
  196. refLogMessage = null;
  197. refLogIncludeResult = false;
  198. return this;
  199. }
  200. /**
  201. * Force writing a reflog for the updated ref.
  202. *
  203. * @param force whether to force.
  204. * @return {@code this}
  205. * @since 4.9
  206. */
  207. public BatchRefUpdate setForceRefLog(boolean force) {
  208. forceRefLog = force;
  209. return this;
  210. }
  211. /**
  212. * Check whether log has been disabled by {@link #disableRefLog()}.
  213. *
  214. * @return true if disabled.
  215. */
  216. public boolean isRefLogDisabled() {
  217. return refLogMessage == null;
  218. }
  219. /**
  220. * Check whether the reflog should be written regardless of repo defaults.
  221. *
  222. * @return whether force writing is enabled.
  223. * @since 4.9
  224. */
  225. protected boolean isForceRefLog() {
  226. return forceRefLog;
  227. }
  228. /**
  229. * Request that all updates in this batch be performed atomically.
  230. * <p>
  231. * When atomic updates are used, either all commands apply successfully, or
  232. * none do. Commands that might have otherwise succeeded are rejected with
  233. * {@code REJECTED_OTHER_REASON}.
  234. * <p>
  235. * This method only works if the underlying ref database supports atomic
  236. * transactions, i.e.
  237. * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}
  238. * returns true. Calling this method with true if the underlying ref
  239. * database does not support atomic transactions will cause all commands to
  240. * fail with {@code
  241. * REJECTED_OTHER_REASON}.
  242. *
  243. * @param atomic
  244. * whether updates should be atomic.
  245. * @return {@code this}
  246. * @since 4.4
  247. */
  248. public BatchRefUpdate setAtomic(boolean atomic) {
  249. this.atomic = atomic;
  250. return this;
  251. }
  252. /**
  253. * Whether updates should be atomic.
  254. *
  255. * @return atomic whether updates should be atomic.
  256. * @since 4.4
  257. */
  258. public boolean isAtomic() {
  259. return atomic;
  260. }
  261. /**
  262. * Set a push certificate associated with this update.
  263. * <p>
  264. * This usually includes commands to update the refs in this batch, but is not
  265. * required to.
  266. *
  267. * @param cert
  268. * push certificate, may be null.
  269. * @since 4.1
  270. */
  271. public void setPushCertificate(PushCertificate cert) {
  272. pushCert = cert;
  273. }
  274. /**
  275. * Set the push certificate associated with this update.
  276. * <p>
  277. * This usually includes commands to update the refs in this batch, but is not
  278. * required to.
  279. *
  280. * @return push certificate, may be null.
  281. * @since 4.1
  282. */
  283. protected PushCertificate getPushCertificate() {
  284. return pushCert;
  285. }
  286. /**
  287. * Get commands this update will process.
  288. *
  289. * @return commands this update will process.
  290. */
  291. public List<ReceiveCommand> getCommands() {
  292. return Collections.unmodifiableList(commands);
  293. }
  294. /**
  295. * Add a single command to this batch update.
  296. *
  297. * @param cmd
  298. * the command to add, must not be null.
  299. * @return {@code this}.
  300. */
  301. public BatchRefUpdate addCommand(ReceiveCommand cmd) {
  302. commands.add(cmd);
  303. return this;
  304. }
  305. /**
  306. * Add commands to this batch update.
  307. *
  308. * @param cmd
  309. * the commands to add, must not be null.
  310. * @return {@code this}.
  311. */
  312. public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
  313. return addCommand(Arrays.asList(cmd));
  314. }
  315. /**
  316. * Add commands to this batch update.
  317. *
  318. * @param cmd
  319. * the commands to add, must not be null.
  320. * @return {@code this}.
  321. */
  322. public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
  323. commands.addAll(cmd);
  324. return this;
  325. }
  326. /**
  327. * Gets the list of option strings associated with this update.
  328. *
  329. * @return push options that were passed to {@link #execute}; prior to calling
  330. * {@link #execute}, always returns null.
  331. * @since 4.5
  332. */
  333. @Nullable
  334. public List<String> getPushOptions() {
  335. return pushOptions;
  336. }
  337. /**
  338. * Set push options associated with this update.
  339. * <p>
  340. * Implementations must call this at the top of {@link #execute(RevWalk,
  341. * ProgressMonitor, List)}.
  342. *
  343. * @param options options passed to {@code execute}.
  344. * @since 4.9
  345. */
  346. protected void setPushOptions(List<String> options) {
  347. pushOptions = options;
  348. }
  349. /**
  350. * Get list of timestamps the batch must wait for.
  351. *
  352. * @return list of timestamps the batch must wait for.
  353. * @since 4.6
  354. */
  355. public List<ProposedTimestamp> getProposedTimestamps() {
  356. if (timestamps != null) {
  357. return Collections.unmodifiableList(timestamps);
  358. }
  359. return Collections.emptyList();
  360. }
  361. /**
  362. * Request the batch to wait for the affected timestamps to resolve.
  363. *
  364. * @param ts
  365. * a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object.
  366. * @return {@code this}.
  367. * @since 4.6
  368. */
  369. public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) {
  370. if (timestamps == null) {
  371. timestamps = new ArrayList<>(4);
  372. }
  373. timestamps.add(ts);
  374. return this;
  375. }
  376. /**
  377. * Execute this batch update.
  378. * <p>
  379. * The default implementation of this method performs a sequential reference
  380. * update over each reference.
  381. * <p>
  382. * Implementations must respect the atomicity requirements of the underlying
  383. * database as described in {@link #setAtomic(boolean)} and
  384. * {@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}.
  385. *
  386. * @param walk
  387. * a RevWalk to parse tags in case the storage system wants to
  388. * store them pre-peeled, a common performance optimization.
  389. * @param monitor
  390. * progress monitor to receive update status on.
  391. * @param options
  392. * a list of option strings; set null to execute without
  393. * @throws java.io.IOException
  394. * the database is unable to accept the update. Individual
  395. * command status must be tested to determine if there is a
  396. * partial failure, or a total failure.
  397. * @since 4.5
  398. */
  399. public void execute(RevWalk walk, ProgressMonitor monitor,
  400. List<String> options) throws IOException {
  401. if (atomic && !refdb.performsAtomicTransactions()) {
  402. for (ReceiveCommand c : commands) {
  403. if (c.getResult() == NOT_ATTEMPTED) {
  404. c.setResult(REJECTED_OTHER_REASON,
  405. JGitText.get().atomicRefUpdatesNotSupported);
  406. }
  407. }
  408. return;
  409. }
  410. if (!blockUntilTimestamps(MAX_WAIT)) {
  411. return;
  412. }
  413. if (options != null) {
  414. setPushOptions(options);
  415. }
  416. monitor.beginTask(JGitText.get().updatingReferences, commands.size());
  417. List<ReceiveCommand> commands2 = new ArrayList<>(
  418. commands.size());
  419. // First delete refs. This may free the name space for some of the
  420. // updates.
  421. for (ReceiveCommand cmd : commands) {
  422. try {
  423. if (cmd.getResult() == NOT_ATTEMPTED) {
  424. if (isMissing(walk, cmd.getOldId())
  425. || isMissing(walk, cmd.getNewId())) {
  426. cmd.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
  427. continue;
  428. }
  429. cmd.updateType(walk);
  430. switch (cmd.getType()) {
  431. case CREATE:
  432. commands2.add(cmd);
  433. break;
  434. case UPDATE:
  435. case UPDATE_NONFASTFORWARD:
  436. commands2.add(cmd);
  437. break;
  438. case DELETE:
  439. RefUpdate rud = newUpdate(cmd);
  440. monitor.update(1);
  441. cmd.setResult(rud.delete(walk));
  442. }
  443. }
  444. } catch (IOException err) {
  445. cmd.setResult(
  446. REJECTED_OTHER_REASON,
  447. MessageFormat.format(JGitText.get().lockError,
  448. err.getMessage()));
  449. }
  450. }
  451. if (!commands2.isEmpty()) {
  452. // Perform updates that may require more room in the name space
  453. for (ReceiveCommand cmd : commands2) {
  454. try {
  455. if (cmd.getResult() == NOT_ATTEMPTED) {
  456. cmd.updateType(walk);
  457. RefUpdate ru = newUpdate(cmd);
  458. switch (cmd.getType()) {
  459. case DELETE:
  460. // Performed in the first phase
  461. break;
  462. case UPDATE:
  463. case UPDATE_NONFASTFORWARD:
  464. RefUpdate ruu = newUpdate(cmd);
  465. cmd.setResult(ruu.update(walk));
  466. break;
  467. case CREATE:
  468. cmd.setResult(ru.update(walk));
  469. break;
  470. }
  471. }
  472. } catch (IOException err) {
  473. cmd.setResult(REJECTED_OTHER_REASON, MessageFormat.format(
  474. JGitText.get().lockError, err.getMessage()));
  475. } finally {
  476. monitor.update(1);
  477. }
  478. }
  479. }
  480. monitor.endTask();
  481. }
  482. private static boolean isMissing(RevWalk walk, ObjectId id)
  483. throws IOException {
  484. if (id.equals(ObjectId.zeroId())) {
  485. return false; // Explicit add or delete is not missing.
  486. }
  487. try {
  488. walk.parseAny(id);
  489. return false;
  490. } catch (MissingObjectException e) {
  491. return true;
  492. }
  493. }
  494. /**
  495. * Wait for timestamps to be in the past, aborting commands on timeout.
  496. *
  497. * @param maxWait
  498. * maximum amount of time to wait for timestamps to resolve.
  499. * @return true if timestamps were successfully waited for; false if
  500. * commands were aborted.
  501. * @since 4.6
  502. */
  503. protected boolean blockUntilTimestamps(Duration maxWait) {
  504. if (timestamps == null) {
  505. return true;
  506. }
  507. try {
  508. ProposedTimestamp.blockUntil(timestamps, maxWait);
  509. return true;
  510. } catch (TimeoutException | InterruptedException e) {
  511. String msg = JGitText.get().timeIsUncertain;
  512. for (ReceiveCommand c : commands) {
  513. if (c.getResult() == NOT_ATTEMPTED) {
  514. c.setResult(REJECTED_OTHER_REASON, msg);
  515. }
  516. }
  517. return false;
  518. }
  519. }
  520. /**
  521. * Execute this batch update without option strings.
  522. *
  523. * @param walk
  524. * a RevWalk to parse tags in case the storage system wants to
  525. * store them pre-peeled, a common performance optimization.
  526. * @param monitor
  527. * progress monitor to receive update status on.
  528. * @throws java.io.IOException
  529. * the database is unable to accept the update. Individual
  530. * command status must be tested to determine if there is a
  531. * partial failure, or a total failure.
  532. */
  533. public void execute(RevWalk walk, ProgressMonitor monitor)
  534. throws IOException {
  535. execute(walk, monitor, null);
  536. }
  537. /**
  538. * Get all path prefixes of a ref name.
  539. *
  540. * @param name
  541. * ref name.
  542. * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns
  543. * {@code refs} and {@code refs/heads}.
  544. * @since 4.9
  545. */
  546. protected static Collection<String> getPrefixes(String name) {
  547. Collection<String> ret = new HashSet<>();
  548. addPrefixesTo(name, ret);
  549. return ret;
  550. }
  551. /**
  552. * Add prefixes of a ref name to an existing collection.
  553. *
  554. * @param name
  555. * ref name.
  556. * @param out
  557. * path prefixes of the ref name. For {@code refs/heads/foo},
  558. * returns {@code refs} and {@code refs/heads}.
  559. * @since 4.9
  560. */
  561. protected static void addPrefixesTo(String name, Collection<String> out) {
  562. int p1 = name.indexOf('/');
  563. while (p1 > 0) {
  564. out.add(name.substring(0, p1));
  565. p1 = name.indexOf('/', p1 + 1);
  566. }
  567. }
  568. /**
  569. * Create a new RefUpdate copying the batch settings.
  570. *
  571. * @param cmd
  572. * specific command the update should be created to copy.
  573. * @return a single reference update command.
  574. * @throws java.io.IOException
  575. * the reference database cannot make a new update object for
  576. * the given reference.
  577. */
  578. protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
  579. RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false);
  580. if (isRefLogDisabled(cmd)) {
  581. ru.disableRefLog();
  582. } else {
  583. ru.setRefLogIdent(refLogIdent);
  584. ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd));
  585. ru.setForceRefLog(isForceRefLog(cmd));
  586. }
  587. ru.setPushCertificate(pushCert);
  588. switch (cmd.getType()) {
  589. case DELETE:
  590. if (!ObjectId.zeroId().equals(cmd.getOldId()))
  591. ru.setExpectedOldObjectId(cmd.getOldId());
  592. ru.setForceUpdate(true);
  593. return ru;
  594. case CREATE:
  595. case UPDATE:
  596. case UPDATE_NONFASTFORWARD:
  597. default:
  598. ru.setForceUpdate(isAllowNonFastForwards());
  599. ru.setExpectedOldObjectId(cmd.getOldId());
  600. ru.setNewObjectId(cmd.getNewId());
  601. return ru;
  602. }
  603. }
  604. /**
  605. * Check whether reflog is disabled for a command.
  606. *
  607. * @param cmd
  608. * specific command.
  609. * @return whether the reflog is disabled, taking into account the state from
  610. * this instance as well as overrides in the given command.
  611. * @since 4.9
  612. */
  613. protected boolean isRefLogDisabled(ReceiveCommand cmd) {
  614. return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled();
  615. }
  616. /**
  617. * Get reflog message for a command.
  618. *
  619. * @param cmd
  620. * specific command.
  621. * @return reflog message, taking into account the state from this instance as
  622. * well as overrides in the given command.
  623. * @since 4.9
  624. */
  625. protected String getRefLogMessage(ReceiveCommand cmd) {
  626. return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage();
  627. }
  628. /**
  629. * Check whether the reflog message for a command should include the result.
  630. *
  631. * @param cmd
  632. * specific command.
  633. * @return whether the reflog message should show the result, taking into
  634. * account the state from this instance as well as overrides in the
  635. * given command.
  636. * @since 4.9
  637. */
  638. protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
  639. return cmd.hasCustomRefLog()
  640. ? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
  641. }
  642. /**
  643. * Check whether the reflog for a command should be written regardless of repo
  644. * defaults.
  645. *
  646. * @param cmd
  647. * specific command.
  648. * @return whether force writing is enabled.
  649. * @since 4.9
  650. */
  651. protected boolean isForceRefLog(ReceiveCommand cmd) {
  652. Boolean isForceRefLog = cmd.isForceRefLog();
  653. return isForceRefLog != null ? isForceRefLog.booleanValue()
  654. : isForceRefLog();
  655. }
  656. /** {@inheritDoc} */
  657. @Override
  658. public String toString() {
  659. StringBuilder r = new StringBuilder();
  660. r.append(getClass().getSimpleName()).append('[');
  661. if (commands.isEmpty())
  662. return r.append(']').toString();
  663. r.append('\n');
  664. for (ReceiveCommand cmd : commands) {
  665. r.append(" "); //$NON-NLS-1$
  666. r.append(cmd);
  667. r.append(" (").append(cmd.getResult()); //$NON-NLS-1$
  668. if (cmd.getMessage() != null) {
  669. r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$
  670. }
  671. r.append(")\n"); //$NON-NLS-1$
  672. }
  673. return r.append(']').toString();
  674. }
  675. }