1
2
3
4
5
6
7
8
9
10
11
12 package org.eclipse.jgit.transport;
13
14 import java.io.File;
15 import java.io.FileNotFoundException;
16 import java.io.FileOutputStream;
17 import java.io.IOException;
18 import java.text.MessageFormat;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Set;
27
28 import org.eclipse.jgit.errors.CompoundException;
29 import org.eclipse.jgit.errors.CorruptObjectException;
30 import org.eclipse.jgit.errors.MissingObjectException;
31 import org.eclipse.jgit.errors.TransportException;
32 import org.eclipse.jgit.internal.JGitText;
33 import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
34 import org.eclipse.jgit.internal.storage.file.PackIndex;
35 import org.eclipse.jgit.internal.storage.file.PackLock;
36 import org.eclipse.jgit.internal.storage.file.UnpackedObject;
37 import org.eclipse.jgit.lib.AnyObjectId;
38 import org.eclipse.jgit.lib.Constants;
39 import org.eclipse.jgit.lib.FileMode;
40 import org.eclipse.jgit.lib.MutableObjectId;
41 import org.eclipse.jgit.lib.ObjectChecker;
42 import org.eclipse.jgit.lib.ObjectId;
43 import org.eclipse.jgit.lib.ObjectInserter;
44 import org.eclipse.jgit.lib.ObjectLoader;
45 import org.eclipse.jgit.lib.ObjectReader;
46 import org.eclipse.jgit.lib.ProgressMonitor;
47 import org.eclipse.jgit.lib.Ref;
48 import org.eclipse.jgit.lib.Repository;
49 import org.eclipse.jgit.revwalk.DateRevQueue;
50 import org.eclipse.jgit.revwalk.RevCommit;
51 import org.eclipse.jgit.revwalk.RevFlag;
52 import org.eclipse.jgit.revwalk.RevObject;
53 import org.eclipse.jgit.revwalk.RevTag;
54 import org.eclipse.jgit.revwalk.RevTree;
55 import org.eclipse.jgit.revwalk.RevWalk;
56 import org.eclipse.jgit.treewalk.TreeWalk;
57 import org.eclipse.jgit.util.FileUtils;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 class WalkFetchConnection extends BaseFetchConnection {
81
82 final Repository local;
83
84
85 final ObjectChecker objCheck;
86
87
88
89
90
91
92
93
94 private final List<WalkRemoteObjectDatabase> remotes;
95
96
97 private int lastRemoteIdx;
98
99 private final RevWalk revWalk;
100
101 private final TreeWalk treeWalk;
102
103
104 private final RevFlag COMPLETE;
105
106
107 private final RevFlag IN_WORK_QUEUE;
108
109
110 private final RevFlag LOCALLY_SEEN;
111
112
113 private final DateRevQueue localCommitQueue;
114
115
116 private LinkedList<ObjectId> workQueue;
117
118
119 private final LinkedList<WalkRemoteObjectDatabase> noPacksYet;
120
121
122 private final LinkedList<WalkRemoteObjectDatabase> noAlternatesYet;
123
124
125 private final LinkedList<RemotePack> unfetchedPacks;
126
127
128
129
130
131
132
133
134 private final Set<String> packsConsidered;
135
136 private final MutableObjectIdml#MutableObjectId">MutableObjectId idBuffer = new MutableObjectId();
137
138
139
140
141
142
143
144
145 private final HashMap<ObjectId, List<Throwable>> fetchErrors;
146
147 String lockMessage;
148
149 final List<PackLock> packLocks;
150
151
152 final ObjectInserter inserter;
153
154
155 private final ObjectReader reader;
156
157 WalkFetchConnection(WalkTransport t, WalkRemoteObjectDatabase w) {
158 Transport wt = (Transport)t;
159 local = wt.local;
160 objCheck = wt.getObjectChecker();
161 inserter = local.newObjectInserter();
162 reader = inserter.newReader();
163
164 remotes = new ArrayList<>();
165 remotes.add(w);
166
167 unfetchedPacks = new LinkedList<>();
168 packsConsidered = new HashSet<>();
169
170 noPacksYet = new LinkedList<>();
171 noPacksYet.add(w);
172
173 noAlternatesYet = new LinkedList<>();
174 noAlternatesYet.add(w);
175
176 fetchErrors = new HashMap<>();
177 packLocks = new ArrayList<>(4);
178
179 revWalk = new RevWalk(reader);
180 revWalk.setRetainBody(false);
181 treeWalk = new TreeWalk(reader);
182 COMPLETE = revWalk.newFlag("COMPLETE");
183 IN_WORK_QUEUE = revWalk.newFlag("IN_WORK_QUEUE");
184 LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN");
185
186 localCommitQueue = new DateRevQueue();
187 workQueue = new LinkedList<>();
188 }
189
190
191 @Override
192 public boolean didFetchTestConnectivity() {
193 return true;
194 }
195
196
197 @Override
198 protected void doFetch(final ProgressMonitor monitor,
199 final Collection<Ref> want, final Set<ObjectId> have)
200 throws TransportException {
201 markLocalRefsComplete(have);
202 queueWants(want);
203
204 while (!monitor.isCancelled() && !workQueue.isEmpty()) {
205 final ObjectId id = workQueue.removeFirst();
206 if (!(id instanceof RevObject../../../../org/eclipse/jgit/revwalk/RevObject.html#RevObject">RevObject) || !((RevObject) id).has(COMPLETE))
207 downloadObject(monitor, id);
208 process(id);
209 }
210
211 try {
212 inserter.flush();
213 } catch (IOException e) {
214 throw new TransportException(e.getMessage(), e);
215 }
216 }
217
218
219 @Override
220 public Collection<PackLock> getPackLocks() {
221 return packLocks;
222 }
223
224
225 @Override
226 public void setPackLockMessage(String message) {
227 lockMessage = message;
228 }
229
230
231 @Override
232 public void close() {
233 inserter.close();
234 reader.close();
235 for (RemotePack p : unfetchedPacks) {
236 if (p.tmpIdx != null)
237 p.tmpIdx.delete();
238 }
239 for (WalkRemoteObjectDatabase r : remotes)
240 r.close();
241 }
242
243 private void queueWants(Collection<Ref> want)
244 throws TransportException {
245 final HashSet<ObjectId> inWorkQueue = new HashSet<>();
246 for (Ref r : want) {
247 final ObjectId id = r.getObjectId();
248 if (id == null) {
249 throw new NullPointerException(MessageFormat.format(
250 JGitText.get().transportProvidedRefWithNoObjectId, r.getName()));
251 }
252 try {
253 final RevObject obj = revWalk.parseAny(id);
254 if (obj.has(COMPLETE))
255 continue;
256 if (inWorkQueue.add(id)) {
257 obj.add(IN_WORK_QUEUE);
258 workQueue.add(obj);
259 }
260 } catch (MissingObjectException e) {
261 if (inWorkQueue.add(id))
262 workQueue.add(id);
263 } catch (IOException e) {
264 throw new TransportException(MessageFormat.format(JGitText.get().cannotRead, id.name()), e);
265 }
266 }
267 }
268
269 private void process(ObjectId id) throws TransportException {
270 final RevObject obj;
271 try {
272 if (id instanceof RevObject) {
273 obj = (RevObject) id;
274 if (obj.has(COMPLETE))
275 return;
276 revWalk.parseHeaders(obj);
277 } else {
278 obj = revWalk.parseAny(id);
279 if (obj.has(COMPLETE))
280 return;
281 }
282 } catch (IOException e) {
283 throw new TransportException(MessageFormat.format(JGitText.get().cannotRead, id.name()), e);
284 }
285
286 switch (obj.getType()) {
287 case Constants.OBJ_BLOB:
288 processBlob(obj);
289 break;
290 case Constants.OBJ_TREE:
291 processTree(obj);
292 break;
293 case Constants.OBJ_COMMIT:
294 processCommit(obj);
295 break;
296 case Constants.OBJ_TAG:
297 processTag(obj);
298 break;
299 default:
300 throw new TransportException(MessageFormat.format(JGitText.get().unknownObjectType, id.name()));
301 }
302
303
304
305
306 fetchErrors.remove(id);
307 }
308
309 private void processBlob(RevObject obj) throws TransportException {
310 try {
311 if (reader.has(obj, Constants.OBJ_BLOB))
312 obj.add(COMPLETE);
313 else
314 throw new TransportException(MessageFormat.format(JGitText
315 .get().cannotReadBlob, obj.name()),
316 new MissingObjectException(obj, Constants.TYPE_BLOB));
317 } catch (IOException error) {
318 throw new TransportException(MessageFormat.format(
319 JGitText.get().cannotReadBlob, obj.name()), error);
320 }
321 }
322
323 private void processTree(RevObject obj) throws TransportException {
324 try {
325 treeWalk.reset(obj);
326 while (treeWalk.next()) {
327 final FileMode mode = treeWalk.getFileMode(0);
328 final int sType = mode.getObjectType();
329
330 switch (sType) {
331 case Constants.OBJ_BLOB:
332 case Constants.OBJ_TREE:
333 treeWalk.getObjectId(idBuffer, 0);
334 needs(revWalk.lookupAny(idBuffer, sType));
335 continue;
336
337 default:
338 if (FileMode.GITLINK.equals(mode))
339 continue;
340 treeWalk.getObjectId(idBuffer, 0);
341 throw new CorruptObjectException(MessageFormat.format(JGitText.get().invalidModeFor
342 , mode, idBuffer.name(), treeWalk.getPathString(), obj.getId().name()));
343 }
344 }
345 } catch (IOException ioe) {
346 throw new TransportException(MessageFormat.format(JGitText.get().cannotReadTree, obj.name()), ioe);
347 }
348 obj.add(COMPLETE);
349 }
350
351 private void processCommit(RevObject obj) throws TransportException {
352 final RevCommit../../../org/eclipse/jgit/revwalk/RevCommit.html#RevCommit">RevCommit commit = (RevCommit) obj;
353 markLocalCommitsComplete(commit.getCommitTime());
354 needs(commit.getTree());
355 for (RevCommit p : commit.getParents())
356 needs(p);
357 obj.add(COMPLETE);
358 }
359
360 private void processTag(RevObject obj) {
361 final RevTagf="../../../../org/eclipse/jgit/revwalk/RevTag.html#RevTag">RevTag tag = (RevTag) obj;
362 needs(tag.getObject());
363 obj.add(COMPLETE);
364 }
365
366 private void needs(RevObject obj) {
367 if (obj.has(COMPLETE))
368 return;
369 if (!obj.has(IN_WORK_QUEUE)) {
370 obj.add(IN_WORK_QUEUE);
371 workQueue.add(obj);
372 }
373 }
374
375 private void downloadObject(ProgressMonitor pm, AnyObjectId id)
376 throws TransportException {
377 if (alreadyHave(id))
378 return;
379
380 for (;;) {
381
382
383
384
385 if (downloadPackedObject(pm, id))
386 return;
387
388
389
390
391 final String idStr = id.name();
392 final String subdir = idStr.substring(0, 2);
393 final String file = idStr.substring(2);
394 final String looseName = subdir + "/" + file;
395
396 for (int i = lastRemoteIdx; i < remotes.size(); i++) {
397 if (downloadLooseObject(id, looseName, remotes.get(i))) {
398 lastRemoteIdx = i;
399 return;
400 }
401 }
402 for (int i = 0; i < lastRemoteIdx; i++) {
403 if (downloadLooseObject(id, looseName, remotes.get(i))) {
404 lastRemoteIdx = i;
405 return;
406 }
407 }
408
409
410
411 while (!noPacksYet.isEmpty()) {
412 final WalkRemoteObjectDatabase wrr = noPacksYet.removeFirst();
413 final Collection<String> packNameList;
414 try {
415 pm.beginTask(JGitText.get().listingPacks,
416 ProgressMonitor.UNKNOWN);
417 packNameList = wrr.getPackNames();
418 } catch (IOException e) {
419
420
421 recordError(id, e);
422 continue;
423 } finally {
424 pm.endTask();
425 }
426
427 if (packNameList == null || packNameList.isEmpty())
428 continue;
429 for (String packName : packNameList) {
430 if (packsConsidered.add(packName))
431 unfetchedPacks.add(new RemotePack(wrr, packName));
432 }
433 if (downloadPackedObject(pm, id))
434 return;
435 }
436
437
438
439 Collection<WalkRemoteObjectDatabase> al = expandOneAlternate(id, pm);
440 if (al != null && !al.isEmpty()) {
441 for (WalkRemoteObjectDatabase alt : al) {
442 remotes.add(alt);
443 noPacksYet.add(alt);
444 noAlternatesYet.add(alt);
445 }
446 continue;
447 }
448
449
450
451 List<Throwable> failures = fetchErrors.get(id);
452 final TransportException te;
453
454 te = new TransportException(MessageFormat.format(JGitText.get().cannotGet, id.name()));
455 if (failures != null && !failures.isEmpty()) {
456 if (failures.size() == 1)
457 te.initCause(failures.get(0));
458 else
459 te.initCause(new CompoundException(failures));
460 }
461 throw te;
462 }
463 }
464
465 private boolean alreadyHave(AnyObjectId id) throws TransportException {
466 try {
467 return reader.has(id);
468 } catch (IOException error) {
469 throw new TransportException(MessageFormat.format(
470 JGitText.get().cannotReadObject, id.name()), error);
471 }
472 }
473
474 private boolean downloadPackedObject(final ProgressMonitor monitor,
475 final AnyObjectId id) throws TransportException {
476
477
478
479 final Iterator<RemotePack> packItr = unfetchedPacks.iterator();
480 while (packItr.hasNext() && !monitor.isCancelled()) {
481 final RemotePack pack = packItr.next();
482 try {
483 pack.openIndex(monitor);
484 } catch (IOException err) {
485
486
487
488
489
490 recordError(id, err);
491 packItr.remove();
492 continue;
493 }
494
495 if (monitor.isCancelled()) {
496
497
498
499
500 return false;
501 }
502
503 if (!pack.index.hasObject(id)) {
504
505
506 continue;
507 }
508
509
510
511
512
513 Throwable e1 = null;
514 try {
515 pack.downloadPack(monitor);
516 } catch (IOException err) {
517
518
519
520
521
522 recordError(id, err);
523 e1 = err;
524 continue;
525 } finally {
526
527
528
529
530
531
532 try {
533 if (pack.tmpIdx != null)
534 FileUtils.delete(pack.tmpIdx);
535 } catch (IOException e) {
536 if (e1 != null) {
537 e.addSuppressed(e1);
538 }
539 throw new TransportException(e.getMessage(), e);
540 }
541 packItr.remove();
542 }
543
544 if (!alreadyHave(id)) {
545
546
547
548
549 recordError(id, new FileNotFoundException(MessageFormat.format(
550 JGitText.get().objectNotFoundIn, id.name(), pack.packName)));
551 continue;
552 }
553
554
555
556 final Iterator<ObjectId> pending = swapFetchQueue();
557 while (pending.hasNext()) {
558 final ObjectId p = pending.next();
559 if (pack.index.hasObject(p)) {
560 pending.remove();
561 process(p);
562 } else {
563 workQueue.add(p);
564 }
565 }
566 return true;
567
568 }
569 return false;
570 }
571
572 private Iterator<ObjectId> swapFetchQueue() {
573 final Iterator<ObjectId> r = workQueue.iterator();
574 workQueue = new LinkedList<>();
575 return r;
576 }
577
578 private boolean downloadLooseObject(final AnyObjectId id,
579 final String looseName, final WalkRemoteObjectDatabase remote)
580 throws TransportException {
581 try {
582 final byte[] compressed = remote.open(looseName).toArray();
583 verifyAndInsertLooseObject(id, compressed);
584 return true;
585 } catch (FileNotFoundException e) {
586
587
588
589 recordError(id, e);
590 return false;
591 } catch (IOException e) {
592 throw new TransportException(MessageFormat.format(JGitText.get().cannotDownload, id.name()), e);
593 }
594 }
595
596 private void verifyAndInsertLooseObject(final AnyObjectId id,
597 final byte[] compressed) throws IOException {
598 final ObjectLoader uol;
599 try {
600 uol = UnpackedObject.parse(compressed, id);
601 } catch (CorruptObjectException parsingError) {
602
603
604
605
606
607
608
609
610
611
612
613 final FileNotFoundException e;
614 e = new FileNotFoundException(id.name());
615 e.initCause(parsingError);
616 throw e;
617 }
618
619 final int type = uol.getType();
620 final byte[] raw = uol.getCachedBytes();
621 if (objCheck != null) {
622 try {
623 objCheck.check(id, type, raw);
624 } catch (CorruptObjectException e) {
625 throw new TransportException(MessageFormat.format(
626 JGitText.get().transportExceptionInvalid,
627 Constants.typeString(type), id.name(), e.getMessage()));
628 }
629 }
630
631 ObjectId act = inserter.insert(type, raw);
632 if (!AnyObjectId.isEqual(id, act)) {
633 throw new TransportException(MessageFormat.format(
634 JGitText.get().incorrectHashFor, id.name(), act.name(),
635 Constants.typeString(type),
636 Integer.valueOf(compressed.length)));
637 }
638 }
639
640 private Collection<WalkRemoteObjectDatabase> expandOneAlternate(
641 final AnyObjectId id, final ProgressMonitor pm) {
642 while (!noAlternatesYet.isEmpty()) {
643 final WalkRemoteObjectDatabase wrr = noAlternatesYet.removeFirst();
644 try {
645 pm.beginTask(JGitText.get().listingAlternates, ProgressMonitor.UNKNOWN);
646 Collection<WalkRemoteObjectDatabase> altList = wrr
647 .getAlternates();
648 if (altList != null && !altList.isEmpty())
649 return altList;
650 } catch (IOException e) {
651
652
653 recordError(id, e);
654 } finally {
655 pm.endTask();
656 }
657 }
658 return null;
659 }
660
661 private void markLocalRefsComplete(Set<ObjectId> have) throws TransportException {
662 List<Ref> refs;
663 try {
664 refs = local.getRefDatabase().getRefs();
665 } catch (IOException e) {
666 throw new TransportException(e.getMessage(), e);
667 }
668 for (Ref r : refs) {
669 try {
670 markLocalObjComplete(revWalk.parseAny(r.getObjectId()));
671 } catch (IOException readError) {
672 throw new TransportException(MessageFormat.format(JGitText.get().localRefIsMissingObjects, r.getName()), readError);
673 }
674 }
675 for (ObjectId id : have) {
676 try {
677 markLocalObjComplete(revWalk.parseAny(id));
678 } catch (IOException readError) {
679 throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionMissingAssumed, id.name()), readError);
680 }
681 }
682 }
683
684 private void markLocalObjComplete(RevObject obj) throws IOException {
685 while (obj.getType() == Constants.OBJ_TAG) {
686 obj.add(COMPLETE);
687 obj = ((RevTag) obj).getObject();
688 revWalk.parseHeaders(obj);
689 }
690
691 switch (obj.getType()) {
692 case Constants.OBJ_BLOB:
693 obj.add(COMPLETE);
694 break;
695 case Constants.OBJ_COMMIT:
696 pushLocalCommit((RevCommit) obj);
697 break;
698 case Constants.OBJ_TREE:
699 markTreeComplete((RevTree) obj);
700 break;
701 }
702 }
703
704 private void markLocalCommitsComplete(int until)
705 throws TransportException {
706 try {
707 for (;;) {
708 final RevCommit c = localCommitQueue.peek();
709 if (c == null || c.getCommitTime() < until)
710 return;
711 localCommitQueue.next();
712
713 markTreeComplete(c.getTree());
714 for (RevCommit p : c.getParents())
715 pushLocalCommit(p);
716 }
717 } catch (IOException err) {
718 throw new TransportException(JGitText.get().localObjectsIncomplete, err);
719 }
720 }
721
722 private void pushLocalCommit(RevCommit p)
723 throws MissingObjectException, IOException {
724 if (p.has(LOCALLY_SEEN))
725 return;
726 revWalk.parseHeaders(p);
727 p.add(LOCALLY_SEEN);
728 p.add(COMPLETE);
729 p.carry(COMPLETE);
730 localCommitQueue.add(p);
731 }
732
733 private void markTreeComplete(RevTree tree) throws IOException {
734 if (tree.has(COMPLETE))
735 return;
736 tree.add(COMPLETE);
737 treeWalk.reset(tree);
738 while (treeWalk.next()) {
739 final FileMode mode = treeWalk.getFileMode(0);
740 final int sType = mode.getObjectType();
741
742 switch (sType) {
743 case Constants.OBJ_BLOB:
744 treeWalk.getObjectId(idBuffer, 0);
745 revWalk.lookupAny(idBuffer, sType).add(COMPLETE);
746 continue;
747
748 case Constants.OBJ_TREE: {
749 treeWalk.getObjectId(idBuffer, 0);
750 final RevObject o = revWalk.lookupAny(idBuffer, sType);
751 if (!o.has(COMPLETE)) {
752 o.add(COMPLETE);
753 treeWalk.enterSubtree();
754 }
755 continue;
756 }
757 default:
758 if (FileMode.GITLINK.equals(mode))
759 continue;
760 treeWalk.getObjectId(idBuffer, 0);
761 throw new CorruptObjectException(MessageFormat.format(JGitText.get().corruptObjectInvalidMode3
762 , mode, idBuffer.name(), treeWalk.getPathString(), tree.name()));
763 }
764 }
765 }
766
767 private void recordError(AnyObjectId id, Throwable what) {
768 final ObjectId objId = id.copy();
769 List<Throwable> errors = fetchErrors.get(objId);
770 if (errors == null) {
771 errors = new ArrayList<>(2);
772 fetchErrors.put(objId, errors);
773 }
774 errors.add(what);
775 }
776
777 private class RemotePack {
778 final WalkRemoteObjectDatabase connection;
779
780 final String packName;
781
782 final String idxName;
783
784 File tmpIdx;
785
786 PackIndex index;
787
788 RemotePack(WalkRemoteObjectDatabase c, String pn) {
789 connection = c;
790 packName = pn;
791 idxName = packName.substring(0, packName.length() - 5) + ".idx";
792
793 String tn = idxName;
794 if (tn.startsWith("pack-"))
795 tn = tn.substring(5);
796 if (tn.endsWith(".idx"))
797 tn = tn.substring(0, tn.length() - 4);
798
799 if (local.getObjectDatabase() instanceof ObjectDirectory) {
800 tmpIdx = new File(((ObjectDirectory) local.getObjectDatabase())
801 .getDirectory(),
802 "walk-" + tn + ".walkidx");
803 }
804 }
805
806 void openIndex(ProgressMonitor pm) throws IOException {
807 if (index != null)
808 return;
809 if (tmpIdx == null)
810 tmpIdx = File.createTempFile("jgit-walk-", ".idx");
811 else if (tmpIdx.isFile()) {
812 try {
813 index = PackIndex.open(tmpIdx);
814 return;
815 } catch (FileNotFoundException err) {
816
817 }
818 }
819
820 final WalkRemoteObjectDatabase.FileStream s;
821 s = connection.open("pack/" + idxName);
822 pm.beginTask("Get " + idxName.substring(0, 12) + "..idx",
823 s.length < 0 ? ProgressMonitor.UNKNOWN
824 : (int) (s.length / 1024));
825 try (FileOutputStream fos = new FileOutputStream(tmpIdx)) {
826 final byte[] buf = new byte[2048];
827 int cnt;
828 while (!pm.isCancelled() && (cnt = s.in.read(buf)) >= 0) {
829 fos.write(buf, 0, cnt);
830 pm.update(cnt / 1024);
831 }
832 } catch (IOException err) {
833 FileUtils.delete(tmpIdx);
834 throw err;
835 } finally {
836 s.in.close();
837 }
838 pm.endTask();
839
840 if (pm.isCancelled()) {
841 FileUtils.delete(tmpIdx);
842 return;
843 }
844
845 try {
846 index = PackIndex.open(tmpIdx);
847 } catch (IOException e) {
848 FileUtils.delete(tmpIdx);
849 throw e;
850 }
851 }
852
853 void downloadPack(ProgressMonitor monitor) throws IOException {
854 String name = "pack/" + packName;
855 WalkRemoteObjectDatabase.FileStream s = connection.open(name);
856 try {
857 PackParser parser = inserter.newPackParser(s.in);
858 parser.setAllowThin(false);
859 parser.setObjectChecker(objCheck);
860 parser.setLockMessage(lockMessage);
861 PackLock lock = parser.parse(monitor);
862 if (lock != null)
863 packLocks.add(lock);
864 } finally {
865 s.in.close();
866 }
867 }
868 }
869 }