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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. /*
  2. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  4. *
  5. * This program and the accompanying materials are made available under the
  6. * terms of the Eclipse Distribution License v. 1.0 which is available at
  7. * https://www.eclipse.org/org/documents/edl-v10.php.
  8. *
  9. * SPDX-License-Identifier: BSD-3-Clause
  10. */
  11. package org.eclipse.jgit.transport;
  12. import static java.nio.charset.StandardCharsets.UTF_8;
  13. import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
  14. import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
  15. import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
  16. import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
  17. import java.io.File;
  18. import java.io.IOException;
  19. import java.io.OutputStreamWriter;
  20. import java.io.Writer;
  21. import java.text.MessageFormat;
  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.Collections;
  25. import java.util.HashMap;
  26. import java.util.HashSet;
  27. import java.util.Iterator;
  28. import java.util.Map;
  29. import java.util.Set;
  30. import java.util.concurrent.TimeUnit;
  31. import org.eclipse.jgit.errors.MissingObjectException;
  32. import org.eclipse.jgit.errors.NotSupportedException;
  33. import org.eclipse.jgit.errors.TransportException;
  34. import org.eclipse.jgit.internal.JGitText;
  35. import org.eclipse.jgit.internal.storage.file.LockFile;
  36. import org.eclipse.jgit.internal.storage.file.PackLock;
  37. import org.eclipse.jgit.lib.BatchRefUpdate;
  38. import org.eclipse.jgit.lib.BatchingProgressMonitor;
  39. import org.eclipse.jgit.lib.Constants;
  40. import org.eclipse.jgit.lib.ObjectId;
  41. import org.eclipse.jgit.lib.ObjectIdRef;
  42. import org.eclipse.jgit.lib.ProgressMonitor;
  43. import org.eclipse.jgit.lib.Ref;
  44. import org.eclipse.jgit.lib.RefDatabase;
  45. import org.eclipse.jgit.revwalk.ObjectWalk;
  46. import org.eclipse.jgit.revwalk.RevWalk;
  47. class FetchProcess {
  48. /** Transport we will fetch over. */
  49. private final Transport transport;
  50. /** List of things we want to fetch from the remote repository. */
  51. private final Collection<RefSpec> toFetch;
  52. /** Set of refs we will actually wind up asking to obtain. */
  53. private final HashMap<ObjectId, Ref> askFor = new HashMap<>();
  54. /** Objects we know we have locally. */
  55. private final HashSet<ObjectId> have = new HashSet<>();
  56. /** Updates to local tracking branches (if any). */
  57. private final ArrayList<TrackingRefUpdate> localUpdates = new ArrayList<>();
  58. /** Records to be recorded into FETCH_HEAD. */
  59. private final ArrayList<FetchHeadRecord> fetchHeadUpdates = new ArrayList<>();
  60. private final ArrayList<PackLock> packLocks = new ArrayList<>();
  61. private FetchConnection conn;
  62. private Map<String, Ref> localRefs;
  63. FetchProcess(Transport t, Collection<RefSpec> f) {
  64. transport = t;
  65. toFetch = f;
  66. }
  67. void execute(ProgressMonitor monitor, FetchResult result)
  68. throws NotSupportedException, TransportException {
  69. askFor.clear();
  70. localUpdates.clear();
  71. fetchHeadUpdates.clear();
  72. packLocks.clear();
  73. localRefs = null;
  74. try {
  75. executeImp(monitor, result);
  76. } finally {
  77. try {
  78. for (PackLock lock : packLocks)
  79. lock.unlock();
  80. } catch (IOException e) {
  81. throw new TransportException(e.getMessage(), e);
  82. }
  83. }
  84. }
  85. private void executeImp(final ProgressMonitor monitor,
  86. final FetchResult result) throws NotSupportedException,
  87. TransportException {
  88. conn = transport.openFetch();
  89. try {
  90. result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap());
  91. result.peerUserAgent = conn.getPeerUserAgent();
  92. final Set<Ref> matched = new HashSet<>();
  93. for (RefSpec spec : toFetch) {
  94. if (spec.getSource() == null)
  95. throw new TransportException(MessageFormat.format(
  96. JGitText.get().sourceRefNotSpecifiedForRefspec, spec));
  97. if (spec.isWildcard())
  98. expandWildcard(spec, matched);
  99. else
  100. expandSingle(spec, matched);
  101. }
  102. Collection<Ref> additionalTags = Collections.<Ref> emptyList();
  103. final TagOpt tagopt = transport.getTagOpt();
  104. if (tagopt == TagOpt.AUTO_FOLLOW)
  105. additionalTags = expandAutoFollowTags();
  106. else if (tagopt == TagOpt.FETCH_TAGS)
  107. expandFetchTags();
  108. final boolean includedTags;
  109. if (!askFor.isEmpty() && !askForIsComplete()) {
  110. fetchObjects(monitor);
  111. includedTags = conn.didFetchIncludeTags();
  112. // Connection was used for object transfer. If we
  113. // do another fetch we must open a new connection.
  114. //
  115. closeConnection(result);
  116. } else {
  117. includedTags = false;
  118. }
  119. if (tagopt == TagOpt.AUTO_FOLLOW && !additionalTags.isEmpty()) {
  120. // There are more tags that we want to follow, but
  121. // not all were asked for on the initial request.
  122. //
  123. have.addAll(askFor.keySet());
  124. askFor.clear();
  125. for (Ref r : additionalTags) {
  126. ObjectId id = r.getPeeledObjectId();
  127. if (id == null)
  128. id = r.getObjectId();
  129. if (localHasObject(id))
  130. wantTag(r);
  131. }
  132. if (!askFor.isEmpty() && (!includedTags || !askForIsComplete())) {
  133. reopenConnection();
  134. if (!askFor.isEmpty())
  135. fetchObjects(monitor);
  136. }
  137. }
  138. } finally {
  139. closeConnection(result);
  140. }
  141. BatchRefUpdate batch = transport.local.getRefDatabase()
  142. .newBatchUpdate()
  143. .setAllowNonFastForwards(true)
  144. .setRefLogMessage("fetch", true); //$NON-NLS-1$
  145. try (RevWalk walk = new RevWalk(transport.local)) {
  146. walk.setRetainBody(false);
  147. if (monitor instanceof BatchingProgressMonitor) {
  148. ((BatchingProgressMonitor) monitor).setDelayStart(
  149. 250, TimeUnit.MILLISECONDS);
  150. }
  151. if (transport.isRemoveDeletedRefs()) {
  152. deleteStaleTrackingRefs(result, batch);
  153. }
  154. addUpdateBatchCommands(result, batch);
  155. for (ReceiveCommand cmd : batch.getCommands()) {
  156. cmd.updateType(walk);
  157. if (cmd.getType() == UPDATE_NONFASTFORWARD
  158. && cmd instanceof TrackingRefUpdate.Command
  159. && !((TrackingRefUpdate.Command) cmd).canForceUpdate())
  160. cmd.setResult(REJECTED_NONFASTFORWARD);
  161. }
  162. if (transport.isDryRun()) {
  163. for (ReceiveCommand cmd : batch.getCommands()) {
  164. if (cmd.getResult() == NOT_ATTEMPTED)
  165. cmd.setResult(OK);
  166. }
  167. } else {
  168. batch.execute(walk, monitor);
  169. }
  170. } catch (TransportException e) {
  171. throw e;
  172. } catch (IOException err) {
  173. throw new TransportException(MessageFormat.format(
  174. JGitText.get().failureUpdatingTrackingRef,
  175. getFirstFailedRefName(batch), err.getMessage()), err);
  176. }
  177. if (!fetchHeadUpdates.isEmpty()) {
  178. try {
  179. updateFETCH_HEAD(result);
  180. } catch (IOException err) {
  181. throw new TransportException(MessageFormat.format(
  182. JGitText.get().failureUpdatingFETCH_HEAD, err.getMessage()), err);
  183. }
  184. }
  185. }
  186. private void addUpdateBatchCommands(FetchResult result,
  187. BatchRefUpdate batch) throws TransportException {
  188. Map<String, ObjectId> refs = new HashMap<>();
  189. for (TrackingRefUpdate u : localUpdates) {
  190. // Try to skip duplicates if they'd update to the same object ID
  191. ObjectId existing = refs.get(u.getLocalName());
  192. if (existing == null) {
  193. refs.put(u.getLocalName(), u.getNewObjectId());
  194. result.add(u);
  195. batch.addCommand(u.asReceiveCommand());
  196. } else if (!existing.equals(u.getNewObjectId())) {
  197. throw new TransportException(MessageFormat
  198. .format(JGitText.get().duplicateRef, u.getLocalName()));
  199. }
  200. }
  201. }
  202. private void fetchObjects(ProgressMonitor monitor)
  203. throws TransportException {
  204. try {
  205. conn.setPackLockMessage("jgit fetch " + transport.uri); //$NON-NLS-1$
  206. conn.fetch(monitor, askFor.values(), have);
  207. } finally {
  208. packLocks.addAll(conn.getPackLocks());
  209. }
  210. if (transport.isCheckFetchedObjects()
  211. && !conn.didFetchTestConnectivity() && !askForIsComplete())
  212. throw new TransportException(transport.getURI(),
  213. JGitText.get().peerDidNotSupplyACompleteObjectGraph);
  214. }
  215. private void closeConnection(FetchResult result) {
  216. if (conn != null) {
  217. conn.close();
  218. result.addMessages(conn.getMessages());
  219. conn = null;
  220. }
  221. }
  222. private void reopenConnection() throws NotSupportedException,
  223. TransportException {
  224. if (conn != null)
  225. return;
  226. conn = transport.openFetch();
  227. // Since we opened a new connection we cannot be certain
  228. // that the system we connected to has the same exact set
  229. // of objects available (think round-robin DNS and mirrors
  230. // that aren't updated at the same time).
  231. //
  232. // We rebuild our askFor list using only the refs that the
  233. // new connection has offered to us.
  234. //
  235. final HashMap<ObjectId, Ref> avail = new HashMap<>();
  236. for (Ref r : conn.getRefs())
  237. avail.put(r.getObjectId(), r);
  238. final Collection<Ref> wants = new ArrayList<>(askFor.values());
  239. askFor.clear();
  240. for (Ref want : wants) {
  241. final Ref newRef = avail.get(want.getObjectId());
  242. if (newRef != null) {
  243. askFor.put(newRef.getObjectId(), newRef);
  244. } else {
  245. removeFetchHeadRecord(want.getObjectId());
  246. removeTrackingRefUpdate(want.getObjectId());
  247. }
  248. }
  249. }
  250. private void removeTrackingRefUpdate(ObjectId want) {
  251. final Iterator<TrackingRefUpdate> i = localUpdates.iterator();
  252. while (i.hasNext()) {
  253. final TrackingRefUpdate u = i.next();
  254. if (u.getNewObjectId().equals(want))
  255. i.remove();
  256. }
  257. }
  258. private void removeFetchHeadRecord(ObjectId want) {
  259. final Iterator<FetchHeadRecord> i = fetchHeadUpdates.iterator();
  260. while (i.hasNext()) {
  261. final FetchHeadRecord fh = i.next();
  262. if (fh.newValue.equals(want))
  263. i.remove();
  264. }
  265. }
  266. private void updateFETCH_HEAD(FetchResult result) throws IOException {
  267. File meta = transport.local.getDirectory();
  268. if (meta == null)
  269. return;
  270. final LockFile lock = new LockFile(new File(meta, "FETCH_HEAD")); //$NON-NLS-1$
  271. try {
  272. if (lock.lock()) {
  273. try (Writer w = new OutputStreamWriter(
  274. lock.getOutputStream(), UTF_8)) {
  275. for (FetchHeadRecord h : fetchHeadUpdates) {
  276. h.write(w);
  277. result.add(h);
  278. }
  279. }
  280. lock.commit();
  281. }
  282. } finally {
  283. lock.unlock();
  284. }
  285. }
  286. private boolean askForIsComplete() throws TransportException {
  287. try {
  288. try (ObjectWalk ow = new ObjectWalk(transport.local)) {
  289. for (ObjectId want : askFor.keySet())
  290. ow.markStart(ow.parseAny(want));
  291. for (Ref ref : localRefs().values())
  292. ow.markUninteresting(ow.parseAny(ref.getObjectId()));
  293. ow.checkConnectivity();
  294. }
  295. return true;
  296. } catch (MissingObjectException e) {
  297. return false;
  298. } catch (IOException e) {
  299. throw new TransportException(JGitText.get().unableToCheckConnectivity, e);
  300. }
  301. }
  302. private void expandWildcard(RefSpec spec, Set<Ref> matched)
  303. throws TransportException {
  304. for (Ref src : conn.getRefs()) {
  305. if (spec.matchSource(src) && matched.add(src))
  306. want(src, spec.expandFromSource(src));
  307. }
  308. }
  309. private void expandSingle(RefSpec spec, Set<Ref> matched)
  310. throws TransportException {
  311. String want = spec.getSource();
  312. if (ObjectId.isId(want)) {
  313. want(ObjectId.fromString(want));
  314. return;
  315. }
  316. Ref src = conn.getRef(want);
  317. if (src == null) {
  318. throw new TransportException(MessageFormat.format(JGitText.get().remoteDoesNotHaveSpec, want));
  319. }
  320. if (matched.add(src)) {
  321. want(src, spec);
  322. }
  323. }
  324. private boolean localHasObject(ObjectId id) throws TransportException {
  325. try {
  326. return transport.local.getObjectDatabase().has(id);
  327. } catch (IOException err) {
  328. throw new TransportException(
  329. MessageFormat.format(
  330. JGitText.get().readingObjectsFromLocalRepositoryFailed,
  331. err.getMessage()),
  332. err);
  333. }
  334. }
  335. private Collection<Ref> expandAutoFollowTags() throws TransportException {
  336. final Collection<Ref> additionalTags = new ArrayList<>();
  337. final Map<String, Ref> haveRefs = localRefs();
  338. for (Ref r : conn.getRefs()) {
  339. if (!isTag(r))
  340. continue;
  341. Ref local = haveRefs.get(r.getName());
  342. if (local != null)
  343. // We already have a tag with this name, don't fetch it (even if
  344. // the local is different).
  345. continue;
  346. ObjectId obj = r.getPeeledObjectId();
  347. if (obj == null)
  348. obj = r.getObjectId();
  349. if (askFor.containsKey(obj) || localHasObject(obj))
  350. wantTag(r);
  351. else
  352. additionalTags.add(r);
  353. }
  354. return additionalTags;
  355. }
  356. private void expandFetchTags() throws TransportException {
  357. final Map<String, Ref> haveRefs = localRefs();
  358. for (Ref r : conn.getRefs()) {
  359. if (!isTag(r)) {
  360. continue;
  361. }
  362. ObjectId id = r.getObjectId();
  363. if (id == null) {
  364. continue;
  365. }
  366. final Ref local = haveRefs.get(r.getName());
  367. if (local == null || !id.equals(local.getObjectId())) {
  368. wantTag(r);
  369. }
  370. }
  371. }
  372. private void wantTag(Ref r) throws TransportException {
  373. want(r, new RefSpec().setSource(r.getName())
  374. .setDestination(r.getName()).setForceUpdate(true));
  375. }
  376. private void want(Ref src, RefSpec spec)
  377. throws TransportException {
  378. final ObjectId newId = src.getObjectId();
  379. if (newId == null) {
  380. throw new NullPointerException(MessageFormat.format(
  381. JGitText.get().transportProvidedRefWithNoObjectId,
  382. src.getName()));
  383. }
  384. if (spec.getDestination() != null) {
  385. final TrackingRefUpdate tru = createUpdate(spec, newId);
  386. if (newId.equals(tru.getOldObjectId()))
  387. return;
  388. localUpdates.add(tru);
  389. }
  390. askFor.put(newId, src);
  391. final FetchHeadRecord fhr = new FetchHeadRecord();
  392. fhr.newValue = newId;
  393. fhr.notForMerge = spec.getDestination() != null;
  394. fhr.sourceName = src.getName();
  395. fhr.sourceURI = transport.getURI();
  396. fetchHeadUpdates.add(fhr);
  397. }
  398. private void want(ObjectId id) {
  399. askFor.put(id,
  400. new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(), id));
  401. }
  402. private TrackingRefUpdate createUpdate(RefSpec spec, ObjectId newId)
  403. throws TransportException {
  404. Ref ref = localRefs().get(spec.getDestination());
  405. ObjectId oldId = ref != null && ref.getObjectId() != null
  406. ? ref.getObjectId()
  407. : ObjectId.zeroId();
  408. return new TrackingRefUpdate(
  409. spec.isForceUpdate(),
  410. spec.getSource(),
  411. spec.getDestination(),
  412. oldId,
  413. newId);
  414. }
  415. private Map<String, Ref> localRefs() throws TransportException {
  416. if (localRefs == null) {
  417. try {
  418. localRefs = transport.local.getRefDatabase()
  419. .getRefs(RefDatabase.ALL);
  420. } catch (IOException err) {
  421. throw new TransportException(JGitText.get().cannotListRefs, err);
  422. }
  423. }
  424. return localRefs;
  425. }
  426. private void deleteStaleTrackingRefs(FetchResult result,
  427. BatchRefUpdate batch) throws IOException {
  428. Set<Ref> processed = new HashSet<>();
  429. for (Ref ref : localRefs().values()) {
  430. if (ref.isSymbolic()) {
  431. continue;
  432. }
  433. String refname = ref.getName();
  434. for (RefSpec spec : toFetch) {
  435. if (spec.matchDestination(refname)) {
  436. RefSpec s = spec.expandFromDestination(refname);
  437. if (result.getAdvertisedRef(s.getSource()) == null
  438. && processed.add(ref)) {
  439. deleteTrackingRef(result, batch, s, ref);
  440. }
  441. }
  442. }
  443. }
  444. }
  445. private void deleteTrackingRef(final FetchResult result,
  446. final BatchRefUpdate batch, final RefSpec spec, final Ref localRef) {
  447. if (localRef.getObjectId() == null)
  448. return;
  449. TrackingRefUpdate update = new TrackingRefUpdate(
  450. true,
  451. spec.getSource(),
  452. localRef.getName(),
  453. localRef.getObjectId(),
  454. ObjectId.zeroId());
  455. result.add(update);
  456. batch.addCommand(update.asReceiveCommand());
  457. }
  458. private static boolean isTag(Ref r) {
  459. return isTag(r.getName());
  460. }
  461. private static boolean isTag(String name) {
  462. return name.startsWith(Constants.R_TAGS);
  463. }
  464. private static String getFirstFailedRefName(BatchRefUpdate batch) {
  465. for (ReceiveCommand cmd : batch.getCommands()) {
  466. if (cmd.getResult() != ReceiveCommand.Result.OK)
  467. return cmd.getRefName();
  468. }
  469. return ""; //$NON-NLS-1$
  470. }
  471. }