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