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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216
  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. * Check whether the client expects a side-band stream.
  393. *
  394. * @return true if the client has advertised a side-band capability, false
  395. * otherwise.
  396. * @throws RequestNotYetReadException
  397. * if the client's request has not yet been read from the wire, so
  398. * we do not know if they expect side-band. Note that the client
  399. * may have already written the request, it just has not been
  400. * read.
  401. */
  402. public boolean isSideBand() throws RequestNotYetReadException {
  403. if (options == null)
  404. throw new RequestNotYetReadException();
  405. return (options.contains(OPTION_SIDE_BAND)
  406. || options.contains(OPTION_SIDE_BAND_64K));
  407. }
  408. /**
  409. * Execute the upload task on the socket.
  410. *
  411. * @param input
  412. * raw input to read client commands from. Caller must ensure the
  413. * input is buffered, otherwise read performance may suffer.
  414. * @param output
  415. * response back to the Git network client, to write the pack
  416. * data onto. Caller must ensure the output is buffered,
  417. * otherwise write performance may suffer.
  418. * @param messages
  419. * secondary "notice" channel to send additional messages out
  420. * through. When run over SSH this should be tied back to the
  421. * standard error channel of the command execution. For most
  422. * other network connections this should be null.
  423. * @throws IOException
  424. */
  425. public void upload(final InputStream input, final OutputStream output,
  426. final OutputStream messages) throws IOException {
  427. try {
  428. rawIn = input;
  429. rawOut = output;
  430. if (timeout > 0) {
  431. final Thread caller = Thread.currentThread();
  432. timer = new InterruptTimer(caller.getName() + "-Timer");
  433. TimeoutInputStream i = new TimeoutInputStream(rawIn, timer);
  434. TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);
  435. i.setTimeout(timeout * 1000);
  436. o.setTimeout(timeout * 1000);
  437. rawIn = i;
  438. rawOut = o;
  439. }
  440. pckIn = new PacketLineIn(rawIn);
  441. pckOut = new PacketLineOut(rawOut);
  442. service();
  443. } finally {
  444. walk.release();
  445. if (timer != null) {
  446. try {
  447. timer.terminate();
  448. } finally {
  449. timer = null;
  450. }
  451. }
  452. }
  453. }
  454. /**
  455. * Get the PackWriter's statistics if a pack was sent to the client.
  456. *
  457. * @return statistics about pack output, if a pack was sent. Null if no pack
  458. * was sent, such as during the negotation phase of a smart HTTP
  459. * connection, or if the client was already up-to-date.
  460. */
  461. public PackWriter.Statistics getPackStatistics() {
  462. return statistics;
  463. }
  464. private Map<String, Ref> getAdvertisedOrDefaultRefs() {
  465. if (refs == null)
  466. setAdvertisedRefs(null);
  467. return refs;
  468. }
  469. private void service() throws IOException {
  470. if (biDirectionalPipe)
  471. sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
  472. else if (requestPolicy == RequestPolicy.ANY)
  473. advertised = Collections.emptySet();
  474. else {
  475. advertised = new HashSet<ObjectId>();
  476. for (Ref ref : getAdvertisedOrDefaultRefs().values()) {
  477. if (ref.getObjectId() != null)
  478. advertised.add(ref.getObjectId());
  479. }
  480. }
  481. boolean sendPack;
  482. try {
  483. recvWants();
  484. if (wantIds.isEmpty()) {
  485. preUploadHook.onBeginNegotiateRound(this, wantIds, 0);
  486. preUploadHook.onEndNegotiateRound(this, wantIds, 0, 0, false);
  487. return;
  488. }
  489. if (options.contains(OPTION_MULTI_ACK_DETAILED)) {
  490. multiAck = MultiAck.DETAILED;
  491. noDone = options.contains(OPTION_NO_DONE);
  492. } else if (options.contains(OPTION_MULTI_ACK))
  493. multiAck = MultiAck.CONTINUE;
  494. else
  495. multiAck = MultiAck.OFF;
  496. if (depth != 0)
  497. processShallow();
  498. sendPack = negotiate();
  499. } catch (PackProtocolException err) {
  500. reportErrorDuringNegotiate(err.getMessage());
  501. throw err;
  502. } catch (ServiceMayNotContinueException err) {
  503. if (!err.isOutput() && err.getMessage() != null) {
  504. try {
  505. pckOut.writeString("ERR " + err.getMessage() + "\n");
  506. err.setOutput();
  507. } catch (Throwable err2) {
  508. // Ignore this secondary failure (and not mark output).
  509. }
  510. }
  511. throw err;
  512. } catch (IOException err) {
  513. reportErrorDuringNegotiate(JGitText.get().internalServerError);
  514. throw err;
  515. } catch (RuntimeException err) {
  516. reportErrorDuringNegotiate(JGitText.get().internalServerError);
  517. throw err;
  518. } catch (Error err) {
  519. reportErrorDuringNegotiate(JGitText.get().internalServerError);
  520. throw err;
  521. }
  522. if (sendPack)
  523. sendPack();
  524. }
  525. private void reportErrorDuringNegotiate(String msg) {
  526. try {
  527. pckOut.writeString("ERR " + msg + "\n");
  528. } catch (Throwable err) {
  529. // Ignore this secondary failure.
  530. }
  531. }
  532. private void processShallow() throws IOException {
  533. DepthWalk.RevWalk depthWalk =
  534. new DepthWalk.RevWalk(walk.getObjectReader(), depth);
  535. // Find all the commits which will be shallow
  536. for (ObjectId o : wantIds) {
  537. try {
  538. depthWalk.markRoot(depthWalk.parseCommit(o));
  539. } catch (IncorrectObjectTypeException notCommit) {
  540. // Ignore non-commits in this loop.
  541. }
  542. }
  543. RevCommit o;
  544. while ((o = depthWalk.next()) != null) {
  545. DepthWalk.Commit c = (DepthWalk.Commit) o;
  546. // Commits at the boundary which aren't already shallow in
  547. // the client need to be marked as such
  548. if (c.getDepth() == depth && !clientShallowCommits.contains(c))
  549. pckOut.writeString("shallow " + o.name());
  550. // Commits not on the boundary which are shallow in the client
  551. // need to become unshallowed
  552. if (c.getDepth() < depth && clientShallowCommits.contains(c)) {
  553. unshallowCommits.add(c.copy());
  554. pckOut.writeString("unshallow " + c.name());
  555. }
  556. }
  557. pckOut.end();
  558. }
  559. /**
  560. * Generate an advertisement of available refs and capabilities.
  561. *
  562. * @param adv
  563. * the advertisement formatter.
  564. * @throws IOException
  565. * the formatter failed to write an advertisement.
  566. * @throws ServiceMayNotContinueException
  567. * the hook denied advertisement.
  568. */
  569. public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException,
  570. ServiceMayNotContinueException {
  571. try {
  572. advertiseRefsHook.advertiseRefs(this);
  573. } catch (ServiceMayNotContinueException fail) {
  574. if (fail.getMessage() != null) {
  575. adv.writeOne("ERR " + fail.getMessage());
  576. fail.setOutput();
  577. }
  578. throw fail;
  579. }
  580. adv.init(db);
  581. adv.advertiseCapability(OPTION_INCLUDE_TAG);
  582. adv.advertiseCapability(OPTION_MULTI_ACK_DETAILED);
  583. adv.advertiseCapability(OPTION_MULTI_ACK);
  584. adv.advertiseCapability(OPTION_OFS_DELTA);
  585. adv.advertiseCapability(OPTION_SIDE_BAND);
  586. adv.advertiseCapability(OPTION_SIDE_BAND_64K);
  587. adv.advertiseCapability(OPTION_THIN_PACK);
  588. adv.advertiseCapability(OPTION_NO_PROGRESS);
  589. adv.advertiseCapability(OPTION_SHALLOW);
  590. if (!biDirectionalPipe)
  591. adv.advertiseCapability(OPTION_NO_DONE);
  592. adv.setDerefTags(true);
  593. advertised = adv.send(getAdvertisedOrDefaultRefs());
  594. adv.end();
  595. }
  596. private void recvWants() throws IOException {
  597. boolean isFirst = true;
  598. for (;;) {
  599. String line;
  600. try {
  601. line = pckIn.readString();
  602. } catch (EOFException eof) {
  603. if (isFirst)
  604. break;
  605. throw eof;
  606. }
  607. if (line == PacketLineIn.END)
  608. break;
  609. if (line.startsWith("deepen ")) {
  610. depth = Integer.parseInt(line.substring(7));
  611. continue;
  612. }
  613. if (line.startsWith("shallow ")) {
  614. clientShallowCommits.add(ObjectId.fromString(line.substring(8)));
  615. continue;
  616. }
  617. if (!line.startsWith("want ") || line.length() < 45)
  618. throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "want", line));
  619. if (isFirst && line.length() > 45) {
  620. final FirstLine firstLine = new FirstLine(line);
  621. options = firstLine.getOptions();
  622. line = firstLine.getLine();
  623. }
  624. wantIds.add(ObjectId.fromString(line.substring(5)));
  625. isFirst = false;
  626. }
  627. }
  628. private boolean negotiate() throws IOException {
  629. okToGiveUp = Boolean.FALSE;
  630. ObjectId last = ObjectId.zeroId();
  631. List<ObjectId> peerHas = new ArrayList<ObjectId>(64);
  632. for (;;) {
  633. String line;
  634. try {
  635. line = pckIn.readString();
  636. } catch (EOFException eof) {
  637. // EOF on stateless RPC (aka smart HTTP) and non-shallow request
  638. // means the client asked for the updated shallow/unshallow data,
  639. // disconnected, and will try another request with actual want/have.
  640. // Don't report the EOF here, its a bug in the protocol that the client
  641. // just disconnects without sending an END.
  642. if (!biDirectionalPipe && depth > 0)
  643. return false;
  644. throw eof;
  645. }
  646. if (line == PacketLineIn.END) {
  647. last = processHaveLines(peerHas, last);
  648. if (commonBase.isEmpty() || multiAck != MultiAck.OFF)
  649. pckOut.writeString("NAK\n");
  650. if (noDone && sentReady) {
  651. pckOut.writeString("ACK " + last.name() + "\n");
  652. return true;
  653. }
  654. if (!biDirectionalPipe)
  655. return false;
  656. pckOut.flush();
  657. } else if (line.startsWith("have ") && line.length() == 45) {
  658. peerHas.add(ObjectId.fromString(line.substring(5)));
  659. } else if (line.equals("done")) {
  660. last = processHaveLines(peerHas, last);
  661. if (commonBase.isEmpty())
  662. pckOut.writeString("NAK\n");
  663. else if (multiAck != MultiAck.OFF)
  664. pckOut.writeString("ACK " + last.name() + "\n");
  665. return true;
  666. } else {
  667. throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "have", line));
  668. }
  669. }
  670. }
  671. private ObjectId processHaveLines(List<ObjectId> peerHas, ObjectId last)
  672. throws IOException {
  673. preUploadHook.onBeginNegotiateRound(this, wantIds, peerHas.size());
  674. if (peerHas.isEmpty())
  675. return last;
  676. List<ObjectId> toParse = peerHas;
  677. HashSet<ObjectId> peerHasSet = null;
  678. boolean needMissing = false;
  679. sentReady = false;
  680. if (wantAll.isEmpty() && !wantIds.isEmpty()) {
  681. // We have not yet parsed the want list. Parse it now.
  682. peerHasSet = new HashSet<ObjectId>(peerHas);
  683. int cnt = wantIds.size() + peerHasSet.size();
  684. toParse = new ArrayList<ObjectId>(cnt);
  685. toParse.addAll(wantIds);
  686. toParse.addAll(peerHasSet);
  687. needMissing = true;
  688. }
  689. Set<RevObject> notAdvertisedWants = null;
  690. int haveCnt = 0;
  691. AsyncRevObjectQueue q = walk.parseAny(toParse, needMissing);
  692. try {
  693. for (;;) {
  694. RevObject obj;
  695. try {
  696. obj = q.next();
  697. } catch (MissingObjectException notFound) {
  698. ObjectId id = notFound.getObjectId();
  699. if (wantIds.contains(id)) {
  700. String msg = MessageFormat.format(
  701. JGitText.get().wantNotValid, id.name());
  702. throw new PackProtocolException(msg, notFound);
  703. }
  704. continue;
  705. }
  706. if (obj == null)
  707. break;
  708. // If the object is still found in wantIds, the want
  709. // list wasn't parsed earlier, and was done in this batch.
  710. //
  711. if (wantIds.remove(obj)) {
  712. if (!advertised.contains(obj) && requestPolicy != RequestPolicy.ANY) {
  713. if (notAdvertisedWants == null)
  714. notAdvertisedWants = new HashSet<RevObject>();
  715. notAdvertisedWants.add(obj);
  716. }
  717. if (!obj.has(WANT)) {
  718. obj.add(WANT);
  719. wantAll.add(obj);
  720. }
  721. if (!(obj instanceof RevCommit))
  722. obj.add(SATISFIED);
  723. if (obj instanceof RevTag) {
  724. RevObject target = walk.peel(obj);
  725. if (target instanceof RevCommit) {
  726. if (!target.has(WANT)) {
  727. target.add(WANT);
  728. wantAll.add(target);
  729. }
  730. }
  731. }
  732. if (!peerHasSet.contains(obj))
  733. continue;
  734. }
  735. last = obj;
  736. haveCnt++;
  737. if (obj instanceof RevCommit) {
  738. RevCommit c = (RevCommit) obj;
  739. if (oldestTime == 0 || c.getCommitTime() < oldestTime)
  740. oldestTime = c.getCommitTime();
  741. }
  742. if (obj.has(PEER_HAS))
  743. continue;
  744. obj.add(PEER_HAS);
  745. if (obj instanceof RevCommit)
  746. ((RevCommit) obj).carry(PEER_HAS);
  747. addCommonBase(obj);
  748. // If both sides have the same object; let the client know.
  749. //
  750. switch (multiAck) {
  751. case OFF:
  752. if (commonBase.size() == 1)
  753. pckOut.writeString("ACK " + obj.name() + "\n");
  754. break;
  755. case CONTINUE:
  756. pckOut.writeString("ACK " + obj.name() + " continue\n");
  757. break;
  758. case DETAILED:
  759. pckOut.writeString("ACK " + obj.name() + " common\n");
  760. break;
  761. }
  762. }
  763. } finally {
  764. q.release();
  765. }
  766. // If the client asked for non advertised object, check our policy.
  767. if (notAdvertisedWants != null && !notAdvertisedWants.isEmpty()) {
  768. switch (requestPolicy) {
  769. case ADVERTISED:
  770. default:
  771. throw new PackProtocolException(MessageFormat.format(
  772. JGitText.get().wantNotValid,
  773. notAdvertisedWants.iterator().next().name()));
  774. case REACHABLE_COMMIT:
  775. checkNotAdvertisedWants(notAdvertisedWants);
  776. break;
  777. case ANY:
  778. // Allow whatever was asked for.
  779. break;
  780. }
  781. }
  782. int missCnt = peerHas.size() - haveCnt;
  783. // If we don't have one of the objects but we're also willing to
  784. // create a pack at this point, let the client know so it stops
  785. // telling us about its history.
  786. //
  787. boolean didOkToGiveUp = false;
  788. if (0 < missCnt) {
  789. for (int i = peerHas.size() - 1; i >= 0; i--) {
  790. ObjectId id = peerHas.get(i);
  791. if (walk.lookupOrNull(id) == null) {
  792. didOkToGiveUp = true;
  793. if (okToGiveUp()) {
  794. switch (multiAck) {
  795. case OFF:
  796. break;
  797. case CONTINUE:
  798. pckOut.writeString("ACK " + id.name() + " continue\n");
  799. break;
  800. case DETAILED:
  801. pckOut.writeString("ACK " + id.name() + " ready\n");
  802. sentReady = true;
  803. break;
  804. }
  805. }
  806. break;
  807. }
  808. }
  809. }
  810. if (multiAck == MultiAck.DETAILED && !didOkToGiveUp && okToGiveUp()) {
  811. ObjectId id = peerHas.get(peerHas.size() - 1);
  812. sentReady = true;
  813. pckOut.writeString("ACK " + id.name() + " ready\n");
  814. sentReady = true;
  815. }
  816. preUploadHook.onEndNegotiateRound(this, wantAll, haveCnt, missCnt, sentReady);
  817. peerHas.clear();
  818. return last;
  819. }
  820. private void checkNotAdvertisedWants(Set<RevObject> notAdvertisedWants)
  821. throws MissingObjectException, IncorrectObjectTypeException, IOException {
  822. // Walk the requested commits back to the advertised commits.
  823. // If any commit exists, a branch was deleted or rewound and
  824. // the repository owner no longer exports that requested item.
  825. // If the requested commit is merged into an advertised branch
  826. // it will be marked UNINTERESTING and no commits return.
  827. for (RevObject o : notAdvertisedWants) {
  828. if (!(o instanceof RevCommit)) {
  829. throw new PackProtocolException(MessageFormat.format(
  830. JGitText.get().wantNotValid,
  831. notAdvertisedWants.iterator().next().name()));
  832. }
  833. walk.markStart((RevCommit) o);
  834. }
  835. for (ObjectId id : advertised) {
  836. try {
  837. walk.markUninteresting(walk.parseCommit(id));
  838. } catch (IncorrectObjectTypeException notCommit) {
  839. continue;
  840. }
  841. }
  842. RevCommit bad = walk.next();
  843. if (bad != null) {
  844. throw new PackProtocolException(MessageFormat.format(
  845. JGitText.get().wantNotValid,
  846. bad.name()));
  847. }
  848. walk.reset();
  849. }
  850. private void addCommonBase(final RevObject o) {
  851. if (!o.has(COMMON)) {
  852. o.add(COMMON);
  853. commonBase.add(o);
  854. okToGiveUp = null;
  855. }
  856. }
  857. private boolean okToGiveUp() throws PackProtocolException {
  858. if (okToGiveUp == null)
  859. okToGiveUp = Boolean.valueOf(okToGiveUpImp());
  860. return okToGiveUp.booleanValue();
  861. }
  862. private boolean okToGiveUpImp() throws PackProtocolException {
  863. if (commonBase.isEmpty())
  864. return false;
  865. try {
  866. for (RevObject obj : wantAll) {
  867. if (!wantSatisfied(obj))
  868. return false;
  869. }
  870. return true;
  871. } catch (IOException e) {
  872. throw new PackProtocolException(JGitText.get().internalRevisionError, e);
  873. }
  874. }
  875. private boolean wantSatisfied(final RevObject want) throws IOException {
  876. if (want.has(SATISFIED))
  877. return true;
  878. walk.resetRetain(SAVE);
  879. walk.markStart((RevCommit) want);
  880. if (oldestTime != 0)
  881. walk.setRevFilter(CommitTimeRevFilter.after(oldestTime * 1000L));
  882. for (;;) {
  883. final RevCommit c = walk.next();
  884. if (c == null)
  885. break;
  886. if (c.has(PEER_HAS)) {
  887. addCommonBase(c);
  888. want.add(SATISFIED);
  889. return true;
  890. }
  891. }
  892. return false;
  893. }
  894. private void sendPack() throws IOException {
  895. final boolean sideband = options.contains(OPTION_SIDE_BAND)
  896. || options.contains(OPTION_SIDE_BAND_64K);
  897. if (!biDirectionalPipe) {
  898. // Ensure the request was fully consumed. Any remaining input must
  899. // be a protocol error. If we aren't at EOF the implementation is broken.
  900. int eof = rawIn.read();
  901. if (0 <= eof)
  902. throw new CorruptObjectException(MessageFormat.format(
  903. JGitText.get().expectedEOFReceived,
  904. "\\x" + Integer.toHexString(eof)));
  905. }
  906. if (sideband) {
  907. try {
  908. sendPack(true);
  909. } catch (ServiceMayNotContinueException noPack) {
  910. // This was already reported on (below).
  911. throw noPack;
  912. } catch (IOException err) {
  913. if (reportInternalServerErrorOverSideband())
  914. throw new UploadPackInternalServerErrorException(err);
  915. else
  916. throw err;
  917. } catch (RuntimeException err) {
  918. if (reportInternalServerErrorOverSideband())
  919. throw new UploadPackInternalServerErrorException(err);
  920. else
  921. throw err;
  922. } catch (Error err) {
  923. if (reportInternalServerErrorOverSideband())
  924. throw new UploadPackInternalServerErrorException(err);
  925. else
  926. throw err;
  927. }
  928. } else {
  929. sendPack(false);
  930. }
  931. }
  932. private boolean reportInternalServerErrorOverSideband() {
  933. try {
  934. SideBandOutputStream err = new SideBandOutputStream(
  935. SideBandOutputStream.CH_ERROR,
  936. SideBandOutputStream.SMALL_BUF,
  937. rawOut);
  938. err.write(Constants.encode(JGitText.get().internalServerError));
  939. err.flush();
  940. return true;
  941. } catch (Throwable cannotReport) {
  942. // Ignore the reason. This is a secondary failure.
  943. return false;
  944. }
  945. }
  946. private void sendPack(final boolean sideband) throws IOException {
  947. ProgressMonitor pm = NullProgressMonitor.INSTANCE;
  948. OutputStream packOut = rawOut;
  949. SideBandOutputStream msgOut = null;
  950. if (sideband) {
  951. int bufsz = SideBandOutputStream.SMALL_BUF;
  952. if (options.contains(OPTION_SIDE_BAND_64K))
  953. bufsz = SideBandOutputStream.MAX_BUF;
  954. packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA,
  955. bufsz, rawOut);
  956. if (!options.contains(OPTION_NO_PROGRESS)) {
  957. msgOut = new SideBandOutputStream(
  958. SideBandOutputStream.CH_PROGRESS, bufsz, rawOut);
  959. pm = new SideBandProgressMonitor(msgOut);
  960. }
  961. }
  962. try {
  963. if (wantAll.isEmpty()) {
  964. preUploadHook.onSendPack(this, wantIds, commonBase);
  965. } else {
  966. preUploadHook.onSendPack(this, wantAll, commonBase);
  967. }
  968. } catch (ServiceMayNotContinueException noPack) {
  969. if (sideband && noPack.getMessage() != null) {
  970. noPack.setOutput();
  971. SideBandOutputStream err = new SideBandOutputStream(
  972. SideBandOutputStream.CH_ERROR,
  973. SideBandOutputStream.SMALL_BUF, rawOut);
  974. err.write(Constants.encode(noPack.getMessage()));
  975. err.flush();
  976. }
  977. throw noPack;
  978. }
  979. PackConfig cfg = packConfig;
  980. if (cfg == null)
  981. cfg = new PackConfig(db);
  982. final PackWriter pw = new PackWriter(cfg, walk.getObjectReader());
  983. try {
  984. pw.setUseCachedPacks(true);
  985. pw.setReuseDeltaCommits(true);
  986. pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
  987. pw.setThin(options.contains(OPTION_THIN_PACK));
  988. pw.setReuseValidatingObjects(false);
  989. if (commonBase.isEmpty() && refs != null) {
  990. Set<ObjectId> tagTargets = new HashSet<ObjectId>();
  991. for (Ref ref : refs.values()) {
  992. if (ref.getPeeledObjectId() != null)
  993. tagTargets.add(ref.getPeeledObjectId());
  994. else if (ref.getObjectId() == null)
  995. continue;
  996. else if (ref.getName().startsWith(Constants.R_HEADS))
  997. tagTargets.add(ref.getObjectId());
  998. }
  999. pw.setTagTargets(tagTargets);
  1000. }
  1001. if (depth > 0)
  1002. pw.setShallowPack(depth, unshallowCommits);
  1003. RevWalk rw = walk;
  1004. if (wantAll.isEmpty()) {
  1005. pw.preparePack(pm, wantIds, commonBase);
  1006. } else {
  1007. walk.reset();
  1008. ObjectWalk ow = walk.toObjectWalkWithSameObjects();
  1009. pw.preparePack(pm, ow, wantAll, commonBase);
  1010. rw = ow;
  1011. }
  1012. if (options.contains(OPTION_INCLUDE_TAG) && refs != null) {
  1013. for (Ref ref : refs.values()) {
  1014. ObjectId objectId = ref.getObjectId();
  1015. // If the object was already requested, skip it.
  1016. if (wantAll.isEmpty()) {
  1017. if (wantIds.contains(objectId))
  1018. continue;
  1019. } else {
  1020. RevObject obj = rw.lookupOrNull(objectId);
  1021. if (obj != null && obj.has(WANT))
  1022. continue;
  1023. }
  1024. if (!ref.isPeeled())
  1025. ref = db.peel(ref);
  1026. ObjectId peeledId = ref.getPeeledObjectId();
  1027. if (peeledId == null)
  1028. continue;
  1029. objectId = ref.getObjectId();
  1030. if (pw.willInclude(peeledId) && !pw.willInclude(objectId))
  1031. pw.addObject(rw.parseAny(objectId));
  1032. }
  1033. }
  1034. pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut);
  1035. statistics = pw.getStatistics();
  1036. if (msgOut != null) {
  1037. String msg = pw.getStatistics().getMessage() + '\n';
  1038. msgOut.write(Constants.encode(msg));
  1039. msgOut.flush();
  1040. }
  1041. } finally {
  1042. pw.release();
  1043. }
  1044. if (sideband)
  1045. pckOut.end();
  1046. if (statistics != null)
  1047. logger.onPackStatistics(statistics);
  1048. }
  1049. }