Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

FetchProcess.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  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. final TagOpt tagopt = transport.getTagOpt();
  89. String getTags = (tagopt == TagOpt.NO_TAGS) ? null : Constants.R_TAGS;
  90. String getHead = null;
  91. try {
  92. // If we don't have a HEAD yet, we're cloning and need to get the
  93. // upstream HEAD, too.
  94. Ref head = transport.local.exactRef(Constants.HEAD);
  95. ObjectId id = head != null ? head.getObjectId() : null;
  96. if (id == null || id.equals(ObjectId.zeroId())) {
  97. getHead = Constants.HEAD;
  98. }
  99. } catch (IOException e) {
  100. // Ignore
  101. }
  102. conn = transport.openFetch(toFetch, getTags, getHead);
  103. try {
  104. result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap());
  105. result.peerUserAgent = conn.getPeerUserAgent();
  106. final Set<Ref> matched = new HashSet<>();
  107. for (RefSpec spec : toFetch) {
  108. if (spec.getSource() == null)
  109. throw new TransportException(MessageFormat.format(
  110. JGitText.get().sourceRefNotSpecifiedForRefspec, spec));
  111. if (spec.isWildcard())
  112. expandWildcard(spec, matched);
  113. else
  114. expandSingle(spec, matched);
  115. }
  116. Collection<Ref> additionalTags = Collections.<Ref> emptyList();
  117. if (tagopt == TagOpt.AUTO_FOLLOW)
  118. additionalTags = expandAutoFollowTags();
  119. else if (tagopt == TagOpt.FETCH_TAGS)
  120. expandFetchTags();
  121. final boolean includedTags;
  122. if (!askFor.isEmpty() && !askForIsComplete()) {
  123. fetchObjects(monitor);
  124. includedTags = conn.didFetchIncludeTags();
  125. // Connection was used for object transfer. If we
  126. // do another fetch we must open a new connection.
  127. //
  128. closeConnection(result);
  129. } else {
  130. includedTags = false;
  131. }
  132. if (tagopt == TagOpt.AUTO_FOLLOW && !additionalTags.isEmpty()) {
  133. // There are more tags that we want to follow, but
  134. // not all were asked for on the initial request.
  135. //
  136. have.addAll(askFor.keySet());
  137. askFor.clear();
  138. for (Ref r : additionalTags) {
  139. ObjectId id = r.getPeeledObjectId();
  140. if (id == null)
  141. id = r.getObjectId();
  142. if (localHasObject(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. BatchRefUpdate batch = transport.local.getRefDatabase()
  155. .newBatchUpdate()
  156. .setAllowNonFastForwards(true)
  157. .setRefLogMessage("fetch", true); //$NON-NLS-1$
  158. try (RevWalk walk = new RevWalk(transport.local)) {
  159. walk.setRetainBody(false);
  160. if (monitor instanceof BatchingProgressMonitor) {
  161. ((BatchingProgressMonitor) monitor).setDelayStart(
  162. 250, TimeUnit.MILLISECONDS);
  163. }
  164. if (transport.isRemoveDeletedRefs()) {
  165. deleteStaleTrackingRefs(result, batch);
  166. }
  167. addUpdateBatchCommands(result, batch);
  168. for (ReceiveCommand cmd : batch.getCommands()) {
  169. cmd.updateType(walk);
  170. if (cmd.getType() == UPDATE_NONFASTFORWARD
  171. && cmd instanceof TrackingRefUpdate.Command
  172. && !((TrackingRefUpdate.Command) cmd).canForceUpdate())
  173. cmd.setResult(REJECTED_NONFASTFORWARD);
  174. }
  175. if (transport.isDryRun()) {
  176. for (ReceiveCommand cmd : batch.getCommands()) {
  177. if (cmd.getResult() == NOT_ATTEMPTED)
  178. cmd.setResult(OK);
  179. }
  180. } else {
  181. batch.execute(walk, monitor);
  182. }
  183. } catch (TransportException e) {
  184. throw e;
  185. } catch (IOException err) {
  186. throw new TransportException(MessageFormat.format(
  187. JGitText.get().failureUpdatingTrackingRef,
  188. getFirstFailedRefName(batch), err.getMessage()), err);
  189. }
  190. if (!fetchHeadUpdates.isEmpty()) {
  191. try {
  192. updateFETCH_HEAD(result);
  193. } catch (IOException err) {
  194. throw new TransportException(MessageFormat.format(
  195. JGitText.get().failureUpdatingFETCH_HEAD, err.getMessage()), err);
  196. }
  197. }
  198. }
  199. private void addUpdateBatchCommands(FetchResult result,
  200. BatchRefUpdate batch) throws TransportException {
  201. Map<String, ObjectId> refs = new HashMap<>();
  202. for (TrackingRefUpdate u : localUpdates) {
  203. // Try to skip duplicates if they'd update to the same object ID
  204. ObjectId existing = refs.get(u.getLocalName());
  205. if (existing == null) {
  206. refs.put(u.getLocalName(), u.getNewObjectId());
  207. result.add(u);
  208. batch.addCommand(u.asReceiveCommand());
  209. } else if (!existing.equals(u.getNewObjectId())) {
  210. throw new TransportException(MessageFormat
  211. .format(JGitText.get().duplicateRef, u.getLocalName()));
  212. }
  213. }
  214. }
  215. private void fetchObjects(ProgressMonitor monitor)
  216. throws TransportException {
  217. try {
  218. conn.setPackLockMessage("jgit fetch " + transport.uri); //$NON-NLS-1$
  219. conn.fetch(monitor, askFor.values(), have);
  220. } finally {
  221. packLocks.addAll(conn.getPackLocks());
  222. }
  223. if (transport.isCheckFetchedObjects()
  224. && !conn.didFetchTestConnectivity() && !askForIsComplete())
  225. throw new TransportException(transport.getURI(),
  226. JGitText.get().peerDidNotSupplyACompleteObjectGraph);
  227. }
  228. private void closeConnection(FetchResult result) {
  229. if (conn != null) {
  230. conn.close();
  231. result.addMessages(conn.getMessages());
  232. conn = null;
  233. }
  234. }
  235. private void reopenConnection() throws NotSupportedException,
  236. TransportException {
  237. if (conn != null)
  238. return;
  239. // Build prefixes
  240. Set<String> prefixes = new HashSet<>();
  241. for (Ref toGet : askFor.values()) {
  242. String src = toGet.getName();
  243. prefixes.add(src);
  244. prefixes.add(Constants.R_REFS + src);
  245. prefixes.add(Constants.R_HEADS + src);
  246. prefixes.add(Constants.R_TAGS + src);
  247. }
  248. conn = transport.openFetch(Collections.emptyList(),
  249. prefixes.toArray(new String[0]));
  250. // Since we opened a new connection we cannot be certain
  251. // that the system we connected to has the same exact set
  252. // of objects available (think round-robin DNS and mirrors
  253. // that aren't updated at the same time).
  254. //
  255. // We rebuild our askFor list using only the refs that the
  256. // new connection has offered to us.
  257. //
  258. final HashMap<ObjectId, Ref> avail = new HashMap<>();
  259. for (Ref r : conn.getRefs())
  260. avail.put(r.getObjectId(), r);
  261. final Collection<Ref> wants = new ArrayList<>(askFor.values());
  262. askFor.clear();
  263. for (Ref want : wants) {
  264. final Ref newRef = avail.get(want.getObjectId());
  265. if (newRef != null) {
  266. askFor.put(newRef.getObjectId(), newRef);
  267. } else {
  268. removeFetchHeadRecord(want.getObjectId());
  269. removeTrackingRefUpdate(want.getObjectId());
  270. }
  271. }
  272. }
  273. private void removeTrackingRefUpdate(ObjectId want) {
  274. final Iterator<TrackingRefUpdate> i = localUpdates.iterator();
  275. while (i.hasNext()) {
  276. final TrackingRefUpdate u = i.next();
  277. if (u.getNewObjectId().equals(want))
  278. i.remove();
  279. }
  280. }
  281. private void removeFetchHeadRecord(ObjectId want) {
  282. final Iterator<FetchHeadRecord> i = fetchHeadUpdates.iterator();
  283. while (i.hasNext()) {
  284. final FetchHeadRecord fh = i.next();
  285. if (fh.newValue.equals(want))
  286. i.remove();
  287. }
  288. }
  289. private void updateFETCH_HEAD(FetchResult result) throws IOException {
  290. File meta = transport.local.getDirectory();
  291. if (meta == null)
  292. return;
  293. final LockFile lock = new LockFile(new File(meta, "FETCH_HEAD")); //$NON-NLS-1$
  294. try {
  295. if (lock.lock()) {
  296. try (Writer w = new OutputStreamWriter(
  297. lock.getOutputStream(), UTF_8)) {
  298. for (FetchHeadRecord h : fetchHeadUpdates) {
  299. h.write(w);
  300. result.add(h);
  301. }
  302. }
  303. lock.commit();
  304. }
  305. } finally {
  306. lock.unlock();
  307. }
  308. }
  309. private boolean askForIsComplete() throws TransportException {
  310. try {
  311. try (ObjectWalk ow = new ObjectWalk(transport.local)) {
  312. for (ObjectId want : askFor.keySet())
  313. ow.markStart(ow.parseAny(want));
  314. for (Ref ref : localRefs().values())
  315. ow.markUninteresting(ow.parseAny(ref.getObjectId()));
  316. ow.checkConnectivity();
  317. }
  318. return true;
  319. } catch (MissingObjectException e) {
  320. return false;
  321. } catch (IOException e) {
  322. throw new TransportException(JGitText.get().unableToCheckConnectivity, e);
  323. }
  324. }
  325. private void expandWildcard(RefSpec spec, Set<Ref> matched)
  326. throws TransportException {
  327. for (Ref src : conn.getRefs()) {
  328. if (spec.matchSource(src) && matched.add(src))
  329. want(src, spec.expandFromSource(src));
  330. }
  331. }
  332. private void expandSingle(RefSpec spec, Set<Ref> matched)
  333. throws TransportException {
  334. String want = spec.getSource();
  335. if (ObjectId.isId(want)) {
  336. want(ObjectId.fromString(want));
  337. return;
  338. }
  339. Ref src = conn.getRef(want);
  340. if (src == null) {
  341. throw new TransportException(MessageFormat.format(JGitText.get().remoteDoesNotHaveSpec, want));
  342. }
  343. if (matched.add(src)) {
  344. want(src, spec);
  345. }
  346. }
  347. private boolean localHasObject(ObjectId id) throws TransportException {
  348. try {
  349. return transport.local.getObjectDatabase().has(id);
  350. } catch (IOException err) {
  351. throw new TransportException(
  352. MessageFormat.format(
  353. JGitText.get().readingObjectsFromLocalRepositoryFailed,
  354. err.getMessage()),
  355. err);
  356. }
  357. }
  358. private Collection<Ref> expandAutoFollowTags() throws TransportException {
  359. final Collection<Ref> additionalTags = new ArrayList<>();
  360. final Map<String, Ref> haveRefs = localRefs();
  361. for (Ref r : conn.getRefs()) {
  362. if (!isTag(r))
  363. continue;
  364. Ref local = haveRefs.get(r.getName());
  365. if (local != null)
  366. // We already have a tag with this name, don't fetch it (even if
  367. // the local is different).
  368. continue;
  369. ObjectId obj = r.getPeeledObjectId();
  370. if (obj == null)
  371. obj = r.getObjectId();
  372. if (askFor.containsKey(obj) || localHasObject(obj))
  373. wantTag(r);
  374. else
  375. additionalTags.add(r);
  376. }
  377. return additionalTags;
  378. }
  379. private void expandFetchTags() throws TransportException {
  380. final Map<String, Ref> haveRefs = localRefs();
  381. for (Ref r : conn.getRefs()) {
  382. if (!isTag(r)) {
  383. continue;
  384. }
  385. ObjectId id = r.getObjectId();
  386. if (id == null) {
  387. continue;
  388. }
  389. final Ref local = haveRefs.get(r.getName());
  390. if (local == null || !id.equals(local.getObjectId())) {
  391. wantTag(r);
  392. }
  393. }
  394. }
  395. private void wantTag(Ref r) throws TransportException {
  396. want(r, new RefSpec().setSource(r.getName())
  397. .setDestination(r.getName()).setForceUpdate(true));
  398. }
  399. private void want(Ref src, RefSpec spec)
  400. throws TransportException {
  401. final ObjectId newId = src.getObjectId();
  402. if (newId == null) {
  403. throw new NullPointerException(MessageFormat.format(
  404. JGitText.get().transportProvidedRefWithNoObjectId,
  405. src.getName()));
  406. }
  407. if (spec.getDestination() != null) {
  408. final TrackingRefUpdate tru = createUpdate(spec, newId);
  409. if (newId.equals(tru.getOldObjectId()))
  410. return;
  411. localUpdates.add(tru);
  412. }
  413. askFor.put(newId, src);
  414. final FetchHeadRecord fhr = new FetchHeadRecord();
  415. fhr.newValue = newId;
  416. fhr.notForMerge = spec.getDestination() != null;
  417. fhr.sourceName = src.getName();
  418. fhr.sourceURI = transport.getURI();
  419. fetchHeadUpdates.add(fhr);
  420. }
  421. private void want(ObjectId id) {
  422. askFor.put(id,
  423. new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(), id));
  424. }
  425. private TrackingRefUpdate createUpdate(RefSpec spec, ObjectId newId)
  426. throws TransportException {
  427. Ref ref = localRefs().get(spec.getDestination());
  428. ObjectId oldId = ref != null && ref.getObjectId() != null
  429. ? ref.getObjectId()
  430. : ObjectId.zeroId();
  431. return new TrackingRefUpdate(
  432. spec.isForceUpdate(),
  433. spec.getSource(),
  434. spec.getDestination(),
  435. oldId,
  436. newId);
  437. }
  438. private Map<String, Ref> localRefs() throws TransportException {
  439. if (localRefs == null) {
  440. try {
  441. localRefs = transport.local.getRefDatabase()
  442. .getRefs(RefDatabase.ALL);
  443. } catch (IOException err) {
  444. throw new TransportException(JGitText.get().cannotListRefs, err);
  445. }
  446. }
  447. return localRefs;
  448. }
  449. private void deleteStaleTrackingRefs(FetchResult result,
  450. BatchRefUpdate batch) throws IOException {
  451. Set<Ref> processed = new HashSet<>();
  452. for (Ref ref : localRefs().values()) {
  453. if (ref.isSymbolic()) {
  454. continue;
  455. }
  456. String refname = ref.getName();
  457. for (RefSpec spec : toFetch) {
  458. if (spec.matchDestination(refname)) {
  459. RefSpec s = spec.expandFromDestination(refname);
  460. if (result.getAdvertisedRef(s.getSource()) == null
  461. && processed.add(ref)) {
  462. deleteTrackingRef(result, batch, s, ref);
  463. }
  464. }
  465. }
  466. }
  467. }
  468. private void deleteTrackingRef(final FetchResult result,
  469. final BatchRefUpdate batch, final RefSpec spec, final Ref localRef) {
  470. if (localRef.getObjectId() == null)
  471. return;
  472. TrackingRefUpdate update = new TrackingRefUpdate(
  473. true,
  474. spec.getSource(),
  475. localRef.getName(),
  476. localRef.getObjectId(),
  477. ObjectId.zeroId());
  478. result.add(update);
  479. batch.addCommand(update.asReceiveCommand());
  480. }
  481. private static boolean isTag(Ref r) {
  482. return isTag(r.getName());
  483. }
  484. private static boolean isTag(String name) {
  485. return name.startsWith(Constants.R_TAGS);
  486. }
  487. private static String getFirstFailedRefName(BatchRefUpdate batch) {
  488. for (ReceiveCommand cmd : batch.getCommands()) {
  489. if (cmd.getResult() != ReceiveCommand.Result.OK)
  490. return cmd.getRefName();
  491. }
  492. return ""; //$NON-NLS-1$
  493. }
  494. }