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.

UploadPack.java 35KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  1. /*
  2. * Copyright (C) 2008-2010, 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.transport;
  44. import java.io.EOFException;
  45. import java.io.IOException;
  46. import java.io.InputStream;
  47. import java.io.OutputStream;
  48. import java.text.MessageFormat;
  49. import java.util.ArrayList;
  50. import java.util.Collections;
  51. import java.util.HashSet;
  52. import java.util.List;
  53. import java.util.Map;
  54. import java.util.Set;
  55. import org.eclipse.jgit.JGitText;
  56. import org.eclipse.jgit.errors.CorruptObjectException;
  57. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  58. import org.eclipse.jgit.errors.MissingObjectException;
  59. import org.eclipse.jgit.errors.PackProtocolException;
  60. import org.eclipse.jgit.lib.Constants;
  61. import org.eclipse.jgit.lib.NullProgressMonitor;
  62. import org.eclipse.jgit.lib.ObjectId;
  63. import org.eclipse.jgit.lib.ProgressMonitor;
  64. import org.eclipse.jgit.lib.Ref;
  65. import org.eclipse.jgit.lib.Repository;
  66. import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
  67. import org.eclipse.jgit.revwalk.DepthWalk;
  68. import org.eclipse.jgit.revwalk.ObjectWalk;
  69. import org.eclipse.jgit.revwalk.RevCommit;
  70. import org.eclipse.jgit.revwalk.RevFlag;
  71. import org.eclipse.jgit.revwalk.RevFlagSet;
  72. import org.eclipse.jgit.revwalk.RevObject;
  73. import org.eclipse.jgit.revwalk.RevTag;
  74. import org.eclipse.jgit.revwalk.RevWalk;
  75. import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
  76. import org.eclipse.jgit.storage.pack.PackConfig;
  77. import org.eclipse.jgit.storage.pack.PackWriter;
  78. import org.eclipse.jgit.transport.BasePackFetchConnection.MultiAck;
  79. import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
  80. import org.eclipse.jgit.util.io.InterruptTimer;
  81. import org.eclipse.jgit.util.io.TimeoutInputStream;
  82. import org.eclipse.jgit.util.io.TimeoutOutputStream;
  83. /**
  84. * Implements the server side of a fetch connection, transmitting objects.
  85. */
  86. public class UploadPack {
  87. static final String OPTION_INCLUDE_TAG = BasePackFetchConnection.OPTION_INCLUDE_TAG;
  88. static final String OPTION_MULTI_ACK = BasePackFetchConnection.OPTION_MULTI_ACK;
  89. static final String OPTION_MULTI_ACK_DETAILED = BasePackFetchConnection.OPTION_MULTI_ACK_DETAILED;
  90. static final String OPTION_THIN_PACK = BasePackFetchConnection.OPTION_THIN_PACK;
  91. static final String OPTION_SIDE_BAND = BasePackFetchConnection.OPTION_SIDE_BAND;
  92. static final String OPTION_SIDE_BAND_64K = BasePackFetchConnection.OPTION_SIDE_BAND_64K;
  93. static final String OPTION_OFS_DELTA = BasePackFetchConnection.OPTION_OFS_DELTA;
  94. static final String OPTION_NO_PROGRESS = BasePackFetchConnection.OPTION_NO_PROGRESS;
  95. static final String OPTION_NO_DONE = BasePackFetchConnection.OPTION_NO_DONE;
  96. static final String OPTION_SHALLOW = BasePackFetchConnection.OPTION_SHALLOW;
  97. /** Policy the server uses to validate client requests */
  98. public static enum RequestPolicy {
  99. /** Client may only ask for objects the server advertised a reference for. */
  100. ADVERTISED,
  101. /** Client may ask for any commit reachable from a reference. */
  102. REACHABLE_COMMIT,
  103. /** Client may ask for any SHA-1 in the repository. */
  104. ANY;
  105. }
  106. /** Data in the first line of a request, the line itself plus options. */
  107. public static class FirstLine {
  108. private final String line;
  109. private final Set<String> options;
  110. /**
  111. * Parse the first line of a receive-pack request.
  112. *
  113. * @param line
  114. * line from the client.
  115. */
  116. public FirstLine(String line) {
  117. if (line.length() > 45) {
  118. final HashSet<String> opts = new HashSet<String>();
  119. String opt = line.substring(45);
  120. if (opt.startsWith(" "))
  121. opt = opt.substring(1);
  122. for (String c : opt.split(" "))
  123. opts.add(c);
  124. this.line = line.substring(0, 45);
  125. this.options = Collections.unmodifiableSet(opts);
  126. } else {
  127. this.line = line;
  128. this.options = Collections.emptySet();
  129. }
  130. }
  131. /** @return non-capabilities part of the line. */
  132. public String getLine() {
  133. return line;
  134. }
  135. /** @return options parsed from the line. */
  136. public Set<String> getOptions() {
  137. return options;
  138. }
  139. }
  140. /** Database we read the objects from. */
  141. private final Repository db;
  142. /** Revision traversal support over {@link #db}. */
  143. private final RevWalk walk;
  144. /** Configuration to pass into the PackWriter. */
  145. private PackConfig packConfig;
  146. /** Timeout in seconds to wait for client interaction. */
  147. private int timeout;
  148. /**
  149. * Is the client connection a bi-directional socket or pipe?
  150. * <p>
  151. * If true, this class assumes it can perform multiple read and write cycles
  152. * with the client over the input and output streams. This matches the
  153. * functionality available with a standard TCP/IP connection, or a local
  154. * operating system or in-memory pipe.
  155. * <p>
  156. * If false, this class runs in a read everything then output results mode,
  157. * making it suitable for single round-trip systems RPCs such as HTTP.
  158. */
  159. private boolean biDirectionalPipe = true;
  160. /** Timer to manage {@link #timeout}. */
  161. private InterruptTimer timer;
  162. private InputStream rawIn;
  163. private OutputStream rawOut;
  164. private PacketLineIn pckIn;
  165. private PacketLineOut pckOut;
  166. /** The refs we advertised as existing at the start of the connection. */
  167. private Map<String, Ref> refs;
  168. /** Hook used while advertising the refs to the client. */
  169. private AdvertiseRefsHook advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
  170. /** Filter used while advertising the refs to the client. */
  171. private RefFilter refFilter = RefFilter.DEFAULT;
  172. /** Hook handling the various upload phases. */
  173. private PreUploadHook preUploadHook = PreUploadHook.NULL;
  174. /** Capabilities requested by the client. */
  175. private Set<String> options;
  176. /** Raw ObjectIds the client has asked for, before validating them. */
  177. private final Set<ObjectId> wantIds = new HashSet<ObjectId>();
  178. /** Objects the client wants to obtain. */
  179. private final Set<RevObject> wantAll = new HashSet<RevObject>();
  180. /** Objects on both sides, these don't have to be sent. */
  181. private final Set<RevObject> commonBase = new HashSet<RevObject>();
  182. /** Shallow commits the client already has. */
  183. private final Set<ObjectId> clientShallowCommits = new HashSet<ObjectId>();
  184. /** Shallow commits on the client which are now becoming unshallow */
  185. private final List<ObjectId> unshallowCommits = new ArrayList<ObjectId>();
  186. /** Desired depth from the client on a shallow request. */
  187. private int depth;
  188. /** Commit time of the oldest common commit, in seconds. */
  189. private int oldestTime;
  190. /** null if {@link #commonBase} should be examined again. */
  191. private Boolean okToGiveUp;
  192. private boolean sentReady;
  193. /** Objects we sent in our advertisement list, clients can ask for these. */
  194. private Set<ObjectId> advertised;
  195. /** Marked on objects the client has asked us to give them. */
  196. private final RevFlag WANT;
  197. /** Marked on objects both we and the client have. */
  198. private final RevFlag PEER_HAS;
  199. /** Marked on objects in {@link #commonBase}. */
  200. private final RevFlag COMMON;
  201. /** Objects where we found a path from the want list to a common base. */
  202. private final RevFlag SATISFIED;
  203. private final RevFlagSet SAVE;
  204. private RequestPolicy requestPolicy = RequestPolicy.ADVERTISED;
  205. private MultiAck multiAck = MultiAck.OFF;
  206. private boolean noDone;
  207. private PackWriter.Statistics statistics;
  208. private UploadPackLogger logger = UploadPackLogger.NULL;
  209. /**
  210. * Create a new pack upload for an open repository.
  211. *
  212. * @param copyFrom
  213. * the source repository.
  214. */
  215. public UploadPack(final Repository copyFrom) {
  216. db = copyFrom;
  217. walk = new RevWalk(db);
  218. walk.setRetainBody(false);
  219. WANT = walk.newFlag("WANT");
  220. PEER_HAS = walk.newFlag("PEER_HAS");
  221. COMMON = walk.newFlag("COMMON");
  222. SATISFIED = walk.newFlag("SATISFIED");
  223. walk.carry(PEER_HAS);
  224. SAVE = new RevFlagSet();
  225. SAVE.add(WANT);
  226. SAVE.add(PEER_HAS);
  227. SAVE.add(COMMON);
  228. SAVE.add(SATISFIED);
  229. }
  230. /** @return the repository this upload is reading from. */
  231. public final Repository getRepository() {
  232. return db;
  233. }
  234. /** @return the RevWalk instance used by this connection. */
  235. public final RevWalk getRevWalk() {
  236. return walk;
  237. }
  238. /**
  239. * Get refs which were advertised to the client.
  240. *
  241. * @return all refs which were advertised to the client, or null if
  242. * {@link #setAdvertisedRefs(Map)} has not been called yet.
  243. */
  244. public final Map<String, Ref> getAdvertisedRefs() {
  245. return refs;
  246. }
  247. /**
  248. * Set the refs advertised by this UploadPack.
  249. * <p>
  250. * Intended to be called from a {@link PreUploadHook}.
  251. *
  252. * @param allRefs
  253. * explicit set of references to claim as advertised by this
  254. * UploadPack instance. This overrides any references that
  255. * may exist in the source repository. The map is passed
  256. * to the configured {@link #getRefFilter()}. If null, assumes
  257. * all refs were advertised.
  258. */
  259. public void setAdvertisedRefs(Map<String, Ref> allRefs) {
  260. if (allRefs != null)
  261. refs = allRefs;
  262. else
  263. refs = db.getAllRefs();
  264. refs = refFilter.filter(refs);
  265. }
  266. /** @return timeout (in seconds) before aborting an IO operation. */
  267. public int getTimeout() {
  268. return timeout;
  269. }
  270. /**
  271. * Set the timeout before willing to abort an IO call.
  272. *
  273. * @param seconds
  274. * number of seconds to wait (with no data transfer occurring)
  275. * before aborting an IO read or write operation with the
  276. * connected client.
  277. */
  278. public void setTimeout(final int seconds) {
  279. timeout = seconds;
  280. }
  281. /**
  282. * @return true if this class expects a bi-directional pipe opened between
  283. * the client and itself. The default is true.
  284. */
  285. public boolean isBiDirectionalPipe() {
  286. return biDirectionalPipe;
  287. }
  288. /**
  289. * @param twoWay
  290. * if true, this class will assume the socket is a fully
  291. * bidirectional pipe between the two peers and takes advantage
  292. * of that by first transmitting the known refs, then waiting to
  293. * read commands. If false, this class assumes it must read the
  294. * commands before writing output and does not perform the
  295. * initial advertising.
  296. */
  297. public void setBiDirectionalPipe(final boolean twoWay) {
  298. biDirectionalPipe = twoWay;
  299. if (!biDirectionalPipe && requestPolicy == RequestPolicy.ADVERTISED)
  300. requestPolicy = RequestPolicy.REACHABLE_COMMIT;
  301. }
  302. /** @return policy used by the service to validate client requests. */
  303. public RequestPolicy getRequestPolicy() {
  304. return requestPolicy;
  305. }
  306. /**
  307. * @param policy
  308. * the policy used to enforce validation of a client's want list.
  309. * By default the policy is {@link RequestPolicy#ADVERTISED},
  310. * which is the Git default requiring clients to only ask for an
  311. * object that a reference directly points to. This may be relaxed
  312. * to {@link RequestPolicy#REACHABLE_COMMIT} when callers
  313. * have {@link #setBiDirectionalPipe(boolean)} set to false.
  314. */
  315. public void setRequestPolicy(RequestPolicy policy) {
  316. requestPolicy = policy != null ? policy : RequestPolicy.ADVERTISED;
  317. }
  318. /** @return the hook used while advertising the refs to the client */
  319. public AdvertiseRefsHook getAdvertiseRefsHook() {
  320. return advertiseRefsHook;
  321. }
  322. /** @return the filter used while advertising the refs to the client */
  323. public RefFilter getRefFilter() {
  324. return refFilter;
  325. }
  326. /**
  327. * Set the hook used while advertising the refs to the client.
  328. * <p>
  329. * If the {@link AdvertiseRefsHook} chooses to call
  330. * {@link #setAdvertisedRefs(Map)}, only refs set by this hook <em>and</em>
  331. * selected by the {@link RefFilter} will be shown to the client.
  332. *
  333. * @param advertiseRefsHook
  334. * the hook; may be null to show all refs.
  335. */
  336. public void setAdvertiseRefsHook(final AdvertiseRefsHook advertiseRefsHook) {
  337. if (advertiseRefsHook != null)
  338. this.advertiseRefsHook = advertiseRefsHook;
  339. else
  340. this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
  341. }
  342. /**
  343. * Set the filter used while advertising the refs to the client.
  344. * <p>
  345. * Only refs allowed by this filter will be sent to the client.
  346. * The filter is run against the refs specified by the
  347. * {@link AdvertiseRefsHook} (if applicable).
  348. *
  349. * @param refFilter
  350. * the filter; may be null to show all refs.
  351. */
  352. public void setRefFilter(final RefFilter refFilter) {
  353. this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT;
  354. }
  355. /** @return the configured upload hook. */
  356. public PreUploadHook getPreUploadHook() {
  357. return preUploadHook;
  358. }
  359. /**
  360. * Set the hook that controls how this instance will behave.
  361. *
  362. * @param hook
  363. * the hook; if null no special actions are taken.
  364. */
  365. public void setPreUploadHook(PreUploadHook hook) {
  366. preUploadHook = hook != null ? hook : PreUploadHook.NULL;
  367. }
  368. /**
  369. * Set the configuration used by the pack generator.
  370. *
  371. * @param pc
  372. * configuration controlling packing parameters. If null the
  373. * source repository's settings will be used.
  374. */
  375. public void setPackConfig(PackConfig pc) {
  376. this.packConfig = pc;
  377. }
  378. /** @return the configured logger. */
  379. public UploadPackLogger getLogger() {
  380. return logger;
  381. }
  382. /**
  383. * Set the logger.
  384. *
  385. * @param logger
  386. * the logger instance. If null, no logging occurs.
  387. */
  388. public void setLogger(UploadPackLogger logger) {
  389. this.logger = logger;
  390. }
  391. /**
  392. * Execute the upload task on the socket.
  393. *
  394. * @param input
  395. * raw input to read client commands from. Caller must ensure the
  396. * input is buffered, otherwise read performance may suffer.
  397. * @param output
  398. * response back to the Git network client, to write the pack
  399. * data onto. Caller must ensure the output is buffered,
  400. * otherwise write performance may suffer.
  401. * @param messages
  402. * secondary "notice" channel to send additional messages out
  403. * through. When run over SSH this should be tied back to the
  404. * standard error channel of the command execution. For most
  405. * other network connections this should be null.
  406. * @throws IOException
  407. */
  408. public void upload(final InputStream input, final OutputStream output,
  409. final OutputStream messages) throws IOException {
  410. try {
  411. rawIn = input;
  412. rawOut = output;
  413. if (timeout > 0) {
  414. final Thread caller = Thread.currentThread();
  415. timer = new InterruptTimer(caller.getName() + "-Timer");
  416. TimeoutInputStream i = new TimeoutInputStream(rawIn, timer);
  417. TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);
  418. i.setTimeout(timeout * 1000);
  419. o.setTimeout(timeout * 1000);
  420. rawIn = i;
  421. rawOut = o;
  422. }
  423. pckIn = new PacketLineIn(rawIn);
  424. pckOut = new PacketLineOut(rawOut);
  425. service();
  426. } finally {
  427. walk.release();
  428. if (timer != null) {
  429. try {
  430. timer.terminate();
  431. } finally {
  432. timer = null;
  433. }
  434. }
  435. }
  436. }
  437. /**
  438. * Get the PackWriter's statistics if a pack was sent to the client.
  439. *
  440. * @return statistics about pack output, if a pack was sent. Null if no pack
  441. * was sent, such as during the negotation phase of a smart HTTP
  442. * connection, or if the client was already up-to-date.
  443. */
  444. public PackWriter.Statistics getPackStatistics() {
  445. return statistics;
  446. }
  447. private Map<String, Ref> getAdvertisedOrDefaultRefs() {
  448. if (refs == null)
  449. setAdvertisedRefs(null);
  450. return refs;
  451. }
  452. private void service() throws IOException {
  453. if (biDirectionalPipe)
  454. sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
  455. else if (requestPolicy == RequestPolicy.ANY)
  456. advertised = Collections.emptySet();
  457. else {
  458. advertised = new HashSet<ObjectId>();
  459. for (Ref ref : getAdvertisedOrDefaultRefs().values()) {
  460. if (ref.getObjectId() != null)
  461. advertised.add(ref.getObjectId());
  462. }
  463. }
  464. boolean sendPack;
  465. try {
  466. recvWants();
  467. if (wantIds.isEmpty()) {
  468. preUploadHook.onBeginNegotiateRound(this, wantIds, 0);
  469. preUploadHook.onEndNegotiateRound(this, wantIds, 0, 0, false);
  470. return;
  471. }
  472. if (options.contains(OPTION_MULTI_ACK_DETAILED)) {
  473. multiAck = MultiAck.DETAILED;
  474. noDone = options.contains(OPTION_NO_DONE);
  475. } else if (options.contains(OPTION_MULTI_ACK))
  476. multiAck = MultiAck.CONTINUE;
  477. else
  478. multiAck = MultiAck.OFF;
  479. if (depth != 0)
  480. processShallow();
  481. sendPack = negotiate();
  482. } catch (PackProtocolException err) {
  483. reportErrorDuringNegotiate(err.getMessage());
  484. throw err;
  485. } catch (ServiceMayNotContinueException err) {
  486. if (!err.isOutput() && err.getMessage() != null) {
  487. try {
  488. pckOut.writeString("ERR " + err.getMessage() + "\n");
  489. err.setOutput();
  490. } catch (Throwable err2) {
  491. // Ignore this secondary failure (and not mark output).
  492. }
  493. }
  494. throw err;
  495. } catch (IOException err) {
  496. reportErrorDuringNegotiate(JGitText.get().internalServerError);
  497. throw err;
  498. } catch (RuntimeException err) {
  499. reportErrorDuringNegotiate(JGitText.get().internalServerError);
  500. throw err;
  501. } catch (Error err) {
  502. reportErrorDuringNegotiate(JGitText.get().internalServerError);
  503. throw err;
  504. }
  505. if (sendPack)
  506. sendPack();
  507. }
  508. private void reportErrorDuringNegotiate(String msg) {
  509. try {
  510. pckOut.writeString("ERR " + msg + "\n");
  511. } catch (Throwable err) {
  512. // Ignore this secondary failure.
  513. }
  514. }
  515. private void processShallow() throws IOException {
  516. DepthWalk.RevWalk depthWalk =
  517. new DepthWalk.RevWalk(walk.getObjectReader(), depth);
  518. // Find all the commits which will be shallow
  519. for (ObjectId o : wantIds) {
  520. try {
  521. depthWalk.markRoot(depthWalk.parseCommit(o));
  522. } catch (IncorrectObjectTypeException notCommit) {
  523. // Ignore non-commits in this loop.
  524. }
  525. }
  526. RevCommit o;
  527. while ((o = depthWalk.next()) != null) {
  528. DepthWalk.Commit c = (DepthWalk.Commit) o;
  529. // Commits at the boundary which aren't already shallow in
  530. // the client need to be marked as such
  531. if (c.getDepth() == depth && !clientShallowCommits.contains(c))
  532. pckOut.writeString("shallow " + o.name());
  533. // Commits not on the boundary which are shallow in the client
  534. // need to become unshallowed
  535. if (c.getDepth() < depth && clientShallowCommits.contains(c)) {
  536. unshallowCommits.add(c.copy());
  537. pckOut.writeString("unshallow " + c.name());
  538. }
  539. }
  540. pckOut.end();
  541. }
  542. /**
  543. * Generate an advertisement of available refs and capabilities.
  544. *
  545. * @param adv
  546. * the advertisement formatter.
  547. * @throws IOException
  548. * the formatter failed to write an advertisement.
  549. * @throws ServiceMayNotContinueException
  550. * the hook denied advertisement.
  551. */
  552. public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException,
  553. ServiceMayNotContinueException {
  554. try {
  555. advertiseRefsHook.advertiseRefs(this);
  556. } catch (ServiceMayNotContinueException fail) {
  557. if (fail.getMessage() != null) {
  558. adv.writeOne("ERR " + fail.getMessage());
  559. fail.setOutput();
  560. }
  561. throw fail;
  562. }
  563. adv.init(db);
  564. adv.advertiseCapability(OPTION_INCLUDE_TAG);
  565. adv.advertiseCapability(OPTION_MULTI_ACK_DETAILED);
  566. adv.advertiseCapability(OPTION_MULTI_ACK);
  567. adv.advertiseCapability(OPTION_OFS_DELTA);
  568. adv.advertiseCapability(OPTION_SIDE_BAND);
  569. adv.advertiseCapability(OPTION_SIDE_BAND_64K);
  570. adv.advertiseCapability(OPTION_THIN_PACK);
  571. adv.advertiseCapability(OPTION_NO_PROGRESS);
  572. adv.advertiseCapability(OPTION_SHALLOW);
  573. if (!biDirectionalPipe)
  574. adv.advertiseCapability(OPTION_NO_DONE);
  575. adv.setDerefTags(true);
  576. advertised = adv.send(getAdvertisedOrDefaultRefs());
  577. adv.end();
  578. }
  579. private void recvWants() throws IOException {
  580. boolean isFirst = true;
  581. for (;;) {
  582. String line;
  583. try {
  584. line = pckIn.readString();
  585. } catch (EOFException eof) {
  586. if (isFirst)
  587. break;
  588. throw eof;
  589. }
  590. if (line == PacketLineIn.END)
  591. break;
  592. if (line.startsWith("deepen ")) {
  593. depth = Integer.parseInt(line.substring(7));
  594. continue;
  595. }
  596. if (line.startsWith("shallow ")) {
  597. clientShallowCommits.add(ObjectId.fromString(line.substring(8)));
  598. continue;
  599. }
  600. if (!line.startsWith("want ") || line.length() < 45)
  601. throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "want", line));
  602. if (isFirst && line.length() > 45) {
  603. final FirstLine firstLine = new FirstLine(line);
  604. options = firstLine.getOptions();
  605. line = firstLine.getLine();
  606. }
  607. wantIds.add(ObjectId.fromString(line.substring(5)));
  608. isFirst = false;
  609. }
  610. }
  611. private boolean negotiate() throws IOException {
  612. okToGiveUp = Boolean.FALSE;
  613. ObjectId last = ObjectId.zeroId();
  614. List<ObjectId> peerHas = new ArrayList<ObjectId>(64);
  615. for (;;) {
  616. String line;
  617. try {
  618. line = pckIn.readString();
  619. } catch (EOFException eof) {
  620. // EOF on stateless RPC (aka smart HTTP) and non-shallow request
  621. // means the client asked for the updated shallow/unshallow data,
  622. // disconnected, and will try another request with actual want/have.
  623. // Don't report the EOF here, its a bug in the protocol that the client
  624. // just disconnects without sending an END.
  625. if (!biDirectionalPipe && depth > 0)
  626. return false;
  627. throw eof;
  628. }
  629. if (line == PacketLineIn.END) {
  630. last = processHaveLines(peerHas, last);
  631. if (commonBase.isEmpty() || multiAck != MultiAck.OFF)
  632. pckOut.writeString("NAK\n");
  633. if (noDone && sentReady) {
  634. pckOut.writeString("ACK " + last.name() + "\n");
  635. return true;
  636. }
  637. if (!biDirectionalPipe)
  638. return false;
  639. pckOut.flush();
  640. } else if (line.startsWith("have ") && line.length() == 45) {
  641. peerHas.add(ObjectId.fromString(line.substring(5)));
  642. } else if (line.equals("done")) {
  643. last = processHaveLines(peerHas, last);
  644. if (commonBase.isEmpty())
  645. pckOut.writeString("NAK\n");
  646. else if (multiAck != MultiAck.OFF)
  647. pckOut.writeString("ACK " + last.name() + "\n");
  648. return true;
  649. } else {
  650. throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "have", line));
  651. }
  652. }
  653. }
  654. private ObjectId processHaveLines(List<ObjectId> peerHas, ObjectId last)
  655. throws IOException {
  656. preUploadHook.onBeginNegotiateRound(this, wantIds, peerHas.size());
  657. if (peerHas.isEmpty())
  658. return last;
  659. List<ObjectId> toParse = peerHas;
  660. HashSet<ObjectId> peerHasSet = null;
  661. boolean needMissing = false;
  662. sentReady = false;
  663. if (wantAll.isEmpty() && !wantIds.isEmpty()) {
  664. // We have not yet parsed the want list. Parse it now.
  665. peerHasSet = new HashSet<ObjectId>(peerHas);
  666. int cnt = wantIds.size() + peerHasSet.size();
  667. toParse = new ArrayList<ObjectId>(cnt);
  668. toParse.addAll(wantIds);
  669. toParse.addAll(peerHasSet);
  670. needMissing = true;
  671. }
  672. Set<RevObject> notAdvertisedWants = null;
  673. int haveCnt = 0;
  674. AsyncRevObjectQueue q = walk.parseAny(toParse, needMissing);
  675. try {
  676. for (;;) {
  677. RevObject obj;
  678. try {
  679. obj = q.next();
  680. } catch (MissingObjectException notFound) {
  681. ObjectId id = notFound.getObjectId();
  682. if (wantIds.contains(id)) {
  683. String msg = MessageFormat.format(
  684. JGitText.get().wantNotValid, id.name());
  685. throw new PackProtocolException(msg, notFound);
  686. }
  687. continue;
  688. }
  689. if (obj == null)
  690. break;
  691. // If the object is still found in wantIds, the want
  692. // list wasn't parsed earlier, and was done in this batch.
  693. //
  694. if (wantIds.remove(obj)) {
  695. if (!advertised.contains(obj) && requestPolicy != RequestPolicy.ANY) {
  696. if (notAdvertisedWants == null)
  697. notAdvertisedWants = new HashSet<RevObject>();
  698. notAdvertisedWants.add(obj);
  699. }
  700. if (!obj.has(WANT)) {
  701. obj.add(WANT);
  702. wantAll.add(obj);
  703. }
  704. if (!(obj instanceof RevCommit))
  705. obj.add(SATISFIED);
  706. if (obj instanceof RevTag) {
  707. RevObject target = walk.peel(obj);
  708. if (target instanceof RevCommit) {
  709. if (!target.has(WANT)) {
  710. target.add(WANT);
  711. wantAll.add(target);
  712. }
  713. }
  714. }
  715. if (!peerHasSet.contains(obj))
  716. continue;
  717. }
  718. last = obj;
  719. haveCnt++;
  720. if (obj instanceof RevCommit) {
  721. RevCommit c = (RevCommit) obj;
  722. if (oldestTime == 0 || c.getCommitTime() < oldestTime)
  723. oldestTime = c.getCommitTime();
  724. }
  725. if (obj.has(PEER_HAS))
  726. continue;
  727. obj.add(PEER_HAS);
  728. if (obj instanceof RevCommit)
  729. ((RevCommit) obj).carry(PEER_HAS);
  730. addCommonBase(obj);
  731. // If both sides have the same object; let the client know.
  732. //
  733. switch (multiAck) {
  734. case OFF:
  735. if (commonBase.size() == 1)
  736. pckOut.writeString("ACK " + obj.name() + "\n");
  737. break;
  738. case CONTINUE:
  739. pckOut.writeString("ACK " + obj.name() + " continue\n");
  740. break;
  741. case DETAILED:
  742. pckOut.writeString("ACK " + obj.name() + " common\n");
  743. break;
  744. }
  745. }
  746. } finally {
  747. q.release();
  748. }
  749. // If the client asked for non advertised object, check our policy.
  750. if (notAdvertisedWants != null && !notAdvertisedWants.isEmpty()) {
  751. switch (requestPolicy) {
  752. case ADVERTISED:
  753. default:
  754. throw new PackProtocolException(MessageFormat.format(
  755. JGitText.get().wantNotValid,
  756. notAdvertisedWants.iterator().next().name()));
  757. case REACHABLE_COMMIT:
  758. checkNotAdvertisedWants(notAdvertisedWants);
  759. break;
  760. case ANY:
  761. // Allow whatever was asked for.
  762. break;
  763. }
  764. }
  765. int missCnt = peerHas.size() - haveCnt;
  766. // If we don't have one of the objects but we're also willing to
  767. // create a pack at this point, let the client know so it stops
  768. // telling us about its history.
  769. //
  770. boolean didOkToGiveUp = false;
  771. if (0 < missCnt) {
  772. for (int i = peerHas.size() - 1; i >= 0; i--) {
  773. ObjectId id = peerHas.get(i);
  774. if (walk.lookupOrNull(id) == null) {
  775. didOkToGiveUp = true;
  776. if (okToGiveUp()) {
  777. switch (multiAck) {
  778. case OFF:
  779. break;
  780. case CONTINUE:
  781. pckOut.writeString("ACK " + id.name() + " continue\n");
  782. break;
  783. case DETAILED:
  784. pckOut.writeString("ACK " + id.name() + " ready\n");
  785. sentReady = true;
  786. break;
  787. }
  788. }
  789. break;
  790. }
  791. }
  792. }
  793. if (multiAck == MultiAck.DETAILED && !didOkToGiveUp && okToGiveUp()) {
  794. ObjectId id = peerHas.get(peerHas.size() - 1);
  795. sentReady = true;
  796. pckOut.writeString("ACK " + id.name() + " ready\n");
  797. sentReady = true;
  798. }
  799. preUploadHook.onEndNegotiateRound(this, wantAll, haveCnt, missCnt, sentReady);
  800. peerHas.clear();
  801. return last;
  802. }
  803. private void checkNotAdvertisedWants(Set<RevObject> notAdvertisedWants)
  804. throws MissingObjectException, IncorrectObjectTypeException, IOException {
  805. // Walk the requested commits back to the advertised commits.
  806. // If any commit exists, a branch was deleted or rewound and
  807. // the repository owner no longer exports that requested item.
  808. // If the requested commit is merged into an advertised branch
  809. // it will be marked UNINTERESTING and no commits return.
  810. for (RevObject o : notAdvertisedWants) {
  811. if (!(o instanceof RevCommit)) {
  812. throw new PackProtocolException(MessageFormat.format(
  813. JGitText.get().wantNotValid,
  814. notAdvertisedWants.iterator().next().name()));
  815. }
  816. walk.markStart((RevCommit) o);
  817. }
  818. for (ObjectId id : advertised) {
  819. try {
  820. walk.markUninteresting(walk.parseCommit(id));
  821. } catch (IncorrectObjectTypeException notCommit) {
  822. continue;
  823. }
  824. }
  825. RevCommit bad = walk.next();
  826. if (bad != null) {
  827. throw new PackProtocolException(MessageFormat.format(
  828. JGitText.get().wantNotValid,
  829. bad.name()));
  830. }
  831. walk.reset();
  832. }
  833. private void addCommonBase(final RevObject o) {
  834. if (!o.has(COMMON)) {
  835. o.add(COMMON);
  836. commonBase.add(o);
  837. okToGiveUp = null;
  838. }
  839. }
  840. private boolean okToGiveUp() throws PackProtocolException {
  841. if (okToGiveUp == null)
  842. okToGiveUp = Boolean.valueOf(okToGiveUpImp());
  843. return okToGiveUp.booleanValue();
  844. }
  845. private boolean okToGiveUpImp() throws PackProtocolException {
  846. if (commonBase.isEmpty())
  847. return false;
  848. try {
  849. for (RevObject obj : wantAll) {
  850. if (!wantSatisfied(obj))
  851. return false;
  852. }
  853. return true;
  854. } catch (IOException e) {
  855. throw new PackProtocolException(JGitText.get().internalRevisionError, e);
  856. }
  857. }
  858. private boolean wantSatisfied(final RevObject want) throws IOException {
  859. if (want.has(SATISFIED))
  860. return true;
  861. walk.resetRetain(SAVE);
  862. walk.markStart((RevCommit) want);
  863. if (oldestTime != 0)
  864. walk.setRevFilter(CommitTimeRevFilter.after(oldestTime * 1000L));
  865. for (;;) {
  866. final RevCommit c = walk.next();
  867. if (c == null)
  868. break;
  869. if (c.has(PEER_HAS)) {
  870. addCommonBase(c);
  871. want.add(SATISFIED);
  872. return true;
  873. }
  874. }
  875. return false;
  876. }
  877. private void sendPack() throws IOException {
  878. final boolean sideband = options.contains(OPTION_SIDE_BAND)
  879. || options.contains(OPTION_SIDE_BAND_64K);
  880. if (!biDirectionalPipe) {
  881. // Ensure the request was fully consumed. Any remaining input must
  882. // be a protocol error. If we aren't at EOF the implementation is broken.
  883. int eof = rawIn.read();
  884. if (0 <= eof)
  885. throw new CorruptObjectException(MessageFormat.format(
  886. JGitText.get().expectedEOFReceived,
  887. "\\x" + Integer.toHexString(eof)));
  888. }
  889. if (sideband) {
  890. try {
  891. sendPack(true);
  892. } catch (ServiceMayNotContinueException noPack) {
  893. // This was already reported on (below).
  894. throw noPack;
  895. } catch (IOException err) {
  896. if (reportInternalServerErrorOverSideband())
  897. throw new UploadPackInternalServerErrorException(err);
  898. else
  899. throw err;
  900. } catch (RuntimeException err) {
  901. if (reportInternalServerErrorOverSideband())
  902. throw new UploadPackInternalServerErrorException(err);
  903. else
  904. throw err;
  905. } catch (Error err) {
  906. if (reportInternalServerErrorOverSideband())
  907. throw new UploadPackInternalServerErrorException(err);
  908. else
  909. throw err;
  910. }
  911. } else {
  912. sendPack(false);
  913. }
  914. }
  915. private boolean reportInternalServerErrorOverSideband() {
  916. try {
  917. SideBandOutputStream err = new SideBandOutputStream(
  918. SideBandOutputStream.CH_ERROR,
  919. SideBandOutputStream.SMALL_BUF,
  920. rawOut);
  921. err.write(Constants.encode(JGitText.get().internalServerError));
  922. err.flush();
  923. return true;
  924. } catch (Throwable cannotReport) {
  925. // Ignore the reason. This is a secondary failure.
  926. return false;
  927. }
  928. }
  929. private void sendPack(final boolean sideband) throws IOException {
  930. ProgressMonitor pm = NullProgressMonitor.INSTANCE;
  931. OutputStream packOut = rawOut;
  932. SideBandOutputStream msgOut = null;
  933. if (sideband) {
  934. int bufsz = SideBandOutputStream.SMALL_BUF;
  935. if (options.contains(OPTION_SIDE_BAND_64K))
  936. bufsz = SideBandOutputStream.MAX_BUF;
  937. packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA,
  938. bufsz, rawOut);
  939. if (!options.contains(OPTION_NO_PROGRESS)) {
  940. msgOut = new SideBandOutputStream(
  941. SideBandOutputStream.CH_PROGRESS, bufsz, rawOut);
  942. pm = new SideBandProgressMonitor(msgOut);
  943. }
  944. }
  945. try {
  946. if (wantAll.isEmpty()) {
  947. preUploadHook.onSendPack(this, wantIds, commonBase);
  948. } else {
  949. preUploadHook.onSendPack(this, wantAll, commonBase);
  950. }
  951. } catch (ServiceMayNotContinueException noPack) {
  952. if (sideband && noPack.getMessage() != null) {
  953. noPack.setOutput();
  954. SideBandOutputStream err = new SideBandOutputStream(
  955. SideBandOutputStream.CH_ERROR,
  956. SideBandOutputStream.SMALL_BUF, rawOut);
  957. err.write(Constants.encode(noPack.getMessage()));
  958. err.flush();
  959. }
  960. throw noPack;
  961. }
  962. PackConfig cfg = packConfig;
  963. if (cfg == null)
  964. cfg = new PackConfig(db);
  965. final PackWriter pw = new PackWriter(cfg, walk.getObjectReader());
  966. try {
  967. pw.setUseCachedPacks(true);
  968. pw.setReuseDeltaCommits(true);
  969. pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
  970. pw.setThin(options.contains(OPTION_THIN_PACK));
  971. pw.setReuseValidatingObjects(false);
  972. if (commonBase.isEmpty() && refs != null) {
  973. Set<ObjectId> tagTargets = new HashSet<ObjectId>();
  974. for (Ref ref : refs.values()) {
  975. if (ref.getPeeledObjectId() != null)
  976. tagTargets.add(ref.getPeeledObjectId());
  977. else if (ref.getObjectId() == null)
  978. continue;
  979. else if (ref.getName().startsWith(Constants.R_HEADS))
  980. tagTargets.add(ref.getObjectId());
  981. }
  982. pw.setTagTargets(tagTargets);
  983. }
  984. if (depth > 0)
  985. pw.setShallowPack(depth, unshallowCommits);
  986. RevWalk rw = walk;
  987. if (wantAll.isEmpty()) {
  988. pw.preparePack(pm, wantIds, commonBase);
  989. } else {
  990. walk.reset();
  991. ObjectWalk ow = walk.toObjectWalkWithSameObjects();
  992. pw.preparePack(pm, ow, wantAll, commonBase);
  993. rw = ow;
  994. }
  995. if (options.contains(OPTION_INCLUDE_TAG) && refs != null) {
  996. for (Ref ref : refs.values()) {
  997. ObjectId objectId = ref.getObjectId();
  998. // If the object was already requested, skip it.
  999. if (wantAll.isEmpty()) {
  1000. if (wantIds.contains(objectId))
  1001. continue;
  1002. } else {
  1003. RevObject obj = rw.lookupOrNull(objectId);
  1004. if (obj != null && obj.has(WANT))
  1005. continue;
  1006. }
  1007. if (!ref.isPeeled())
  1008. ref = db.peel(ref);
  1009. ObjectId peeledId = ref.getPeeledObjectId();
  1010. if (peeledId == null)
  1011. continue;
  1012. objectId = ref.getObjectId();
  1013. if (pw.willInclude(peeledId) && !pw.willInclude(objectId))
  1014. pw.addObject(rw.parseAny(objectId));
  1015. }
  1016. }
  1017. pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut);
  1018. statistics = pw.getStatistics();
  1019. if (msgOut != null) {
  1020. String msg = pw.getStatistics().getMessage() + '\n';
  1021. msgOut.write(Constants.encode(msg));
  1022. msgOut.flush();
  1023. }
  1024. } finally {
  1025. pw.release();
  1026. }
  1027. if (sideband)
  1028. pckOut.end();
  1029. if (statistics != null)
  1030. logger.onPackStatistics(statistics);
  1031. }
  1032. }