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.

BasePackPushConnection.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /*
  2. * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  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.transport;
  12. import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
  13. import java.io.IOException;
  14. import java.io.InputStream;
  15. import java.io.OutputStream;
  16. import java.text.MessageFormat;
  17. import java.util.Collection;
  18. import java.util.HashSet;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Set;
  22. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  23. import org.eclipse.jgit.errors.NotSupportedException;
  24. import org.eclipse.jgit.errors.PackProtocolException;
  25. import org.eclipse.jgit.errors.TooLargeObjectInPackException;
  26. import org.eclipse.jgit.errors.TooLargePackException;
  27. import org.eclipse.jgit.errors.TransportException;
  28. import org.eclipse.jgit.internal.JGitText;
  29. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  30. import org.eclipse.jgit.lib.ObjectId;
  31. import org.eclipse.jgit.lib.ProgressMonitor;
  32. import org.eclipse.jgit.lib.Ref;
  33. import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
  34. /**
  35. * Push implementation using the native Git pack transfer service.
  36. * <p>
  37. * This is the canonical implementation for transferring objects to the remote
  38. * repository from the local repository by talking to the 'git-receive-pack'
  39. * service. Objects are packed on the local side into a pack file and then sent
  40. * to the remote repository.
  41. * <p>
  42. * This connection requires only a bi-directional pipe or socket, and thus is
  43. * easily wrapped up into a local process pipe, anonymous TCP socket, or a
  44. * command executed through an SSH tunnel.
  45. * <p>
  46. * This implementation honors
  47. * {@link org.eclipse.jgit.transport.Transport#isPushThin()} option.
  48. * <p>
  49. * Concrete implementations should just call
  50. * {@link #init(java.io.InputStream, java.io.OutputStream)} and
  51. * {@link #readAdvertisedRefs()} methods in constructor or before any use. They
  52. * should also handle resources releasing in {@link #close()} method if needed.
  53. */
  54. public abstract class BasePackPushConnection extends BasePackConnection implements
  55. PushConnection {
  56. /**
  57. * The client expects a status report after the server processes the pack.
  58. * @since 2.0
  59. */
  60. public static final String CAPABILITY_REPORT_STATUS = GitProtocolConstants.CAPABILITY_REPORT_STATUS;
  61. /**
  62. * The server supports deleting refs.
  63. * @since 2.0
  64. */
  65. public static final String CAPABILITY_DELETE_REFS = GitProtocolConstants.CAPABILITY_DELETE_REFS;
  66. /**
  67. * The server supports packs with OFS deltas.
  68. * @since 2.0
  69. */
  70. public static final String CAPABILITY_OFS_DELTA = GitProtocolConstants.CAPABILITY_OFS_DELTA;
  71. /**
  72. * The client supports using the 64K side-band for progress messages.
  73. * @since 2.0
  74. */
  75. public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
  76. /**
  77. * The server supports the receiving of push options.
  78. * @since 4.5
  79. */
  80. public static final String CAPABILITY_PUSH_OPTIONS = GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
  81. private final boolean thinPack;
  82. private final boolean atomic;
  83. /** A list of option strings associated with this push. */
  84. private List<String> pushOptions;
  85. private boolean capableAtomic;
  86. private boolean capableDeleteRefs;
  87. private boolean capableReport;
  88. private boolean capableSideBand;
  89. private boolean capableOfsDelta;
  90. private boolean capablePushOptions;
  91. private boolean sentCommand;
  92. private boolean writePack;
  93. /** Time in milliseconds spent transferring the pack data. */
  94. private long packTransferTime;
  95. /**
  96. * Create a new connection to push using the native git transport.
  97. *
  98. * @param packTransport
  99. * the transport.
  100. */
  101. public BasePackPushConnection(PackTransport packTransport) {
  102. super(packTransport);
  103. thinPack = transport.isPushThin();
  104. atomic = transport.isPushAtomic();
  105. pushOptions = transport.getPushOptions();
  106. }
  107. /** {@inheritDoc} */
  108. @Override
  109. public void push(final ProgressMonitor monitor,
  110. final Map<String, RemoteRefUpdate> refUpdates)
  111. throws TransportException {
  112. push(monitor, refUpdates, null);
  113. }
  114. /** {@inheritDoc} */
  115. @Override
  116. public void push(final ProgressMonitor monitor,
  117. final Map<String, RemoteRefUpdate> refUpdates, OutputStream outputStream)
  118. throws TransportException {
  119. markStartedOperation();
  120. doPush(monitor, refUpdates, outputStream);
  121. }
  122. /** {@inheritDoc} */
  123. @Override
  124. protected TransportException noRepository() {
  125. // Sadly we cannot tell the "invalid URI" case from "push not allowed".
  126. // Opening a fetch connection can help us tell the difference, as any
  127. // useful repository is going to support fetch if it also would allow
  128. // push. So if fetch throws NoRemoteRepositoryException we know the
  129. // URI is wrong. Otherwise we can correctly state push isn't allowed
  130. // as the fetch connection opened successfully.
  131. //
  132. try {
  133. transport.openFetch().close();
  134. } catch (NotSupportedException e) {
  135. // Fall through.
  136. } catch (NoRemoteRepositoryException e) {
  137. // Fetch concluded the repository doesn't exist.
  138. //
  139. return e;
  140. } catch (TransportException e) {
  141. // Fall through.
  142. }
  143. return new TransportException(uri, JGitText.get().pushNotPermitted);
  144. }
  145. /**
  146. * Push one or more objects and update the remote repository.
  147. *
  148. * @param monitor
  149. * progress monitor to receive status updates.
  150. * @param refUpdates
  151. * update commands to be applied to the remote repository.
  152. * @param outputStream
  153. * output stream to write sideband messages to
  154. * @throws org.eclipse.jgit.errors.TransportException
  155. * if any exception occurs.
  156. * @since 3.0
  157. */
  158. protected void doPush(final ProgressMonitor monitor,
  159. final Map<String, RemoteRefUpdate> refUpdates,
  160. OutputStream outputStream) throws TransportException {
  161. try {
  162. writeCommands(refUpdates.values(), monitor, outputStream);
  163. if (pushOptions != null && capablePushOptions)
  164. transmitOptions();
  165. if (writePack)
  166. writePack(refUpdates, monitor);
  167. if (sentCommand) {
  168. if (capableReport)
  169. readStatusReport(refUpdates);
  170. if (capableSideBand) {
  171. // Ensure the data channel is at EOF, so we know we have
  172. // read all side-band data from all channels and have a
  173. // complete copy of the messages (if any) buffered from
  174. // the other data channels.
  175. //
  176. int b = in.read();
  177. if (0 <= b)
  178. throw new TransportException(uri, MessageFormat.format(
  179. JGitText.get().expectedEOFReceived,
  180. Character.valueOf((char) b)));
  181. }
  182. }
  183. } catch (TransportException e) {
  184. throw e;
  185. } catch (Exception e) {
  186. throw new TransportException(uri, e.getMessage(), e);
  187. } finally {
  188. close();
  189. }
  190. }
  191. private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
  192. final ProgressMonitor monitor, OutputStream outputStream) throws IOException {
  193. final String capabilities = enableCapabilities(monitor, outputStream);
  194. if (atomic && !capableAtomic) {
  195. throw new TransportException(uri,
  196. JGitText.get().atomicPushNotSupported);
  197. }
  198. if (pushOptions != null && !capablePushOptions) {
  199. throw new TransportException(uri,
  200. MessageFormat.format(JGitText.get().pushOptionsNotSupported,
  201. pushOptions.toString()));
  202. }
  203. for (RemoteRefUpdate rru : refUpdates) {
  204. if (!capableDeleteRefs && rru.isDelete()) {
  205. rru.setStatus(Status.REJECTED_NODELETE);
  206. continue;
  207. }
  208. final StringBuilder sb = new StringBuilder();
  209. ObjectId oldId = rru.getExpectedOldObjectId();
  210. if (oldId == null) {
  211. final Ref advertised = getRef(rru.getRemoteName());
  212. oldId = advertised != null ? advertised.getObjectId() : null;
  213. if (oldId == null) {
  214. oldId = ObjectId.zeroId();
  215. }
  216. }
  217. sb.append(oldId.name());
  218. sb.append(' ');
  219. sb.append(rru.getNewObjectId().name());
  220. sb.append(' ');
  221. sb.append(rru.getRemoteName());
  222. if (!sentCommand) {
  223. sentCommand = true;
  224. sb.append(capabilities);
  225. }
  226. pckOut.writeString(sb.toString());
  227. rru.setStatus(Status.AWAITING_REPORT);
  228. if (!rru.isDelete())
  229. writePack = true;
  230. }
  231. if (monitor.isCancelled())
  232. throw new TransportException(uri, JGitText.get().pushCancelled);
  233. pckOut.end();
  234. outNeedsEnd = false;
  235. }
  236. private void transmitOptions() throws IOException {
  237. for (String pushOption : pushOptions) {
  238. pckOut.writeString(pushOption);
  239. }
  240. pckOut.end();
  241. }
  242. private String enableCapabilities(final ProgressMonitor monitor,
  243. OutputStream outputStream) {
  244. final StringBuilder line = new StringBuilder();
  245. if (atomic)
  246. capableAtomic = wantCapability(line, CAPABILITY_ATOMIC);
  247. capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS);
  248. capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
  249. capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA);
  250. if (pushOptions != null) {
  251. capablePushOptions = wantCapability(line, CAPABILITY_PUSH_OPTIONS);
  252. }
  253. capableSideBand = wantCapability(line, CAPABILITY_SIDE_BAND_64K);
  254. if (capableSideBand) {
  255. in = new SideBandInputStream(in, monitor, getMessageWriter(),
  256. outputStream);
  257. pckIn = new PacketLineIn(in);
  258. }
  259. addUserAgentCapability(line);
  260. if (line.length() > 0)
  261. line.setCharAt(0, '\0');
  262. return line.toString();
  263. }
  264. private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
  265. final ProgressMonitor monitor) throws IOException {
  266. Set<ObjectId> remoteObjects = new HashSet<>();
  267. Set<ObjectId> newObjects = new HashSet<>();
  268. try (PackWriter writer = new PackWriter(transport.getPackConfig(),
  269. local.newObjectReader())) {
  270. for (Ref r : getRefs()) {
  271. // only add objects that we actually have
  272. ObjectId oid = r.getObjectId();
  273. if (local.getObjectDatabase().has(oid))
  274. remoteObjects.add(oid);
  275. }
  276. remoteObjects.addAll(additionalHaves);
  277. for (RemoteRefUpdate r : refUpdates.values()) {
  278. if (!ObjectId.zeroId().equals(r.getNewObjectId()))
  279. newObjects.add(r.getNewObjectId());
  280. }
  281. writer.setIndexDisabled(true);
  282. writer.setUseCachedPacks(true);
  283. writer.setUseBitmaps(true);
  284. writer.setThin(thinPack);
  285. writer.setReuseValidatingObjects(false);
  286. writer.setDeltaBaseAsOffset(capableOfsDelta);
  287. writer.preparePack(monitor, newObjects, remoteObjects);
  288. OutputStream packOut = out;
  289. if (capableSideBand) {
  290. packOut = new CheckingSideBandOutputStream(in, out);
  291. }
  292. writer.writePack(monitor, monitor, packOut);
  293. packTransferTime = writer.getStatistics().getTimeWriting();
  294. }
  295. }
  296. private void readStatusReport(Map<String, RemoteRefUpdate> refUpdates)
  297. throws IOException {
  298. final String unpackLine = readStringLongTimeout();
  299. if (!unpackLine.startsWith("unpack ")) //$NON-NLS-1$
  300. throw new PackProtocolException(uri, MessageFormat
  301. .format(JGitText.get().unexpectedReportLine, unpackLine));
  302. final String unpackStatus = unpackLine.substring("unpack ".length()); //$NON-NLS-1$
  303. if (unpackStatus.startsWith("error Pack exceeds the limit of")) {//$NON-NLS-1$
  304. throw new TooLargePackException(uri,
  305. unpackStatus.substring("error ".length())); //$NON-NLS-1$
  306. } else if (unpackStatus.startsWith("error Object too large")) {//$NON-NLS-1$
  307. throw new TooLargeObjectInPackException(uri,
  308. unpackStatus.substring("error ".length())); //$NON-NLS-1$
  309. } else if (!unpackStatus.equals("ok")) { //$NON-NLS-1$
  310. throw new TransportException(uri, MessageFormat.format(
  311. JGitText.get().errorOccurredDuringUnpackingOnTheRemoteEnd, unpackStatus));
  312. }
  313. for (String refLine : pckIn.readStrings()) {
  314. boolean ok = false;
  315. int refNameEnd = -1;
  316. if (refLine.startsWith("ok ")) { //$NON-NLS-1$
  317. ok = true;
  318. refNameEnd = refLine.length();
  319. } else if (refLine.startsWith("ng ")) { //$NON-NLS-1$
  320. ok = false;
  321. refNameEnd = refLine.indexOf(' ', 3);
  322. }
  323. if (refNameEnd == -1)
  324. throw new PackProtocolException(MessageFormat.format(JGitText.get().unexpectedReportLine2
  325. , uri, refLine));
  326. final String refName = refLine.substring(3, refNameEnd);
  327. final String message = (ok ? null : refLine
  328. .substring(refNameEnd + 1));
  329. final RemoteRefUpdate rru = refUpdates.get(refName);
  330. if (rru == null)
  331. throw new PackProtocolException(MessageFormat.format(JGitText.get().unexpectedRefReport, uri, refName));
  332. if (ok) {
  333. rru.setStatus(Status.OK);
  334. } else {
  335. rru.setStatus(Status.REJECTED_OTHER_REASON);
  336. rru.setMessage(message);
  337. }
  338. }
  339. for (RemoteRefUpdate rru : refUpdates.values()) {
  340. if (rru.getStatus() == Status.AWAITING_REPORT)
  341. throw new PackProtocolException(MessageFormat.format(
  342. JGitText.get().expectedReportForRefNotReceived , uri, rru.getRemoteName()));
  343. }
  344. }
  345. private String readStringLongTimeout() throws IOException {
  346. if (timeoutIn == null)
  347. return pckIn.readString();
  348. // The remote side may need a lot of time to choke down the pack
  349. // we just sent them. There may be many deltas that need to be
  350. // resolved by the remote. Its hard to say how long the other
  351. // end is going to be silent. Taking 10x the configured timeout
  352. // or the time spent transferring the pack, whichever is larger,
  353. // gives the other side some reasonable window to process the data,
  354. // but this is just a wild guess.
  355. //
  356. final int oldTimeout = timeoutIn.getTimeout();
  357. final int sendTime = (int) Math.min(packTransferTime, 28800000L);
  358. try {
  359. int timeout = 10 * Math.max(sendTime, oldTimeout);
  360. timeoutIn.setTimeout((timeout < 0) ? Integer.MAX_VALUE : timeout);
  361. return pckIn.readString();
  362. } finally {
  363. timeoutIn.setTimeout(oldTimeout);
  364. }
  365. }
  366. /**
  367. * Gets the list of option strings associated with this push.
  368. *
  369. * @return pushOptions
  370. * @since 4.5
  371. */
  372. public List<String> getPushOptions() {
  373. return pushOptions;
  374. }
  375. private static class CheckingSideBandOutputStream extends OutputStream {
  376. private final InputStream in;
  377. private final OutputStream out;
  378. CheckingSideBandOutputStream(InputStream in, OutputStream out) {
  379. this.in = in;
  380. this.out = out;
  381. }
  382. @Override
  383. public void write(int b) throws IOException {
  384. write(new byte[] { (byte) b });
  385. }
  386. @Override
  387. public void write(byte[] buf, int ptr, int cnt) throws IOException {
  388. try {
  389. out.write(buf, ptr, cnt);
  390. } catch (IOException e) {
  391. throw checkError(e);
  392. }
  393. }
  394. @Override
  395. public void flush() throws IOException {
  396. try {
  397. out.flush();
  398. } catch (IOException e) {
  399. throw checkError(e);
  400. }
  401. }
  402. private IOException checkError(IOException e1) {
  403. try {
  404. in.read();
  405. } catch (TransportException e2) {
  406. return e2;
  407. } catch (IOException e2) {
  408. return e1;
  409. }
  410. return e1;
  411. }
  412. }
  413. }