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.

BasePackConnection.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. /*
  2. * Copyright (C) 2008, 2010 Google Inc.
  3. * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  4. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  5. * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
  6. *
  7. * This program and the accompanying materials are made available under the
  8. * terms of the Eclipse Distribution License v. 1.0 which is available at
  9. * https://www.eclipse.org/org/documents/edl-v10.php.
  10. *
  11. * SPDX-License-Identifier: BSD-3-Clause
  12. */
  13. package org.eclipse.jgit.transport;
  14. import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS;
  15. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
  16. import static org.eclipse.jgit.transport.GitProtocolConstants.REF_ATTR_PEELED;
  17. import static org.eclipse.jgit.transport.GitProtocolConstants.REF_ATTR_SYMREF_TARGET;
  18. import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_1;
  19. import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_2;
  20. import java.io.EOFException;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.OutputStream;
  24. import java.text.MessageFormat;
  25. import java.util.Arrays;
  26. import java.util.Collection;
  27. import java.util.Collections;
  28. import java.util.HashMap;
  29. import java.util.HashSet;
  30. import java.util.Iterator;
  31. import java.util.LinkedHashMap;
  32. import java.util.Map;
  33. import java.util.Objects;
  34. import java.util.Set;
  35. import org.eclipse.jgit.annotations.NonNull;
  36. import org.eclipse.jgit.errors.InvalidObjectIdException;
  37. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  38. import org.eclipse.jgit.errors.PackProtocolException;
  39. import org.eclipse.jgit.errors.RemoteRepositoryException;
  40. import org.eclipse.jgit.errors.TransportException;
  41. import org.eclipse.jgit.internal.JGitText;
  42. import org.eclipse.jgit.lib.Constants;
  43. import org.eclipse.jgit.lib.ObjectId;
  44. import org.eclipse.jgit.lib.ObjectIdRef;
  45. import org.eclipse.jgit.lib.Ref;
  46. import org.eclipse.jgit.lib.Repository;
  47. import org.eclipse.jgit.lib.SymbolicRef;
  48. import org.eclipse.jgit.util.StringUtils;
  49. import org.eclipse.jgit.util.io.InterruptTimer;
  50. import org.eclipse.jgit.util.io.TimeoutInputStream;
  51. import org.eclipse.jgit.util.io.TimeoutOutputStream;
  52. /**
  53. * Base helper class for pack-based operations implementations. Provides partial
  54. * implementation of pack-protocol - refs advertising and capabilities support,
  55. * and some other helper methods.
  56. *
  57. * @see BasePackFetchConnection
  58. * @see BasePackPushConnection
  59. */
  60. abstract class BasePackConnection extends BaseConnection {
  61. protected static final String CAPABILITY_SYMREF_PREFIX = "symref="; //$NON-NLS-1$
  62. /** The repository this transport fetches into, or pushes out of. */
  63. protected final Repository local;
  64. /** Remote repository location. */
  65. protected final URIish uri;
  66. /** A transport connected to {@link #uri}. */
  67. protected final Transport transport;
  68. /** Low-level input stream, if a timeout was configured. */
  69. protected TimeoutInputStream timeoutIn;
  70. /** Low-level output stream, if a timeout was configured. */
  71. protected TimeoutOutputStream timeoutOut;
  72. /** Timer to manage {@link #timeoutIn} and {@link #timeoutOut}. */
  73. private InterruptTimer myTimer;
  74. /** Input stream reading from the remote. */
  75. protected InputStream in;
  76. /** Output stream sending to the remote. */
  77. protected OutputStream out;
  78. /** Packet line decoder around {@link #in}. */
  79. protected PacketLineIn pckIn;
  80. /** Packet line encoder around {@link #out}. */
  81. protected PacketLineOut pckOut;
  82. /** Send {@link PacketLineOut#end()} before closing {@link #out}? */
  83. protected boolean outNeedsEnd;
  84. /** True if this is a stateless RPC connection. */
  85. protected boolean statelessRPC;
  86. /** Capability tokens advertised by the remote side. */
  87. private final Map<String, String> remoteCapabilities = new HashMap<>();
  88. /** Extra objects the remote has, but which aren't offered as refs. */
  89. protected final Set<ObjectId> additionalHaves = new HashSet<>();
  90. private TransferConfig.ProtocolVersion protocol = TransferConfig.ProtocolVersion.V0;
  91. BasePackConnection(PackTransport packTransport) {
  92. transport = (Transport) packTransport;
  93. local = transport.local;
  94. uri = transport.uri;
  95. }
  96. TransferConfig.ProtocolVersion getProtocolVersion() {
  97. return protocol;
  98. }
  99. void setProtocolVersion(@NonNull TransferConfig.ProtocolVersion protocol) {
  100. this.protocol = protocol;
  101. }
  102. /**
  103. * Configure this connection with the directional pipes.
  104. *
  105. * @param myIn
  106. * input stream to receive data from the peer. Caller must ensure
  107. * the input is buffered, otherwise read performance may suffer.
  108. * @param myOut
  109. * output stream to transmit data to the peer. Caller must ensure
  110. * the output is buffered, otherwise write performance may
  111. * suffer.
  112. */
  113. protected final void init(InputStream myIn, OutputStream myOut) {
  114. final int timeout = transport.getTimeout();
  115. if (timeout > 0) {
  116. final Thread caller = Thread.currentThread();
  117. if (myTimer == null) {
  118. myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
  119. }
  120. timeoutIn = new TimeoutInputStream(myIn, myTimer);
  121. timeoutOut = new TimeoutOutputStream(myOut, myTimer);
  122. timeoutIn.setTimeout(timeout * 1000);
  123. timeoutOut.setTimeout(timeout * 1000);
  124. myIn = timeoutIn;
  125. myOut = timeoutOut;
  126. }
  127. in = myIn;
  128. out = myOut;
  129. pckIn = new PacketLineIn(in);
  130. pckOut = new PacketLineOut(out);
  131. outNeedsEnd = true;
  132. }
  133. /**
  134. * Reads the advertised references through the initialized stream.
  135. * <p>
  136. * Subclass implementations may call this method only after setting up the
  137. * input and output streams with {@link #init(InputStream, OutputStream)}.
  138. * <p>
  139. * If any errors occur, this connection is automatically closed by invoking
  140. * {@link #close()} and the exception is wrapped (if necessary) and thrown
  141. * as a {@link org.eclipse.jgit.errors.TransportException}.
  142. *
  143. * @return {@code true} if the refs were read; {@code false} otherwise
  144. * indicating that {@link #lsRefs} must be called
  145. *
  146. * @throws org.eclipse.jgit.errors.TransportException
  147. * the reference list could not be scanned.
  148. */
  149. protected boolean readAdvertisedRefs() throws TransportException {
  150. try {
  151. return readAdvertisedRefsImpl();
  152. } catch (TransportException err) {
  153. close();
  154. throw err;
  155. } catch (IOException | RuntimeException err) {
  156. close();
  157. throw new TransportException(err.getMessage(), err);
  158. }
  159. }
  160. private String readLine() throws IOException {
  161. String line = pckIn.readString();
  162. if (PacketLineIn.isEnd(line)) {
  163. return null;
  164. }
  165. if (line.startsWith("ERR ")) { //$NON-NLS-1$
  166. // This is a customized remote service error.
  167. // Users should be informed about it.
  168. throw new RemoteRepositoryException(uri, line.substring(4));
  169. }
  170. return line;
  171. }
  172. private boolean readAdvertisedRefsImpl() throws IOException {
  173. final Map<String, Ref> avail = new LinkedHashMap<>();
  174. final Map<String, String> symRefs = new LinkedHashMap<>();
  175. for (boolean first = true;; first = false) {
  176. String line;
  177. if (first) {
  178. boolean isV1 = false;
  179. try {
  180. line = readLine();
  181. } catch (EOFException e) {
  182. TransportException noRepo = noRepository();
  183. noRepo.initCause(e);
  184. throw noRepo;
  185. }
  186. if (line != null && VERSION_1.equals(line)) {
  187. // Same as V0, except for this extra line. We shouldn't get
  188. // it since we never request V1.
  189. setProtocolVersion(TransferConfig.ProtocolVersion.V0);
  190. isV1 = true;
  191. line = readLine();
  192. }
  193. if (line == null) {
  194. break;
  195. }
  196. final int nul = line.indexOf('\0');
  197. if (nul >= 0) {
  198. // Protocol V0: The first line (if any) may contain
  199. // "hidden" capability values after a NUL byte.
  200. for (String capability : line.substring(nul + 1)
  201. .split(" ")) { //$NON-NLS-1$
  202. if (capability.startsWith(CAPABILITY_SYMREF_PREFIX)) {
  203. String[] parts = capability
  204. .substring(
  205. CAPABILITY_SYMREF_PREFIX.length())
  206. .split(":", 2); //$NON-NLS-1$
  207. if (parts.length == 2) {
  208. symRefs.put(parts[0], parts[1]);
  209. }
  210. } else {
  211. addCapability(capability);
  212. }
  213. }
  214. line = line.substring(0, nul);
  215. setProtocolVersion(TransferConfig.ProtocolVersion.V0);
  216. } else if (!isV1 && VERSION_2.equals(line)) {
  217. // Protocol V2: remaining lines are capabilities as
  218. // key=value pairs
  219. setProtocolVersion(TransferConfig.ProtocolVersion.V2);
  220. readCapabilitiesV2();
  221. // Break out here so that stateless RPC transports get a
  222. // chance to set up the output stream.
  223. return false;
  224. } else {
  225. setProtocolVersion(TransferConfig.ProtocolVersion.V0);
  226. }
  227. } else {
  228. line = readLine();
  229. if (line == null) {
  230. break;
  231. }
  232. }
  233. // Expecting to get a line in the form "sha1 refname"
  234. if (line.length() < 41 || line.charAt(40) != ' ') {
  235. throw invalidRefAdvertisementLine(line);
  236. }
  237. String name = line.substring(41, line.length());
  238. if (first && name.equals("capabilities^{}")) { //$NON-NLS-1$
  239. // special line from git-receive-pack (protocol V0) to show
  240. // capabilities when there are no refs to advertise
  241. continue;
  242. }
  243. final ObjectId id = toId(line, line.substring(0, 40));
  244. if (name.equals(".have")) { //$NON-NLS-1$
  245. additionalHaves.add(id);
  246. } else {
  247. processLineV1(name, id, avail);
  248. }
  249. }
  250. updateWithSymRefs(avail, symRefs);
  251. available(avail);
  252. return true;
  253. }
  254. /**
  255. * Issue a protocol V2 ls-refs command and read its response.
  256. *
  257. * @param refSpecs
  258. * to produce ref prefixes from if the server supports git
  259. * protocol V2
  260. * @param additionalPatterns
  261. * to use for ref prefixes if the server supports git protocol V2
  262. * @throws TransportException
  263. * if the command could not be run or its output not be read
  264. */
  265. protected void lsRefs(Collection<RefSpec> refSpecs,
  266. String... additionalPatterns) throws TransportException {
  267. try {
  268. lsRefsImpl(refSpecs, additionalPatterns);
  269. } catch (TransportException err) {
  270. close();
  271. throw err;
  272. } catch (IOException | RuntimeException err) {
  273. close();
  274. throw new TransportException(err.getMessage(), err);
  275. }
  276. }
  277. private void lsRefsImpl(Collection<RefSpec> refSpecs,
  278. String... additionalPatterns) throws IOException {
  279. pckOut.writeString("command=" + COMMAND_LS_REFS); //$NON-NLS-1$
  280. // Add the user-agent
  281. String agent = UserAgent.get();
  282. if (agent != null && isCapableOf(OPTION_AGENT)) {
  283. pckOut.writeString(OPTION_AGENT + '=' + agent);
  284. }
  285. pckOut.writeDelim();
  286. pckOut.writeString("peel"); //$NON-NLS-1$
  287. pckOut.writeString("symrefs"); //$NON-NLS-1$
  288. for (String refPrefix : getRefPrefixes(refSpecs, additionalPatterns)) {
  289. pckOut.writeString("ref-prefix " + refPrefix); //$NON-NLS-1$
  290. }
  291. pckOut.end();
  292. final Map<String, Ref> avail = new LinkedHashMap<>();
  293. final Map<String, String> symRefs = new LinkedHashMap<>();
  294. for (;;) {
  295. String line = readLine();
  296. if (line == null) {
  297. break;
  298. }
  299. // Expecting to get a line in the form "sha1 refname"
  300. if (line.length() < 41 || line.charAt(40) != ' ') {
  301. throw invalidRefAdvertisementLine(line);
  302. }
  303. String name = line.substring(41, line.length());
  304. final ObjectId id = toId(line, line.substring(0, 40));
  305. if (name.equals(".have")) { //$NON-NLS-1$
  306. additionalHaves.add(id);
  307. } else {
  308. processLineV2(line, id, name, avail, symRefs);
  309. }
  310. }
  311. updateWithSymRefs(avail, symRefs);
  312. available(avail);
  313. }
  314. private Collection<String> getRefPrefixes(Collection<RefSpec> refSpecs,
  315. String... additionalPatterns) {
  316. if (refSpecs.isEmpty() && (additionalPatterns == null
  317. || additionalPatterns.length == 0)) {
  318. return Collections.emptyList();
  319. }
  320. Set<String> patterns = new HashSet<>();
  321. if (additionalPatterns != null) {
  322. Arrays.stream(additionalPatterns).filter(Objects::nonNull)
  323. .forEach(patterns::add);
  324. }
  325. for (RefSpec spec : refSpecs) {
  326. // TODO: for now we only do protocol V2 for fetch. For push
  327. // RefSpecs, the logic would need to be different. (At the
  328. // minimum, take spec.getDestination().)
  329. String src = spec.getSource();
  330. if (ObjectId.isId(src)) {
  331. continue;
  332. }
  333. if (spec.isWildcard()) {
  334. patterns.add(src.substring(0, src.indexOf('*')));
  335. } else {
  336. patterns.add(src);
  337. patterns.add(Constants.R_REFS + src);
  338. patterns.add(Constants.R_HEADS + src);
  339. patterns.add(Constants.R_TAGS + src);
  340. }
  341. }
  342. return patterns;
  343. }
  344. private void readCapabilitiesV2() throws IOException {
  345. // In git protocol V2, capabilities are different. If it's a key-value
  346. // pair, the key may be a command name, and the value a space-separated
  347. // list of capabilities for that command. We still store it in the same
  348. // map as for protocol v0/v1. Protocol v2 code has to account for this.
  349. for (;;) {
  350. String line = readLine();
  351. if (line == null) {
  352. break;
  353. }
  354. addCapability(line);
  355. }
  356. }
  357. private void addCapability(String capability) {
  358. String parts[] = capability.split("=", 2); //$NON-NLS-1$
  359. if (parts.length == 2) {
  360. remoteCapabilities.put(parts[0], parts[1]);
  361. }
  362. remoteCapabilities.put(capability, null);
  363. }
  364. private ObjectId toId(String line, String value)
  365. throws PackProtocolException {
  366. try {
  367. return ObjectId.fromString(value);
  368. } catch (InvalidObjectIdException e) {
  369. PackProtocolException ppe = invalidRefAdvertisementLine(line);
  370. ppe.initCause(e);
  371. throw ppe;
  372. }
  373. }
  374. private void processLineV1(String name, ObjectId id, Map<String, Ref> avail)
  375. throws IOException {
  376. if (name.endsWith("^{}")) { //$NON-NLS-1$
  377. name = name.substring(0, name.length() - 3);
  378. final Ref prior = avail.get(name);
  379. if (prior == null) {
  380. throw new PackProtocolException(uri, MessageFormat.format(
  381. JGitText.get().advertisementCameBefore, name, name));
  382. }
  383. if (prior.getPeeledObjectId() != null) {
  384. throw duplicateAdvertisement(name + "^{}"); //$NON-NLS-1$
  385. }
  386. avail.put(name, new ObjectIdRef.PeeledTag(Ref.Storage.NETWORK, name,
  387. prior.getObjectId(), id));
  388. } else {
  389. final Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
  390. Ref.Storage.NETWORK, name, id));
  391. if (prior != null) {
  392. throw duplicateAdvertisement(name);
  393. }
  394. }
  395. }
  396. private void processLineV2(String line, ObjectId id, String rest,
  397. Map<String, Ref> avail, Map<String, String> symRefs)
  398. throws IOException {
  399. String[] parts = rest.split(" "); //$NON-NLS-1$
  400. String name = parts[0];
  401. // Two attributes possible, symref-target or peeled
  402. String symRefTarget = null;
  403. String peeled = null;
  404. for (int i = 1; i < parts.length; i++) {
  405. if (parts[i].startsWith(REF_ATTR_SYMREF_TARGET)) {
  406. if (symRefTarget != null) {
  407. throw new PackProtocolException(uri, MessageFormat.format(
  408. JGitText.get().duplicateRefAttribute, line));
  409. }
  410. symRefTarget = parts[i]
  411. .substring(REF_ATTR_SYMREF_TARGET.length());
  412. } else if (parts[i].startsWith(REF_ATTR_PEELED)) {
  413. if (peeled != null) {
  414. throw new PackProtocolException(uri, MessageFormat.format(
  415. JGitText.get().duplicateRefAttribute, line));
  416. }
  417. peeled = parts[i].substring(REF_ATTR_PEELED.length());
  418. }
  419. if (peeled != null && symRefTarget != null) {
  420. break;
  421. }
  422. }
  423. Ref idRef;
  424. if (peeled != null) {
  425. idRef = new ObjectIdRef.PeeledTag(Ref.Storage.NETWORK, name, id,
  426. toId(line, peeled));
  427. } else {
  428. idRef = new ObjectIdRef.PeeledNonTag(Ref.Storage.NETWORK, name, id);
  429. }
  430. Ref prior = avail.put(name, idRef);
  431. if (prior != null) {
  432. throw duplicateAdvertisement(name);
  433. }
  434. if (!StringUtils.isEmptyOrNull(symRefTarget)) {
  435. symRefs.put(name, symRefTarget);
  436. }
  437. }
  438. /**
  439. * Updates the given refMap with {@link SymbolicRef}s defined by the given
  440. * symRefs.
  441. * <p>
  442. * For each entry, symRef, in symRefs, whose value is a key in refMap, adds
  443. * a new entry to refMap with that same key and value of a new
  444. * {@link SymbolicRef} with source=symRef.key and
  445. * target=refMap.get(symRef.value), then removes that entry from symRefs.
  446. * <p>
  447. * If refMap already contains an entry for symRef.key, it is replaced.
  448. * </p>
  449. * </p>
  450. * <p>
  451. * For example, given:
  452. * </p>
  453. *
  454. * <pre>
  455. * refMap.put("refs/heads/main", ref);
  456. * symRefs.put("HEAD", "refs/heads/main");
  457. * </pre>
  458. *
  459. * then:
  460. *
  461. * <pre>
  462. * updateWithSymRefs(refMap, symRefs);
  463. * </pre>
  464. *
  465. * has the <em>effect</em> of:
  466. *
  467. * <pre>
  468. * refMap.put("HEAD",
  469. * new SymbolicRef("HEAD", refMap.get(symRefs.remove("HEAD"))))
  470. * </pre>
  471. * <p>
  472. * Any entry in symRefs whose value is not a key in refMap is ignored. Any
  473. * circular symRefs are ignored.
  474. * </p>
  475. * <p>
  476. * Upon completion, symRefs will contain only any unresolvable entries.
  477. * </p>
  478. *
  479. * @param refMap
  480. * a non-null, modifiable, Map to update, and the provider of
  481. * symref targets.
  482. * @param symRefs
  483. * a non-null, modifiable, Map of symrefs.
  484. * @throws NullPointerException
  485. * if refMap or symRefs is null
  486. */
  487. static void updateWithSymRefs(Map<String, Ref> refMap, Map<String, String> symRefs) {
  488. boolean haveNewRefMapEntries = !refMap.isEmpty();
  489. while (!symRefs.isEmpty() && haveNewRefMapEntries) {
  490. haveNewRefMapEntries = false;
  491. final Iterator<Map.Entry<String, String>> iterator = symRefs.entrySet().iterator();
  492. while (iterator.hasNext()) {
  493. final Map.Entry<String, String> symRef = iterator.next();
  494. if (!symRefs.containsKey(symRef.getValue())) { // defer forward reference
  495. final Ref r = refMap.get(symRef.getValue());
  496. if (r != null) {
  497. refMap.put(symRef.getKey(), new SymbolicRef(symRef.getKey(), r));
  498. haveNewRefMapEntries = true;
  499. iterator.remove();
  500. }
  501. }
  502. }
  503. }
  504. // If HEAD is still in the symRefs map here, the real ref was not
  505. // reported, but we know it must point to the object reported for HEAD.
  506. // So fill it in in the refMap.
  507. String headRefName = symRefs.get(Constants.HEAD);
  508. if (headRefName != null && !refMap.containsKey(headRefName)) {
  509. Ref headRef = refMap.get(Constants.HEAD);
  510. if (headRef != null) {
  511. ObjectId headObj = headRef.getObjectId();
  512. headRef = new ObjectIdRef.PeeledNonTag(Ref.Storage.NETWORK,
  513. headRefName, headObj);
  514. refMap.put(headRefName, headRef);
  515. headRef = new SymbolicRef(Constants.HEAD, headRef);
  516. refMap.put(Constants.HEAD, headRef);
  517. symRefs.remove(Constants.HEAD);
  518. }
  519. }
  520. }
  521. /**
  522. * Create an exception to indicate problems finding a remote repository. The
  523. * caller is expected to throw the returned exception.
  524. *
  525. * Subclasses may override this method to provide better diagnostics.
  526. *
  527. * @return a TransportException saying a repository cannot be found and
  528. * possibly why.
  529. */
  530. protected TransportException noRepository() {
  531. return new NoRemoteRepositoryException(uri, JGitText.get().notFound);
  532. }
  533. /**
  534. * Whether this option is supported
  535. *
  536. * @param option
  537. * option string
  538. * @return whether this option is supported
  539. */
  540. protected boolean isCapableOf(String option) {
  541. return remoteCapabilities.containsKey(option);
  542. }
  543. /**
  544. * Request capability
  545. *
  546. * @param b
  547. * buffer
  548. * @param option
  549. * option we want
  550. * @return {@code true} if the requested option is supported
  551. */
  552. protected boolean wantCapability(StringBuilder b, String option) {
  553. if (!isCapableOf(option))
  554. return false;
  555. b.append(' ');
  556. b.append(option);
  557. return true;
  558. }
  559. /**
  560. * Return a capability value.
  561. *
  562. * @param option
  563. * to get
  564. * @return the value stored, if any.
  565. */
  566. protected String getCapability(String option) {
  567. return remoteCapabilities.get(option);
  568. }
  569. /**
  570. * Add user agent capability
  571. *
  572. * @param b
  573. * a {@link java.lang.StringBuilder} object.
  574. */
  575. protected void addUserAgentCapability(StringBuilder b) {
  576. String a = UserAgent.get();
  577. if (a != null && remoteCapabilities.get(OPTION_AGENT) != null) {
  578. b.append(' ').append(OPTION_AGENT).append('=').append(a);
  579. }
  580. }
  581. /** {@inheritDoc} */
  582. @Override
  583. public String getPeerUserAgent() {
  584. String agent = remoteCapabilities.get(OPTION_AGENT);
  585. return agent != null ? agent : super.getPeerUserAgent();
  586. }
  587. private PackProtocolException duplicateAdvertisement(String name) {
  588. return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name));
  589. }
  590. private PackProtocolException invalidRefAdvertisementLine(String line) {
  591. return new PackProtocolException(uri, MessageFormat.format(JGitText.get().invalidRefAdvertisementLine, line));
  592. }
  593. /** {@inheritDoc} */
  594. @Override
  595. public void close() {
  596. if (out != null) {
  597. try {
  598. if (outNeedsEnd) {
  599. outNeedsEnd = false;
  600. pckOut.end();
  601. }
  602. out.close();
  603. } catch (IOException err) {
  604. // Ignore any close errors.
  605. } finally {
  606. out = null;
  607. pckOut = null;
  608. }
  609. }
  610. if (in != null) {
  611. try {
  612. in.close();
  613. } catch (IOException err) {
  614. // Ignore any close errors.
  615. } finally {
  616. in = null;
  617. pckIn = null;
  618. }
  619. }
  620. if (myTimer != null) {
  621. try {
  622. myTimer.terminate();
  623. } finally {
  624. myTimer = null;
  625. timeoutIn = null;
  626. timeoutOut = null;
  627. }
  628. }
  629. }
  630. /**
  631. * Tell the peer we are disconnecting, if it cares to know.
  632. */
  633. protected void endOut() {
  634. if (outNeedsEnd && out != null) {
  635. try {
  636. outNeedsEnd = false;
  637. pckOut.end();
  638. } catch (IOException e) {
  639. try {
  640. out.close();
  641. } catch (IOException err) {
  642. // Ignore any close errors.
  643. } finally {
  644. out = null;
  645. pckOut = null;
  646. }
  647. }
  648. }
  649. }
  650. }