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.

FetchProcess.java 14KB

Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*
  2. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  4. * and other copyright owners as documented in the project's IP log.
  5. *
  6. * This program and the accompanying materials are made available
  7. * under the terms of the Eclipse Distribution License v1.0 which
  8. * accompanies this distribution, is reproduced below, and is
  9. * available at http://www.eclipse.org/org/documents/edl-v10.php
  10. *
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or
  14. * without modification, are permitted provided that the following
  15. * conditions are met:
  16. *
  17. * - Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. *
  20. * - Redistributions in binary form must reproduce the above
  21. * copyright notice, this list of conditions and the following
  22. * disclaimer in the documentation and/or other materials provided
  23. * with the distribution.
  24. *
  25. * - Neither the name of the Eclipse Foundation, Inc. nor the
  26. * names of its contributors may be used to endorse or promote
  27. * products derived from this software without specific prior
  28. * written permission.
  29. *
  30. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  31. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  32. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  33. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  34. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  35. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  36. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  37. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  38. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  39. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  41. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  42. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  43. */
  44. package org.eclipse.jgit.transport;
  45. import java.io.File;
  46. import java.io.IOException;
  47. import java.io.OutputStreamWriter;
  48. import java.io.Writer;
  49. import java.util.ArrayList;
  50. import java.util.Collection;
  51. import java.util.Collections;
  52. import java.util.HashMap;
  53. import java.util.HashSet;
  54. import java.util.Iterator;
  55. import java.util.Map;
  56. import java.util.Set;
  57. import org.eclipse.jgit.errors.MissingObjectException;
  58. import org.eclipse.jgit.errors.NotSupportedException;
  59. import org.eclipse.jgit.errors.TransportException;
  60. import org.eclipse.jgit.lib.Constants;
  61. import org.eclipse.jgit.lib.LockFile;
  62. import org.eclipse.jgit.lib.ObjectId;
  63. import org.eclipse.jgit.lib.PackLock;
  64. import org.eclipse.jgit.lib.ProgressMonitor;
  65. import org.eclipse.jgit.lib.Ref;
  66. import org.eclipse.jgit.lib.Repository;
  67. import org.eclipse.jgit.revwalk.ObjectWalk;
  68. import org.eclipse.jgit.revwalk.RevWalk;
  69. class FetchProcess {
  70. /** Transport we will fetch over. */
  71. private final Transport transport;
  72. /** List of things we want to fetch from the remote repository. */
  73. private final Collection<RefSpec> toFetch;
  74. /** Set of refs we will actually wind up asking to obtain. */
  75. private final HashMap<ObjectId, Ref> askFor = new HashMap<ObjectId, Ref>();
  76. /** Objects we know we have locally. */
  77. private final HashSet<ObjectId> have = new HashSet<ObjectId>();
  78. /** Updates to local tracking branches (if any). */
  79. private final ArrayList<TrackingRefUpdate> localUpdates = new ArrayList<TrackingRefUpdate>();
  80. /** Records to be recorded into FETCH_HEAD. */
  81. private final ArrayList<FetchHeadRecord> fetchHeadUpdates = new ArrayList<FetchHeadRecord>();
  82. private final ArrayList<PackLock> packLocks = new ArrayList<PackLock>();
  83. private FetchConnection conn;
  84. FetchProcess(final Transport t, final Collection<RefSpec> f) {
  85. transport = t;
  86. toFetch = f;
  87. }
  88. void execute(final ProgressMonitor monitor, final FetchResult result)
  89. throws NotSupportedException, TransportException {
  90. askFor.clear();
  91. localUpdates.clear();
  92. fetchHeadUpdates.clear();
  93. packLocks.clear();
  94. try {
  95. executeImp(monitor, result);
  96. } finally {
  97. for (final PackLock lock : packLocks)
  98. lock.unlock();
  99. }
  100. }
  101. private void executeImp(final ProgressMonitor monitor,
  102. final FetchResult result) throws NotSupportedException,
  103. TransportException {
  104. conn = transport.openFetch();
  105. try {
  106. result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap());
  107. final Set<Ref> matched = new HashSet<Ref>();
  108. for (final RefSpec spec : toFetch) {
  109. if (spec.getSource() == null)
  110. throw new TransportException(
  111. "Source ref not specified for refspec: " + spec);
  112. if (spec.isWildcard())
  113. expandWildcard(spec, matched);
  114. else
  115. expandSingle(spec, matched);
  116. }
  117. Collection<Ref> additionalTags = Collections.<Ref> emptyList();
  118. final TagOpt tagopt = transport.getTagOpt();
  119. if (tagopt == TagOpt.AUTO_FOLLOW)
  120. additionalTags = expandAutoFollowTags();
  121. else if (tagopt == TagOpt.FETCH_TAGS)
  122. expandFetchTags();
  123. final boolean includedTags;
  124. if (!askFor.isEmpty() && !askForIsComplete()) {
  125. fetchObjects(monitor);
  126. includedTags = conn.didFetchIncludeTags();
  127. // Connection was used for object transfer. If we
  128. // do another fetch we must open a new connection.
  129. //
  130. closeConnection(result);
  131. } else {
  132. includedTags = false;
  133. }
  134. if (tagopt == TagOpt.AUTO_FOLLOW && !additionalTags.isEmpty()) {
  135. // There are more tags that we want to follow, but
  136. // not all were asked for on the initial request.
  137. //
  138. have.addAll(askFor.keySet());
  139. askFor.clear();
  140. for (final Ref r : additionalTags) {
  141. final ObjectId id = r.getPeeledObjectId();
  142. if (id == null || transport.local.hasObject(id))
  143. wantTag(r);
  144. }
  145. if (!askFor.isEmpty() && (!includedTags || !askForIsComplete())) {
  146. reopenConnection();
  147. if (!askFor.isEmpty())
  148. fetchObjects(monitor);
  149. }
  150. }
  151. } finally {
  152. closeConnection(result);
  153. }
  154. final RevWalk walk = new RevWalk(transport.local);
  155. if (transport.isRemoveDeletedRefs())
  156. deleteStaleTrackingRefs(result, walk);
  157. for (TrackingRefUpdate u : localUpdates) {
  158. try {
  159. u.update(walk);
  160. result.add(u);
  161. } catch (IOException err) {
  162. throw new TransportException("Failure updating tracking ref "
  163. + u.getLocalName() + ": " + err.getMessage(), err);
  164. }
  165. }
  166. if (!fetchHeadUpdates.isEmpty()) {
  167. try {
  168. updateFETCH_HEAD(result);
  169. } catch (IOException err) {
  170. throw new TransportException("Failure updating FETCH_HEAD: "
  171. + err.getMessage(), err);
  172. }
  173. }
  174. }
  175. private void fetchObjects(final ProgressMonitor monitor)
  176. throws TransportException {
  177. try {
  178. conn.setPackLockMessage("jgit fetch " + transport.uri);
  179. conn.fetch(monitor, askFor.values(), have);
  180. } finally {
  181. packLocks.addAll(conn.getPackLocks());
  182. }
  183. if (transport.isCheckFetchedObjects()
  184. && !conn.didFetchTestConnectivity() && !askForIsComplete())
  185. throw new TransportException(transport.getURI(),
  186. "peer did not supply a complete object graph");
  187. }
  188. private void closeConnection(final FetchResult result) {
  189. if (conn != null) {
  190. conn.close();
  191. result.addMessages(conn.getMessages());
  192. conn = null;
  193. }
  194. }
  195. private void reopenConnection() throws NotSupportedException,
  196. TransportException {
  197. if (conn != null)
  198. return;
  199. conn = transport.openFetch();
  200. // Since we opened a new connection we cannot be certain
  201. // that the system we connected to has the same exact set
  202. // of objects available (think round-robin DNS and mirrors
  203. // that aren't updated at the same time).
  204. //
  205. // We rebuild our askFor list using only the refs that the
  206. // new connection has offered to us.
  207. //
  208. final HashMap<ObjectId, Ref> avail = new HashMap<ObjectId, Ref>();
  209. for (final Ref r : conn.getRefs())
  210. avail.put(r.getObjectId(), r);
  211. final Collection<Ref> wants = new ArrayList<Ref>(askFor.values());
  212. askFor.clear();
  213. for (final Ref want : wants) {
  214. final Ref newRef = avail.get(want.getObjectId());
  215. if (newRef != null) {
  216. askFor.put(newRef.getObjectId(), newRef);
  217. } else {
  218. removeFetchHeadRecord(want.getObjectId());
  219. removeTrackingRefUpdate(want.getObjectId());
  220. }
  221. }
  222. }
  223. private void removeTrackingRefUpdate(final ObjectId want) {
  224. final Iterator<TrackingRefUpdate> i = localUpdates.iterator();
  225. while (i.hasNext()) {
  226. final TrackingRefUpdate u = i.next();
  227. if (u.getNewObjectId().equals(want))
  228. i.remove();
  229. }
  230. }
  231. private void removeFetchHeadRecord(final ObjectId want) {
  232. final Iterator<FetchHeadRecord> i = fetchHeadUpdates.iterator();
  233. while (i.hasNext()) {
  234. final FetchHeadRecord fh = i.next();
  235. if (fh.newValue.equals(want))
  236. i.remove();
  237. }
  238. }
  239. private void updateFETCH_HEAD(final FetchResult result) throws IOException {
  240. final LockFile lock = new LockFile(new File(transport.local
  241. .getDirectory(), "FETCH_HEAD"));
  242. try {
  243. if (lock.lock()) {
  244. final Writer w = new OutputStreamWriter(lock.getOutputStream());
  245. try {
  246. for (final FetchHeadRecord h : fetchHeadUpdates) {
  247. h.write(w);
  248. result.add(h);
  249. }
  250. } finally {
  251. w.close();
  252. }
  253. lock.commit();
  254. }
  255. } finally {
  256. lock.unlock();
  257. }
  258. }
  259. private boolean askForIsComplete() throws TransportException {
  260. try {
  261. final ObjectWalk ow = new ObjectWalk(transport.local);
  262. for (final ObjectId want : askFor.keySet())
  263. ow.markStart(ow.parseAny(want));
  264. for (final Ref ref : transport.local.getAllRefs().values())
  265. ow.markUninteresting(ow.parseAny(ref.getObjectId()));
  266. ow.checkConnectivity();
  267. return true;
  268. } catch (MissingObjectException e) {
  269. return false;
  270. } catch (IOException e) {
  271. throw new TransportException("Unable to check connectivity.", e);
  272. }
  273. }
  274. private void expandWildcard(final RefSpec spec, final Set<Ref> matched)
  275. throws TransportException {
  276. for (final Ref src : conn.getRefs()) {
  277. if (spec.matchSource(src) && matched.add(src))
  278. want(src, spec.expandFromSource(src));
  279. }
  280. }
  281. private void expandSingle(final RefSpec spec, final Set<Ref> matched)
  282. throws TransportException {
  283. final Ref src = conn.getRef(spec.getSource());
  284. if (src == null) {
  285. throw new TransportException("Remote does not have "
  286. + spec.getSource() + " available for fetch.");
  287. }
  288. if (matched.add(src))
  289. want(src, spec);
  290. }
  291. private Collection<Ref> expandAutoFollowTags() throws TransportException {
  292. final Collection<Ref> additionalTags = new ArrayList<Ref>();
  293. final Map<String, Ref> haveRefs = transport.local.getAllRefs();
  294. for (final Ref r : conn.getRefs()) {
  295. if (!isTag(r))
  296. continue;
  297. if (r.getPeeledObjectId() == null) {
  298. additionalTags.add(r);
  299. continue;
  300. }
  301. final Ref local = haveRefs.get(r.getName());
  302. if (local != null) {
  303. if (!r.getObjectId().equals(local.getObjectId()))
  304. wantTag(r);
  305. } else if (askFor.containsKey(r.getPeeledObjectId())
  306. || transport.local.hasObject(r.getPeeledObjectId()))
  307. wantTag(r);
  308. else
  309. additionalTags.add(r);
  310. }
  311. return additionalTags;
  312. }
  313. private void expandFetchTags() throws TransportException {
  314. final Map<String, Ref> haveRefs = transport.local.getAllRefs();
  315. for (final Ref r : conn.getRefs()) {
  316. if (!isTag(r))
  317. continue;
  318. final Ref local = haveRefs.get(r.getName());
  319. if (local == null || !r.getObjectId().equals(local.getObjectId()))
  320. wantTag(r);
  321. }
  322. }
  323. private void wantTag(final Ref r) throws TransportException {
  324. want(r, new RefSpec().setSource(r.getName())
  325. .setDestination(r.getName()));
  326. }
  327. private void want(final Ref src, final RefSpec spec)
  328. throws TransportException {
  329. final ObjectId newId = src.getObjectId();
  330. if (spec.getDestination() != null) {
  331. try {
  332. final TrackingRefUpdate tru = createUpdate(spec, newId);
  333. if (newId.equals(tru.getOldObjectId()))
  334. return;
  335. localUpdates.add(tru);
  336. } catch (IOException err) {
  337. // Bad symbolic ref? That is the most likely cause.
  338. //
  339. throw new TransportException("Cannot resolve"
  340. + " local tracking ref " + spec.getDestination()
  341. + " for updating.", err);
  342. }
  343. }
  344. askFor.put(newId, src);
  345. final FetchHeadRecord fhr = new FetchHeadRecord();
  346. fhr.newValue = newId;
  347. fhr.notForMerge = spec.getDestination() != null;
  348. fhr.sourceName = src.getName();
  349. fhr.sourceURI = transport.getURI();
  350. fetchHeadUpdates.add(fhr);
  351. }
  352. private TrackingRefUpdate createUpdate(final RefSpec spec,
  353. final ObjectId newId) throws IOException {
  354. return new TrackingRefUpdate(transport.local, spec, newId, "fetch");
  355. }
  356. private void deleteStaleTrackingRefs(final FetchResult result,
  357. final RevWalk walk) throws TransportException {
  358. final Repository db = transport.local;
  359. for (final Ref ref : db.getAllRefs().values()) {
  360. final String refname = ref.getName();
  361. for (final RefSpec spec : toFetch) {
  362. if (spec.matchDestination(refname)) {
  363. final RefSpec s = spec.expandFromDestination(refname);
  364. if (result.getAdvertisedRef(s.getSource()) == null) {
  365. deleteTrackingRef(result, db, walk, s, ref);
  366. }
  367. }
  368. }
  369. }
  370. }
  371. private void deleteTrackingRef(final FetchResult result,
  372. final Repository db, final RevWalk walk, final RefSpec spec,
  373. final Ref localRef) throws TransportException {
  374. final String name = localRef.getName();
  375. try {
  376. final TrackingRefUpdate u = new TrackingRefUpdate(db, name, spec
  377. .getSource(), true, ObjectId.zeroId(), "deleted");
  378. result.add(u);
  379. if (transport.isDryRun()){
  380. return;
  381. }
  382. u.delete(walk);
  383. switch (u.getResult()) {
  384. case NEW:
  385. case NO_CHANGE:
  386. case FAST_FORWARD:
  387. case FORCED:
  388. break;
  389. default:
  390. throw new TransportException(transport.getURI(),
  391. "Cannot delete stale tracking ref " + name + ": "
  392. + u.getResult().name());
  393. }
  394. } catch (IOException e) {
  395. throw new TransportException(transport.getURI(),
  396. "Cannot delete stale tracking ref " + name, e);
  397. }
  398. }
  399. private static boolean isTag(final Ref r) {
  400. return isTag(r.getName());
  401. }
  402. private static boolean isTag(final String name) {
  403. return name.startsWith(Constants.R_TAGS);
  404. }
  405. }