aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/.classpath7
-rw-r--r--org.eclipse.jgit/.fbprefs125
-rw-r--r--org.eclipse.jgit/.gitignore1
-rw-r--r--org.eclipse.jgit/.project28
-rw-r--r--org.eclipse.jgit/.settings/org.eclipse.core.resources.prefs3
-rw-r--r--org.eclipse.jgit/.settings/org.eclipse.core.runtime.prefs3
-rw-r--r--org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs321
-rw-r--r--org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs9
-rw-r--r--org.eclipse.jgit/META-INF/MANIFEST.MF21
-rw-r--r--org.eclipse.jgit/build.properties5
-rw-r--r--org.eclipse.jgit/findBugs/FindBugsExcludeFilter.xml27
-rw-r--r--org.eclipse.jgit/plugin.properties2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java190
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/awtui/AwtAuthenticator.java197
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/awtui/CommitGraphPane.java256
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/awtui/SwingCommitList.java87
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java220
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java195
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java99
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java190
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/Sequence.java84
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java201
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java756
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java133
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java252
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java272
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java502
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java239
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java575
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/CheckoutConflictException.java82
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java87
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/ConfigInvalidException.java71
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java91
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/EntryExistsException.java64
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/GitlinksNotSupportedException.java65
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/IncorrectObjectTypeException.java86
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java76
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidPatternException.java75
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingBundlePrerequisiteException.java79
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingObjectException.java81
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/NoClosingBracketException.java79
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/NoRemoteRepositoryException.java65
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/NotSupportedException.java77
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/ObjectWritingException.java75
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java62
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java61
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java107
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/RepositoryNotFoundException.java71
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/RevWalkException.java70
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java85
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/StopWalkException.java61
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/SymlinksNotSupportedException.java64
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/TransportException.java109
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/UnmergedPathException.java73
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java81
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java60
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java382
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java227
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java57
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java63
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java59
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java56
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java260
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractIndexTreeVisitor.java79
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java133
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java492
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java141
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java129
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java97
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java110
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java185
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java380
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java1154
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java463
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java87
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java82
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java116
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java81
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java149
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java254
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java117
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ForceModified.java75
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java976
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java92
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java61
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java166
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeVisitor.java103
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeWalker.java248
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java109
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java411
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java186
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java78
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java365
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java383
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java517
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java273
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java183
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java105
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java412
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/OffsetCache.java539
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java515
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java316
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java201
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java277
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java273
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java84
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java107
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java90
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java107
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java191
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java1045
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java193
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java339
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java97
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java285
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java79
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java522
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java155
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java175
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java642
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java171
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java183
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java61
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java1176
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java60
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java390
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java70
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java135
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java69
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java161
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java90
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java302
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java142
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TransferConfig.java71
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java608
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java301
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeIterator.java209
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitor.java95
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitorWithCurrentDirectory.java78
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Treeish.java63
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java195
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java212
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java155
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java107
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java264
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java176
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java180
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java406
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/WriteTree.java95
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java141
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java235
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java105
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java193
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java52
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java136
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java149
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java246
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java324
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java714
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java115
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java383
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java382
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java273
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java166
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java196
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotLane.java72
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java139
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java156
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockObjQueue.java144
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java169
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BoundaryGenerator.java134
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java161
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DelayRevQueue.java99
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/EndGenerator.java62
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java165
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FixUninterestingGenerator.java84
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java83
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java151
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java106
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java121
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java224
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java432
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java182
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java67
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java529
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java359
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java101
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java154
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java218
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java125
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevSort.java90
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java221
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java67
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java1085
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java188
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java216
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java173
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java129
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AndRevFilter.java184
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AuthorRevFilter.java116
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java167
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitterRevFilter.java116
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MessageRevFilter.java116
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java93
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/OrRevFilter.java182
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/PatternMatchRevFilter.java146
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java232
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java161
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java125
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java795
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java109
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java100
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java287
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java538
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java296
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java273
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java203
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java110
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java391
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonClient.java115
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java149
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java175
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java177
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchHeadRecord.java95
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java451
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java69
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java69
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java1107
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java158
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java400
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java139
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PackTransport.java61
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java115
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java139
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java103
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java83
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java100
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java114
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java242
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushResult.java92
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java229
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java913
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java195
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java463
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java508
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java360
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java235
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java99
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java156
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigSessionFactory.java236
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java130
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java153
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TagOpt.java110
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TcpTransport.java69
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java141
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java932
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java336
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java62
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java103
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java121
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java197
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java392
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java282
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java407
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java432
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java366
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java486
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java194
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java878
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java382
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java513
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkTransport.java63
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java595
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java368
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java135
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java175
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java312
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java921
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java457
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java196
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java99
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java194
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java113
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java206
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java103
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java225
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java1475
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java184
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java5.java60
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java6.java107
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java74
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java121
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java171
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java134
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/MutableInteger.java50
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java368
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java368
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java97
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java1016
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java137
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java121
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java153
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java334
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java222
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java139
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java152
306 files changed, 68444 insertions, 0 deletions
diff --git a/org.eclipse.jgit/.classpath b/org.eclipse.jgit/.classpath
new file mode 100644
index 0000000000..304e86186a
--- /dev/null
+++ b/org.eclipse.jgit/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.eclipse.jgit/.fbprefs b/org.eclipse.jgit/.fbprefs
new file mode 100644
index 0000000000..81a0767ff6
--- /dev/null
+++ b/org.eclipse.jgit/.fbprefs
@@ -0,0 +1,125 @@
+#FindBugs User Preferences
+#Mon May 04 16:24:13 PDT 2009
+detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
+detectorBadAppletConstructor=BadAppletConstructor|false
+detectorBadResultSetAccess=BadResultSetAccess|true
+detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
+detectorBadUseOfReturnValue=BadUseOfReturnValue|true
+detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
+detectorBooleanReturnNull=BooleanReturnNull|true
+detectorCallToUnsupportedMethod=CallToUnsupportedMethod|true
+detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
+detectorCheckTypeQualifiers=CheckTypeQualifiers|true
+detectorCloneIdiom=CloneIdiom|false
+detectorComparatorIdiom=ComparatorIdiom|true
+detectorConfusedInheritance=ConfusedInheritance|true
+detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
+detectorCrossSiteScripting=CrossSiteScripting|true
+detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
+detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
+detectorDontUseEnum=DontUseEnum|true
+detectorDroppedException=DroppedException|true
+detectorDumbMethodInvocations=DumbMethodInvocations|true
+detectorDumbMethods=DumbMethods|true
+detectorDuplicateBranches=DuplicateBranches|true
+detectorEmptyZipFileEntry=EmptyZipFileEntry|true
+detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
+detectorFinalizerNullsFields=FinalizerNullsFields|true
+detectorFindBadCast2=FindBadCast2|true
+detectorFindBadForLoop=FindBadForLoop|true
+detectorFindCircularDependencies=FindCircularDependencies|false
+detectorFindDeadLocalStores=FindDeadLocalStores|true
+detectorFindDoubleCheck=FindDoubleCheck|true
+detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
+detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
+detectorFindFinalizeInvocations=FindFinalizeInvocations|true
+detectorFindFloatEquality=FindFloatEquality|true
+detectorFindHEmismatch=FindHEmismatch|true
+detectorFindInconsistentSync2=FindInconsistentSync2|true
+detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
+detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
+detectorFindMaskedFields=FindMaskedFields|true
+detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
+detectorFindNakedNotify=FindNakedNotify|true
+detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true
+detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true
+detectorFindNonShortCircuit=FindNonShortCircuit|true
+detectorFindNullDeref=FindNullDeref|true
+detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
+detectorFindOpenStream=FindOpenStream|true
+detectorFindPuzzlers=FindPuzzlers|true
+detectorFindRefComparison=FindRefComparison|true
+detectorFindReturnRef=FindReturnRef|true
+detectorFindRunInvocations=FindRunInvocations|true
+detectorFindSelfComparison=FindSelfComparison|true
+detectorFindSelfComparison2=FindSelfComparison2|true
+detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
+detectorFindSpinLoop=FindSpinLoop|true
+detectorFindSqlInjection=FindSqlInjection|true
+detectorFindTwoLockWait=FindTwoLockWait|true
+detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
+detectorFindUnconditionalWait=FindUnconditionalWait|true
+detectorFindUninitializedGet=FindUninitializedGet|true
+detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
+detectorFindUnreleasedLock=FindUnreleasedLock|true
+detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
+detectorFindUnsyncGet=FindUnsyncGet|true
+detectorFindUselessControlFlow=FindUselessControlFlow|true
+detectorFormatStringChecker=FormatStringChecker|true
+detectorHugeSharedStringConstants=HugeSharedStringConstants|true
+detectorIDivResultCastToDouble=IDivResultCastToDouble|true
+detectorIncompatMask=IncompatMask|true
+detectorInconsistentAnnotations=InconsistentAnnotations|true
+detectorInefficientMemberAccess=InefficientMemberAccess|false
+detectorInefficientToArray=InefficientToArray|true
+detectorInfiniteLoop=InfiniteLoop|true
+detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
+detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false
+detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
+detectorInitializationChain=InitializationChain|true
+detectorInstantiateStaticClass=InstantiateStaticClass|true
+detectorInvalidJUnitTest=InvalidJUnitTest|true
+detectorIteratorIdioms=IteratorIdioms|true
+detectorLazyInit=LazyInit|true
+detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
+detectorMethodReturnCheck=MethodReturnCheck|true
+detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
+detectorMutableLock=MutableLock|true
+detectorMutableStaticFields=MutableStaticFields|true
+detectorNaming=Naming|true
+detectorNumberConstructor=NumberConstructor|true
+detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
+detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
+detectorPublicSemaphores=PublicSemaphores|false
+detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
+detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
+detectorRedundantInterfaces=RedundantInterfaces|true
+detectorRepeatedConditionals=RepeatedConditionals|true
+detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
+detectorSerializableIdiom=SerializableIdiom|true
+detectorStartInConstructor=StartInConstructor|true
+detectorStaticCalendarDetector=StaticCalendarDetector|true
+detectorStringConcatenation=StringConcatenation|true
+detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
+detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
+detectorSwitchFallthrough=SwitchFallthrough|true
+detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
+detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
+detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
+detectorURLProblems=URLProblems|true
+detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
+detectorUnnecessaryMath=UnnecessaryMath|true
+detectorUnreadFields=UnreadFields|true
+detectorUseObjectEquals=UseObjectEquals|false
+detectorUselessSubclassMethod=UselessSubclassMethod|false
+detectorVarArgsProblems=VarArgsProblems|true
+detectorVolatileUsage=VolatileUsage|true
+detectorWaitInLoop=WaitInLoop|true
+detectorWrongMapIterator=WrongMapIterator|true
+detectorXMLFactoryBypass=XMLFactoryBypass|true
+detector_threshold=2
+effort=default
+excludefilter0=findBugs/FindBugsExcludeFilter.xml
+filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false
+filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL|
+run_at_full_build=true
diff --git a/org.eclipse.jgit/.gitignore b/org.eclipse.jgit/.gitignore
new file mode 100644
index 0000000000..ba077a4031
--- /dev/null
+++ b/org.eclipse.jgit/.gitignore
@@ -0,0 +1 @@
+bin
diff --git a/org.eclipse.jgit/.project b/org.eclipse.jgit/.project
new file mode 100644
index 0000000000..19aeef1fb8
--- /dev/null
+++ b/org.eclipse.jgit/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.jgit</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ </natures>
+</projectDescription>
diff --git a/org.eclipse.jgit/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000000..66ac15c47c
--- /dev/null
+++ b/org.eclipse.jgit/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:46:12 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.eclipse.jgit/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000000..cce05685b4
--- /dev/null
+++ b/org.eclipse.jgit/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,3 @@
+#Mon Mar 24 18:55:50 EDT 2008
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000000..8e8e17240c
--- /dev/null
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,321 @@
+#Sun Mar 15 01:13:43 CET 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=error
+org.eclipse.jdt.core.compiler.problem.unusedLabel=error
+org.eclipse.jdt.core.compiler.problem.unusedLocal=error
+org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error
+org.eclipse.jdt.core.compiler.source=1.5
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=1
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false
+org.eclipse.jdt.core.formatter.comment.format_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=80
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=tab
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000000..709a44074c
--- /dev/null
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,9 @@
+#Wed May 09 00:20:24 CEST 2007
+eclipse.preferences.version=1
+formatter_profile=_JGit
+formatter_settings_version=10
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=java;javax;org;com;
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..fed005ccd5
--- /dev/null
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -0,0 +1,21 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %plugin_name
+Bundle-SymbolicName: org.eclipse.jgit
+Bundle-Version: 0.5.0.qualifier
+Bundle-Localization: plugin
+Bundle-Vendor: %provider_name
+Export-Package: org.eclipse.jgit.dircache,
+ org.eclipse.jgit.errors;uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.lib,
+ org.eclipse.jgit.revplot,
+ org.eclipse.jgit.revwalk,
+ org.eclipse.jgit.revwalk.filter,
+ org.eclipse.jgit.transport;uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.treewalk,
+ org.eclipse.jgit.treewalk.filter,
+ org.eclipse.jgit.util
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Bundle-ClassPath: .
+Require-Bundle: com.jcraft.jsch;visibility:=reexport
diff --git a/org.eclipse.jgit/build.properties b/org.eclipse.jgit/build.properties
new file mode 100644
index 0000000000..aa1a008269
--- /dev/null
+++ b/org.eclipse.jgit/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.properties
diff --git a/org.eclipse.jgit/findBugs/FindBugsExcludeFilter.xml b/org.eclipse.jgit/findBugs/FindBugsExcludeFilter.xml
new file mode 100644
index 0000000000..ba9d1d0996
--- /dev/null
+++ b/org.eclipse.jgit/findBugs/FindBugsExcludeFilter.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<FindBugsFilter>
+ <!-- Silence PackFile.mmap calls GC, we need to force it to remove stale
+ memory mapped segments if the JVM heap is out of address space.
+ -->
+ <Match>
+ <Class name="org.eclipse.jgit.lib.PackFile" />
+ <Method name="mmap" />
+ <Bug pattern="DM_GC" />
+ </Match>
+
+ <!-- Silence the construction of our magic String instance.
+ -->
+ <Match>
+ <Class name="org.eclipse.jgit.lib.Config" />
+ <Bug pattern="DM_STRING_VOID_CTOR"/>
+ </Match>
+
+ <!-- Silence comparison of string by == or !=. This class is built
+ only to provide compare of string values, we won't make a mistake
+ here with == assuming .equals() style equality.
+ -->
+ <Match>
+ <Class name="org.eclipse.jgit.lib.util.StringUtils" />
+ <Bug pattern="ES_COMPARING_PARAMETER_STRING_WITH_EQ" />
+ </Match>
+</FindBugsFilter>
diff --git a/org.eclipse.jgit/plugin.properties b/org.eclipse.jgit/plugin.properties
new file mode 100644
index 0000000000..b075cc6f2a
--- /dev/null
+++ b/org.eclipse.jgit/plugin.properties
@@ -0,0 +1,2 @@
+plugin_name=Java Git Core
+provider_name=eclipse.org
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java
new file mode 100644
index 0000000000..647b103d28
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.awtui;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+
+import org.eclipse.jgit.awtui.CommitGraphPane.GraphCellRender;
+import org.eclipse.jgit.awtui.SwingCommitList.SwingLane;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revplot.AbstractPlotRenderer;
+import org.eclipse.jgit.revplot.PlotCommit;
+
+final class AWTPlotRenderer extends AbstractPlotRenderer<SwingLane, Color> {
+
+ final GraphCellRender cell;
+
+ Graphics2D g;
+
+ AWTPlotRenderer(final GraphCellRender c) {
+ cell = c;
+ }
+
+ void paint(final Graphics in, final PlotCommit<SwingLane> commit) {
+ g = (Graphics2D) in.create();
+ try {
+ final int h = cell.getHeight();
+ g.setColor(cell.getBackground());
+ g.fillRect(0, 0, cell.getWidth(), h);
+ if (commit != null)
+ paintCommit(commit, h);
+ } finally {
+ g.dispose();
+ g = null;
+ }
+ }
+
+ @Override
+ protected void drawLine(final Color color, int x1, int y1, int x2,
+ int y2, int width) {
+ if (y1 == y2) {
+ x1 -= width / 2;
+ x2 -= width / 2;
+ } else if (x1 == x2) {
+ y1 -= width / 2;
+ y2 -= width / 2;
+ }
+
+ g.setColor(color);
+ g.setStroke(CommitGraphPane.stroke(width));
+ g.drawLine(x1, y1, x2, y2);
+ }
+
+ @Override
+ protected void drawCommitDot(final int x, final int y, final int w,
+ final int h) {
+ g.setColor(Color.blue);
+ g.setStroke(CommitGraphPane.strokeCache[1]);
+ g.fillOval(x, y, w, h);
+ g.setColor(Color.black);
+ g.drawOval(x, y, w, h);
+ }
+
+ @Override
+ protected void drawBoundaryDot(final int x, final int y, final int w,
+ final int h) {
+ g.setColor(cell.getBackground());
+ g.setStroke(CommitGraphPane.strokeCache[1]);
+ g.fillOval(x, y, w, h);
+ g.setColor(Color.black);
+ g.drawOval(x, y, w, h);
+ }
+
+ @Override
+ protected void drawText(final String msg, final int x, final int y) {
+ final int texth = g.getFontMetrics().getHeight();
+ final int y0 = y - texth/2 + (cell.getHeight() - texth)/2;
+ g.setColor(cell.getForeground());
+ g.drawString(msg, x, y0 + texth - g.getFontMetrics().getDescent());
+ }
+
+ @Override
+ protected Color laneColor(final SwingLane myLane) {
+ return myLane != null ? myLane.color : Color.black;
+ }
+
+ void paintTriangleDown(final int cx, final int y, final int h) {
+ final int tipX = cx;
+ final int tipY = y + h;
+ final int baseX1 = cx - 10 / 2;
+ final int baseX2 = tipX + 10 / 2;
+ final int baseY = y;
+ final Polygon triangle = new Polygon();
+ triangle.addPoint(tipX, tipY);
+ triangle.addPoint(baseX1, baseY);
+ triangle.addPoint(baseX2, baseY);
+ g.fillPolygon(triangle);
+ g.drawPolygon(triangle);
+ }
+
+ @Override
+ protected int drawLabel(int x, int y, Ref ref) {
+ String txt;
+ String name = ref.getOrigName();
+ if (name.startsWith(Constants.R_HEADS)) {
+ g.setBackground(Color.GREEN);
+ txt = name.substring(Constants.R_HEADS.length());
+ } else if (name.startsWith(Constants.R_REMOTES)){
+ g.setBackground(Color.LIGHT_GRAY);
+ txt = name.substring(Constants.R_REMOTES.length());
+ } else if (name.startsWith(Constants.R_TAGS)){
+ g.setBackground(Color.YELLOW);
+ txt = name.substring(Constants.R_TAGS.length());
+ } else {
+ // Whatever this would be
+ g.setBackground(Color.WHITE);
+ if (name.startsWith(Constants.R_REFS))
+ txt = name.substring(Constants.R_REFS.length());
+ else
+ txt = name; // HEAD and such
+ }
+ if (ref.getPeeledObjectId() != null) {
+ float[] colorComponents = g.getBackground().getRGBColorComponents(null);
+ colorComponents[0] *= 0.9;
+ colorComponents[1] *= 0.9;
+ colorComponents[2] *= 0.9;
+ g.setBackground(new Color(colorComponents[0],colorComponents[1],colorComponents[2]));
+ }
+ if (txt.length() > 12)
+ txt = txt.substring(0,11) + "\u2026"; // ellipsis "…" (in UTF-8)
+
+ final int texth = g.getFontMetrics().getHeight();
+ int textw = g.getFontMetrics().stringWidth(txt);
+ g.setColor(g.getBackground());
+ int arcHeight = texth/4;
+ int y0 = y - texth/2 + (cell.getHeight() - texth)/2;
+ g.fillRoundRect(x , y0, textw + arcHeight*2, texth -1, arcHeight, arcHeight);
+ g.setColor(g.getColor().darker());
+ g.drawRoundRect(x, y0, textw + arcHeight*2, texth -1 , arcHeight, arcHeight);
+ g.setColor(Color.BLACK);
+ g.drawString(txt, x + arcHeight, y0 + texth - g.getFontMetrics().getDescent());
+
+ return arcHeight * 3 + textw;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/awtui/AwtAuthenticator.java b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/AwtAuthenticator.java
new file mode 100644
index 0000000000..ccd47ddda2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/AwtAuthenticator.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.awtui;
+
+import java.awt.Container;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+/** Basic network prompt for username/password when using AWT. */
+public class AwtAuthenticator extends Authenticator {
+ private static final AwtAuthenticator me = new AwtAuthenticator();
+
+ /** Install this authenticator implementation into the JVM. */
+ public static void install() {
+ setDefault(me);
+ }
+
+ /**
+ * Add a cached authentication for future use.
+ *
+ * @param ca
+ * the information we should remember.
+ */
+ public static void add(final CachedAuthentication ca) {
+ synchronized (me) {
+ me.cached.add(ca);
+ }
+ }
+
+ private final Collection<CachedAuthentication> cached = new ArrayList<CachedAuthentication>();
+
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ for (final CachedAuthentication ca : cached) {
+ if (ca.host.equals(getRequestingHost())
+ && ca.port == getRequestingPort())
+ return ca.toPasswordAuthentication();
+ }
+
+ final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1,
+ GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0);
+ final Container panel = new JPanel();
+ panel.setLayout(new GridBagLayout());
+
+ final StringBuilder instruction = new StringBuilder();
+ instruction.append("Enter username and password for ");
+ if (getRequestorType() == RequestorType.PROXY) {
+ instruction.append(getRequestorType());
+ instruction.append(" ");
+ instruction.append(getRequestingHost());
+ if (getRequestingPort() > 0) {
+ instruction.append(":");
+ instruction.append(getRequestingPort());
+ }
+ } else {
+ instruction.append(getRequestingURL());
+ }
+
+ gbc.weightx = 1.0;
+ gbc.gridwidth = GridBagConstraints.REMAINDER;
+ gbc.gridx = 0;
+ panel.add(new JLabel(instruction.toString()), gbc);
+ gbc.gridy++;
+
+ gbc.gridwidth = GridBagConstraints.RELATIVE;
+
+ // Username
+ //
+ final JTextField username;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.gridx = 0;
+ gbc.weightx = 1;
+ panel.add(new JLabel("Username:"), gbc);
+
+ gbc.gridx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.weighty = 1;
+ username = new JTextField(20);
+ panel.add(username, gbc);
+ gbc.gridy++;
+
+ // Password
+ //
+ final JPasswordField password;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.gridx = 0;
+ gbc.weightx = 1;
+ panel.add(new JLabel("Password:"), gbc);
+
+ gbc.gridx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.weighty = 1;
+ password = new JPasswordField(20);
+ panel.add(password, gbc);
+ gbc.gridy++;
+
+ if (JOptionPane.showConfirmDialog(null, panel,
+ "Authentication Required", JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) {
+ final CachedAuthentication ca = new CachedAuthentication(
+ getRequestingHost(), getRequestingPort(), username
+ .getText(), new String(password.getPassword()));
+ cached.add(ca);
+ return ca.toPasswordAuthentication();
+ }
+
+ return null; // cancel
+ }
+
+ /** Authentication data to remember and reuse. */
+ public static class CachedAuthentication {
+ final String host;
+
+ final int port;
+
+ final String user;
+
+ final String pass;
+
+ /**
+ * Create a new cached authentication.
+ *
+ * @param aHost
+ * system this is for.
+ * @param aPort
+ * port number of the service.
+ * @param aUser
+ * username at the service.
+ * @param aPass
+ * password at the service.
+ */
+ public CachedAuthentication(final String aHost, final int aPort,
+ final String aUser, final String aPass) {
+ host = aHost;
+ port = aPort;
+ user = aUser;
+ pass = aPass;
+ }
+
+ PasswordAuthentication toPasswordAuthentication() {
+ return new PasswordAuthentication(user, pass.toCharArray());
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/awtui/CommitGraphPane.java b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/CommitGraphPane.java
new file mode 100644
index 0000000000..8d78e47df1
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/CommitGraphPane.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.awtui;
+
+import java.awt.BasicStroke;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Stroke;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.TableModel;
+
+import org.eclipse.jgit.awtui.SwingCommitList.SwingLane;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revplot.PlotCommit;
+import org.eclipse.jgit.revplot.PlotCommitList;
+
+/**
+ * Draws a commit graph in a JTable.
+ * <p>
+ * This class is currently a very primitive commit visualization tool. It shows
+ * a table of 3 columns:
+ * <ol>
+ * <li>Commit graph and short message</li>
+ * <li>Author name and email address</li>
+ * <li>Author date and time</li>
+ * </ul>
+ */
+public class CommitGraphPane extends JTable {
+ private static final long serialVersionUID = 1L;
+
+ private final SwingCommitList allCommits;
+
+ /** Create a new empty panel. */
+ public CommitGraphPane() {
+ allCommits = new SwingCommitList();
+ configureHeader();
+ setShowHorizontalLines(false);
+ setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ configureRowHeight();
+ }
+
+ private void configureRowHeight() {
+ int h = 0;
+ for (int i = 0; i<getColumnCount(); ++i) {
+ TableCellRenderer renderer = getDefaultRenderer(getColumnClass(i));
+ Component c = renderer.getTableCellRendererComponent(this, "Ã…Oj", false, false, 0, i);
+ h = Math.max(h, c.getPreferredSize().height);
+ }
+ setRowHeight(h + getRowMargin());
+ }
+
+ /**
+ * Get the commit list this pane renders from.
+ *
+ * @return the list the caller must populate.
+ */
+ public PlotCommitList getCommitList() {
+ return allCommits;
+ }
+
+ @Override
+ public void setModel(final TableModel dataModel) {
+ if (dataModel != null && !(dataModel instanceof CommitTableModel))
+ throw new ClassCastException("Must be special table model.");
+ super.setModel(dataModel);
+ }
+
+ @Override
+ protected TableModel createDefaultDataModel() {
+ return new CommitTableModel();
+ }
+
+ private void configureHeader() {
+ final JTableHeader th = getTableHeader();
+ final TableColumnModel cols = th.getColumnModel();
+
+ final TableColumn graph = cols.getColumn(0);
+ final TableColumn author = cols.getColumn(1);
+ final TableColumn date = cols.getColumn(2);
+
+ graph.setHeaderValue("");
+ author.setHeaderValue("Author");
+ date.setHeaderValue("Date");
+
+ graph.setCellRenderer(new GraphCellRender());
+ author.setCellRenderer(new NameCellRender());
+ date.setCellRenderer(new DateCellRender());
+ }
+
+ class CommitTableModel extends AbstractTableModel {
+ private static final long serialVersionUID = 1L;
+
+ PlotCommit<SwingLane> lastCommit;
+
+ PersonIdent lastAuthor;
+
+ public int getColumnCount() {
+ return 3;
+ }
+
+ public int getRowCount() {
+ return allCommits != null ? allCommits.size() : 0;
+ }
+
+ public Object getValueAt(final int rowIndex, final int columnIndex) {
+ final PlotCommit<SwingLane> c = allCommits.get(rowIndex);
+ switch (columnIndex) {
+ case 0:
+ return c;
+ case 1:
+ return authorFor(c);
+ case 2:
+ return authorFor(c);
+ default:
+ return null;
+ }
+ }
+
+ PersonIdent authorFor(final PlotCommit<SwingLane> c) {
+ if (c != lastCommit) {
+ lastCommit = c;
+ lastAuthor = c.getAuthorIdent();
+ }
+ return lastAuthor;
+ }
+ }
+
+ class NameCellRender extends DefaultTableCellRenderer {
+ private static final long serialVersionUID = 1L;
+
+ public Component getTableCellRendererComponent(final JTable table,
+ final Object value, final boolean isSelected,
+ final boolean hasFocus, final int row, final int column) {
+ final PersonIdent pi = (PersonIdent) value;
+
+ final String valueStr;
+ if (pi != null)
+ valueStr = pi.getName() + " <" + pi.getEmailAddress() + ">";
+ else
+ valueStr = "";
+ return super.getTableCellRendererComponent(table, valueStr,
+ isSelected, hasFocus, row, column);
+ }
+ }
+
+ class DateCellRender extends DefaultTableCellRenderer {
+ private static final long serialVersionUID = 1L;
+
+ private final DateFormat fmt = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss");
+
+ public Component getTableCellRendererComponent(final JTable table,
+ final Object value, final boolean isSelected,
+ final boolean hasFocus, final int row, final int column) {
+ final PersonIdent pi = (PersonIdent) value;
+
+ final String valueStr;
+ if (pi != null)
+ valueStr = fmt.format(pi.getWhen());
+ else
+ valueStr = "";
+ return super.getTableCellRendererComponent(table, valueStr,
+ isSelected, hasFocus, row, column);
+ }
+ }
+
+ class GraphCellRender extends DefaultTableCellRenderer {
+ private static final long serialVersionUID = 1L;
+
+ private final AWTPlotRenderer renderer = new AWTPlotRenderer(this);
+
+ PlotCommit<SwingLane> commit;
+
+ public Component getTableCellRendererComponent(final JTable table,
+ final Object value, final boolean isSelected,
+ final boolean hasFocus, final int row, final int column) {
+ super.getTableCellRendererComponent(table, value, isSelected,
+ hasFocus, row, column);
+ commit = (PlotCommit<SwingLane>) value;
+ return this;
+ }
+
+ @Override
+ protected void paintComponent(final Graphics inputGraphics) {
+ if (inputGraphics == null)
+ return;
+ renderer.paint(inputGraphics, commit);
+ }
+ }
+
+ static final Stroke[] strokeCache;
+
+ static {
+ strokeCache = new Stroke[4];
+ for (int i = 1; i < strokeCache.length; i++)
+ strokeCache[i] = new BasicStroke(i);
+ }
+
+ static Stroke stroke(final int width) {
+ if (width < strokeCache.length)
+ return strokeCache[width];
+ return new BasicStroke(width);
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/awtui/SwingCommitList.java b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/SwingCommitList.java
new file mode 100644
index 0000000000..4a11964473
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/SwingCommitList.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.awtui;
+
+import java.awt.Color;
+import java.util.LinkedList;
+
+import org.eclipse.jgit.revplot.PlotCommitList;
+import org.eclipse.jgit.revplot.PlotLane;
+
+class SwingCommitList extends PlotCommitList<SwingCommitList.SwingLane> {
+ final LinkedList<Color> colors;
+
+ SwingCommitList() {
+ colors = new LinkedList<Color>();
+ repackColors();
+ }
+
+ private void repackColors() {
+ colors.add(Color.green);
+ colors.add(Color.blue);
+ colors.add(Color.red);
+ colors.add(Color.magenta);
+ colors.add(Color.darkGray);
+ colors.add(Color.yellow.darker());
+ colors.add(Color.orange);
+ }
+
+ @Override
+ protected SwingLane createLane() {
+ final SwingLane lane = new SwingLane();
+ if (colors.isEmpty())
+ repackColors();
+ lane.color = colors.removeFirst();
+ return lane;
+ }
+
+ @Override
+ protected void recycleLane(final SwingLane lane) {
+ colors.add(lane.color);
+ }
+
+ static class SwingLane extends PlotLane {
+ Color color;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
new file mode 100644
index 0000000000..639ed77ee8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008-2009, Johannes E. Schindelin <johannes.schindelin@gmx.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.diff;
+
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.eclipse.jgit.patch.FileHeader;
+
+/**
+ * Format an {@link EditList} as a Git style unified patch script.
+ */
+public class DiffFormatter {
+ private static final byte[] noNewLine = encodeASCII("\\ No newline at end of file\n");
+
+ private int context;
+
+ /** Create a new formatter with a default level of context. */
+ public DiffFormatter() {
+ setContext(3);
+ }
+
+ /**
+ * Change the number of lines of context to display.
+ *
+ * @param lineCount
+ * number of lines of context to see before the first
+ * modification and after the last modification within a hunk of
+ * the modified file.
+ */
+ public void setContext(final int lineCount) {
+ if (lineCount < 0)
+ throw new IllegalArgumentException("context must be >= 0");
+ context = lineCount;
+ }
+
+ /**
+ * Format a patch script, reusing a previously parsed FileHeader.
+ * <p>
+ * This formatter is primarily useful for editing an existing patch script
+ * to increase or reduce the number of lines of context within the script.
+ * All header lines are reused as-is from the supplied FileHeader.
+ *
+ * @param out
+ * stream to write the patch script out to.
+ * @param head
+ * existing file header containing the header lines to copy.
+ * @param a
+ * text source for the pre-image version of the content. This
+ * must match the content of {@link FileHeader#getOldId()}.
+ * @param b
+ * text source for the post-image version of the content. This
+ * must match the content of {@link FileHeader#getNewId()}.
+ * @throws IOException
+ * writing to the supplied stream failed.
+ */
+ public void format(final OutputStream out, final FileHeader head,
+ final RawText a, final RawText b) throws IOException {
+ // Reuse the existing FileHeader as-is by blindly copying its
+ // header lines, but avoiding its hunks. Instead we recreate
+ // the hunks from the text instances we have been supplied.
+ //
+ final int start = head.getStartOffset();
+ int end = head.getEndOffset();
+ if (!head.getHunks().isEmpty())
+ end = head.getHunks().get(0).getStartOffset();
+ out.write(head.getBuffer(), start, end - start);
+
+ formatEdits(out, a, b, head.toEditList());
+ }
+
+ private void formatEdits(final OutputStream out, final RawText a,
+ final RawText b, final EditList edits) throws IOException {
+ for (int curIdx = 0; curIdx < edits.size();) {
+ Edit curEdit = edits.get(curIdx);
+ final int endIdx = findCombinedEnd(edits, curIdx);
+ final Edit endEdit = edits.get(endIdx);
+
+ int aCur = Math.max(0, curEdit.getBeginA() - context);
+ int bCur = Math.max(0, curEdit.getBeginB() - context);
+ final int aEnd = Math.min(a.size(), endEdit.getEndA() + context);
+ final int bEnd = Math.min(b.size(), endEdit.getEndB() + context);
+
+ writeHunkHeader(out, aCur, aEnd, bCur, bEnd);
+
+ while (aCur < aEnd || bCur < bEnd) {
+ if (aCur < curEdit.getBeginA() || endIdx + 1 < curIdx) {
+ writeLine(out, ' ', a, aCur);
+ aCur++;
+ bCur++;
+
+ } else if (aCur < curEdit.getEndA()) {
+ writeLine(out, '-', a, aCur++);
+
+ } else if (bCur < curEdit.getEndB()) {
+ writeLine(out, '+', b, bCur++);
+ }
+
+ if (end(curEdit, aCur, bCur) && ++curIdx < edits.size())
+ curEdit = edits.get(curIdx);
+ }
+ }
+ }
+
+ private void writeHunkHeader(final OutputStream out, int aCur, int aEnd,
+ int bCur, int bEnd) throws IOException {
+ out.write('@');
+ out.write('@');
+ writeRange(out, '-', aCur + 1, aEnd - aCur);
+ writeRange(out, '+', bCur + 1, bEnd - bCur);
+ out.write(' ');
+ out.write('@');
+ out.write('@');
+ out.write('\n');
+ }
+
+ private static void writeRange(final OutputStream out, final char prefix,
+ final int begin, final int cnt) throws IOException {
+ out.write(' ');
+ out.write(prefix);
+ switch (cnt) {
+ case 0:
+ // If the range is empty, its beginning number must be the
+ // line just before the range, or 0 if the range is at the
+ // start of the file stream. Here, begin is always 1 based,
+ // so an empty file would produce "0,0".
+ //
+ out.write(encodeASCII(begin - 1));
+ out.write(',');
+ out.write('0');
+ break;
+
+ case 1:
+ // If the range is exactly one line, produce only the number.
+ //
+ out.write(encodeASCII(begin));
+ break;
+
+ default:
+ out.write(encodeASCII(begin));
+ out.write(',');
+ out.write(encodeASCII(cnt));
+ break;
+ }
+ }
+
+ private static void writeLine(final OutputStream out, final char prefix,
+ final RawText text, final int cur) throws IOException {
+ out.write(prefix);
+ text.writeLine(out, cur);
+ out.write('\n');
+ if (cur + 1 == text.size() && text.isMissingNewlineAtEnd())
+ out.write(noNewLine);
+ }
+
+ private int findCombinedEnd(final List<Edit> edits, final int i) {
+ int end = i + 1;
+ while (end < edits.size()
+ && (combineA(edits, end) || combineB(edits, end)))
+ end++;
+ return end - 1;
+ }
+
+ private boolean combineA(final List<Edit> e, final int i) {
+ return e.get(i).getBeginA() - e.get(i - 1).getEndA() <= 2 * context;
+ }
+
+ private boolean combineB(final List<Edit> e, final int i) {
+ return e.get(i).getBeginB() - e.get(i - 1).getEndB() <= 2 * context;
+ }
+
+ private static boolean end(final Edit edit, final int a, final int b) {
+ return edit.getEndA() <= a && edit.getEndB() <= b;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
new file mode 100644
index 0000000000..109c049ccd
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2008-2009, Johannes E. Schindelin <johannes.schindelin@gmx.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.diff;
+
+/**
+ * A modified region detected between two versions of roughly the same content.
+ * <p>
+ * An edit covers the modified region only. It does not cover a common region.
+ * <p>
+ * Regions should be specified using 0 based notation, so add 1 to the start and
+ * end marks for line numbers in a file.
+ * <p>
+ * An edit where <code>beginA == endA && beginB < endB</code> is an insert edit,
+ * that is sequence B inserted the elements in region
+ * <code>[beginB, endB)</code> at <code>beginA</code>.
+ * <p>
+ * An edit where <code>beginA < endA && beginB == endB</code> is a delete edit,
+ * that is sequence B has removed the elements between
+ * <code>[beginA, endA)</code>.
+ * <p>
+ * An edit where <code>beginA < endA && beginB < endB</code> is a replace edit,
+ * that is sequence B has replaced the range of elements between
+ * <code>[beginA, endA)</code> with those found in <code>[beginB, endB)</code>.
+ */
+public class Edit {
+ /** Type of edit */
+ public static enum Type {
+ /** Sequence B has inserted the region. */
+ INSERT,
+
+ /** Sequence B has removed the region. */
+ DELETE,
+
+ /** Sequence B has replaced the region with different content. */
+ REPLACE,
+
+ /** Sequence A and B have zero length, describing nothing. */
+ EMPTY;
+ }
+
+ int beginA;
+
+ int endA;
+
+ int beginB;
+
+ int endB;
+
+ /**
+ * Create a new empty edit.
+ *
+ * @param as
+ * beginA: start and end of region in sequence A; 0 based.
+ * @param bs
+ * beginB: start and end of region in sequence B; 0 based.
+ */
+ public Edit(final int as, final int bs) {
+ this(as, as, bs, bs);
+ }
+
+ /**
+ * Create a new edit.
+ *
+ * @param as
+ * beginA: start of region in sequence A; 0 based.
+ * @param ae
+ * endA: end of region in sequence A; must be >= as.
+ * @param bs
+ * beginB: start of region in sequence B; 0 based.
+ * @param be
+ * endB: end of region in sequence B; must be >= bs.
+ */
+ public Edit(final int as, final int ae, final int bs, final int be) {
+ beginA = as;
+ endA = ae;
+
+ beginB = bs;
+ endB = be;
+ }
+
+ /** @return the type of this region */
+ public final Type getType() {
+ if (beginA == endA && beginB < endB)
+ return Type.INSERT;
+ if (beginA < endA && beginB == endB)
+ return Type.DELETE;
+ if (beginA == endA && beginB == endB)
+ return Type.EMPTY;
+ return Type.REPLACE;
+ }
+
+ /** @return start point in sequence A. */
+ public final int getBeginA() {
+ return beginA;
+ }
+
+ /** @return end point in sequence A. */
+ public final int getEndA() {
+ return endA;
+ }
+
+ /** @return start point in sequence B. */
+ public final int getBeginB() {
+ return beginB;
+ }
+
+ /** @return end point in sequence B. */
+ public final int getEndB() {
+ return endB;
+ }
+
+ /** Increase {@link #getEndA()} by 1. */
+ public void extendA() {
+ endA++;
+ }
+
+ /** Increase {@link #getEndB()} by 1. */
+ public void extendB() {
+ endB++;
+ }
+
+ /** Swap A and B, so the edit goes the other direction. */
+ public void swap() {
+ final int sBegin = beginA;
+ final int sEnd = endA;
+
+ beginA = beginB;
+ endA = endB;
+
+ beginB = sBegin;
+ endB = sEnd;
+ }
+
+ @Override
+ public int hashCode() {
+ return beginA ^ endA;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o instanceof Edit) {
+ final Edit e = (Edit) o;
+ return this.beginA == e.beginA && this.endA == e.endA
+ && this.beginB == e.beginB && this.endB == e.endB;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final Type t = getType();
+ return t + "(" + beginA + "-" + endA + "," + beginB + "-" + endB + ")";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java
new file mode 100644
index 0000000000..85a5396440
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.diff;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+
+/** Specialized list of {@link Edit}s in a document. */
+public class EditList extends AbstractList<Edit> {
+ private final ArrayList<Edit> container;
+
+ /** Create a new, empty edit list. */
+ public EditList() {
+ container = new ArrayList<Edit>();
+ }
+
+ @Override
+ public int size() {
+ return container.size();
+ }
+
+ @Override
+ public Edit get(final int index) {
+ return container.get(index);
+ }
+
+ @Override
+ public Edit set(final int index, final Edit element) {
+ return container.set(index, element);
+ }
+
+ @Override
+ public void add(final int index, final Edit element) {
+ container.add(index, element);
+ }
+
+ @Override
+ public Edit remove(final int index) {
+ return container.remove(index);
+ }
+
+ @Override
+ public int hashCode() {
+ return container.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o instanceof EditList)
+ return container.equals(((EditList) o).container);
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "EditList" + container.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
new file mode 100644
index 0000000000..61a3ef41cd
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008-2009, Johannes E. Schindelin <johannes.schindelin@gmx.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.diff;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.util.IntList;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * A Sequence supporting UNIX formatted text in byte[] format.
+ * <p>
+ * Elements of the sequence are the lines of the file, as delimited by the UNIX
+ * newline character ('\n'). The file content is treated as 8 bit binary text,
+ * with no assumptions or requirements on character encoding.
+ * <p>
+ * Note that the first line of the file is element 0, as defined by the Sequence
+ * interface API. Traditionally in a text editor a patch file the first line is
+ * line number 1. Callers may need to subtract 1 prior to invoking methods if
+ * they are converting from "line number" to "element index".
+ */
+public class RawText implements Sequence {
+ /** The file content for this sequence. */
+ protected final byte[] content;
+
+ /** Map of line number to starting position within {@link #content}. */
+ protected final IntList lines;
+
+ /** Hash code for each line, for fast equality elimination. */
+ protected final IntList hashes;
+
+ /**
+ * Create a new sequence from an existing content byte array.
+ * <p>
+ * The entire array (indexes 0 through length-1) is used as the content.
+ *
+ * @param input
+ * the content array. The array is never modified, so passing
+ * through cached arrays is safe.
+ */
+ public RawText(final byte[] input) {
+ content = input;
+ lines = RawParseUtils.lineMap(content, 0, content.length);
+ hashes = computeHashes();
+ }
+
+ public int size() {
+ // The line map is always 2 entries larger than the number of lines in
+ // the file. Index 0 is padded out/unused. The last index is the total
+ // length of the buffer, and acts as a sentinel.
+ //
+ return lines.size() - 2;
+ }
+
+ public boolean equals(final int i, final Sequence other, final int j) {
+ return equals(this, i + 1, (RawText) other, j + 1);
+ }
+
+ private static boolean equals(final RawText a, final int ai,
+ final RawText b, final int bi) {
+ if (a.hashes.get(ai) != b.hashes.get(bi))
+ return false;
+
+ int as = a.lines.get(ai);
+ int bs = b.lines.get(bi);
+ final int ae = a.lines.get(ai + 1);
+ final int be = b.lines.get(bi + 1);
+
+ if (ae - as != be - bs)
+ return false;
+
+ while (as < ae) {
+ if (a.content[as++] != b.content[bs++])
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Write a specific line to the output stream, without its trailing LF.
+ * <p>
+ * The specified line is copied as-is, with no character encoding
+ * translation performed.
+ * <p>
+ * If the specified line ends with an LF ('\n'), the LF is <b>not</b>
+ * copied. It is up to the caller to write the LF, if desired, between
+ * output lines.
+ *
+ * @param out
+ * stream to copy the line data onto.
+ * @param i
+ * index of the line to extract. Note this is 0-based, so line
+ * number 1 is actually index 0.
+ * @throws IOException
+ * the stream write operation failed.
+ */
+ public void writeLine(final OutputStream out, final int i)
+ throws IOException {
+ final int start = lines.get(i + 1);
+ int end = lines.get(i + 2);
+ if (content[end - 1] == '\n')
+ end--;
+ out.write(content, start, end - start);
+ }
+
+ /**
+ * Determine if the file ends with a LF ('\n').
+ *
+ * @return true if the last line has an LF; false otherwise.
+ */
+ public boolean isMissingNewlineAtEnd() {
+ final int end = lines.get(lines.size() - 1);
+ if (end == 0)
+ return true;
+ return content[end - 1] != '\n';
+ }
+
+ private IntList computeHashes() {
+ final IntList r = new IntList(lines.size());
+ r.add(0);
+ for (int lno = 1; lno < lines.size() - 1; lno++) {
+ final int ptr = lines.get(lno);
+ final int end = lines.get(lno + 1);
+ r.add(hashLine(content, ptr, end));
+ }
+ r.add(0);
+ return r;
+ }
+
+ /**
+ * Compute a hash code for a single line.
+ *
+ * @param raw
+ * the raw file content.
+ * @param ptr
+ * first byte of the content line to hash.
+ * @param end
+ * 1 past the last byte of the content line.
+ * @return hash code for the region <code>[ptr, end)</code> of raw.
+ */
+ protected int hashLine(final byte[] raw, int ptr, final int end) {
+ int hash = 5381;
+ for (; ptr < end; ptr++)
+ hash = (hash << 5) ^ (raw[ptr] & 0xff);
+ return hash;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Sequence.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Sequence.java
new file mode 100644
index 0000000000..3a33564113
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Sequence.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2008-2009, Johannes E. Schindelin <johannes.schindelin@gmx.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.diff;
+
+/**
+ * Arbitrary sequence of elements with fast comparison support.
+ * <p>
+ * A sequence of elements is defined to contain elements in the index range
+ * <code>[0, {@link #size()})</code>, like a standard Java List implementation.
+ * Unlike a List, the members of the sequence are not directly obtainable, but
+ * element equality can be tested if two Sequences are the same implementation.
+ * <p>
+ * An implementation may chose to implement the equals semantic as necessary,
+ * including fuzzy matching rules such as ignoring insignificant sub-elements,
+ * e.g. ignoring whitespace differences in text.
+ * <p>
+ * Implementations of Sequence are primarily intended for use in content
+ * difference detection algorithms, to produce an {@link EditList} of
+ * {@link Edit} instances describing how two Sequence instances differ.
+ */
+public interface Sequence {
+ /** @return total number of items in the sequence. */
+ public int size();
+
+ /**
+ * Determine if the i-th member is equal to the j-th member.
+ * <p>
+ * Implementations must ensure <code>equals(thisIdx,other,otherIdx)</code>
+ * returns the same as <code>other.equals(otherIdx,this,thisIdx)</code>.
+ *
+ * @param thisIdx
+ * index within <code>this</code> sequence; must be in the range
+ * <code>[ 0, this.size() )</code>.
+ * @param other
+ * another sequence; must be the same implementation class, that
+ * is <code>this.getClass() == other.getClass()</code>.
+ * @param otherIdx
+ * index within <code>other</code> sequence; must be in the range
+ * <code>[ 0, other.size() )</code>.
+ * @return true if the elements are equal; false if they are not equal.
+ */
+ public boolean equals(int thisIdx, Sequence other, int otherIdx);
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
new file mode 100644
index 0000000000..70f80aeb7a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.dircache;
+
+import java.io.IOException;
+
+/**
+ * Generic update/editing support for {@link DirCache}.
+ * <p>
+ * The different update strategies extend this class to provide their own unique
+ * services to applications.
+ */
+abstract class BaseDirCacheEditor {
+ /** The cache instance this editor updates during {@link #finish()}. */
+ protected DirCache cache;
+
+ /**
+ * Entry table this builder will eventually replace into {@link #cache}.
+ * <p>
+ * Use {@link #fastAdd(DirCacheEntry)} or {@link #fastKeep(int, int)} to
+ * make additions to this table. The table is automatically expanded if it
+ * is too small for a new addition.
+ * <p>
+ * Typically the entries in here are sorted by their path names, just like
+ * they are in the DirCache instance.
+ */
+ protected DirCacheEntry[] entries;
+
+ /** Total number of valid entries in {@link #entries}. */
+ protected int entryCnt;
+
+ /**
+ * Construct a new editor.
+ *
+ * @param dc
+ * the cache this editor will eventually update.
+ * @param ecnt
+ * estimated number of entries the editor will have upon
+ * completion. This sizes the initial entry table.
+ */
+ protected BaseDirCacheEditor(final DirCache dc, final int ecnt) {
+ cache = dc;
+ entries = new DirCacheEntry[ecnt];
+ }
+
+ /**
+ * @return the cache we will update on {@link #finish()}.
+ */
+ public DirCache getDirCache() {
+ return cache;
+ }
+
+ /**
+ * Append one entry into the resulting entry list.
+ * <p>
+ * The entry is placed at the end of the entry list. The caller is
+ * responsible for making sure the final table is correctly sorted.
+ * <p>
+ * The {@link #entries} table is automatically expanded if there is
+ * insufficient space for the new addition.
+ *
+ * @param newEntry
+ * the new entry to add.
+ */
+ protected void fastAdd(final DirCacheEntry newEntry) {
+ if (entries.length == entryCnt) {
+ final DirCacheEntry[] n = new DirCacheEntry[(entryCnt + 16) * 3 / 2];
+ System.arraycopy(entries, 0, n, 0, entryCnt);
+ entries = n;
+ }
+ entries[entryCnt++] = newEntry;
+ }
+
+ /**
+ * Add a range of existing entries from the destination cache.
+ * <p>
+ * The entries are placed at the end of the entry list, preserving their
+ * current order. The caller is responsible for making sure the final table
+ * is correctly sorted.
+ * <p>
+ * This method copies from the destination cache, which has not yet been
+ * updated with this editor's new table. So all offsets into the destination
+ * cache are not affected by any updates that may be currently taking place
+ * in this editor.
+ * <p>
+ * The {@link #entries} table is automatically expanded if there is
+ * insufficient space for the new additions.
+ *
+ * @param pos
+ * first entry to copy from the destination cache.
+ * @param cnt
+ * number of entries to copy.
+ */
+ protected void fastKeep(final int pos, int cnt) {
+ if (entryCnt + cnt > entries.length) {
+ final int m1 = (entryCnt + 16) * 3 / 2;
+ final int m2 = entryCnt + cnt;
+ final DirCacheEntry[] n = new DirCacheEntry[Math.max(m1, m2)];
+ System.arraycopy(entries, 0, n, 0, entryCnt);
+ entries = n;
+ }
+
+ cache.toArray(pos, entries, entryCnt, cnt);
+ entryCnt += cnt;
+ }
+
+ /**
+ * Finish this builder and update the destination {@link DirCache}.
+ * <p>
+ * When this method completes this builder instance is no longer usable by
+ * the calling application. A new builder must be created to make additional
+ * changes to the index entries.
+ * <p>
+ * After completion the DirCache returned by {@link #getDirCache()} will
+ * contain all modifications.
+ * <p>
+ * <i>Note to implementors:</i> Make sure {@link #entries} is fully sorted
+ * then invoke {@link #replace()} to update the DirCache with the new table.
+ */
+ public abstract void finish();
+
+ /**
+ * Update the DirCache with the contents of {@link #entries}.
+ * <p>
+ * This method should be invoked only during an implementation of
+ * {@link #finish()}, and only after {@link #entries} is sorted.
+ */
+ protected void replace() {
+ if (entryCnt < entries.length / 2) {
+ final DirCacheEntry[] n = new DirCacheEntry[entryCnt];
+ System.arraycopy(entries, 0, n, 0, entryCnt);
+ entries = n;
+ }
+ cache.replace(entries, entryCnt);
+ }
+
+ /**
+ * Finish, write, commit this change, and release the index lock.
+ * <p>
+ * If this method fails (returns false) the lock is still released.
+ * <p>
+ * This is a utility method for applications as the finish-write-commit
+ * pattern is very common after using a builder to update entries.
+ *
+ * @return true if the commit was successful and the file contains the new
+ * data; false if the commit failed and the file remains with the
+ * old data.
+ * @throws IllegalStateException
+ * the lock is not held.
+ * @throws IOException
+ * the output file could not be created. The caller no longer
+ * holds the lock.
+ */
+ public boolean commit() throws IOException {
+ finish();
+ cache.write();
+ return cache.commit();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
new file mode 100644
index 0000000000..d6676e41fd
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -0,0 +1,756 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.dircache;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.UnmergedPathException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.LockFile;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.MutableInteger;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.TemporaryBuffer;
+
+/**
+ * Support for the Git dircache (aka index file).
+ * <p>
+ * The index file keeps track of which objects are currently checked out in the
+ * working directory, and the last modified time of those working files. Changes
+ * in the working directory can be detected by comparing the modification times
+ * to the cached modification time within the index file.
+ * <p>
+ * Index files are also used during merges, where the merge happens within the
+ * index file first, and the working directory is updated as a post-merge step.
+ * Conflicts are stored in the index file to allow tool (and human) based
+ * resolutions to be easily performed.
+ */
+public class DirCache {
+ private static final byte[] SIG_DIRC = { 'D', 'I', 'R', 'C' };
+
+ private static final int EXT_TREE = 0x54524545 /* 'TREE' */;
+
+ private static final int INFO_LEN = DirCacheEntry.INFO_LEN;
+
+ private static final DirCacheEntry[] NO_ENTRIES = {};
+
+ static final Comparator<DirCacheEntry> ENT_CMP = new Comparator<DirCacheEntry>() {
+ public int compare(final DirCacheEntry o1, final DirCacheEntry o2) {
+ final int cr = cmp(o1, o2);
+ if (cr != 0)
+ return cr;
+ return o1.getStage() - o2.getStage();
+ }
+ };
+
+ static int cmp(final DirCacheEntry a, final DirCacheEntry b) {
+ return cmp(a.path, a.path.length, b);
+ }
+
+ static int cmp(final byte[] aPath, final int aLen, final DirCacheEntry b) {
+ return cmp(aPath, aLen, b.path, b.path.length);
+ }
+
+ static int cmp(final byte[] aPath, final int aLen, final byte[] bPath,
+ final int bLen) {
+ for (int cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
+ final int cmp = (aPath[cPos] & 0xff) - (bPath[cPos] & 0xff);
+ if (cmp != 0)
+ return cmp;
+ }
+ return aLen - bLen;
+ }
+
+ /**
+ * Create a new empty index which is never stored on disk.
+ *
+ * @return an empty cache which has no backing store file. The cache may not
+ * be read or written, but it may be queried and updated (in
+ * memory).
+ */
+ public static DirCache newInCore() {
+ return new DirCache(null);
+ }
+
+ /**
+ * Create a new in-core index representation and read an index from disk.
+ * <p>
+ * The new index will be read before it is returned to the caller. Read
+ * failures are reported as exceptions and therefore prevent the method from
+ * returning a partially populated index.
+ *
+ * @param indexLocation
+ * location of the index file on disk.
+ * @return a cache representing the contents of the specified index file (if
+ * it exists) or an empty cache if the file does not exist.
+ * @throws IOException
+ * the index file is present but could not be read.
+ * @throws CorruptObjectException
+ * the index file is using a format or extension that this
+ * library does not support.
+ */
+ public static DirCache read(final File indexLocation)
+ throws CorruptObjectException, IOException {
+ final DirCache c = new DirCache(indexLocation);
+ c.read();
+ return c;
+ }
+
+ /**
+ * Create a new in-core index representation and read an index from disk.
+ * <p>
+ * The new index will be read before it is returned to the caller. Read
+ * failures are reported as exceptions and therefore prevent the method from
+ * returning a partially populated index.
+ *
+ * @param db
+ * repository the caller wants to read the default index of.
+ * @return a cache representing the contents of the specified index file (if
+ * it exists) or an empty cache if the file does not exist.
+ * @throws IOException
+ * the index file is present but could not be read.
+ * @throws CorruptObjectException
+ * the index file is using a format or extension that this
+ * library does not support.
+ */
+ public static DirCache read(final Repository db)
+ throws CorruptObjectException, IOException {
+ return read(new File(db.getDirectory(), "index"));
+ }
+
+ /**
+ * Create a new in-core index representation, lock it, and read from disk.
+ * <p>
+ * The new index will be locked and then read before it is returned to the
+ * caller. Read failures are reported as exceptions and therefore prevent
+ * the method from returning a partially populated index. On read failure,
+ * the lock is released.
+ *
+ * @param indexLocation
+ * location of the index file on disk.
+ * @return a cache representing the contents of the specified index file (if
+ * it exists) or an empty cache if the file does not exist.
+ * @throws IOException
+ * the index file is present but could not be read, or the lock
+ * could not be obtained.
+ * @throws CorruptObjectException
+ * the index file is using a format or extension that this
+ * library does not support.
+ */
+ public static DirCache lock(final File indexLocation)
+ throws CorruptObjectException, IOException {
+ final DirCache c = new DirCache(indexLocation);
+ if (!c.lock())
+ throw new IOException("Cannot lock " + indexLocation);
+
+ try {
+ c.read();
+ } catch (IOException e) {
+ c.unlock();
+ throw e;
+ } catch (RuntimeException e) {
+ c.unlock();
+ throw e;
+ } catch (Error e) {
+ c.unlock();
+ throw e;
+ }
+
+ return c;
+ }
+
+ /**
+ * Create a new in-core index representation, lock it, and read from disk.
+ * <p>
+ * The new index will be locked and then read before it is returned to the
+ * caller. Read failures are reported as exceptions and therefore prevent
+ * the method from returning a partially populated index.
+ *
+ * @param db
+ * repository the caller wants to read the default index of.
+ * @return a cache representing the contents of the specified index file (if
+ * it exists) or an empty cache if the file does not exist.
+ * @throws IOException
+ * the index file is present but could not be read, or the lock
+ * could not be obtained.
+ * @throws CorruptObjectException
+ * the index file is using a format or extension that this
+ * library does not support.
+ */
+ public static DirCache lock(final Repository db)
+ throws CorruptObjectException, IOException {
+ return lock(new File(db.getDirectory(), "index"));
+ }
+
+ /** Location of the current version of the index file. */
+ private final File liveFile;
+
+ /** Modification time of the file at the last read/write we did. */
+ private long lastModified;
+
+ /** Individual file index entries, sorted by path name. */
+ private DirCacheEntry[] sortedEntries;
+
+ /** Number of positions within {@link #sortedEntries} that are valid. */
+ private int entryCnt;
+
+ /** Cache tree for this index; null if the cache tree is not available. */
+ private DirCacheTree tree;
+
+ /** Our active lock (if we hold it); null if we don't have it locked. */
+ private LockFile myLock;
+
+ /**
+ * Create a new in-core index representation.
+ * <p>
+ * The new index will be empty. Callers may wish to read from the on disk
+ * file first with {@link #read()}.
+ *
+ * @param indexLocation
+ * location of the index file on disk.
+ */
+ public DirCache(final File indexLocation) {
+ liveFile = indexLocation;
+ clear();
+ }
+
+ /**
+ * Create a new builder to update this cache.
+ * <p>
+ * Callers should add all entries to the builder, then use
+ * {@link DirCacheBuilder#finish()} to update this instance.
+ *
+ * @return a new builder instance for this cache.
+ */
+ public DirCacheBuilder builder() {
+ return new DirCacheBuilder(this, entryCnt + 16);
+ }
+
+ /**
+ * Create a new editor to recreate this cache.
+ * <p>
+ * Callers should add commands to the editor, then use
+ * {@link DirCacheEditor#finish()} to update this instance.
+ *
+ * @return a new builder instance for this cache.
+ */
+ public DirCacheEditor editor() {
+ return new DirCacheEditor(this, entryCnt + 16);
+ }
+
+ void replace(final DirCacheEntry[] e, final int cnt) {
+ sortedEntries = e;
+ entryCnt = cnt;
+ tree = null;
+ }
+
+ /**
+ * Read the index from disk, if it has changed on disk.
+ * <p>
+ * This method tries to avoid loading the index if it has not changed since
+ * the last time we consulted it. A missing index file will be treated as
+ * though it were present but had no file entries in it.
+ *
+ * @throws IOException
+ * the index file is present but could not be read. This
+ * DirCache instance may not be populated correctly.
+ * @throws CorruptObjectException
+ * the index file is using a format or extension that this
+ * library does not support.
+ */
+ public void read() throws IOException, CorruptObjectException {
+ if (liveFile == null)
+ throw new IOException("DirCache does not have a backing file");
+ if (!liveFile.exists())
+ clear();
+ else if (liveFile.lastModified() != lastModified) {
+ try {
+ final FileInputStream inStream = new FileInputStream(liveFile);
+ try {
+ clear();
+ readFrom(inStream);
+ } finally {
+ try {
+ inStream.close();
+ } catch (IOException err2) {
+ // Ignore any close failures.
+ }
+ }
+ } catch (FileNotFoundException fnfe) {
+ // Someone must have deleted it between our exists test
+ // and actually opening the path. That's fine, its empty.
+ //
+ clear();
+ }
+ }
+ }
+
+ /** Empty this index, removing all entries. */
+ public void clear() {
+ lastModified = 0;
+ sortedEntries = NO_ENTRIES;
+ entryCnt = 0;
+ tree = null;
+ }
+
+ private void readFrom(final FileInputStream inStream) throws IOException,
+ CorruptObjectException {
+ final BufferedInputStream in = new BufferedInputStream(inStream);
+ final MessageDigest md = Constants.newMessageDigest();
+
+ // Read the index header and verify we understand it.
+ //
+ final byte[] hdr = new byte[20];
+ NB.readFully(in, hdr, 0, 12);
+ md.update(hdr, 0, 12);
+ if (!is_DIRC(hdr))
+ throw new CorruptObjectException("Not a DIRC file.");
+ final int ver = NB.decodeInt32(hdr, 4);
+ if (ver != 2)
+ throw new CorruptObjectException("Unknown DIRC version " + ver);
+ entryCnt = NB.decodeInt32(hdr, 8);
+ if (entryCnt < 0)
+ throw new CorruptObjectException("DIRC has too many entries.");
+
+ // Load the individual file entries.
+ //
+ final byte[] infos = new byte[INFO_LEN * entryCnt];
+ sortedEntries = new DirCacheEntry[entryCnt];
+ for (int i = 0; i < entryCnt; i++)
+ sortedEntries[i] = new DirCacheEntry(infos, i * INFO_LEN, in, md);
+ lastModified = liveFile.lastModified();
+
+ // After the file entries are index extensions, and then a footer.
+ //
+ for (;;) {
+ in.mark(21);
+ NB.readFully(in, hdr, 0, 20);
+ if (in.read() < 0) {
+ // No extensions present; the file ended where we expected.
+ //
+ break;
+ }
+ in.reset();
+
+ switch (NB.decodeInt32(hdr, 0)) {
+ case EXT_TREE: {
+ final byte[] raw = new byte[NB.decodeInt32(hdr, 4)];
+ md.update(hdr, 0, 8);
+ NB.skipFully(in, 8);
+ NB.readFully(in, raw, 0, raw.length);
+ md.update(raw, 0, raw.length);
+ tree = new DirCacheTree(raw, new MutableInteger(), null);
+ break;
+ }
+ default:
+ if (hdr[0] >= 'A' && hdr[0] <= 'Z') {
+ // The extension is optional and is here only as
+ // a performance optimization. Since we do not
+ // understand it, we can safely skip past it.
+ //
+ NB.skipFully(in, NB.decodeUInt32(hdr, 4));
+ } else {
+ // The extension is not an optimization and is
+ // _required_ to understand this index format.
+ // Since we did not trap it above we must abort.
+ //
+ throw new CorruptObjectException("DIRC extension '"
+ + Constants.CHARSET.decode(
+ ByteBuffer.wrap(hdr, 0, 4)).toString()
+ + "' not supported by this version.");
+ }
+ }
+ }
+
+ final byte[] exp = md.digest();
+ if (!Arrays.equals(exp, hdr)) {
+ throw new CorruptObjectException("DIRC checksum mismatch");
+ }
+ }
+
+ private static boolean is_DIRC(final byte[] hdr) {
+ if (hdr.length < SIG_DIRC.length)
+ return false;
+ for (int i = 0; i < SIG_DIRC.length; i++)
+ if (hdr[i] != SIG_DIRC[i])
+ return false;
+ return true;
+ }
+
+ /**
+ * Try to establish an update lock on the cache file.
+ *
+ * @return true if the lock is now held by the caller; false if it is held
+ * by someone else.
+ * @throws IOException
+ * the output file could not be created. The caller does not
+ * hold the lock.
+ */
+ public boolean lock() throws IOException {
+ if (liveFile == null)
+ throw new IOException("DirCache does not have a backing file");
+ final LockFile tmp = new LockFile(liveFile);
+ if (tmp.lock()) {
+ tmp.setNeedStatInformation(true);
+ myLock = tmp;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Write the entry records from memory to disk.
+ * <p>
+ * The cache must be locked first by calling {@link #lock()} and receiving
+ * true as the return value. Applications are encouraged to lock the index,
+ * then invoke {@link #read()} to ensure the in-memory data is current,
+ * prior to updating the in-memory entries.
+ * <p>
+ * Once written the lock is closed and must be either committed with
+ * {@link #commit()} or rolled back with {@link #unlock()}.
+ *
+ * @throws IOException
+ * the output file could not be created. The caller no longer
+ * holds the lock.
+ */
+ public void write() throws IOException {
+ final LockFile tmp = myLock;
+ requireLocked(tmp);
+ try {
+ writeTo(new BufferedOutputStream(tmp.getOutputStream()));
+ } catch (IOException err) {
+ tmp.unlock();
+ throw err;
+ } catch (RuntimeException err) {
+ tmp.unlock();
+ throw err;
+ } catch (Error err) {
+ tmp.unlock();
+ throw err;
+ }
+ }
+
+ private void writeTo(final OutputStream os) throws IOException {
+ final MessageDigest foot = Constants.newMessageDigest();
+ final DigestOutputStream dos = new DigestOutputStream(os, foot);
+
+ // Write the header.
+ //
+ final byte[] tmp = new byte[128];
+ System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length);
+ NB.encodeInt32(tmp, 4, /* version */2);
+ NB.encodeInt32(tmp, 8, entryCnt);
+ dos.write(tmp, 0, 12);
+
+ // Write the individual file entries.
+ //
+ if (lastModified <= 0) {
+ // Write a new index, as no entries require smudging.
+ //
+ for (int i = 0; i < entryCnt; i++)
+ sortedEntries[i].write(dos);
+ } else {
+ final int smudge_s = (int) (lastModified / 1000);
+ final int smudge_ns = ((int) (lastModified % 1000)) * 1000000;
+ for (int i = 0; i < entryCnt; i++) {
+ final DirCacheEntry e = sortedEntries[i];
+ if (e.mightBeRacilyClean(smudge_s, smudge_ns))
+ e.smudgeRacilyClean();
+ e.write(dos);
+ }
+ }
+
+ if (tree != null) {
+ final TemporaryBuffer bb = new TemporaryBuffer();
+ tree.write(tmp, bb);
+ bb.close();
+
+ NB.encodeInt32(tmp, 0, EXT_TREE);
+ NB.encodeInt32(tmp, 4, (int) bb.length());
+ dos.write(tmp, 0, 8);
+ bb.writeTo(dos, null);
+ }
+
+ os.write(foot.digest());
+ os.close();
+ }
+
+ /**
+ * Commit this change and release the lock.
+ * <p>
+ * If this method fails (returns false) the lock is still released.
+ *
+ * @return true if the commit was successful and the file contains the new
+ * data; false if the commit failed and the file remains with the
+ * old data.
+ * @throws IllegalStateException
+ * the lock is not held.
+ */
+ public boolean commit() {
+ final LockFile tmp = myLock;
+ requireLocked(tmp);
+ myLock = null;
+ if (!tmp.commit())
+ return false;
+ lastModified = tmp.getCommitLastModified();
+ return true;
+ }
+
+ private void requireLocked(final LockFile tmp) {
+ if (liveFile == null)
+ throw new IllegalStateException("DirCache is not locked");
+ if (tmp == null)
+ throw new IllegalStateException("DirCache "
+ + liveFile.getAbsolutePath() + " not locked.");
+ }
+
+ /**
+ * Unlock this file and abort this change.
+ * <p>
+ * The temporary file (if created) is deleted before returning.
+ */
+ public void unlock() {
+ final LockFile tmp = myLock;
+ if (tmp != null) {
+ myLock = null;
+ tmp.unlock();
+ }
+ }
+
+ /**
+ * Locate the position a path's entry is at in the index.
+ * <p>
+ * If there is at least one entry in the index for this path the position of
+ * the lowest stage is returned. Subsequent stages can be identified by
+ * testing consecutive entries until the path differs.
+ * <p>
+ * If no path matches the entry -(position+1) is returned, where position is
+ * the location it would have gone within the index.
+ *
+ * @param path
+ * the path to search for.
+ * @return if >= 0 then the return value is the position of the entry in the
+ * index; pass to {@link #getEntry(int)} to obtain the entry
+ * information. If < 0 the entry does not exist in the index.
+ */
+ public int findEntry(final String path) {
+ final byte[] p = Constants.encode(path);
+ return findEntry(p, p.length);
+ }
+
+ int findEntry(final byte[] p, final int pLen) {
+ int low = 0;
+ int high = entryCnt;
+ while (low < high) {
+ int mid = (low + high) >>> 1;
+ final int cmp = cmp(p, pLen, sortedEntries[mid]);
+ if (cmp < 0)
+ high = mid;
+ else if (cmp == 0) {
+ while (mid > 0 && cmp(p, pLen, sortedEntries[mid - 1]) == 0)
+ mid--;
+ return mid;
+ } else
+ low = mid + 1;
+ }
+ return -(low + 1);
+ }
+
+ /**
+ * Determine the next index position past all entries with the same name.
+ * <p>
+ * As index entries are sorted by path name, then stage number, this method
+ * advances the supplied position to the first position in the index whose
+ * path name does not match the path name of the supplied position's entry.
+ *
+ * @param position
+ * entry position of the path that should be skipped.
+ * @return position of the next entry whose path is after the input.
+ */
+ public int nextEntry(final int position) {
+ DirCacheEntry last = sortedEntries[position];
+ int nextIdx = position + 1;
+ while (nextIdx < entryCnt) {
+ final DirCacheEntry next = sortedEntries[nextIdx];
+ if (cmp(last, next) != 0)
+ break;
+ last = next;
+ nextIdx++;
+ }
+ return nextIdx;
+ }
+
+ int nextEntry(final byte[] p, final int pLen, int nextIdx) {
+ while (nextIdx < entryCnt) {
+ final DirCacheEntry next = sortedEntries[nextIdx];
+ if (!DirCacheTree.peq(p, next.path, pLen))
+ break;
+ nextIdx++;
+ }
+ return nextIdx;
+ }
+
+ /**
+ * Total number of file entries stored in the index.
+ * <p>
+ * This count includes unmerged stages for a file entry if the file is
+ * currently conflicted in a merge. This means the total number of entries
+ * in the index may be up to 3 times larger than the number of files in the
+ * working directory.
+ * <p>
+ * Note that this value counts only <i>files</i>.
+ *
+ * @return number of entries available.
+ * @see #getEntry(int)
+ */
+ public int getEntryCount() {
+ return entryCnt;
+ }
+
+ /**
+ * Get a specific entry.
+ *
+ * @param i
+ * position of the entry to get.
+ * @return the entry at position <code>i</code>.
+ */
+ public DirCacheEntry getEntry(final int i) {
+ return sortedEntries[i];
+ }
+
+ /**
+ * Get a specific entry.
+ *
+ * @param path
+ * the path to search for.
+ * @return the entry at position <code>i</code>.
+ */
+ public DirCacheEntry getEntry(final String path) {
+ final int i = findEntry(path);
+ return i < 0 ? null : sortedEntries[i];
+ }
+
+ /**
+ * Recursively get all entries within a subtree.
+ *
+ * @param path
+ * the subtree path to get all entries within.
+ * @return all entries recursively contained within the subtree.
+ */
+ public DirCacheEntry[] getEntriesWithin(String path) {
+ if (!path.endsWith("/"))
+ path += "/";
+ final byte[] p = Constants.encode(path);
+ final int pLen = p.length;
+
+ int eIdx = findEntry(p, pLen);
+ if (eIdx < 0)
+ eIdx = -(eIdx + 1);
+ final int lastIdx = nextEntry(p, pLen, eIdx);
+ final DirCacheEntry[] r = new DirCacheEntry[lastIdx - eIdx];
+ System.arraycopy(sortedEntries, eIdx, r, 0, r.length);
+ return r;
+ }
+
+ void toArray(final int i, final DirCacheEntry[] dst, final int off,
+ final int cnt) {
+ System.arraycopy(sortedEntries, i, dst, off, cnt);
+ }
+
+ /**
+ * Obtain (or build) the current cache tree structure.
+ * <p>
+ * This method can optionally recreate the cache tree, without flushing the
+ * tree objects themselves to disk.
+ *
+ * @param build
+ * if true and the cache tree is not present in the index it will
+ * be generated and returned to the caller.
+ * @return the cache tree; null if there is no current cache tree available
+ * and <code>build</code> was false.
+ */
+ public DirCacheTree getCacheTree(final boolean build) {
+ if (build) {
+ if (tree == null)
+ tree = new DirCacheTree();
+ tree.validate(sortedEntries, entryCnt, 0, 0);
+ }
+ return tree;
+ }
+
+ /**
+ * Write all index trees to the object store, returning the root tree.
+ *
+ * @param ow
+ * the writer to use when serializing to the store.
+ * @return identity for the root tree.
+ * @throws UnmergedPathException
+ * one or more paths contain higher-order stages (stage > 0),
+ * which cannot be stored in a tree object.
+ * @throws IllegalStateException
+ * one or more paths contain an invalid mode which should never
+ * appear in a tree object.
+ * @throws IOException
+ * an unexpected error occurred writing to the object store.
+ */
+ public ObjectId writeTree(final ObjectWriter ow)
+ throws UnmergedPathException, IOException {
+ return getCacheTree(true).writeTree(sortedEntries, 0, 0, ow);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
new file mode 100644
index 0000000000..ce1d0638b2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.dircache;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+
+/**
+ * Iterate and update a {@link DirCache} as part of a <code>TreeWalk</code>.
+ * <p>
+ * Like {@link DirCacheIterator} this iterator allows a DirCache to be used in
+ * parallel with other sorts of iterators in a TreeWalk. However any entry which
+ * appears in the source DirCache and which is skipped by the TreeFilter is
+ * automatically copied into {@link DirCacheBuilder}, thus retaining it in the
+ * newly updated index.
+ * <p>
+ * This iterator is suitable for update processes, or even a simple delete
+ * algorithm. For example deleting a path:
+ *
+ * <pre>
+ * final DirCache dirc = DirCache.lock(db);
+ * final DirCacheBuilder edit = dirc.builder();
+ *
+ * final TreeWalk walk = new TreeWalk(db);
+ * walk.reset();
+ * walk.setRecursive(true);
+ * walk.setFilter(PathFilter.create(&quot;name/to/remove&quot;));
+ * walk.addTree(new DirCacheBuildIterator(edit));
+ *
+ * while (walk.next())
+ * ; // do nothing on a match as we want to remove matches
+ * edit.commit();
+ * </pre>
+ */
+public class DirCacheBuildIterator extends DirCacheIterator {
+ private final DirCacheBuilder builder;
+
+ /**
+ * Create a new iterator for an already loaded DirCache instance.
+ * <p>
+ * The iterator implementation may copy part of the cache's data during
+ * construction, so the cache must be read in prior to creating the
+ * iterator.
+ *
+ * @param dcb
+ * the cache builder for the cache to walk. The cache must be
+ * already loaded into memory.
+ */
+ public DirCacheBuildIterator(final DirCacheBuilder dcb) {
+ super(dcb.getDirCache());
+ builder = dcb;
+ }
+
+ DirCacheBuildIterator(final DirCacheBuildIterator p,
+ final DirCacheTree dct) {
+ super(p, dct);
+ builder = p.builder;
+ }
+
+ @Override
+ public AbstractTreeIterator createSubtreeIterator(final Repository repo)
+ throws IncorrectObjectTypeException, IOException {
+ if (currentSubtree == null)
+ throw new IncorrectObjectTypeException(getEntryObjectId(),
+ Constants.TYPE_TREE);
+ return new DirCacheBuildIterator(this, currentSubtree);
+ }
+
+ @Override
+ public void skip() throws CorruptObjectException {
+ if (currentSubtree != null)
+ builder.keep(ptr, currentSubtree.getEntrySpan());
+ else
+ builder.add(currentEntry);
+ next(1);
+ }
+
+ @Override
+ public void stopWalk() {
+ final int cur = ptr;
+ final int cnt = cache.getEntryCount();
+ if (cur < cnt)
+ builder.keep(cur, cnt - cur);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
new file mode 100644
index 0000000000..f294f5cf51
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.dircache;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+/**
+ * Updates a {@link DirCache} by adding individual {@link DirCacheEntry}s.
+ * <p>
+ * A builder always starts from a clean slate and appends in every single
+ * <code>DirCacheEntry</code> which the final updated index must have to reflect
+ * its new content.
+ * <p>
+ * For maximum performance applications should add entries in path name order.
+ * Adding entries out of order is permitted, however a final sorting pass will
+ * be implicitly performed during {@link #finish()} to correct any out-of-order
+ * entries. Duplicate detection is also delayed until the sorting is complete.
+ *
+ * @see DirCacheEditor
+ */
+public class DirCacheBuilder extends BaseDirCacheEditor {
+ private boolean sorted;
+
+ /**
+ * Construct a new builder.
+ *
+ * @param dc
+ * the cache this builder will eventually update.
+ * @param ecnt
+ * estimated number of entries the builder will have upon
+ * completion. This sizes the initial entry table.
+ */
+ protected DirCacheBuilder(final DirCache dc, final int ecnt) {
+ super(dc, ecnt);
+ }
+
+ /**
+ * Append one entry into the resulting entry list.
+ * <p>
+ * The entry is placed at the end of the entry list. If the entry causes the
+ * list to now be incorrectly sorted a final sorting phase will be
+ * automatically enabled within {@link #finish()}.
+ * <p>
+ * The internal entry table is automatically expanded if there is
+ * insufficient space for the new addition.
+ *
+ * @param newEntry
+ * the new entry to add.
+ */
+ public void add(final DirCacheEntry newEntry) {
+ beforeAdd(newEntry);
+ fastAdd(newEntry);
+ }
+
+ /**
+ * Add a range of existing entries from the destination cache.
+ * <p>
+ * The entries are placed at the end of the entry list. If any of the
+ * entries causes the list to now be incorrectly sorted a final sorting
+ * phase will be automatically enabled within {@link #finish()}.
+ * <p>
+ * This method copies from the destination cache, which has not yet been
+ * updated with this editor's new table. So all offsets into the destination
+ * cache are not affected by any updates that may be currently taking place
+ * in this editor.
+ * <p>
+ * The internal entry table is automatically expanded if there is
+ * insufficient space for the new additions.
+ *
+ * @param pos
+ * first entry to copy from the destination cache.
+ * @param cnt
+ * number of entries to copy.
+ */
+ public void keep(final int pos, int cnt) {
+ beforeAdd(cache.getEntry(pos));
+ fastKeep(pos, cnt);
+ }
+
+ /**
+ * Recursively add an entire tree into this builder.
+ * <p>
+ * If pathPrefix is "a/b" and the tree contains file "c" then the resulting
+ * DirCacheEntry will have the path "a/b/c".
+ * <p>
+ * All entries are inserted at stage 0, therefore assuming that the
+ * application will not insert any other paths with the same pathPrefix.
+ *
+ * @param pathPrefix
+ * UTF-8 encoded prefix to mount the tree's entries at. If the
+ * path does not end with '/' one will be automatically inserted
+ * as necessary.
+ * @param stage
+ * stage of the entries when adding them.
+ * @param db
+ * repository the tree(s) will be read from during recursive
+ * traversal. This must be the same repository that the resulting
+ * DirCache would be written out to (or used in) otherwise the
+ * caller is simply asking for deferred MissingObjectExceptions.
+ * @param tree
+ * the tree to recursively add. This tree's contents will appear
+ * under <code>pathPrefix</code>. The ObjectId must be that of a
+ * tree; the caller is responsible for dereferencing a tag or
+ * commit (if necessary).
+ * @throws IOException
+ * a tree cannot be read to iterate through its entries.
+ */
+ public void addTree(final byte[] pathPrefix, final int stage,
+ final Repository db, final AnyObjectId tree) throws IOException {
+ final TreeWalk tw = new TreeWalk(db);
+ tw.reset();
+ final WindowCursor curs = new WindowCursor();
+ try {
+ tw.addTree(new CanonicalTreeParser(pathPrefix, db, tree
+ .toObjectId(), curs));
+ } finally {
+ curs.release();
+ }
+ tw.setRecursive(true);
+ if (tw.next()) {
+ final DirCacheEntry newEntry = toEntry(stage, tw);
+ beforeAdd(newEntry);
+ fastAdd(newEntry);
+ while (tw.next())
+ fastAdd(toEntry(stage, tw));
+ }
+ }
+
+ private DirCacheEntry toEntry(final int stage, final TreeWalk tw) {
+ final DirCacheEntry e = new DirCacheEntry(tw.getRawPath(), stage);
+ final AbstractTreeIterator i;
+
+ i = tw.getTree(0, AbstractTreeIterator.class);
+ e.setFileMode(tw.getFileMode(0));
+ e.setObjectIdFromRaw(i.idBuffer(), i.idOffset());
+ return e;
+ }
+
+ public void finish() {
+ if (!sorted)
+ resort();
+ replace();
+ }
+
+ private void beforeAdd(final DirCacheEntry newEntry) {
+ if (FileMode.TREE.equals(newEntry.getRawMode()))
+ throw bad(newEntry, "Adding subtree not allowed");
+ if (sorted && entryCnt > 0) {
+ final DirCacheEntry lastEntry = entries[entryCnt - 1];
+ final int cr = DirCache.cmp(lastEntry, newEntry);
+ if (cr > 0) {
+ // The new entry sorts before the old entry; we are
+ // no longer sorted correctly. We'll need to redo
+ // the sorting before we can close out the build.
+ //
+ sorted = false;
+ } else if (cr == 0) {
+ // Same file path; we can only insert this if the
+ // stages won't be violated.
+ //
+ final int peStage = lastEntry.getStage();
+ final int dceStage = newEntry.getStage();
+ if (peStage == dceStage)
+ throw bad(newEntry, "Duplicate stages not allowed");
+ if (peStage == 0 || dceStage == 0)
+ throw bad(newEntry, "Mixed stages not allowed");
+ if (peStage > dceStage)
+ sorted = false;
+ }
+ }
+ }
+
+ private void resort() {
+ Arrays.sort(entries, 0, entryCnt, DirCache.ENT_CMP);
+
+ for (int entryIdx = 1; entryIdx < entryCnt; entryIdx++) {
+ final DirCacheEntry pe = entries[entryIdx - 1];
+ final DirCacheEntry ce = entries[entryIdx];
+ final int cr = DirCache.cmp(pe, ce);
+ if (cr == 0) {
+ // Same file path; we can only allow this if the stages
+ // are 1-3 and no 0 exists.
+ //
+ final int peStage = pe.getStage();
+ final int ceStage = ce.getStage();
+ if (peStage == ceStage)
+ throw bad(ce, "Duplicate stages not allowed");
+ if (peStage == 0 || ceStage == 0)
+ throw bad(ce, "Mixed stages not allowed");
+ }
+ }
+
+ sorted = true;
+ }
+
+ private static IllegalStateException bad(final DirCacheEntry a,
+ final String msg) {
+ return new IllegalStateException(msg + ": " + a.getStage() + " "
+ + a.getPathString());
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
new file mode 100644
index 0000000000..1ad8e355d5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.dircache;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Constants;
+
+/**
+ * Updates a {@link DirCache} by supplying discrete edit commands.
+ * <p>
+ * An editor updates a DirCache by taking a list of {@link PathEdit} commands
+ * and executing them against the entries of the destination cache to produce a
+ * new cache. This edit style allows applications to insert a few commands and
+ * then have the editor compute the proper entry indexes necessary to perform an
+ * efficient in-order update of the index records. This can be easier to use
+ * than {@link DirCacheBuilder}.
+ * <p>
+ *
+ * @see DirCacheBuilder
+ */
+public class DirCacheEditor extends BaseDirCacheEditor {
+ private static final Comparator<PathEdit> EDIT_CMP = new Comparator<PathEdit>() {
+ public int compare(final PathEdit o1, final PathEdit o2) {
+ final byte[] a = o1.path;
+ final byte[] b = o2.path;
+ return DirCache.cmp(a, a.length, b, b.length);
+ }
+ };
+
+ private final List<PathEdit> edits;
+
+ /**
+ * Construct a new editor.
+ *
+ * @param dc
+ * the cache this editor will eventually update.
+ * @param ecnt
+ * estimated number of entries the editor will have upon
+ * completion. This sizes the initial entry table.
+ */
+ protected DirCacheEditor(final DirCache dc, final int ecnt) {
+ super(dc, ecnt);
+ edits = new ArrayList<PathEdit>();
+ }
+
+ /**
+ * Append one edit command to the list of commands to be applied.
+ * <p>
+ * Edit commands may be added in any order chosen by the application. They
+ * are automatically rearranged by the builder to provide the most efficient
+ * update possible.
+ *
+ * @param edit
+ * another edit command.
+ */
+ public void add(final PathEdit edit) {
+ edits.add(edit);
+ }
+
+ @Override
+ public boolean commit() throws IOException {
+ if (edits.isEmpty()) {
+ // No changes? Don't rewrite the index.
+ //
+ cache.unlock();
+ return true;
+ }
+ return super.commit();
+ }
+
+ public void finish() {
+ if (!edits.isEmpty()) {
+ applyEdits();
+ replace();
+ }
+ }
+
+ private void applyEdits() {
+ Collections.sort(edits, EDIT_CMP);
+
+ final int maxIdx = cache.getEntryCount();
+ int lastIdx = 0;
+ for (final PathEdit e : edits) {
+ int eIdx = cache.findEntry(e.path, e.path.length);
+ final boolean missing = eIdx < 0;
+ if (eIdx < 0)
+ eIdx = -(eIdx + 1);
+ final int cnt = Math.min(eIdx, maxIdx) - lastIdx;
+ if (cnt > 0)
+ fastKeep(lastIdx, cnt);
+ lastIdx = missing ? eIdx : cache.nextEntry(eIdx);
+
+ if (e instanceof DeletePath)
+ continue;
+ if (e instanceof DeleteTree) {
+ lastIdx = cache.nextEntry(e.path, e.path.length, eIdx);
+ continue;
+ }
+
+ final DirCacheEntry ent;
+ if (missing)
+ ent = new DirCacheEntry(e.path);
+ else
+ ent = cache.getEntry(eIdx);
+ e.apply(ent);
+ fastAdd(ent);
+ }
+
+ final int cnt = maxIdx - lastIdx;
+ if (cnt > 0)
+ fastKeep(lastIdx, cnt);
+ }
+
+ /**
+ * Any index record update.
+ * <p>
+ * Applications should subclass and provide their own implementation for the
+ * {@link #apply(DirCacheEntry)} method. The editor will invoke apply once
+ * for each record in the index which matches the path name. If there are
+ * multiple records (for example in stages 1, 2 and 3), the edit instance
+ * will be called multiple times, once for each stage.
+ */
+ public abstract static class PathEdit {
+ final byte[] path;
+
+ /**
+ * Create a new update command by path name.
+ *
+ * @param entryPath
+ * path of the file within the repository.
+ */
+ public PathEdit(final String entryPath) {
+ path = Constants.encode(entryPath);
+ }
+
+ /**
+ * Create a new update command for an existing entry instance.
+ *
+ * @param ent
+ * entry instance to match path of. Only the path of this
+ * entry is actually considered during command evaluation.
+ */
+ public PathEdit(final DirCacheEntry ent) {
+ path = ent.path;
+ }
+
+ /**
+ * Apply the update to a single cache entry matching the path.
+ * <p>
+ * After apply is invoked the entry is added to the output table, and
+ * will be included in the new index.
+ *
+ * @param ent
+ * the entry being processed. All fields are zeroed out if
+ * the path is a new path in the index.
+ */
+ public abstract void apply(DirCacheEntry ent);
+ }
+
+ /**
+ * Deletes a single file entry from the index.
+ * <p>
+ * This deletion command removes only a single file at the given location,
+ * but removes multiple stages (if present) for that path. To remove a
+ * complete subtree use {@link DeleteTree} instead.
+ *
+ * @see DeleteTree
+ */
+ public static final class DeletePath extends PathEdit {
+ /**
+ * Create a new deletion command by path name.
+ *
+ * @param entryPath
+ * path of the file within the repository.
+ */
+ public DeletePath(final String entryPath) {
+ super(entryPath);
+ }
+
+ /**
+ * Create a new deletion command for an existing entry instance.
+ *
+ * @param ent
+ * entry instance to remove. Only the path of this entry is
+ * actually considered during command evaluation.
+ */
+ public DeletePath(final DirCacheEntry ent) {
+ super(ent);
+ }
+
+ public void apply(final DirCacheEntry ent) {
+ throw new UnsupportedOperationException("No apply in delete");
+ }
+ }
+
+ /**
+ * Recursively deletes all paths under a subtree.
+ * <p>
+ * This deletion command is more generic than {@link DeletePath} as it can
+ * remove all records which appear recursively under the same subtree.
+ * Multiple stages are removed (if present) for any deleted entry.
+ * <p>
+ * This command will not remove a single file entry. To remove a single file
+ * use {@link DeletePath}.
+ *
+ * @see DeletePath
+ */
+ public static final class DeleteTree extends PathEdit {
+ /**
+ * Create a new tree deletion command by path name.
+ *
+ * @param entryPath
+ * path of the subtree within the repository. If the path
+ * does not end with "/" a "/" is implicitly added to ensure
+ * only the subtree's contents are matched by the command.
+ */
+ public DeleteTree(final String entryPath) {
+ super(entryPath.endsWith("/") ? entryPath : entryPath + "/");
+ }
+
+ public void apply(final DirCacheEntry ent) {
+ throw new UnsupportedOperationException("No apply in delete");
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
new file mode 100644
index 0000000000..d3e118a550
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.dircache;
+
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * A single file (or stage of a file) in a {@link DirCache}.
+ * <p>
+ * An entry represents exactly one stage of a file. If a file path is unmerged
+ * then multiple DirCacheEntry instances may appear for the same path name.
+ */
+public class DirCacheEntry {
+ private static final byte[] nullpad = new byte[8];
+
+ /** The standard (fully merged) stage for an entry. */
+ public static final int STAGE_0 = 0;
+
+ /** The base tree revision for an entry. */
+ public static final int STAGE_1 = 1;
+
+ /** The first tree revision (usually called "ours"). */
+ public static final int STAGE_2 = 2;
+
+ /** The second tree revision (usually called "theirs"). */
+ public static final int STAGE_3 = 3;
+
+ // private static final int P_CTIME = 0;
+
+ // private static final int P_CTIME_NSEC = 4;
+
+ private static final int P_MTIME = 8;
+
+ // private static final int P_MTIME_NSEC = 12;
+
+ // private static final int P_DEV = 16;
+
+ // private static final int P_INO = 20;
+
+ private static final int P_MODE = 24;
+
+ // private static final int P_UID = 28;
+
+ // private static final int P_GID = 32;
+
+ private static final int P_SIZE = 36;
+
+ private static final int P_OBJECTID = 40;
+
+ private static final int P_FLAGS = 60;
+
+ /** Mask applied to data in {@link #P_FLAGS} to get the name length. */
+ private static final int NAME_MASK = 0xfff;
+
+ static final int INFO_LEN = 62;
+
+ private static final int ASSUME_VALID = 0x80;
+
+ /** (Possibly shared) header information storage. */
+ private final byte[] info;
+
+ /** First location within {@link #info} where our header starts. */
+ private final int infoOffset;
+
+ /** Our encoded path name, from the root of the repository. */
+ final byte[] path;
+
+ DirCacheEntry(final byte[] sharedInfo, final int infoAt,
+ final InputStream in, final MessageDigest md) throws IOException {
+ info = sharedInfo;
+ infoOffset = infoAt;
+
+ NB.readFully(in, info, infoOffset, INFO_LEN);
+ md.update(info, infoOffset, INFO_LEN);
+
+ int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
+ int skipped = 0;
+ if (pathLen < NAME_MASK) {
+ path = new byte[pathLen];
+ NB.readFully(in, path, 0, pathLen);
+ md.update(path, 0, pathLen);
+ } else {
+ final ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+ {
+ final byte[] buf = new byte[NAME_MASK];
+ NB.readFully(in, buf, 0, NAME_MASK);
+ tmp.write(buf);
+ }
+ for (;;) {
+ final int c = in.read();
+ if (c < 0)
+ throw new EOFException("Short read of block.");
+ if (c == 0)
+ break;
+ tmp.write(c);
+ }
+ path = tmp.toByteArray();
+ pathLen = path.length;
+ skipped = 1; // we already skipped 1 '\0' above to break the loop.
+ md.update(path, 0, pathLen);
+ md.update((byte) 0);
+ }
+
+ // Index records are padded out to the next 8 byte alignment
+ // for historical reasons related to how C Git read the files.
+ //
+ final int actLen = INFO_LEN + pathLen;
+ final int expLen = (actLen + 8) & ~7;
+ final int padLen = expLen - actLen - skipped;
+ if (padLen > 0) {
+ NB.skipFully(in, padLen);
+ md.update(nullpad, 0, padLen);
+ }
+ }
+
+ /**
+ * Create an empty entry at stage 0.
+ *
+ * @param newPath
+ * name of the cache entry.
+ */
+ public DirCacheEntry(final String newPath) {
+ this(Constants.encode(newPath));
+ }
+
+ /**
+ * Create an empty entry at the specified stage.
+ *
+ * @param newPath
+ * name of the cache entry.
+ * @param stage
+ * the stage index of the new entry.
+ */
+ public DirCacheEntry(final String newPath, final int stage) {
+ this(Constants.encode(newPath), stage);
+ }
+
+ /**
+ * Create an empty entry at stage 0.
+ *
+ * @param newPath
+ * name of the cache entry, in the standard encoding.
+ */
+ public DirCacheEntry(final byte[] newPath) {
+ this(newPath, STAGE_0);
+ }
+
+ /**
+ * Create an empty entry at the specified stage.
+ *
+ * @param newPath
+ * name of the cache entry, in the standard encoding.
+ * @param stage
+ * the stage index of the new entry.
+ */
+ public DirCacheEntry(final byte[] newPath, final int stage) {
+ info = new byte[INFO_LEN];
+ infoOffset = 0;
+ path = newPath;
+
+ int flags = ((stage & 0x3) << 12);
+ if (path.length < NAME_MASK)
+ flags |= path.length;
+ else
+ flags |= NAME_MASK;
+ NB.encodeInt16(info, infoOffset + P_FLAGS, flags);
+ }
+
+ void write(final OutputStream os) throws IOException {
+ final int pathLen = path.length;
+ os.write(info, infoOffset, INFO_LEN);
+ os.write(path, 0, pathLen);
+
+ // Index records are padded out to the next 8 byte alignment
+ // for historical reasons related to how C Git read the files.
+ //
+ final int actLen = INFO_LEN + pathLen;
+ final int expLen = (actLen + 8) & ~7;
+ if (actLen != expLen)
+ os.write(nullpad, 0, expLen - actLen);
+ }
+
+ /**
+ * Is it possible for this entry to be accidentally assumed clean?
+ * <p>
+ * The "racy git" problem happens when a work file can be updated faster
+ * than the filesystem records file modification timestamps. It is possible
+ * for an application to edit a work file, update the index, then edit it
+ * again before the filesystem will give the work file a new modification
+ * timestamp. This method tests to see if file was written out at the same
+ * time as the index.
+ *
+ * @param smudge_s
+ * seconds component of the index's last modified time.
+ * @param smudge_ns
+ * nanoseconds component of the index's last modified time.
+ * @return true if extra careful checks should be used.
+ */
+ final boolean mightBeRacilyClean(final int smudge_s, final int smudge_ns) {
+ // If the index has a modification time then it came from disk
+ // and was not generated from scratch in memory. In such cases
+ // the entry is 'racily clean' if the entry's cached modification
+ // time is equal to or later than the index modification time. In
+ // such cases the work file is too close to the index to tell if
+ // it is clean or not based on the modification time alone.
+ //
+ final int base = infoOffset + P_MTIME;
+ final int mtime = NB.decodeInt32(info, base);
+ if (smudge_s < mtime)
+ return true;
+ if (smudge_s == mtime)
+ return smudge_ns <= NB.decodeInt32(info, base + 4) / 1000000;
+ return false;
+ }
+
+ /**
+ * Force this entry to no longer match its working tree file.
+ * <p>
+ * This avoids the "racy git" problem by making this index entry no longer
+ * match the file in the working directory. Later git will be forced to
+ * compare the file content to ensure the file matches the working tree.
+ */
+ final void smudgeRacilyClean() {
+ // We don't use the same approach as C Git to smudge the entry,
+ // as we cannot compare the working tree file to our SHA-1 and
+ // thus cannot use the "size to 0" trick without accidentally
+ // thinking a zero length file is clean.
+ //
+ // Instead we force the mtime to the largest possible value, so
+ // it is certainly after the index's own modification time and
+ // on a future read will cause mightBeRacilyClean to say "yes!".
+ // It is also unlikely to match with the working tree file.
+ //
+ // I'll see you again before Jan 19, 2038, 03:14:07 AM GMT.
+ //
+ final int base = infoOffset + P_MTIME;
+ Arrays.fill(info, base, base + 8, (byte) 127);
+ }
+
+ final byte[] idBuffer() {
+ return info;
+ }
+
+ final int idOffset() {
+ return infoOffset + P_OBJECTID;
+ }
+
+ /**
+ * Is this entry always thought to be unmodified?
+ * <p>
+ * Most entries in the index do not have this flag set. Users may however
+ * set them on if the file system stat() costs are too high on this working
+ * directory, such as on NFS or SMB volumes.
+ *
+ * @return true if we must assume the entry is unmodified.
+ */
+ public boolean isAssumeValid() {
+ return (info[infoOffset + P_FLAGS] & ASSUME_VALID) != 0;
+ }
+
+ /**
+ * Set the assume valid flag for this entry,
+ *
+ * @param assume
+ * true to ignore apparent modifications; false to look at last
+ * modified to detect file modifications.
+ */
+ public void setAssumeValid(final boolean assume) {
+ if (assume)
+ info[infoOffset + P_FLAGS] |= ASSUME_VALID;
+ else
+ info[infoOffset + P_FLAGS] &= ~ASSUME_VALID;
+ }
+
+ /**
+ * Get the stage of this entry.
+ * <p>
+ * Entries have one of 4 possible stages: 0-3.
+ *
+ * @return the stage of this entry.
+ */
+ public int getStage() {
+ return (info[infoOffset + P_FLAGS] >>> 4) & 0x3;
+ }
+
+ /**
+ * Obtain the raw {@link FileMode} bits for this entry.
+ *
+ * @return mode bits for the entry.
+ * @see FileMode#fromBits(int)
+ */
+ public int getRawMode() {
+ return NB.decodeInt32(info, infoOffset + P_MODE);
+ }
+
+ /**
+ * Obtain the {@link FileMode} for this entry.
+ *
+ * @return the file mode singleton for this entry.
+ */
+ public FileMode getFileMode() {
+ return FileMode.fromBits(getRawMode());
+ }
+
+ /**
+ * Set the file mode for this entry.
+ *
+ * @param mode
+ * the new mode constant.
+ */
+ public void setFileMode(final FileMode mode) {
+ NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
+ }
+
+ /**
+ * Get the cached last modification date of this file, in milliseconds.
+ * <p>
+ * One of the indicators that the file has been modified by an application
+ * changing the working tree is if the last modification time for the file
+ * differs from the time stored in this entry.
+ *
+ * @return last modification time of this file, in milliseconds since the
+ * Java epoch (midnight Jan 1, 1970 UTC).
+ */
+ public long getLastModified() {
+ return decodeTS(P_MTIME);
+ }
+
+ /**
+ * Set the cached last modification date of this file, using milliseconds.
+ *
+ * @param when
+ * new cached modification date of the file, in milliseconds.
+ */
+ public void setLastModified(final long when) {
+ encodeTS(P_MTIME, when);
+ }
+
+ /**
+ * Get the cached size (in bytes) of this file.
+ * <p>
+ * One of the indicators that the file has been modified by an application
+ * changing the working tree is if the size of the file (in bytes) differs
+ * from the size stored in this entry.
+ * <p>
+ * Note that this is the length of the file in the working directory, which
+ * may differ from the size of the decompressed blob if work tree filters
+ * are being used, such as LF<->CRLF conversion.
+ *
+ * @return cached size of the working directory file, in bytes.
+ */
+ public int getLength() {
+ return NB.decodeInt32(info, infoOffset + P_SIZE);
+ }
+
+ /**
+ * Set the cached size (in bytes) of this file.
+ *
+ * @param sz
+ * new cached size of the file, as bytes.
+ */
+ public void setLength(final int sz) {
+ NB.encodeInt32(info, infoOffset + P_SIZE, sz);
+ }
+
+ /**
+ * Obtain the ObjectId for the entry.
+ * <p>
+ * Using this method to compare ObjectId values between entries is
+ * inefficient as it causes memory allocation.
+ *
+ * @return object identifier for the entry.
+ */
+ public ObjectId getObjectId() {
+ return ObjectId.fromRaw(idBuffer(), idOffset());
+ }
+
+ /**
+ * Set the ObjectId for the entry.
+ *
+ * @param id
+ * new object identifier for the entry. May be
+ * {@link ObjectId#zeroId()} to remove the current identifier.
+ */
+ public void setObjectId(final AnyObjectId id) {
+ id.copyRawTo(idBuffer(), idOffset());
+ }
+
+ /**
+ * Set the ObjectId for the entry from the raw binary representation.
+ *
+ * @param bs
+ * the raw byte buffer to read from. At least 20 bytes after p
+ * must be available within this byte array.
+ * @param p
+ * position to read the first byte of data from.
+ */
+ public void setObjectIdFromRaw(final byte[] bs, final int p) {
+ final int n = Constants.OBJECT_ID_LENGTH;
+ System.arraycopy(bs, p, idBuffer(), idOffset(), n);
+ }
+
+ /**
+ * Get the entry's complete path.
+ * <p>
+ * This method is not very efficient and is primarily meant for debugging
+ * and final output generation. Applications should try to avoid calling it,
+ * and if invoked do so only once per interesting entry, where the name is
+ * absolutely required for correct function.
+ *
+ * @return complete path of the entry, from the root of the repository. If
+ * the entry is in a subtree there will be at least one '/' in the
+ * returned string.
+ */
+ public String getPathString() {
+ return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
+ }
+
+ /**
+ * Copy the ObjectId and other meta fields from an existing entry.
+ * <p>
+ * This method copies everything except the path from one entry to another,
+ * supporting renaming.
+ *
+ * @param src
+ * the entry to copy ObjectId and meta fields from.
+ */
+ public void copyMetaData(final DirCacheEntry src) {
+ final int pLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
+ System.arraycopy(src.info, src.infoOffset, info, infoOffset, INFO_LEN);
+ NB.encodeInt16(info, infoOffset + P_FLAGS, pLen
+ | NB.decodeUInt16(info, infoOffset + P_FLAGS) & ~NAME_MASK);
+ }
+
+ private long decodeTS(final int pIdx) {
+ final int base = infoOffset + pIdx;
+ final int sec = NB.decodeInt32(info, base);
+ final int ms = NB.decodeInt32(info, base + 4) / 1000000;
+ return 1000L * sec + ms;
+ }
+
+ private void encodeTS(final int pIdx, final long when) {
+ final int base = infoOffset + pIdx;
+ NB.encodeInt32(info, base, (int) (when / 1000));
+ NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
new file mode 100644
index 0000000000..9c47187821
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.dircache;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.EmptyTreeIterator;
+
+/**
+ * Iterate a {@link DirCache} as part of a <code>TreeWalk</code>.
+ * <p>
+ * This is an iterator to adapt a loaded <code>DirCache</code> instance (such as
+ * read from an existing <code>.git/index</code> file) to the tree structure
+ * used by a <code>TreeWalk</code>, making it possible for applications to walk
+ * over any combination of tree objects already in the object database, index
+ * files, or working directories.
+ *
+ * @see org.eclipse.jgit.treewalk.TreeWalk
+ */
+public class DirCacheIterator extends AbstractTreeIterator {
+ /** The cache this iterator was created to walk. */
+ protected final DirCache cache;
+
+ /** The tree this iterator is walking. */
+ private final DirCacheTree tree;
+
+ /** First position in this tree. */
+ private final int treeStart;
+
+ /** Last position in this tree. */
+ private final int treeEnd;
+
+ /** Special buffer to hold the ObjectId of {@link #currentSubtree}. */
+ private final byte[] subtreeId;
+
+ /** Index of entry within {@link #cache}. */
+ protected int ptr;
+
+ /** Next subtree to consider within {@link #tree}. */
+ private int nextSubtreePos;
+
+ /** The current file entry from {@link #cache}. */
+ protected DirCacheEntry currentEntry;
+
+ /** The subtree containing {@link #currentEntry} if this is first entry. */
+ protected DirCacheTree currentSubtree;
+
+ /**
+ * Create a new iterator for an already loaded DirCache instance.
+ * <p>
+ * The iterator implementation may copy part of the cache's data during
+ * construction, so the cache must be read in prior to creating the
+ * iterator.
+ *
+ * @param dc
+ * the cache to walk. It must be already loaded into memory.
+ */
+ public DirCacheIterator(final DirCache dc) {
+ cache = dc;
+ tree = dc.getCacheTree(true);
+ treeStart = 0;
+ treeEnd = tree.getEntrySpan();
+ subtreeId = new byte[Constants.OBJECT_ID_LENGTH];
+ if (!eof())
+ parseEntry();
+ }
+
+ DirCacheIterator(final DirCacheIterator p, final DirCacheTree dct) {
+ super(p, p.path, p.pathLen + 1);
+ cache = p.cache;
+ tree = dct;
+ treeStart = p.ptr;
+ treeEnd = treeStart + tree.getEntrySpan();
+ subtreeId = p.subtreeId;
+ ptr = p.ptr;
+ parseEntry();
+ }
+
+ @Override
+ public AbstractTreeIterator createSubtreeIterator(final Repository repo)
+ throws IncorrectObjectTypeException, IOException {
+ if (currentSubtree == null)
+ throw new IncorrectObjectTypeException(getEntryObjectId(),
+ Constants.TYPE_TREE);
+ return new DirCacheIterator(this, currentSubtree);
+ }
+
+ @Override
+ public EmptyTreeIterator createEmptyTreeIterator() {
+ final byte[] n = new byte[Math.max(pathLen + 1, DEFAULT_PATH_SIZE)];
+ System.arraycopy(path, 0, n, 0, pathLen);
+ n[pathLen] = '/';
+ return new EmptyTreeIterator(this, n, pathLen + 1);
+ }
+
+ @Override
+ public byte[] idBuffer() {
+ if (currentSubtree != null)
+ return subtreeId;
+ if (currentEntry != null)
+ return currentEntry.idBuffer();
+ return zeroid;
+ }
+
+ @Override
+ public int idOffset() {
+ if (currentSubtree != null)
+ return 0;
+ if (currentEntry != null)
+ return currentEntry.idOffset();
+ return 0;
+ }
+
+ @Override
+ public boolean first() {
+ return ptr == treeStart;
+ }
+
+ @Override
+ public boolean eof() {
+ return ptr == treeEnd;
+ }
+
+ @Override
+ public void next(int delta) {
+ while (--delta >= 0) {
+ if (currentSubtree != null)
+ ptr += currentSubtree.getEntrySpan();
+ else
+ ptr++;
+ if (eof())
+ break;
+ parseEntry();
+ }
+ }
+
+ @Override
+ public void back(int delta) {
+ while (--delta >= 0) {
+ if (currentSubtree != null)
+ nextSubtreePos--;
+ ptr--;
+ parseEntry();
+ if (currentSubtree != null)
+ ptr -= currentSubtree.getEntrySpan() - 1;
+ }
+ }
+
+ private void parseEntry() {
+ currentEntry = cache.getEntry(ptr);
+ final byte[] cep = currentEntry.path;
+
+ if (nextSubtreePos != tree.getChildCount()) {
+ final DirCacheTree s = tree.getChild(nextSubtreePos);
+ if (s.contains(cep, pathOffset, cep.length)) {
+ // The current position is the first file of this subtree.
+ // Use the subtree instead as the current position.
+ //
+ currentSubtree = s;
+ nextSubtreePos++;
+
+ if (s.isValid())
+ s.getObjectId().copyRawTo(subtreeId, 0);
+ else
+ Arrays.fill(subtreeId, (byte) 0);
+ mode = FileMode.TREE.getBits();
+ path = cep;
+ pathLen = pathOffset + s.nameLength();
+ return;
+ }
+ }
+
+ // The current position is a file/symlink/gitlink so we
+ // do not have a subtree located here.
+ //
+ mode = currentEntry.getRawMode();
+ path = cep;
+ pathLen = cep.length;
+ currentSubtree = null;
+ }
+
+ /**
+ * Get the DirCacheEntry for the current file.
+ *
+ * @return the current cache entry, if this iterator is positioned on a
+ * non-tree.
+ */
+ public DirCacheEntry getDirCacheEntry() {
+ return currentSubtree == null ? currentEntry : null;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
new file mode 100644
index 0000000000..fc29aa71b8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.dircache;
+
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.eclipse.jgit.errors.UnmergedPathException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.util.MutableInteger;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Single tree record from the 'TREE' {@link DirCache} extension.
+ * <p>
+ * A valid cache tree record contains the object id of a tree object and the
+ * total number of {@link DirCacheEntry} instances (counted recursively) from
+ * the DirCache contained within the tree. This information facilitates faster
+ * traversal of the index and quicker generation of tree objects prior to
+ * creating a new commit.
+ * <p>
+ * An invalid cache tree record indicates a known subtree whose file entries
+ * have changed in ways that cause the tree to no longer have a known object id.
+ * Invalid cache tree records must be revalidated prior to use.
+ */
+public class DirCacheTree {
+ private static final byte[] NO_NAME = {};
+
+ private static final DirCacheTree[] NO_CHILDREN = {};
+
+ private static final Comparator<DirCacheTree> TREE_CMP = new Comparator<DirCacheTree>() {
+ public int compare(final DirCacheTree o1, final DirCacheTree o2) {
+ final byte[] a = o1.encodedName;
+ final byte[] b = o2.encodedName;
+ final int aLen = a.length;
+ final int bLen = b.length;
+ int cPos;
+ for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
+ final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
+ if (cmp != 0)
+ return cmp;
+ }
+ if (aLen == bLen)
+ return 0;
+ if (aLen < bLen)
+ return '/' - (b[cPos] & 0xff);
+ return (a[cPos] & 0xff) - '/';
+ }
+ };
+
+ /** Tree this tree resides in; null if we are the root. */
+ private DirCacheTree parent;
+
+ /** Name of this tree within its parent. */
+ private byte[] encodedName;
+
+ /** Number of {@link DirCacheEntry} records that belong to this tree. */
+ private int entrySpan;
+
+ /** Unique SHA-1 of this tree; null if invalid. */
+ private ObjectId id;
+
+ /** Child trees, if any, sorted by {@link #encodedName}. */
+ private DirCacheTree[] children;
+
+ /** Number of valid children in {@link #children}. */
+ private int childCnt;
+
+ DirCacheTree() {
+ encodedName = NO_NAME;
+ children = NO_CHILDREN;
+ childCnt = 0;
+ entrySpan = -1;
+ }
+
+ private DirCacheTree(final DirCacheTree myParent, final byte[] path,
+ final int pathOff, final int pathLen) {
+ parent = myParent;
+ encodedName = new byte[pathLen];
+ System.arraycopy(path, pathOff, encodedName, 0, pathLen);
+ children = NO_CHILDREN;
+ childCnt = 0;
+ entrySpan = -1;
+ }
+
+ DirCacheTree(final byte[] in, final MutableInteger off,
+ final DirCacheTree myParent) {
+ parent = myParent;
+
+ int ptr = RawParseUtils.next(in, off.value, '\0');
+ final int nameLen = ptr - off.value - 1;
+ if (nameLen > 0) {
+ encodedName = new byte[nameLen];
+ System.arraycopy(in, off.value, encodedName, 0, nameLen);
+ } else
+ encodedName = NO_NAME;
+
+ entrySpan = RawParseUtils.parseBase10(in, ptr, off);
+ final int subcnt = RawParseUtils.parseBase10(in, off.value, off);
+ off.value = RawParseUtils.next(in, off.value, '\n');
+
+ if (entrySpan >= 0) {
+ // Valid trees have a positive entry count and an id of a
+ // tree object that should exist in the object database.
+ //
+ id = ObjectId.fromRaw(in, off.value);
+ off.value += Constants.OBJECT_ID_LENGTH;
+ }
+
+ if (subcnt > 0) {
+ boolean alreadySorted = true;
+ children = new DirCacheTree[subcnt];
+ for (int i = 0; i < subcnt; i++) {
+ children[i] = new DirCacheTree(in, off, this);
+
+ // C Git's ordering differs from our own; it prefers to
+ // sort by length first. This sometimes produces a sort
+ // we do not desire. On the other hand it may have been
+ // created by us, and be sorted the way we want.
+ //
+ if (alreadySorted && i > 0
+ && TREE_CMP.compare(children[i - 1], children[i]) > 0)
+ alreadySorted = false;
+ }
+ if (!alreadySorted)
+ Arrays.sort(children, 0, subcnt, TREE_CMP);
+ } else {
+ // Leaf level trees have no children, only (file) entries.
+ //
+ children = NO_CHILDREN;
+ }
+ childCnt = subcnt;
+ }
+
+ void write(final byte[] tmp, final OutputStream os) throws IOException {
+ int ptr = tmp.length;
+ tmp[--ptr] = '\n';
+ ptr = RawParseUtils.formatBase10(tmp, ptr, childCnt);
+ tmp[--ptr] = ' ';
+ ptr = RawParseUtils.formatBase10(tmp, ptr, isValid() ? entrySpan : -1);
+ tmp[--ptr] = 0;
+
+ os.write(encodedName);
+ os.write(tmp, ptr, tmp.length - ptr);
+ if (isValid()) {
+ id.copyRawTo(tmp, 0);
+ os.write(tmp, 0, Constants.OBJECT_ID_LENGTH);
+ }
+ for (int i = 0; i < childCnt; i++)
+ children[i].write(tmp, os);
+ }
+
+ /**
+ * Determine if this cache is currently valid.
+ * <p>
+ * A valid cache tree knows how many {@link DirCacheEntry} instances from
+ * the parent {@link DirCache} reside within this tree (recursively
+ * enumerated). It also knows the object id of the tree, as the tree should
+ * be readily available from the repository's object database.
+ *
+ * @return true if this tree is knows key details about itself; false if the
+ * tree needs to be regenerated.
+ */
+ public boolean isValid() {
+ return id != null;
+ }
+
+ /**
+ * Get the number of entries this tree spans within the DirCache.
+ * <p>
+ * If this tree is not valid (see {@link #isValid()}) this method's return
+ * value is always strictly negative (less than 0) but is otherwise an
+ * undefined result.
+ *
+ * @return total number of entries (recursively) contained within this tree.
+ */
+ public int getEntrySpan() {
+ return entrySpan;
+ }
+
+ /**
+ * Get the number of cached subtrees contained within this tree.
+ *
+ * @return number of child trees available through this tree.
+ */
+ public int getChildCount() {
+ return childCnt;
+ }
+
+ /**
+ * Get the i-th child cache tree.
+ *
+ * @param i
+ * index of the child to obtain.
+ * @return the child tree.
+ */
+ public DirCacheTree getChild(final int i) {
+ return children[i];
+ }
+
+ ObjectId getObjectId() {
+ return id;
+ }
+
+ /**
+ * Get the tree's name within its parent.
+ * <p>
+ * This method is not very efficient and is primarily meant for debugging
+ * and final output generation. Applications should try to avoid calling it,
+ * and if invoked do so only once per interesting entry, where the name is
+ * absolutely required for correct function.
+ *
+ * @return name of the tree. This does not contain any '/' characters.
+ */
+ public String getNameString() {
+ final ByteBuffer bb = ByteBuffer.wrap(encodedName);
+ return Constants.CHARSET.decode(bb).toString();
+ }
+
+ /**
+ * Get the tree's path within the repository.
+ * <p>
+ * This method is not very efficient and is primarily meant for debugging
+ * and final output generation. Applications should try to avoid calling it,
+ * and if invoked do so only once per interesting entry, where the name is
+ * absolutely required for correct function.
+ *
+ * @return path of the tree, relative to the repository root. If this is not
+ * the root tree the path ends with '/'. The root tree's path string
+ * is the empty string ("").
+ */
+ public String getPathString() {
+ final StringBuilder r = new StringBuilder();
+ appendName(r);
+ return r.toString();
+ }
+
+ /**
+ * Write (if necessary) this tree to the object store.
+ *
+ * @param cache
+ * the complete cache from DirCache.
+ * @param cIdx
+ * first position of <code>cache</code> that is a member of this
+ * tree. The path of <code>cache[cacheIdx].path</code> for the
+ * range <code>[0,pathOff-1)</code> matches the complete path of
+ * this tree, from the root of the repository.
+ * @param pathOffset
+ * number of bytes of <code>cache[cacheIdx].path</code> that
+ * matches this tree's path. The value at array position
+ * <code>cache[cacheIdx].path[pathOff-1]</code> is always '/' if
+ * <code>pathOff</code> is > 0.
+ * @param ow
+ * the writer to use when serializing to the store.
+ * @return identity of this tree.
+ * @throws UnmergedPathException
+ * one or more paths contain higher-order stages (stage > 0),
+ * which cannot be stored in a tree object.
+ * @throws IOException
+ * an unexpected error occurred writing to the object store.
+ */
+ ObjectId writeTree(final DirCacheEntry[] cache, int cIdx,
+ final int pathOffset, final ObjectWriter ow)
+ throws UnmergedPathException, IOException {
+ if (id == null) {
+ final int endIdx = cIdx + entrySpan;
+ final int size = computeSize(cache, cIdx, pathOffset, ow);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream(size);
+ int childIdx = 0;
+ int entryIdx = cIdx;
+
+ while (entryIdx < endIdx) {
+ final DirCacheEntry e = cache[entryIdx];
+ final byte[] ep = e.path;
+ if (childIdx < childCnt) {
+ final DirCacheTree st = children[childIdx];
+ if (st.contains(ep, pathOffset, ep.length)) {
+ FileMode.TREE.copyTo(out);
+ out.write(' ');
+ out.write(st.encodedName);
+ out.write(0);
+ st.id.copyRawTo(out);
+
+ entryIdx += st.entrySpan;
+ childIdx++;
+ continue;
+ }
+ }
+
+ e.getFileMode().copyTo(out);
+ out.write(' ');
+ out.write(ep, pathOffset, ep.length - pathOffset);
+ out.write(0);
+ out.write(e.idBuffer(), e.idOffset(), OBJECT_ID_LENGTH);
+ entryIdx++;
+ }
+
+ id = ow.writeCanonicalTree(out.toByteArray());
+ }
+ return id;
+ }
+
+ private int computeSize(final DirCacheEntry[] cache, int cIdx,
+ final int pathOffset, final ObjectWriter ow)
+ throws UnmergedPathException, IOException {
+ final int endIdx = cIdx + entrySpan;
+ int childIdx = 0;
+ int entryIdx = cIdx;
+ int size = 0;
+
+ while (entryIdx < endIdx) {
+ final DirCacheEntry e = cache[entryIdx];
+ if (e.getStage() != 0)
+ throw new UnmergedPathException(e);
+
+ final byte[] ep = e.path;
+ if (childIdx < childCnt) {
+ final DirCacheTree st = children[childIdx];
+ if (st.contains(ep, pathOffset, ep.length)) {
+ final int stOffset = pathOffset + st.nameLength() + 1;
+ st.writeTree(cache, entryIdx, stOffset, ow);
+
+ size += FileMode.TREE.copyToLength();
+ size += st.nameLength();
+ size += OBJECT_ID_LENGTH + 2;
+
+ entryIdx += st.entrySpan;
+ childIdx++;
+ continue;
+ }
+ }
+
+ final FileMode mode = e.getFileMode();
+ if (mode.getObjectType() == Constants.OBJ_BAD)
+ throw new IllegalStateException("Entry \"" + e.getPathString()
+ + "\" has incorrect mode set up.");
+
+ size += mode.copyToLength();
+ size += ep.length - pathOffset;
+ size += OBJECT_ID_LENGTH + 2;
+ entryIdx++;
+ }
+
+ return size;
+ }
+
+ private void appendName(final StringBuilder r) {
+ if (parent != null) {
+ parent.appendName(r);
+ r.append(getNameString());
+ r.append('/');
+ } else if (nameLength() > 0) {
+ r.append(getNameString());
+ r.append('/');
+ }
+ }
+
+ final int nameLength() {
+ return encodedName.length;
+ }
+
+ final boolean contains(final byte[] a, int aOff, final int aLen) {
+ final byte[] e = encodedName;
+ final int eLen = e.length;
+ for (int eOff = 0; eOff < eLen && aOff < aLen; eOff++, aOff++)
+ if (e[eOff] != a[aOff])
+ return false;
+ if (aOff == aLen)
+ return false;
+ return a[aOff] == '/';
+ }
+
+ /**
+ * Update (if necessary) this tree's entrySpan.
+ *
+ * @param cache
+ * the complete cache from DirCache.
+ * @param cCnt
+ * number of entries in <code>cache</code> that are valid for
+ * iteration.
+ * @param cIdx
+ * first position of <code>cache</code> that is a member of this
+ * tree. The path of <code>cache[cacheIdx].path</code> for the
+ * range <code>[0,pathOff-1)</code> matches the complete path of
+ * this tree, from the root of the repository.
+ * @param pathOff
+ * number of bytes of <code>cache[cacheIdx].path</code> that
+ * matches this tree's path. The value at array position
+ * <code>cache[cacheIdx].path[pathOff-1]</code> is always '/' if
+ * <code>pathOff</code> is > 0.
+ */
+ void validate(final DirCacheEntry[] cache, final int cCnt, int cIdx,
+ final int pathOff) {
+ if (entrySpan >= 0) {
+ // If we are valid, our children are also valid.
+ // We have no need to validate them.
+ //
+ return;
+ }
+
+ entrySpan = 0;
+ if (cCnt == 0) {
+ // Special case of an empty index, and we are the root tree.
+ //
+ return;
+ }
+
+ final byte[] firstPath = cache[cIdx].path;
+ int stIdx = 0;
+ while (cIdx < cCnt) {
+ final byte[] currPath = cache[cIdx].path;
+ if (pathOff > 0 && !peq(firstPath, currPath, pathOff)) {
+ // The current entry is no longer in this tree. Our
+ // span is updated and the remainder goes elsewhere.
+ //
+ break;
+ }
+
+ DirCacheTree st = stIdx < childCnt ? children[stIdx] : null;
+ final int cc = namecmp(currPath, pathOff, st);
+ if (cc > 0) {
+ // This subtree is now empty.
+ //
+ removeChild(stIdx);
+ continue;
+ }
+
+ if (cc < 0) {
+ final int p = slash(currPath, pathOff);
+ if (p < 0) {
+ // The entry has no '/' and thus is directly in this
+ // tree. Count it as one of our own.
+ //
+ cIdx++;
+ entrySpan++;
+ continue;
+ }
+
+ // Build a new subtree for this entry.
+ //
+ st = new DirCacheTree(this, currPath, pathOff, p - pathOff);
+ insertChild(stIdx, st);
+ }
+
+ // The entry is contained in this subtree.
+ //
+ st.validate(cache, cCnt, cIdx, pathOff + st.nameLength() + 1);
+ cIdx += st.entrySpan;
+ entrySpan += st.entrySpan;
+ stIdx++;
+ }
+
+ if (stIdx < childCnt) {
+ // None of our remaining children can be in this tree
+ // as the current cache entry is after our own name.
+ //
+ final DirCacheTree[] dct = new DirCacheTree[stIdx];
+ System.arraycopy(children, 0, dct, 0, stIdx);
+ children = dct;
+ }
+ }
+
+ private void insertChild(final int stIdx, final DirCacheTree st) {
+ final DirCacheTree[] c = children;
+ if (childCnt + 1 <= c.length) {
+ if (stIdx < childCnt)
+ System.arraycopy(c, stIdx, c, stIdx + 1, childCnt - stIdx);
+ c[stIdx] = st;
+ childCnt++;
+ return;
+ }
+
+ final int n = c.length;
+ final DirCacheTree[] a = new DirCacheTree[n + 1];
+ if (stIdx > 0)
+ System.arraycopy(c, 0, a, 0, stIdx);
+ a[stIdx] = st;
+ if (stIdx < n)
+ System.arraycopy(c, stIdx, a, stIdx + 1, n - stIdx);
+ children = a;
+ childCnt++;
+ }
+
+ private void removeChild(final int stIdx) {
+ final int n = --childCnt;
+ if (stIdx < n)
+ System.arraycopy(children, stIdx + 1, children, stIdx, n - stIdx);
+ children[n] = null;
+ }
+
+ static boolean peq(final byte[] a, final byte[] b, int aLen) {
+ if (b.length < aLen)
+ return false;
+ for (aLen--; aLen >= 0; aLen--)
+ if (a[aLen] != b[aLen])
+ return false;
+ return true;
+ }
+
+ private static int namecmp(final byte[] a, int aPos, final DirCacheTree ct) {
+ if (ct == null)
+ return -1;
+ final byte[] b = ct.encodedName;
+ final int aLen = a.length;
+ final int bLen = b.length;
+ int bPos = 0;
+ for (; aPos < aLen && bPos < bLen; aPos++, bPos++) {
+ final int cmp = (a[aPos] & 0xff) - (b[bPos] & 0xff);
+ if (cmp != 0)
+ return cmp;
+ }
+ if (bPos == bLen)
+ return a[aPos] == '/' ? 0 : -1;
+ return aLen - bLen;
+ }
+
+ private static int slash(final byte[] a, int aPos) {
+ final int aLen = a.length;
+ for (; aPos < aLen; aPos++)
+ if (a[aPos] == '/')
+ return aPos;
+ return -1;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CheckoutConflictException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CheckoutConflictException.java
new file mode 100644
index 0000000000..8cfc366ea0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CheckoutConflictException.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+/**
+ * Exception thrown if a conflict occurs during a merge checkout.
+ */
+public class CheckoutConflictException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a CheckoutConflictException for the specified file
+ *
+ * @param file
+ */
+ public CheckoutConflictException(String file) {
+ super("Checkout conflict with file: " + file);
+ }
+
+ /**
+ * Construct a CheckoutConflictException for the specified set of files
+ *
+ * @param files
+ */
+ public CheckoutConflictException(String[] files) {
+ super("Checkout conflict with files: " + buildList(files));
+ }
+
+ private static String buildList(String[] files) {
+ StringBuilder builder = new StringBuilder();
+ for (String f : files) {
+ builder.append("\n");
+ builder.append(f);
+ }
+ return builder.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java
new file mode 100644
index 0000000000..0fb14655f0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/** An exception detailing multiple reasons for failure. */
+public class CompoundException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private static String format(final Collection<Throwable> causes) {
+ final StringBuilder msg = new StringBuilder();
+ msg.append("Failure due to one of the following:");
+ for (final Throwable c : causes) {
+ msg.append(" ");
+ msg.append(c.getMessage());
+ msg.append("\n");
+ }
+ return msg.toString();
+ }
+
+ private final List<Throwable> causeList;
+
+ /**
+ * Constructs an exception detailing many potential reasons for failure.
+ *
+ * @param why
+ * Two or more exceptions that may have been the problem.
+ */
+ public CompoundException(final Collection<Throwable> why) {
+ super(format(why));
+ causeList = Collections.unmodifiableList(new ArrayList<Throwable>(why));
+ }
+
+ /**
+ * Get the complete list of reasons why this failure happened.
+ *
+ * @return unmodifiable collection of all possible reasons.
+ */
+ public List<Throwable> getAllCauses() {
+ return causeList;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/ConfigInvalidException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/ConfigInvalidException.java
new file mode 100644
index 0000000000..43fb4bcf8b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/ConfigInvalidException.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+/** Indicates a text string is not a valid Git style configuration. */
+public class ConfigInvalidException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct an invalid configuration error.
+ *
+ * @param message
+ * why the configuration is invalid.
+ */
+ public ConfigInvalidException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Construct an invalid configuration error.
+ *
+ * @param message
+ * why the configuration is invalid.
+ * @param cause
+ * root cause of the error.
+ */
+ public ConfigInvalidException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java
new file mode 100644
index 0000000000..f42b0d7e19
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Exception thrown when an object cannot be read from Git.
+ */
+public class CorruptObjectException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a CorruptObjectException for reporting a problem specified
+ * object id
+ *
+ * @param id
+ * @param why
+ */
+ public CorruptObjectException(final AnyObjectId id, final String why) {
+ this(id.toObjectId(), why);
+ }
+
+ /**
+ * Construct a CorruptObjectException for reporting a problem specified
+ * object id
+ *
+ * @param id
+ * @param why
+ */
+ public CorruptObjectException(final ObjectId id, final String why) {
+ super("Object " + id.name() + " is corrupt: " + why);
+ }
+
+ /**
+ * Construct a CorruptObjectException for reporting a problem not associated
+ * with a specific object id.
+ *
+ * @param why
+ */
+ public CorruptObjectException(final String why) {
+ super(why);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/EntryExistsException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/EntryExistsException.java
new file mode 100644
index 0000000000..893ee9ceb2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/EntryExistsException.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+/**
+ * Attempt to add an entry to a tree that already exists.
+ */
+public class EntryExistsException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct an EntryExistsException when the specified name already
+ * exists in a tree.
+ *
+ * @param name workdir relative file name
+ */
+ public EntryExistsException(final String name) {
+ super("Tree entry \"" + name + "\" already exists.");
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/GitlinksNotSupportedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/GitlinksNotSupportedException.java
new file mode 100644
index 0000000000..d501bda38b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/GitlinksNotSupportedException.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2009, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+/**
+ * An exception thrown when a gitlink entry is found and cannot be
+ * handled.
+ */
+public class GitlinksNotSupportedException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a GitlinksNotSupportedException for the specified link
+ *
+ * @param s name of link in tree or workdir
+ */
+ public GitlinksNotSupportedException(final String s) {
+ super(s);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/IncorrectObjectTypeException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IncorrectObjectTypeException.java
new file mode 100644
index 0000000000..7cf1de214f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IncorrectObjectTypeException.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * An inconsistency with respect to handling different object types.
+ *
+ * This most likely signals a programming error rather than a corrupt
+ * object database.
+ */
+public class IncorrectObjectTypeException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct and IncorrectObjectTypeException for the specified object id.
+ *
+ * Provide the type to make it easier to track down the problem.
+ *
+ * @param id SHA-1
+ * @param type object type
+ */
+ public IncorrectObjectTypeException(final ObjectId id, final String type) {
+ super("Object " + id.name() + " is not a " + type + ".");
+ }
+
+ /**
+ * Construct and IncorrectObjectTypeException for the specified object id.
+ *
+ * Provide the type to make it easier to track down the problem.
+ *
+ * @param id SHA-1
+ * @param type object type
+ */
+ public IncorrectObjectTypeException(final ObjectId id, final int type) {
+ this(id, Constants.typeString(type));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java
new file mode 100644
index 0000000000..e6577213ee
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2009, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Thrown when an invalid object id is passed in as an argument.
+ */
+public class InvalidObjectIdException extends IllegalArgumentException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create exception with bytes of the invalid object id.
+ *
+ * @param bytes containing the invalid id.
+ * @param offset in the byte array where the error occurred.
+ * @param length of the sequence of invalid bytes.
+ */
+ public InvalidObjectIdException(byte[] bytes, int offset, int length) {
+ super("Invalid id" + asAscii(bytes, offset, length));
+ }
+
+ private static String asAscii(byte[] bytes, int offset, int length) {
+ try {
+ return ": " + new String(bytes, offset, length, "US-ASCII");
+ } catch (UnsupportedEncodingException e2) {
+ return "";
+ } catch (StringIndexOutOfBoundsException e2) {
+ return "";
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidPatternException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidPatternException.java
new file mode 100644
index 0000000000..18e78ffe76
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidPatternException.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de>
+ * Copyright (C) 2008, Florian Köberle <florianskarten@web.de>
+ * Copyright (C) 2009, Vasyl' Vavrychuk <vvavrychuk@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+/**
+ * Thrown when a pattern passed in an argument was wrong.
+ *
+ */
+public class InvalidPatternException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private final String pattern;
+
+ /**
+ * @param message
+ * explains what was wrong with the pattern.
+ * @param pattern
+ * the invalid pattern.
+ */
+ public InvalidPatternException(String message, String pattern) {
+ super(message);
+ this.pattern = pattern;
+ }
+
+ /**
+ * @return the invalid pattern.
+ */
+ public String getPattern() {
+ return pattern;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingBundlePrerequisiteException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingBundlePrerequisiteException.java
new file mode 100644
index 0000000000..92d63d2993
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingBundlePrerequisiteException.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.util.Collection;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * Indicates a base/common object was required, but is not found.
+ */
+public class MissingBundlePrerequisiteException extends TransportException {
+ private static final long serialVersionUID = 1L;
+
+ private static String format(final Collection<ObjectId> ids) {
+ final StringBuilder r = new StringBuilder();
+ r.append("missing prerequisite commits:");
+ for (final ObjectId p : ids) {
+ r.append("\n ");
+ r.append(p.name());
+ }
+ return r.toString();
+ }
+
+ /**
+ * Constructs a MissingBundlePrerequisiteException for a set of objects.
+ *
+ * @param uri
+ * URI used for transport
+ * @param ids
+ * the ids of the base/common object(s) we don't have.
+ */
+ public MissingBundlePrerequisiteException(final URIish uri,
+ final Collection<ObjectId> ids) {
+ super(uri, format(ids));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingObjectException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingObjectException.java
new file mode 100644
index 0000000000..41cacb84ad
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingObjectException.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * An expected object is missing.
+ */
+public class MissingObjectException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a MissingObjectException for the specified object id.
+ * Expected type is reported to simplify tracking down the problem.
+ *
+ * @param id SHA-1
+ * @param type object type
+ */
+ public MissingObjectException(final ObjectId id, final String type) {
+ super("Missing " + type + " " + id.name());
+ }
+
+ /**
+ * Construct a MissingObjectException for the specified object id.
+ * Expected type is reported to simplify tracking down the problem.
+ *
+ * @param id SHA-1
+ * @param type object type
+ */
+ public MissingObjectException(final ObjectId id, final int type) {
+ this(id, Constants.typeString(type));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoClosingBracketException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoClosingBracketException.java
new file mode 100644
index 0000000000..623dfa6ec6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoClosingBracketException.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de>
+ * Copyright (C) 2008, Florian Köberle <florianskarten@web.de>
+ * Copyright (C) 2009, Vasyl' Vavrychuk <vvavrychuk@gmail.com>
+ * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+/**
+ * Thrown when a pattern contains a character group which is open to the right
+ * side or a character class which is open to the right side.
+ */
+public class NoClosingBracketException extends InvalidPatternException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @param indexOfOpeningBracket
+ * the position of the [ character which has no ] character.
+ * @param openingBracket
+ * the unclosed bracket.
+ * @param closingBracket
+ * the missing closing bracket.
+ * @param pattern
+ * the invalid pattern.
+ */
+ public NoClosingBracketException(final int indexOfOpeningBracket,
+ final String openingBracket, final String closingBracket,
+ final String pattern) {
+ super(createMessage(indexOfOpeningBracket, openingBracket,
+ closingBracket), pattern);
+ }
+
+ private static String createMessage(final int indexOfOpeningBracket,
+ final String openingBracket, final String closingBracket) {
+ return String.format("No closing %s found for %s at index %s.",
+ closingBracket, openingBracket,
+ Integer.valueOf(indexOfOpeningBracket));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoRemoteRepositoryException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoRemoteRepositoryException.java
new file mode 100644
index 0000000000..fe073d5648
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoRemoteRepositoryException.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * Indicates a remote repository does not exist.
+ */
+public class NoRemoteRepositoryException extends TransportException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an exception indicating a repository does not exist.
+ *
+ * @param uri
+ * URI used for transport
+ * @param s
+ * message
+ */
+ public NoRemoteRepositoryException(final URIish uri, final String s) {
+ super(uri, s);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/NotSupportedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NotSupportedException.java
new file mode 100644
index 0000000000..87044cbf3d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NotSupportedException.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+/**
+ * JGit encountered a case that it knows it cannot yet handle.
+ */
+public class NotSupportedException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a NotSupportedException for some issue JGit cannot
+ * yet handle.
+ *
+ * @param s message describing the issue
+ */
+ public NotSupportedException(final String s) {
+ super(s);
+ }
+
+ /**
+ * Construct a NotSupportedException for some issue JGit cannot yet handle.
+ *
+ * @param s
+ * message describing the issue
+ * @param why
+ * a lower level implementation specific issue.
+ */
+ public NotSupportedException(final String s, final Throwable why) {
+ super(s);
+ initCause(why);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/ObjectWritingException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/ObjectWritingException.java
new file mode 100644
index 0000000000..8af1991b14
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/ObjectWritingException.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+/**
+ * Cannot store an object in the object database. This is a serious
+ * error that users need to be made aware of.
+ */
+public class ObjectWritingException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an ObjectWritingException with the specified detail message.
+ *
+ * @param s message
+ */
+ public ObjectWritingException(final String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs an ObjectWritingException with the specified detail message.
+ *
+ * @param s message
+ * @param cause root cause exception
+ */
+ public ObjectWritingException(final String s, final Throwable cause) {
+ super(s);
+ initCause(cause);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java
new file mode 100644
index 0000000000..a34b80db81
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.File;
+import java.io.IOException;
+
+/** Thrown when a PackFile previously failed and is known to be unusable */
+public class PackInvalidException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a pack invalid error.
+ *
+ * @param path
+ * path of the invalid pack file.
+ */
+ public PackInvalidException(final File path) {
+ super("Pack file invalid: " + path.getAbsolutePath());
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java
new file mode 100644
index 0000000000..b82846530d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+/** Thrown when a PackFile no longer matches the PackIndex. */
+public class PackMismatchException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a pack modification error.
+ *
+ * @param why
+ * description of the type of error.
+ */
+ public PackMismatchException(final String why) {
+ super(why);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java
new file mode 100644
index 0000000000..f9a1bae937
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * Indicates a protocol error has occurred while fetching/pushing objects.
+ */
+public class PackProtocolException extends TransportException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an PackProtocolException with the specified detail message
+ * prefixed with provided URI.
+ *
+ * @param uri
+ * URI used for transport
+ * @param s
+ * message
+ */
+ public PackProtocolException(final URIish uri, final String s) {
+ super(uri + ": " + s);
+ }
+
+ /**
+ * Constructs an PackProtocolException with the specified detail message
+ * prefixed with provided URI.
+ *
+ * @param uri
+ * URI used for transport
+ * @param s
+ * message
+ * @param cause
+ * root cause exception
+ */
+ public PackProtocolException(final URIish uri, final String s,
+ final Throwable cause) {
+ this(uri + ": " + s, cause);
+ }
+
+ /**
+ * Constructs an PackProtocolException with the specified detail message.
+ *
+ * @param s
+ * message
+ */
+ public PackProtocolException(final String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs an PackProtocolException with the specified detail message.
+ *
+ * @param s
+ * message
+ * @param cause
+ * root cause exception
+ */
+ public PackProtocolException(final String s, final Throwable cause) {
+ super(s);
+ initCause(cause);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/RepositoryNotFoundException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/RepositoryNotFoundException.java
new file mode 100644
index 0000000000..d947a2cdc7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/RepositoryNotFoundException.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.File;
+
+/** Indicates a local repository does not exist. */
+public class RepositoryNotFoundException extends TransportException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an exception indicating a local repository does not exist.
+ *
+ * @param location
+ * description of the repository not found, usually file path.
+ */
+ public RepositoryNotFoundException(final File location) {
+ this(location.getPath());
+ }
+
+ /**
+ * Constructs an exception indicating a local repository does not exist.
+ *
+ * @param location
+ * description of the repository not found, usually file path.
+ */
+ public RepositoryNotFoundException(final String location) {
+ super("repository not found: " + location);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevWalkException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevWalkException.java
new file mode 100644
index 0000000000..0ad41ed173
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevWalkException.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * Indicates a checked exception was thrown inside of {@link RevWalk}.
+ * <p>
+ * Usually this exception is thrown from the Iterator created around a RevWalk
+ * instance, as the Iterator API does not allow checked exceptions to be thrown
+ * from hasNext() or next(). The {@link Exception#getCause()} of this exception
+ * is the original checked exception that we really wanted to throw back to the
+ * application for handling and recovery.
+ */
+public class RevWalkException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a new walk exception an original cause.
+ *
+ * @param cause
+ * the checked exception that describes why the walk failed.
+ */
+ public RevWalkException(final Throwable cause) {
+ super("Walk failure.", cause);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java
new file mode 100644
index 0000000000..58ec1b0233
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2009, Vasyl' Vavrychuk <vvavrychuk@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+/**
+ * This signals a revision or object reference was not
+ * properly formatted.
+ */
+public class RevisionSyntaxException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ private final String revstr;
+
+ /**
+ * Construct a RevisionSyntaxException indicating a syntax problem with a
+ * revision (or object) string.
+ *
+ * @param revstr The problematic revision string
+ */
+ public RevisionSyntaxException(String revstr) {
+ this.revstr = revstr;
+ }
+
+ /**
+ * Construct a RevisionSyntaxException indicating a syntax problem with a
+ * revision (or object) string.
+ *
+ * @param message a specific reason
+ * @param revstr The problematic revision string
+ */
+ public RevisionSyntaxException(String message, String revstr) {
+ super(message);
+ this.revstr = revstr;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ":" + revstr;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/StopWalkException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/StopWalkException.java
new file mode 100644
index 0000000000..c9b51de36f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/StopWalkException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+/**
+ * Stops the driver loop of walker and finish with current results.
+ *
+ * @see org.eclipse.jgit.revwalk.filter.RevFilter
+ */
+public class StopWalkException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ /** Singleton instance for throwing within a filter. */
+ public static final StopWalkException INSTANCE = new StopWalkException();
+
+ private StopWalkException() {
+ // Nothing.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/SymlinksNotSupportedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/SymlinksNotSupportedException.java
new file mode 100644
index 0000000000..60d7aa098f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/SymlinksNotSupportedException.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+/**
+ * An exception thrown when a symlink entry is found and cannot be
+ * handled.
+ */
+public class SymlinksNotSupportedException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a SymlinksNotSupportedException for the specified link
+ *
+ * @param s name of link in tree or workdir
+ */
+ public SymlinksNotSupportedException(final String s) {
+ super(s);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TransportException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TransportException.java
new file mode 100644
index 0000000000..2856eb62cc
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TransportException.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * Indicates a protocol error has occurred while fetching/pushing objects.
+ */
+public class TransportException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an TransportException with the specified detail message
+ * prefixed with provided URI.
+ *
+ * @param uri
+ * URI used for transport
+ * @param s
+ * message
+ */
+ public TransportException(final URIish uri, final String s) {
+ super(uri.setPass(null) + ": " + s);
+ }
+
+ /**
+ * Constructs an TransportException with the specified detail message
+ * prefixed with provided URI.
+ *
+ * @param uri
+ * URI used for transport
+ * @param s
+ * message
+ * @param cause
+ * root cause exception
+ */
+ public TransportException(final URIish uri, final String s,
+ final Throwable cause) {
+ this(uri.setPass(null) + ": " + s, cause);
+ }
+
+ /**
+ * Constructs an TransportException with the specified detail message.
+ *
+ * @param s
+ * message
+ */
+ public TransportException(final String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs an TransportException with the specified detail message.
+ *
+ * @param s
+ * message
+ * @param cause
+ * root cause exception
+ */
+ public TransportException(final String s, final Throwable cause) {
+ super(s);
+ initCause(cause);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnmergedPathException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnmergedPathException.java
new file mode 100644
index 0000000000..51e651ca5e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnmergedPathException.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.dircache.DirCacheEntry;
+
+/**
+ * Indicates one or more paths in a DirCache have non-zero stages present.
+ */
+public class UnmergedPathException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ private final DirCacheEntry entry;
+
+ /**
+ * Create a new unmerged path exception.
+ *
+ * @param dce
+ * the first non-zero stage of the unmerged path.
+ */
+ public UnmergedPathException(final DirCacheEntry dce) {
+ super("Unmerged path: " + dce.getPathString());
+ entry = dce;
+ }
+
+ /** @return the first non-zero stage of the unmerged path */
+ public DirCacheEntry getDirCacheEntry() {
+ return entry;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java
new file mode 100644
index 0000000000..42182965a0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de>
+ * Copyright (C) 2008, Florian Köberle <florianskarten@web.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.fnmatch;
+
+import java.util.List;
+
+abstract class AbstractHead implements Head {
+ private List<Head> newHeads = null;
+
+ private final boolean star;
+
+ protected abstract boolean matches(char c);
+
+ AbstractHead(boolean star) {
+ this.star = star;
+ }
+
+ /**
+ *
+ * @param newHeads
+ * a list of {@link Head}s which will not be modified.
+ */
+ public final void setNewHeads(List<Head> newHeads) {
+ if (this.newHeads != null)
+ throw new IllegalStateException("Property is already non null");
+ this.newHeads = newHeads;
+ }
+
+ public List<Head> getNextHeads(char c) {
+ if (matches(c))
+ return newHeads;
+ else
+ return FileNameMatcher.EMPTY_HEAD_LIST;
+ }
+
+ boolean isStar() {
+ return star;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java
new file mode 100644
index 0000000000..699eca9683
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de>
+ * Copyright (C) 2008, Florian Köberle <florianskarten@web.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.fnmatch;
+
+final class CharacterHead extends AbstractHead {
+ private final char expectedCharacter;
+
+ protected CharacterHead(final char expectedCharacter) {
+ super(false);
+ this.expectedCharacter = expectedCharacter;
+ }
+
+ @Override
+ protected final boolean matches(final char c) {
+ return c == expectedCharacter;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
new file mode 100644
index 0000000000..582123bb5b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de>
+ * Copyright (C) 2008, Florian Köberle <florianskarten@web.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.fnmatch;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.errors.InvalidPatternException;
+import org.eclipse.jgit.errors.NoClosingBracketException;
+
+/**
+ * This class can be used to match filenames against fnmatch like patterns. It
+ * is not thread save.
+ * <p>
+ * Supported are the wildcard characters * and ? and groups with:
+ * <ul>
+ * <li> characters e.g. [abc]</li>
+ * <li> ranges e.g. [a-z]</li>
+ * <li> the following character classes
+ * <ul>
+ * <li>[:alnum:]</li>
+ * <li>[:alpha:]</li>
+ * <li>[:blank:]</li>
+ * <li>[:cntrl:]</li>
+ * <li>[:digit:]</li>
+ * <li>[:graph:]</li>
+ * <li>[:lower:]</li>
+ * <li>[:print:]</li>
+ * <li>[:punct:]</li>
+ * <li>[:space:]</li>
+ * <li>[:upper:]</li>
+ * <li>[:word:]</li>
+ * <li>[:xdigit:]</li>
+ * </ul>
+ * e. g. [[:xdigit:]] </li>
+ * </ul>
+ * </p>
+ */
+public class FileNameMatcher {
+ static final List<Head> EMPTY_HEAD_LIST = Collections.emptyList();
+
+ private static final Pattern characterClassStartPattern = Pattern
+ .compile("\\[[.:=]");
+
+ private List<Head> headsStartValue;
+
+ private List<Head> heads;
+
+ /**
+ * {{@link #extendStringToMatchByOneCharacter(char)} needs a list for the
+ * new heads, allocating a new array would be bad for the performance, as
+ * the method gets called very often.
+ *
+ */
+ private List<Head> listForLocalUseage;
+
+ /**
+ *
+ * @param headsStartValue
+ * must be a list which will never be modified.
+ */
+ private FileNameMatcher(final List<Head> headsStartValue) {
+ this(headsStartValue, headsStartValue);
+ }
+
+ /**
+ *
+ * @param headsStartValue
+ * must be a list which will never be modified.
+ * @param heads
+ * a list which will be cloned and then used as current head
+ * list.
+ */
+ private FileNameMatcher(final List<Head> headsStartValue,
+ final List<Head> heads) {
+ this.headsStartValue = headsStartValue;
+ this.heads = new ArrayList<Head>(heads.size());
+ this.heads.addAll(heads);
+ this.listForLocalUseage = new ArrayList<Head>(heads.size());
+ }
+
+ /**
+ * @param patternString
+ * must contain a pattern which fnmatch would accept.
+ * @param invalidWildgetCharacter
+ * if this parameter isn't null then this character will not
+ * match at wildcards(* and ? are wildcards).
+ * @throws InvalidPatternException
+ * if the patternString contains a invalid fnmatch pattern.
+ */
+ public FileNameMatcher(final String patternString,
+ final Character invalidWildgetCharacter)
+ throws InvalidPatternException {
+ this(createHeadsStartValues(patternString, invalidWildgetCharacter));
+ }
+
+ /**
+ * A Copy Constructor which creates a new {@link FileNameMatcher} with the
+ * same state and reset point like <code>other</code>.
+ *
+ * @param other
+ * another {@link FileNameMatcher} instance.
+ */
+ public FileNameMatcher(FileNameMatcher other) {
+ this(other.headsStartValue, other.heads);
+ }
+
+ private static List<Head> createHeadsStartValues(
+ final String patternString, final Character invalidWildgetCharacter)
+ throws InvalidPatternException {
+
+ final List<AbstractHead> allHeads = parseHeads(patternString,
+ invalidWildgetCharacter);
+
+ List<Head> nextHeadsSuggestion = new ArrayList<Head>(2);
+ nextHeadsSuggestion.add(LastHead.INSTANCE);
+ for (int i = allHeads.size() - 1; i >= 0; i--) {
+ final AbstractHead head = allHeads.get(i);
+
+ // explanation:
+ // a and * of the pattern "a*b"
+ // need *b as newHeads
+ // that's why * extends the list for it self and it's left neighbor.
+ if (head.isStar()) {
+ nextHeadsSuggestion.add(head);
+ head.setNewHeads(nextHeadsSuggestion);
+ } else {
+ head.setNewHeads(nextHeadsSuggestion);
+ nextHeadsSuggestion = new ArrayList<Head>(2);
+ nextHeadsSuggestion.add(head);
+ }
+ }
+ return nextHeadsSuggestion;
+ }
+
+ private static int findGroupEnd(final int indexOfStartBracket,
+ final String pattern) throws InvalidPatternException {
+ int firstValidCharClassIndex = indexOfStartBracket + 1;
+ int firstValidEndBracketIndex = indexOfStartBracket + 2;
+
+ if (indexOfStartBracket + 1 >= pattern.length())
+ throw new NoClosingBracketException(indexOfStartBracket, "[", "]",
+ pattern);
+
+ if (pattern.charAt(firstValidCharClassIndex) == '!') {
+ firstValidCharClassIndex++;
+ firstValidEndBracketIndex++;
+ }
+
+ final Matcher charClassStartMatcher = characterClassStartPattern
+ .matcher(pattern);
+
+ int groupEnd = -1;
+ while (groupEnd == -1) {
+
+ final int possibleGroupEnd = pattern.indexOf(']',
+ firstValidEndBracketIndex);
+ if (possibleGroupEnd == -1)
+ throw new NoClosingBracketException(indexOfStartBracket, "[",
+ "]", pattern);
+
+ final boolean foundCharClass = charClassStartMatcher
+ .find(firstValidCharClassIndex);
+
+ if (foundCharClass
+ && charClassStartMatcher.start() < possibleGroupEnd) {
+
+ final String classStart = charClassStartMatcher.group(0);
+ final String classEnd = classStart.charAt(1) + "]";
+
+ final int classStartIndex = charClassStartMatcher.start();
+ final int classEndIndex = pattern.indexOf(classEnd,
+ classStartIndex + 2);
+
+ if (classEndIndex == -1)
+ throw new NoClosingBracketException(classStartIndex,
+ classStart, classEnd, pattern);
+
+ firstValidCharClassIndex = classEndIndex + 2;
+ firstValidEndBracketIndex = firstValidCharClassIndex;
+ } else {
+ groupEnd = possibleGroupEnd;
+ }
+ }
+ return groupEnd;
+ }
+
+ private static List<AbstractHead> parseHeads(final String pattern,
+ final Character invalidWildgetCharacter)
+ throws InvalidPatternException {
+
+ int currentIndex = 0;
+ List<AbstractHead> heads = new ArrayList<AbstractHead>();
+ while (currentIndex < pattern.length()) {
+ final int groupStart = pattern.indexOf('[', currentIndex);
+ if (groupStart == -1) {
+ final String patternPart = pattern.substring(currentIndex);
+ heads.addAll(createSimpleHeads(patternPart,
+ invalidWildgetCharacter));
+ currentIndex = pattern.length();
+ } else {
+ final String patternPart = pattern.substring(currentIndex,
+ groupStart);
+ heads.addAll(createSimpleHeads(patternPart,
+ invalidWildgetCharacter));
+
+ final int groupEnd = findGroupEnd(groupStart, pattern);
+ final String groupPart = pattern.substring(groupStart + 1,
+ groupEnd);
+ heads.add(new GroupHead(groupPart, pattern));
+ currentIndex = groupEnd + 1;
+ }
+ }
+ return heads;
+ }
+
+ private static List<AbstractHead> createSimpleHeads(
+ final String patternPart, final Character invalidWildgetCharacter) {
+ final List<AbstractHead> heads = new ArrayList<AbstractHead>(
+ patternPart.length());
+ for (int i = 0; i < patternPart.length(); i++) {
+ final char c = patternPart.charAt(i);
+ switch (c) {
+ case '*': {
+ final AbstractHead head = createWildCardHead(
+ invalidWildgetCharacter, true);
+ heads.add(head);
+ break;
+ }
+ case '?': {
+ final AbstractHead head = createWildCardHead(
+ invalidWildgetCharacter, false);
+ heads.add(head);
+ break;
+ }
+ default:
+ final CharacterHead head = new CharacterHead(c);
+ heads.add(head);
+ }
+ }
+ return heads;
+ }
+
+ private static AbstractHead createWildCardHead(
+ final Character invalidWildgetCharacter, final boolean star) {
+ if (invalidWildgetCharacter != null)
+ return new RestrictedWildCardHead(invalidWildgetCharacter
+ .charValue(), star);
+ else
+ return new WildCardHead(star);
+ }
+
+ private void extendStringToMatchByOneCharacter(final char c) {
+ final List<Head> newHeads = listForLocalUseage;
+ newHeads.clear();
+ List<Head> lastAddedHeads = null;
+ for (int i = 0; i < heads.size(); i++) {
+ final Head head = heads.get(i);
+ final List<Head> headsToAdd = head.getNextHeads(c);
+ // Why the next performance optimization isn't wrong:
+ // Some times two heads return the very same list.
+ // We save future effort if we don't add these heads again.
+ // This is the case with the heads "a" and "*" of "a*b" which
+ // both can return the list ["*","b"]
+ if (headsToAdd != lastAddedHeads) {
+ newHeads.addAll(headsToAdd);
+ lastAddedHeads = headsToAdd;
+ }
+ }
+ listForLocalUseage = heads;
+ heads = newHeads;
+ }
+
+ /**
+ *
+ * @param stringToMatch
+ * extends the string which is matched against the patterns of
+ * this class.
+ */
+ public void append(final String stringToMatch) {
+ for (int i = 0; i < stringToMatch.length(); i++) {
+ final char c = stringToMatch.charAt(i);
+ extendStringToMatchByOneCharacter(c);
+ }
+ }
+
+ /**
+ * Resets this matcher to it's state right after construction.
+ */
+ public void reset() {
+ heads.clear();
+ heads.addAll(headsStartValue);
+ }
+
+ /**
+ *
+ * @return a {@link FileNameMatcher} instance which uses the same pattern
+ * like this matcher, but has the current state of this matcher as
+ * reset and start point.
+ */
+ public FileNameMatcher createMatcherForSuffix() {
+ final List<Head> copyOfHeads = new ArrayList<Head>(heads.size());
+ copyOfHeads.addAll(heads);
+ return new FileNameMatcher(copyOfHeads);
+ }
+
+ /**
+ *
+ * @return true, if the string currently being matched does match.
+ */
+ public boolean isMatch() {
+ final ListIterator<Head> headIterator = heads
+ .listIterator(heads.size());
+ while (headIterator.hasPrevious()) {
+ final Head head = headIterator.previous();
+ if (head == LastHead.INSTANCE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ *
+ * @return false, if the string being matched will not match when the string
+ * gets extended.
+ */
+ public boolean canAppendMatch() {
+ for (int i = 0; i < heads.size(); i++) {
+ if (heads.get(i) != LastHead.INSTANCE) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java
new file mode 100644
index 0000000000..79f64f859e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de>
+ * Copyright (C) 2008, Florian Köberle <florianskarten@web.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.fnmatch;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.errors.InvalidPatternException;
+
+final class GroupHead extends AbstractHead {
+ private final List<CharacterPattern> characterClasses;
+
+ private static final Pattern REGEX_PATTERN = Pattern
+ .compile("([^-][-][^-]|\\[[.:=].*?[.:=]\\])");
+
+ private final boolean inverse;
+
+ GroupHead(String pattern, final String wholePattern)
+ throws InvalidPatternException {
+ super(false);
+ this.characterClasses = new ArrayList<CharacterPattern>();
+ this.inverse = pattern.startsWith("!");
+ if (inverse) {
+ pattern = pattern.substring(1);
+ }
+ final Matcher matcher = REGEX_PATTERN.matcher(pattern);
+ while (matcher.find()) {
+ final String characterClass = matcher.group(0);
+ if (characterClass.length() == 3 && characterClass.charAt(1) == '-') {
+ final char start = characterClass.charAt(0);
+ final char end = characterClass.charAt(2);
+ characterClasses.add(new CharacterRange(start, end));
+ } else if (characterClass.equals("[:alnum:]")) {
+ characterClasses.add(LetterPattern.INSTANCE);
+ characterClasses.add(DigitPattern.INSTANCE);
+ } else if (characterClass.equals("[:alpha:]")) {
+ characterClasses.add(LetterPattern.INSTANCE);
+ } else if (characterClass.equals("[:blank:]")) {
+ characterClasses.add(new OneCharacterPattern(' '));
+ characterClasses.add(new OneCharacterPattern('\t'));
+ } else if (characterClass.equals("[:cntrl:]")) {
+ characterClasses.add(new CharacterRange('\u0000', '\u001F'));
+ characterClasses.add(new OneCharacterPattern('\u007F'));
+ } else if (characterClass.equals("[:digit:]")) {
+ characterClasses.add(DigitPattern.INSTANCE);
+ } else if (characterClass.equals("[:graph:]")) {
+ characterClasses.add(new CharacterRange('\u0021', '\u007E'));
+ characterClasses.add(LetterPattern.INSTANCE);
+ characterClasses.add(DigitPattern.INSTANCE);
+ } else if (characterClass.equals("[:lower:]")) {
+ characterClasses.add(LowerPattern.INSTANCE);
+ } else if (characterClass.equals("[:print:]")) {
+ characterClasses.add(new CharacterRange('\u0020', '\u007E'));
+ characterClasses.add(LetterPattern.INSTANCE);
+ characterClasses.add(DigitPattern.INSTANCE);
+ } else if (characterClass.equals("[:punct:]")) {
+ characterClasses.add(PunctPattern.INSTANCE);
+ } else if (characterClass.equals("[:space:]")) {
+ characterClasses.add(WhitespacePattern.INSTANCE);
+ } else if (characterClass.equals("[:upper:]")) {
+ characterClasses.add(UpperPattern.INSTANCE);
+ } else if (characterClass.equals("[:xdigit:]")) {
+ characterClasses.add(new CharacterRange('0', '9'));
+ characterClasses.add(new CharacterRange('a', 'f'));
+ characterClasses.add(new CharacterRange('A', 'F'));
+ } else if (characterClass.equals("[:word:]")) {
+ characterClasses.add(new OneCharacterPattern('_'));
+ characterClasses.add(LetterPattern.INSTANCE);
+ characterClasses.add(DigitPattern.INSTANCE);
+ } else {
+ final String message = String.format(
+ "The character class %s is not supported.",
+ characterClass);
+ throw new InvalidPatternException(message, wholePattern);
+ }
+
+ pattern = matcher.replaceFirst("");
+ matcher.reset(pattern);
+ }
+ // pattern contains now no ranges
+ for (int i = 0; i < pattern.length(); i++) {
+ final char c = pattern.charAt(i);
+ characterClasses.add(new OneCharacterPattern(c));
+ }
+ }
+
+ @Override
+ protected final boolean matches(final char c) {
+ for (CharacterPattern pattern : characterClasses) {
+ if (pattern.matches(c)) {
+ return !inverse;
+ }
+ }
+ return inverse;
+ }
+
+ private interface CharacterPattern {
+ /**
+ * @param c
+ * the character to test
+ * @return returns true if the character matches a pattern.
+ */
+ boolean matches(char c);
+ }
+
+ private static final class CharacterRange implements CharacterPattern {
+ private final char start;
+
+ private final char end;
+
+ CharacterRange(char start, char end) {
+ this.start = start;
+ this.end = end;
+ }
+
+ public final boolean matches(char c) {
+ return start <= c && c <= end;
+ }
+ }
+
+ private static final class DigitPattern implements CharacterPattern {
+ static final GroupHead.DigitPattern INSTANCE = new DigitPattern();
+
+ public final boolean matches(char c) {
+ return Character.isDigit(c);
+ }
+ }
+
+ private static final class LetterPattern implements CharacterPattern {
+ static final GroupHead.LetterPattern INSTANCE = new LetterPattern();
+
+ public final boolean matches(char c) {
+ return Character.isLetter(c);
+ }
+ }
+
+ private static final class LowerPattern implements CharacterPattern {
+ static final GroupHead.LowerPattern INSTANCE = new LowerPattern();
+
+ public final boolean matches(char c) {
+ return Character.isLowerCase(c);
+ }
+ }
+
+ private static final class UpperPattern implements CharacterPattern {
+ static final GroupHead.UpperPattern INSTANCE = new UpperPattern();
+
+ public final boolean matches(char c) {
+ return Character.isUpperCase(c);
+ }
+ }
+
+ private static final class WhitespacePattern implements CharacterPattern {
+ static final GroupHead.WhitespacePattern INSTANCE = new WhitespacePattern();
+
+ public final boolean matches(char c) {
+ return Character.isWhitespace(c);
+ }
+ }
+
+ private static final class OneCharacterPattern implements CharacterPattern {
+ private char expectedCharacter;
+
+ OneCharacterPattern(final char c) {
+ this.expectedCharacter = c;
+ }
+
+ public final boolean matches(char c) {
+ return this.expectedCharacter == c;
+ }
+ }
+
+ private static final class PunctPattern implements CharacterPattern {
+ static final GroupHead.PunctPattern INSTANCE = new PunctPattern();
+
+ private static String punctCharacters = "-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~";
+
+ public boolean matches(char c) {
+ return punctCharacters.indexOf(c) != -1;
+ }
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java
new file mode 100644
index 0000000000..3de18a7357
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de>
+ * Copyright (C) 2008, Florian Köberle <florianskarten@web.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.fnmatch;
+
+import java.util.List;
+
+interface Head {
+ /**
+ *
+ * @param c
+ * the character which decides which heads are returned.
+ * @return a list of heads based on the input.
+ */
+ public abstract List<Head> getNextHeads(char c);
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java
new file mode 100644
index 0000000000..78a61b9c51
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de>
+ * Copyright (C) 2008, Florian Köberle <florianskarten@web.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.fnmatch;
+
+import java.util.List;
+
+final class LastHead implements Head {
+ static final Head INSTANCE = new LastHead();
+
+ /**
+ * Don't call this constructor, use {@link #INSTANCE}
+ */
+ private LastHead() {
+ // defined because of javadoc and visibility modifier.
+ }
+
+ public List<Head> getNextHeads(char c) {
+ return FileNameMatcher.EMPTY_HEAD_LIST;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java
new file mode 100644
index 0000000000..6d527d2b2d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de>
+ * Copyright (C) 2008, Florian Köberle <florianskarten@web.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.fnmatch;
+
+final class RestrictedWildCardHead extends AbstractHead {
+ private final char excludedCharacter;
+
+ RestrictedWildCardHead(final char excludedCharacter, final boolean star) {
+ super(star);
+ this.excludedCharacter = excludedCharacter;
+ }
+
+ @Override
+ protected final boolean matches(final char c) {
+ return c != excludedCharacter;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java
new file mode 100644
index 0000000000..b5173d97d0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de>
+ * Copyright (C) 2008, Florian Köberle <florianskarten@web.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.fnmatch;
+
+final class WildCardHead extends AbstractHead {
+ WildCardHead(boolean star) {
+ super(star);
+ }
+
+ @Override
+ protected final boolean matches(final char c) {
+ return true;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
new file mode 100644
index 0000000000..13c54201c6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import org.eclipse.jgit.errors.InvalidObjectIdException;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * A prefix abbreviation of an {@link ObjectId}.
+ * <p>
+ * Sometimes Git produces abbreviated SHA-1 strings, using sufficient leading
+ * digits from the ObjectId name to still be unique within the repository the
+ * string was generated from. These ids are likely to be unique for a useful
+ * period of time, especially if they contain at least 6-10 hex digits.
+ * <p>
+ * This class converts the hex string into a binary form, to make it more
+ * efficient for matching against an object.
+ */
+public final class AbbreviatedObjectId {
+ /**
+ * Convert an AbbreviatedObjectId from hex characters (US-ASCII).
+ *
+ * @param buf
+ * the US-ASCII buffer to read from.
+ * @param offset
+ * position to read the first character from.
+ * @param end
+ * one past the last position to read (<code>end-offset</code> is
+ * the length of the string).
+ * @return the converted object id.
+ */
+ public static final AbbreviatedObjectId fromString(final byte[] buf,
+ final int offset, final int end) {
+ if (end - offset > AnyObjectId.STR_LEN)
+ throw new IllegalArgumentException("Invalid id");
+ return fromHexString(buf, offset, end);
+ }
+
+ /**
+ * Convert an AbbreviatedObjectId from hex characters.
+ *
+ * @param str
+ * the string to read from. Must be &lt;= 40 characters.
+ * @return the converted object id.
+ */
+ public static final AbbreviatedObjectId fromString(final String str) {
+ if (str.length() > AnyObjectId.STR_LEN)
+ throw new IllegalArgumentException("Invalid id: " + str);
+ final byte[] b = Constants.encodeASCII(str);
+ return fromHexString(b, 0, b.length);
+ }
+
+ private static final AbbreviatedObjectId fromHexString(final byte[] bs,
+ int ptr, final int end) {
+ try {
+ final int a = hexUInt32(bs, ptr, end);
+ final int b = hexUInt32(bs, ptr + 8, end);
+ final int c = hexUInt32(bs, ptr + 16, end);
+ final int d = hexUInt32(bs, ptr + 24, end);
+ final int e = hexUInt32(bs, ptr + 32, end);
+ return new AbbreviatedObjectId(end - ptr, a, b, c, d, e);
+ } catch (ArrayIndexOutOfBoundsException e1) {
+ throw new InvalidObjectIdException(bs, ptr, end - ptr);
+ }
+ }
+
+ private static final int hexUInt32(final byte[] bs, int p, final int end) {
+ if (8 <= end - p)
+ return RawParseUtils.parseHexInt32(bs, p);
+
+ int r = 0, n = 0;
+ while (n < 8 && p < end) {
+ r <<= 4;
+ r |= RawParseUtils.parseHexInt4(bs[p++]);
+ n++;
+ }
+ return r << (8 - n) * 4;
+ }
+
+ static int mask(final int nibbles, final int word, final int v) {
+ final int b = (word - 1) * 8;
+ if (b + 8 <= nibbles) {
+ // We have all of the bits required for this word.
+ //
+ return v;
+ }
+
+ if (nibbles <= b) {
+ // We have none of the bits required for this word.
+ //
+ return 0;
+ }
+
+ final int s = 32 - (nibbles - b) * 4;
+ return (v >>> s) << s;
+ }
+
+ /** Number of half-bytes used by this id. */
+ final int nibbles;
+
+ final int w1;
+
+ final int w2;
+
+ final int w3;
+
+ final int w4;
+
+ final int w5;
+
+ AbbreviatedObjectId(final int n, final int new_1, final int new_2,
+ final int new_3, final int new_4, final int new_5) {
+ nibbles = n;
+ w1 = new_1;
+ w2 = new_2;
+ w3 = new_3;
+ w4 = new_4;
+ w5 = new_5;
+ }
+
+ /** @return number of hex digits appearing in this id */
+ public int length() {
+ return nibbles;
+ }
+
+ /** @return true if this ObjectId is actually a complete id. */
+ public boolean isComplete() {
+ return length() == AnyObjectId.RAW_LEN * 2;
+ }
+
+ /** @return a complete ObjectId; null if {@link #isComplete()} is false */
+ public ObjectId toObjectId() {
+ return isComplete() ? new ObjectId(w1, w2, w3, w4, w5) : null;
+ }
+
+ /**
+ * Compares this abbreviation to a full object id.
+ *
+ * @param other
+ * the other object id.
+ * @return &lt;0 if this abbreviation names an object that is less than
+ * <code>other</code>; 0 if this abbreviation exactly matches the
+ * first {@link #length()} digits of <code>other.name()</code>;
+ * &gt;0 if this abbreviation names an object that is after
+ * <code>other</code>.
+ */
+ public int prefixCompare(final AnyObjectId other) {
+ int cmp;
+
+ cmp = NB.compareUInt32(w1, mask(1, other.w1));
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w2, mask(2, other.w2));
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w3, mask(3, other.w3));
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w4, mask(4, other.w4));
+ if (cmp != 0)
+ return cmp;
+
+ return NB.compareUInt32(w5, mask(5, other.w5));
+ }
+
+ private int mask(final int word, final int v) {
+ return mask(nibbles, word, v);
+ }
+
+ @Override
+ public int hashCode() {
+ return w2;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o instanceof AbbreviatedObjectId) {
+ final AbbreviatedObjectId b = (AbbreviatedObjectId) o;
+ return nibbles == b.nibbles && w1 == b.w1 && w2 == b.w2
+ && w3 == b.w3 && w4 == b.w4 && w5 == b.w5;
+ }
+ return false;
+ }
+
+ /**
+ * @return string form of the abbreviation, in lower case hexadecimal.
+ */
+ public final String name() {
+ final char[] b = new char[AnyObjectId.STR_LEN];
+
+ AnyObjectId.formatHexChar(b, 0, w1);
+ if (nibbles <= 8)
+ return new String(b, 0, nibbles);
+
+ AnyObjectId.formatHexChar(b, 8, w2);
+ if (nibbles <= 16)
+ return new String(b, 0, nibbles);
+
+ AnyObjectId.formatHexChar(b, 16, w3);
+ if (nibbles <= 24)
+ return new String(b, 0, nibbles);
+
+ AnyObjectId.formatHexChar(b, 24, w4);
+ if (nibbles <= 32)
+ return new String(b, 0, nibbles);
+
+ AnyObjectId.formatHexChar(b, 32, w5);
+ return new String(b, 0, nibbles);
+ }
+
+ @Override
+ public String toString() {
+ return "AbbreviatedObjectId[" + name() + "]";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractIndexTreeVisitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractIndexTreeVisitor.java
new file mode 100644
index 0000000000..6052aa336d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractIndexTreeVisitor.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.GitIndex.Entry;
+
+/**
+ * Implementation of IndexTreeVisitor that can be subclassed if you don't
+ * case about certain events
+ * @author dwatson
+ *
+ */
+public class AbstractIndexTreeVisitor implements IndexTreeVisitor {
+ public void finishVisitTree(Tree tree, Tree auxTree, String curDir)
+ throws IOException {
+ // Empty
+ }
+
+ public void finishVisitTree(Tree tree, int i, String curDir)
+ throws IOException {
+ // Empty
+ }
+
+ public void visitEntry(TreeEntry treeEntry, Entry indexEntry, File file)
+ throws IOException {
+ // Empty
+ }
+
+ public void visitEntry(TreeEntry treeEntry, TreeEntry auxEntry,
+ Entry indexEntry, File file) throws IOException {
+ // Empty
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java
new file mode 100644
index 0000000000..311839e430
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * An ObjectDatabase of another {@link Repository}.
+ * <p>
+ * This {@code ObjectDatabase} wraps around another {@code Repository}'s object
+ * database, providing its contents to the caller, and closing the Repository
+ * when this database is closed. The primary user of this class is
+ * {@link ObjectDirectory}, when the {@code info/alternates} file points at the
+ * {@code objects/} directory of another repository.
+ */
+public final class AlternateRepositoryDatabase extends ObjectDatabase {
+ private final Repository repository;
+
+ private final ObjectDatabase odb;
+
+ /**
+ * @param alt
+ * the alternate repository to wrap and export.
+ */
+ public AlternateRepositoryDatabase(final Repository alt) {
+ repository = alt;
+ odb = repository.getObjectDatabase();
+ }
+
+ /** @return the alternate repository objects are borrowed from. */
+ public Repository getRepository() {
+ return repository;
+ }
+
+ public void closeSelf() {
+ repository.close();
+ }
+
+ public void create() throws IOException {
+ repository.create();
+ }
+
+ public boolean exists() {
+ return odb.exists();
+ }
+
+ @Override
+ protected boolean hasObject1(final AnyObjectId objectId) {
+ return odb.hasObject1(objectId);
+ }
+
+ @Override
+ protected boolean tryAgain1() {
+ return odb.tryAgain1();
+ }
+
+ @Override
+ protected boolean hasObject2(final String objectName) {
+ return odb.hasObject2(objectName);
+ }
+
+ @Override
+ protected ObjectLoader openObject1(final WindowCursor curs,
+ final AnyObjectId objectId) throws IOException {
+ return odb.openObject1(curs, objectId);
+ }
+
+ @Override
+ protected ObjectLoader openObject2(final WindowCursor curs,
+ final String objectName, final AnyObjectId objectId)
+ throws IOException {
+ return odb.openObject2(curs, objectName, objectId);
+ }
+
+ @Override
+ void openObjectInAllPacks1(final Collection<PackedObjectLoader> out,
+ final WindowCursor curs, final AnyObjectId objectId)
+ throws IOException {
+ odb.openObjectInAllPacks1(out, curs, objectId);
+ }
+
+ @Override
+ protected ObjectDatabase[] loadAlternates() throws IOException {
+ return odb.getAlternates();
+ }
+
+ @Override
+ protected void closeAlternates(final ObjectDatabase[] alt) {
+ // Do nothing; these belong to odb to close, not us.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
new file mode 100644
index 0000000000..4ed440354d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jgit.util.NB;
+
+/**
+ * A (possibly mutable) SHA-1 abstraction.
+ * <p>
+ * If this is an instance of {@link MutableObjectId} the concept of equality
+ * with this instance can alter at any time, if this instance is modified to
+ * represent a different object name.
+ */
+public abstract class AnyObjectId implements Comparable {
+ static final int RAW_LEN = Constants.OBJECT_ID_LENGTH;
+
+ static final int STR_LEN = RAW_LEN * 2;
+
+ static {
+ if (RAW_LEN != 20)
+ throw new LinkageError("ObjectId expects"
+ + " Constants.OBJECT_ID_LENGTH = 20; it is " + RAW_LEN
+ + ".");
+ }
+
+ /**
+ * Compare to object identifier byte sequences for equality.
+ *
+ * @param firstObjectId
+ * the first identifier to compare. Must not be null.
+ * @param secondObjectId
+ * the second identifier to compare. Must not be null.
+ * @return true if the two identifiers are the same.
+ */
+ public static boolean equals(final AnyObjectId firstObjectId,
+ final AnyObjectId secondObjectId) {
+ if (firstObjectId == secondObjectId)
+ return true;
+
+ // We test word 2 first as odds are someone already used our
+ // word 1 as a hash code, and applying that came up with these
+ // two instances we are comparing for equality. Therefore the
+ // first two words are very likely to be identical. We want to
+ // break away from collisions as quickly as possible.
+ //
+ return firstObjectId.w2 == secondObjectId.w2
+ && firstObjectId.w3 == secondObjectId.w3
+ && firstObjectId.w4 == secondObjectId.w4
+ && firstObjectId.w5 == secondObjectId.w5
+ && firstObjectId.w1 == secondObjectId.w1;
+ }
+
+ int w1;
+
+ int w2;
+
+ int w3;
+
+ int w4;
+
+ int w5;
+
+ /**
+ * For ObjectIdMap
+ *
+ * @return a discriminator usable for a fan-out style map
+ */
+ public final int getFirstByte() {
+ return w1 >>> 24;
+ }
+
+ /**
+ * Compare this ObjectId to another and obtain a sort ordering.
+ *
+ * @param other
+ * the other id to compare to. Must not be null.
+ * @return < 0 if this id comes before other; 0 if this id is equal to
+ * other; > 0 if this id comes after other.
+ */
+ public int compareTo(final ObjectId other) {
+ if (this == other)
+ return 0;
+
+ int cmp;
+
+ cmp = NB.compareUInt32(w1, other.w1);
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w2, other.w2);
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w3, other.w3);
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w4, other.w4);
+ if (cmp != 0)
+ return cmp;
+
+ return NB.compareUInt32(w5, other.w5);
+ }
+
+ public int compareTo(final Object other) {
+ return compareTo(((ObjectId) other));
+ }
+
+ int compareTo(final byte[] bs, final int p) {
+ int cmp;
+
+ cmp = NB.compareUInt32(w1, NB.decodeInt32(bs, p));
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w2, NB.decodeInt32(bs, p + 4));
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w3, NB.decodeInt32(bs, p + 8));
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w4, NB.decodeInt32(bs, p + 12));
+ if (cmp != 0)
+ return cmp;
+
+ return NB.compareUInt32(w5, NB.decodeInt32(bs, p + 16));
+ }
+
+ int compareTo(final int[] bs, final int p) {
+ int cmp;
+
+ cmp = NB.compareUInt32(w1, bs[p]);
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w2, bs[p + 1]);
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w3, bs[p + 2]);
+ if (cmp != 0)
+ return cmp;
+
+ cmp = NB.compareUInt32(w4, bs[p + 3]);
+ if (cmp != 0)
+ return cmp;
+
+ return NB.compareUInt32(w5, bs[p + 4]);
+ }
+
+ /**
+ * Tests if this ObjectId starts with the given abbreviation.
+ *
+ * @param abbr
+ * the abbreviation.
+ * @return true if this ObjectId begins with the abbreviation; else false.
+ */
+ public boolean startsWith(final AbbreviatedObjectId abbr) {
+ return abbr.prefixCompare(this) == 0;
+ }
+
+ public int hashCode() {
+ return w2;
+ }
+
+ /**
+ * Determine if this ObjectId has exactly the same value as another.
+ *
+ * @param other
+ * the other id to compare to. May be null.
+ * @return true only if both ObjectIds have identical bits.
+ */
+ public boolean equals(final AnyObjectId other) {
+ return other != null ? equals(this, other) : false;
+ }
+
+ public boolean equals(final Object o) {
+ if (o instanceof AnyObjectId)
+ return equals((AnyObjectId) o);
+ else
+ return false;
+ }
+
+ /**
+ * Copy this ObjectId to an output writer in raw binary.
+ *
+ * @param w
+ * the buffer to copy to. Must be in big endian order.
+ */
+ public void copyRawTo(final ByteBuffer w) {
+ w.putInt(w1);
+ w.putInt(w2);
+ w.putInt(w3);
+ w.putInt(w4);
+ w.putInt(w5);
+ }
+
+ /**
+ * Copy this ObjectId to a byte array.
+ *
+ * @param b
+ * the buffer to copy to.
+ * @param o
+ * the offset within b to write at.
+ */
+ public void copyRawTo(final byte[] b, final int o) {
+ NB.encodeInt32(b, o, w1);
+ NB.encodeInt32(b, o + 4, w2);
+ NB.encodeInt32(b, o + 8, w3);
+ NB.encodeInt32(b, o + 12, w4);
+ NB.encodeInt32(b, o + 16, w5);
+ }
+
+ /**
+ * Copy this ObjectId to an int array.
+ *
+ * @param b
+ * the buffer to copy to.
+ * @param o
+ * the offset within b to write at.
+ */
+ public void copyRawTo(final int[] b, final int o) {
+ b[o] = w1;
+ b[o + 1] = w2;
+ b[o + 2] = w3;
+ b[o + 3] = w4;
+ b[o + 4] = w5;
+ }
+
+ /**
+ * Copy this ObjectId to an output writer in raw binary.
+ *
+ * @param w
+ * the stream to write to.
+ * @throws IOException
+ * the stream writing failed.
+ */
+ public void copyRawTo(final OutputStream w) throws IOException {
+ writeRawInt(w, w1);
+ writeRawInt(w, w2);
+ writeRawInt(w, w3);
+ writeRawInt(w, w4);
+ writeRawInt(w, w5);
+ }
+
+ private static void writeRawInt(final OutputStream w, int v)
+ throws IOException {
+ w.write(v >>> 24);
+ w.write(v >>> 16);
+ w.write(v >>> 8);
+ w.write(v);
+ }
+
+ /**
+ * Copy this ObjectId to an output writer in hex format.
+ *
+ * @param w
+ * the stream to copy to.
+ * @throws IOException
+ * the stream writing failed.
+ */
+ public void copyTo(final OutputStream w) throws IOException {
+ w.write(toHexByteArray());
+ }
+
+ private byte[] toHexByteArray() {
+ final byte[] dst = new byte[STR_LEN];
+ formatHexByte(dst, 0, w1);
+ formatHexByte(dst, 8, w2);
+ formatHexByte(dst, 16, w3);
+ formatHexByte(dst, 24, w4);
+ formatHexByte(dst, 32, w5);
+ return dst;
+ }
+
+ private static final byte[] hexbyte = { '0', '1', '2', '3', '4', '5', '6',
+ '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ private static void formatHexByte(final byte[] dst, final int p, int w) {
+ int o = p + 7;
+ while (o >= p && w != 0) {
+ dst[o--] = hexbyte[w & 0xf];
+ w >>>= 4;
+ }
+ while (o >= p)
+ dst[o--] = '0';
+ }
+
+ /**
+ * Copy this ObjectId to an output writer in hex format.
+ *
+ * @param w
+ * the stream to copy to.
+ * @throws IOException
+ * the stream writing failed.
+ */
+ public void copyTo(final Writer w) throws IOException {
+ w.write(toHexCharArray());
+ }
+
+ /**
+ * Copy this ObjectId to an output writer in hex format.
+ *
+ * @param tmp
+ * temporary char array to buffer construct into before writing.
+ * Must be at least large enough to hold 2 digits for each byte
+ * of object id (40 characters or larger).
+ * @param w
+ * the stream to copy to.
+ * @throws IOException
+ * the stream writing failed.
+ */
+ public void copyTo(final char[] tmp, final Writer w) throws IOException {
+ toHexCharArray(tmp);
+ w.write(tmp, 0, STR_LEN);
+ }
+
+ /**
+ * Copy this ObjectId to a StringBuilder in hex format.
+ *
+ * @param tmp
+ * temporary char array to buffer construct into before writing.
+ * Must be at least large enough to hold 2 digits for each byte
+ * of object id (40 characters or larger).
+ * @param w
+ * the string to append onto.
+ */
+ public void copyTo(final char[] tmp, final StringBuilder w) {
+ toHexCharArray(tmp);
+ w.append(tmp, 0, STR_LEN);
+ }
+
+ private char[] toHexCharArray() {
+ final char[] dst = new char[STR_LEN];
+ toHexCharArray(dst);
+ return dst;
+ }
+
+ private void toHexCharArray(final char[] dst) {
+ formatHexChar(dst, 0, w1);
+ formatHexChar(dst, 8, w2);
+ formatHexChar(dst, 16, w3);
+ formatHexChar(dst, 24, w4);
+ formatHexChar(dst, 32, w5);
+ }
+
+ private static final char[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
+ '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ static void formatHexChar(final char[] dst, final int p, int w) {
+ int o = p + 7;
+ while (o >= p && w != 0) {
+ dst[o--] = hexchar[w & 0xf];
+ w >>>= 4;
+ }
+ while (o >= p)
+ dst[o--] = '0';
+ }
+
+ @Override
+ public String toString() {
+ return "AnyObjectId[" + name() + "]";
+ }
+
+ /**
+ * @return string form of the SHA-1, in lower case hexadecimal.
+ */
+ public final String name() {
+ return new String(toHexCharArray());
+ }
+
+ /**
+ * @return string form of the SHA-1, in lower case hexadecimal.
+ */
+ public final String getName() {
+ return name();
+ }
+
+ /**
+ * Return unique abbreviation (prefix) of this object SHA-1.
+ * <p>
+ * This method is a utility for <code>abbreviate(repo, 8)</code>.
+ *
+ * @param repo
+ * repository for checking uniqueness within.
+ * @return SHA-1 abbreviation.
+ */
+ public AbbreviatedObjectId abbreviate(final Repository repo) {
+ return abbreviate(repo, 8);
+ }
+
+ /**
+ * Return unique abbreviation (prefix) of this object SHA-1.
+ * <p>
+ * Current implementation is not guaranteeing uniqueness, it just returns
+ * fixed-length prefix of SHA-1 string.
+ *
+ * @param repo
+ * repository for checking uniqueness within.
+ * @param len
+ * minimum length of the abbreviated string.
+ * @return SHA-1 abbreviation.
+ */
+ public AbbreviatedObjectId abbreviate(final Repository repo, final int len) {
+ // TODO implement checking for uniqueness
+ final int a = AbbreviatedObjectId.mask(len, 1, w1);
+ final int b = AbbreviatedObjectId.mask(len, 2, w2);
+ final int c = AbbreviatedObjectId.mask(len, 3, w3);
+ final int d = AbbreviatedObjectId.mask(len, 4, w4);
+ final int e = AbbreviatedObjectId.mask(len, 5, w5);
+ return new AbbreviatedObjectId(len, a, b, c, d, e);
+ }
+
+ /**
+ * Obtain an immutable copy of this current object name value.
+ * <p>
+ * Only returns <code>this</code> if this instance is an unsubclassed
+ * instance of {@link ObjectId}; otherwise a new instance is returned
+ * holding the same value.
+ * <p>
+ * This method is useful to shed any additional memory that may be tied to
+ * the subclass, yet retain the unique identity of the object id for future
+ * lookups within maps and repositories.
+ *
+ * @return an immutable copy, using the smallest memory footprint possible.
+ */
+ public final ObjectId copy() {
+ if (getClass() == ObjectId.class)
+ return (ObjectId) this;
+ return new ObjectId(this);
+ }
+
+ /**
+ * Obtain an immutable copy of this current object name value.
+ * <p>
+ * See {@link #copy()} if <code>this</code> is a possibly subclassed (but
+ * immutable) identity and the application needs a lightweight identity
+ * <i>only</i> reference.
+ *
+ * @return an immutable copy. May be <code>this</code> if this is already
+ * an immutable instance.
+ */
+ public abstract ObjectId toObjectId();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java
new file mode 100644
index 0000000000..461e6d4026
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/**
+ * Recreate a stream from a base stream and a GIT pack delta.
+ * <p>
+ * This entire class is heavily cribbed from <code>patch-delta.c</code> in the
+ * GIT project. The original delta patching code was written by Nicolas Pitre
+ * (&lt;nico@cam.org&gt;).
+ * </p>
+ */
+public class BinaryDelta {
+
+ /**
+ * Apply the changes defined by delta to the data in base, yielding a new
+ * array of bytes.
+ *
+ * @param base
+ * some byte representing an object of some kind.
+ * @param delta
+ * a git pack delta defining the transform from one version to
+ * another.
+ * @return patched base
+ */
+ public static final byte[] apply(final byte[] base, final byte[] delta) {
+ int deltaPtr = 0;
+
+ // Length of the base object (a variable length int).
+ //
+ int baseLen = 0;
+ int c, shift = 0;
+ do {
+ c = delta[deltaPtr++] & 0xff;
+ baseLen |= (c & 0x7f) << shift;
+ shift += 7;
+ } while ((c & 0x80) != 0);
+ if (base.length != baseLen)
+ throw new IllegalArgumentException("base length incorrect");
+
+ // Length of the resulting object (a variable length int).
+ //
+ int resLen = 0;
+ shift = 0;
+ do {
+ c = delta[deltaPtr++] & 0xff;
+ resLen |= (c & 0x7f) << shift;
+ shift += 7;
+ } while ((c & 0x80) != 0);
+
+ final byte[] result = new byte[resLen];
+ int resultPtr = 0;
+ while (deltaPtr < delta.length) {
+ final int cmd = delta[deltaPtr++] & 0xff;
+ if ((cmd & 0x80) != 0) {
+ // Determine the segment of the base which should
+ // be copied into the output. The segment is given
+ // as an offset and a length.
+ //
+ int copyOffset = 0;
+ if ((cmd & 0x01) != 0)
+ copyOffset = delta[deltaPtr++] & 0xff;
+ if ((cmd & 0x02) != 0)
+ copyOffset |= (delta[deltaPtr++] & 0xff) << 8;
+ if ((cmd & 0x04) != 0)
+ copyOffset |= (delta[deltaPtr++] & 0xff) << 16;
+ if ((cmd & 0x08) != 0)
+ copyOffset |= (delta[deltaPtr++] & 0xff) << 24;
+
+ int copySize = 0;
+ if ((cmd & 0x10) != 0)
+ copySize = delta[deltaPtr++] & 0xff;
+ if ((cmd & 0x20) != 0)
+ copySize |= (delta[deltaPtr++] & 0xff) << 8;
+ if ((cmd & 0x40) != 0)
+ copySize |= (delta[deltaPtr++] & 0xff) << 16;
+ if (copySize == 0)
+ copySize = 0x10000;
+
+ System.arraycopy(base, copyOffset, result, resultPtr, copySize);
+ resultPtr += copySize;
+ } else if (cmd != 0) {
+ // Anything else the data is literal within the delta
+ // itself.
+ //
+ System.arraycopy(delta, deltaPtr, result, resultPtr, cmd);
+ deltaPtr += cmd;
+ resultPtr += cmd;
+ } else {
+ // cmd == 0 has been reserved for future encoding but
+ // for now its not acceptable.
+ //
+ throw new IllegalArgumentException("unsupported command 0");
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
new file mode 100644
index 0000000000..0a4222fc38
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * The configuration file based on the blobs stored in the repository
+ */
+public class BlobBasedConfig extends Config {
+ /**
+ * The constructor from a byte array
+ *
+ * @param base
+ * the base configuration file
+ * @param blob
+ * the byte array, should be UTF-8 encoded text.
+ * @throws ConfigInvalidException
+ * the byte array is not a valid configuration format.
+ */
+ public BlobBasedConfig(Config base, final byte[] blob)
+ throws ConfigInvalidException {
+ super(base);
+ fromText(RawParseUtils.decode(blob));
+ }
+
+ /**
+ * The constructor from object identifier
+ *
+ * @param base
+ * the base configuration file
+ * @param r
+ * the repository
+ * @param objectId
+ * the object identifier
+ * @throws IOException
+ * the blob cannot be read from the repository.
+ * @throws ConfigInvalidException
+ * the blob is not a valid configuration format.
+ */
+ public BlobBasedConfig(Config base, final Repository r,
+ final ObjectId objectId) throws IOException, ConfigInvalidException {
+ super(base);
+ final ObjectLoader loader = r.openBlob(objectId);
+ if (loader == null)
+ throw new IOException("Blob not found: " + objectId);
+ fromText(RawParseUtils.decode(loader.getBytes()));
+ }
+
+ /**
+ * The constructor from commit and path
+ *
+ * @param base
+ * the base configuration file
+ * @param commit
+ * the commit that contains the object
+ * @param path
+ * the path within the tree of the commit
+ * @throws FileNotFoundException
+ * the path does not exist in the commit's tree.
+ * @throws IOException
+ * the tree and/or blob cannot be accessed.
+ * @throws ConfigInvalidException
+ * the blob is not a valid configuration format.
+ */
+ public BlobBasedConfig(Config base, final Commit commit, final String path)
+ throws FileNotFoundException, IOException, ConfigInvalidException {
+ super(base);
+ final ObjectId treeId = commit.getTreeId();
+ final Repository r = commit.getRepository();
+ final TreeWalk tree = TreeWalk.forPath(r, path, treeId);
+ if (tree == null)
+ throw new FileNotFoundException("Entry not found by path: " + path);
+ final ObjectId blobId = tree.getObjectId(0);
+ final ObjectLoader loader = tree.getRepository().openBlob(blobId);
+ if (loader == null)
+ throw new IOException("Blob not found: " + blobId + " for path: "
+ + path);
+ fromText(RawParseUtils.decode(loader.getBytes()));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java
new file mode 100644
index 0000000000..8042610310
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ * A {@link ByteWindow} with an underlying byte array for storage.
+ */
+final class ByteArrayWindow extends ByteWindow {
+ private final byte[] array;
+
+ ByteArrayWindow(final PackFile pack, final long o, final byte[] b) {
+ super(pack, o, b.length);
+ array = b;
+ }
+
+ @Override
+ protected int copy(final int p, final byte[] b, final int o, int n) {
+ n = Math.min(array.length - p, n);
+ System.arraycopy(array, p, b, o, n);
+ return n;
+ }
+
+ @Override
+ protected int inflate(final int pos, final byte[] b, int o,
+ final Inflater inf) throws DataFormatException {
+ while (!inf.finished()) {
+ if (inf.needsInput()) {
+ inf.setInput(array, pos, array.length - pos);
+ break;
+ }
+ o += inf.inflate(b, o, b.length - o);
+ }
+ while (!inf.finished() && !inf.needsInput())
+ o += inf.inflate(b, o, b.length - o);
+ return o;
+ }
+
+ @Override
+ protected void inflateVerify(final int pos, final Inflater inf)
+ throws DataFormatException {
+ while (!inf.finished()) {
+ if (inf.needsInput()) {
+ inf.setInput(array, pos, array.length - pos);
+ break;
+ }
+ inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length);
+ }
+ while (!inf.finished() && !inf.needsInput())
+ inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java
new file mode 100644
index 0000000000..1b29934d28
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.nio.ByteBuffer;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ * A window for accessing git packs using a {@link ByteBuffer} for storage.
+ *
+ * @see ByteWindow
+ */
+final class ByteBufferWindow extends ByteWindow {
+ private final ByteBuffer buffer;
+
+ ByteBufferWindow(final PackFile pack, final long o, final ByteBuffer b) {
+ super(pack, o, b.capacity());
+ buffer = b;
+ }
+
+ @Override
+ protected int copy(final int p, final byte[] b, final int o, int n) {
+ final ByteBuffer s = buffer.slice();
+ s.position(p);
+ n = Math.min(s.remaining(), n);
+ s.get(b, o, n);
+ return n;
+ }
+
+ @Override
+ protected int inflate(final int pos, final byte[] b, int o,
+ final Inflater inf) throws DataFormatException {
+ final byte[] tmp = new byte[512];
+ final ByteBuffer s = buffer.slice();
+ s.position(pos);
+ while (s.remaining() > 0 && !inf.finished()) {
+ if (inf.needsInput()) {
+ final int n = Math.min(s.remaining(), tmp.length);
+ s.get(tmp, 0, n);
+ inf.setInput(tmp, 0, n);
+ }
+ o += inf.inflate(b, o, b.length - o);
+ }
+ while (!inf.finished() && !inf.needsInput())
+ o += inf.inflate(b, o, b.length - o);
+ return o;
+ }
+
+ @Override
+ protected void inflateVerify(final int pos, final Inflater inf)
+ throws DataFormatException {
+ final byte[] tmp = new byte[512];
+ final ByteBuffer s = buffer.slice();
+ s.position(pos);
+ while (s.remaining() > 0 && !inf.finished()) {
+ if (inf.needsInput()) {
+ final int n = Math.min(s.remaining(), tmp.length);
+ s.get(tmp, 0, n);
+ inf.setInput(tmp, 0, n);
+ }
+ inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length);
+ }
+ while (!inf.finished() && !inf.needsInput())
+ inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java
new file mode 100644
index 0000000000..89bbe7548a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ * A window of data currently stored within a cache.
+ * <p>
+ * All bytes in the window can be assumed to be "immediately available", that is
+ * they are very likely already in memory, unless the operating system's memory
+ * is very low and has paged part of this process out to disk. Therefore copying
+ * bytes from a window is very inexpensive.
+ * </p>
+ */
+abstract class ByteWindow {
+ protected final PackFile pack;
+
+ protected final long start;
+
+ protected final long end;
+
+ protected ByteWindow(final PackFile p, final long s, final int n) {
+ pack = p;
+ start = s;
+ end = start + n;
+ }
+
+ final int size() {
+ return (int) (end - start);
+ }
+
+ final boolean contains(final PackFile neededFile, final long neededPos) {
+ return pack == neededFile && start <= neededPos && neededPos < end;
+ }
+
+ /**
+ * Copy bytes from the window to a caller supplied buffer.
+ *
+ * @param pos
+ * offset within the file to start copying from.
+ * @param dstbuf
+ * destination buffer to copy into.
+ * @param dstoff
+ * offset within <code>dstbuf</code> to start copying into.
+ * @param cnt
+ * number of bytes to copy. This value may exceed the number of
+ * bytes remaining in the window starting at offset
+ * <code>pos</code>.
+ * @return number of bytes actually copied; this may be less than
+ * <code>cnt</code> if <code>cnt</code> exceeded the number of
+ * bytes available.
+ */
+ final int copy(long pos, byte[] dstbuf, int dstoff, int cnt) {
+ return copy((int) (pos - start), dstbuf, dstoff, cnt);
+ }
+
+ /**
+ * Copy bytes from the window to a caller supplied buffer.
+ *
+ * @param pos
+ * offset within the window to start copying from.
+ * @param dstbuf
+ * destination buffer to copy into.
+ * @param dstoff
+ * offset within <code>dstbuf</code> to start copying into.
+ * @param cnt
+ * number of bytes to copy. This value may exceed the number of
+ * bytes remaining in the window starting at offset
+ * <code>pos</code>.
+ * @return number of bytes actually copied; this may be less than
+ * <code>cnt</code> if <code>cnt</code> exceeded the number of
+ * bytes available.
+ */
+ protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt);
+
+ /**
+ * Pump bytes into the supplied inflater as input.
+ *
+ * @param pos
+ * offset within the file to start supplying input from.
+ * @param dstbuf
+ * destination buffer the inflater should output decompressed
+ * data to.
+ * @param dstoff
+ * current offset within <code>dstbuf</code> to inflate into.
+ * @param inf
+ * the inflater to feed input to. The caller is responsible for
+ * initializing the inflater as multiple windows may need to
+ * supply data to the same inflater to completely decompress
+ * something.
+ * @return updated <code>dstoff</code> based on the number of bytes
+ * successfully copied into <code>dstbuf</code> by
+ * <code>inf</code>. If the inflater is not yet finished then
+ * another window's data must still be supplied as input to finish
+ * decompression.
+ * @throws DataFormatException
+ * the inflater encountered an invalid chunk of data. Data
+ * stream corruption is likely.
+ */
+ final int inflate(long pos, byte[] dstbuf, int dstoff, Inflater inf)
+ throws DataFormatException {
+ return inflate((int) (pos - start), dstbuf, dstoff, inf);
+ }
+
+ /**
+ * Pump bytes into the supplied inflater as input.
+ *
+ * @param pos
+ * offset within the window to start supplying input from.
+ * @param dstbuf
+ * destination buffer the inflater should output decompressed
+ * data to.
+ * @param dstoff
+ * current offset within <code>dstbuf</code> to inflate into.
+ * @param inf
+ * the inflater to feed input to. The caller is responsible for
+ * initializing the inflater as multiple windows may need to
+ * supply data to the same inflater to completely decompress
+ * something.
+ * @return updated <code>dstoff</code> based on the number of bytes
+ * successfully copied into <code>dstbuf</code> by
+ * <code>inf</code>. If the inflater is not yet finished then
+ * another window's data must still be supplied as input to finish
+ * decompression.
+ * @throws DataFormatException
+ * the inflater encountered an invalid chunk of data. Data
+ * stream corruption is likely.
+ */
+ protected abstract int inflate(int pos, byte[] dstbuf, int dstoff,
+ Inflater inf) throws DataFormatException;
+
+ protected static final byte[] verifyGarbageBuffer = new byte[2048];
+
+ final void inflateVerify(final long pos, final Inflater inf)
+ throws DataFormatException {
+ inflateVerify((int) (pos - start), inf);
+ }
+
+ protected abstract void inflateVerify(int pos, Inflater inf)
+ throws DataFormatException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java
new file mode 100644
index 0000000000..cdfab7cc38
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2006-2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/**
+ * Instances of this class represent a Commit object. It represents a snapshot
+ * in a Git repository, who created it and when.
+ */
+public class Commit implements Treeish {
+ private static final ObjectId[] EMPTY_OBJECTID_LIST = new ObjectId[0];
+
+ private final Repository objdb;
+
+ private ObjectId commitId;
+
+ private ObjectId treeId;
+
+ private ObjectId[] parentIds;
+
+ private PersonIdent author;
+
+ private PersonIdent committer;
+
+ private String message;
+
+ private Tree treeObj;
+
+ private byte[] raw;
+
+ private Charset encoding;
+
+ /**
+ * Create an empty commit object. More information must be fed to this
+ * object to make it useful.
+ *
+ * @param db
+ * The repository with which to associate it.
+ */
+ public Commit(final Repository db) {
+ objdb = db;
+ parentIds = EMPTY_OBJECTID_LIST;
+ }
+
+ /**
+ * Create a commit associated with these parents and associate it with a
+ * repository.
+ *
+ * @param db
+ * The repository to which this commit object belongs
+ * @param parentIds
+ * Id's of the parent(s)
+ */
+ public Commit(final Repository db, final ObjectId[] parentIds) {
+ objdb = db;
+ this.parentIds = parentIds;
+ }
+
+ /**
+ * Create a commit object with the specified id and data from and existing
+ * commit object in a repository.
+ *
+ * @param db
+ * The repository to which this commit object belongs
+ * @param id
+ * Commit id
+ * @param raw
+ * Raw commit object data
+ */
+ public Commit(final Repository db, final ObjectId id, final byte[] raw) {
+ objdb = db;
+ commitId = id;
+ treeId = ObjectId.fromString(raw, 5);
+ parentIds = new ObjectId[1];
+ int np=0;
+ int rawPtr = 46;
+ for (;;) {
+ if (raw[rawPtr] != 'p')
+ break;
+ if (np == 0) {
+ parentIds[np++] = ObjectId.fromString(raw, rawPtr + 7);
+ } else if (np == 1) {
+ parentIds = new ObjectId[] { parentIds[0], ObjectId.fromString(raw, rawPtr + 7) };
+ np++;
+ } else {
+ if (parentIds.length <= np) {
+ ObjectId[] old = parentIds;
+ parentIds = new ObjectId[parentIds.length+32];
+ for (int i=0; i<np; ++i)
+ parentIds[i] = old[i];
+ }
+ parentIds[np++] = ObjectId.fromString(raw, rawPtr + 7);
+ }
+ rawPtr += 48;
+ }
+ if (np != parentIds.length) {
+ ObjectId[] old = parentIds;
+ parentIds = new ObjectId[np];
+ for (int i=0; i<np; ++i)
+ parentIds[i] = old[i];
+ } else
+ if (np == 0)
+ parentIds = EMPTY_OBJECTID_LIST;
+ this.raw = raw;
+ }
+
+ /**
+ * @return get repository for the commit
+ */
+ public Repository getRepository() {
+ return objdb;
+ }
+
+ /**
+ * @return The commit object id
+ */
+ public ObjectId getCommitId() {
+ return commitId;
+ }
+
+ /**
+ * Set the id of this object.
+ *
+ * @param id
+ * the id that we calculated for this object.
+ */
+ public void setCommitId(final ObjectId id) {
+ commitId = id;
+ }
+
+ public ObjectId getTreeId() {
+ return treeId;
+ }
+
+ /**
+ * Set the tree id for this commit object
+ *
+ * @param id
+ */
+ public void setTreeId(final ObjectId id) {
+ if (treeId==null || !treeId.equals(id)) {
+ treeObj = null;
+ }
+ treeId = id;
+ }
+
+ public Tree getTree() throws IOException {
+ if (treeObj == null) {
+ treeObj = objdb.mapTree(getTreeId());
+ if (treeObj == null) {
+ throw new MissingObjectException(getTreeId(),
+ Constants.TYPE_TREE);
+ }
+ }
+ return treeObj;
+ }
+
+ /**
+ * Set the tree object for this commit
+ * @see #setTreeId
+ * @param t the Tree object
+ */
+ public void setTree(final Tree t) {
+ treeId = t.getTreeId();
+ treeObj = t;
+ }
+
+ /**
+ * @return the author and authoring time for this commit
+ */
+ public PersonIdent getAuthor() {
+ decode();
+ return author;
+ }
+
+ /**
+ * Set the author and authoring time for this commit
+ * @param a
+ */
+ public void setAuthor(final PersonIdent a) {
+ author = a;
+ }
+
+ /**
+ * @return the committer and commit time for this object
+ */
+ public PersonIdent getCommitter() {
+ decode();
+ return committer;
+ }
+
+ /**
+ * Set the committer and commit time for this object
+ * @param c the committer information
+ */
+ public void setCommitter(final PersonIdent c) {
+ committer = c;
+ }
+
+ /**
+ * @return the object ids of this commit
+ */
+ public ObjectId[] getParentIds() {
+ return parentIds;
+ }
+
+ /**
+ * @return the commit message
+ */
+ public String getMessage() {
+ decode();
+ return message;
+ }
+
+ /**
+ * Set the parents of this commit
+ * @param parentIds
+ */
+ public void setParentIds(ObjectId[] parentIds) {
+ this.parentIds = new ObjectId[parentIds.length];
+ for (int i=0; i<parentIds.length; ++i)
+ this.parentIds[i] = parentIds[i];
+ }
+
+ private void decode() {
+ // FIXME: handle I/O errors
+ if (raw != null) {
+ try {
+ DataInputStream br = new DataInputStream(new ByteArrayInputStream(raw));
+ String n = br.readLine();
+ if (n == null || !n.startsWith("tree ")) {
+ throw new CorruptObjectException(commitId, "no tree");
+ }
+ while ((n = br.readLine()) != null && n.startsWith("parent ")) {
+ // empty body
+ }
+ if (n == null || !n.startsWith("author ")) {
+ throw new CorruptObjectException(commitId, "no author");
+ }
+ String rawAuthor = n.substring("author ".length());
+ n = br.readLine();
+ if (n == null || !n.startsWith("committer ")) {
+ throw new CorruptObjectException(commitId, "no committer");
+ }
+ String rawCommitter = n.substring("committer ".length());
+ n = br.readLine();
+ if (n != null && n.startsWith( "encoding"))
+ encoding = Charset.forName(n.substring("encoding ".length()));
+ else
+ if (n == null || !n.equals("")) {
+ throw new CorruptObjectException(commitId,
+ "malformed header:"+n);
+ }
+ byte[] readBuf = new byte[br.available()]; // in-memory stream so this is all bytes left
+ br.read(readBuf);
+ int msgstart = readBuf.length != 0 ? ( readBuf[0] == '\n' ? 1 : 0 ) : 0;
+
+ if (encoding != null) {
+ // TODO: this isn't reliable so we need to guess the encoding from the actual content
+ author = new PersonIdent(new String(rawAuthor.getBytes(),encoding.name()));
+ committer = new PersonIdent(new String(rawCommitter.getBytes(),encoding.name()));
+ message = new String(readBuf,msgstart, readBuf.length-msgstart, encoding.name());
+ } else {
+ // TODO: use config setting / platform / ascii / iso-latin
+ author = new PersonIdent(new String(rawAuthor.getBytes()));
+ committer = new PersonIdent(new String(rawCommitter.getBytes()));
+ message = new String(readBuf, msgstart, readBuf.length-msgstart);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ raw = null;
+ }
+ }
+ }
+
+ /**
+ * Set the commit message
+ *
+ * @param m the commit message
+ */
+ public void setMessage(final String m) {
+ message = m;
+ }
+
+ /**
+ * Persist this commit object
+ *
+ * @throws IOException
+ */
+ public void commit() throws IOException {
+ if (getCommitId() != null)
+ throw new IllegalStateException("exists " + getCommitId());
+ setCommitId(new ObjectWriter(objdb).writeCommit(this));
+ }
+
+ public String toString() {
+ return "Commit[" + ObjectId.toString(getCommitId()) + " " + getAuthor() + "]";
+ }
+
+ /**
+ * State the encoding for the commit information
+ *
+ * @param e
+ * the encoding. See {@link Charset}
+ */
+ public void setEncoding(String e) {
+ encoding = Charset.forName(e);
+ }
+
+ /**
+ * State the encoding for the commit information
+ *
+ * @param e
+ * the encoding. See {@link Charset}
+ */
+ public void setEncoding(Charset e) {
+ encoding = e;
+ }
+
+ /**
+ * @return the encoding used. See {@link Charset}
+ */
+ public String getEncoding() {
+ if (encoding != null)
+ return encoding.name();
+ else
+ return null;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
new file mode 100644
index 0000000000..b6b9c5e05b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -0,0 +1,1154 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2009, Google, Inc.
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.util.StringUtils;
+
+
+/**
+ * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
+ */
+public class Config {
+ private static final String[] EMPTY_STRING_ARRAY = {};
+ private static final long KiB = 1024;
+ private static final long MiB = 1024 * KiB;
+ private static final long GiB = 1024 * MiB;
+
+ /**
+ * Immutable current state of the configuration data.
+ * <p>
+ * This state is copy-on-write. It should always contain an immutable list
+ * of the configuration keys/values.
+ */
+ private final AtomicReference<State> state;
+
+ private final Config baseConfig;
+
+ /**
+ * Magic value indicating a missing entry.
+ * <p>
+ * This value is tested for reference equality in some contexts, so we
+ * must ensure it is a special copy of the empty string. It also must
+ * be treated like the empty string.
+ */
+ private static final String MAGIC_EMPTY_VALUE = new String();
+
+ /** Create a configuration with no default fallback. */
+ public Config() {
+ this(null);
+ }
+
+ /**
+ * Create an empty configuration with a fallback for missing keys.
+ *
+ * @param defaultConfig
+ * the base configuration to be consulted when a key is missing
+ * from this configuration instance.
+ */
+ public Config(Config defaultConfig) {
+ baseConfig = defaultConfig;
+ state = new AtomicReference<State>(newState());
+ }
+
+ /**
+ * Escape the value before saving
+ *
+ * @param x
+ * the value to escape
+ * @return the escaped value
+ */
+ private static String escapeValue(final String x) {
+ boolean inquote = false;
+ int lineStart = 0;
+ final StringBuffer r = new StringBuffer(x.length());
+ for (int k = 0; k < x.length(); k++) {
+ final char c = x.charAt(k);
+ switch (c) {
+ case '\n':
+ if (inquote) {
+ r.append('"');
+ inquote = false;
+ }
+ r.append("\\n\\\n");
+ lineStart = r.length();
+ break;
+
+ case '\t':
+ r.append("\\t");
+ break;
+
+ case '\b':
+ r.append("\\b");
+ break;
+
+ case '\\':
+ r.append("\\\\");
+ break;
+
+ case '"':
+ r.append("\\\"");
+ break;
+
+ case ';':
+ case '#':
+ if (!inquote) {
+ r.insert(lineStart, '"');
+ inquote = true;
+ }
+ r.append(c);
+ break;
+
+ case ' ':
+ if (!inquote && r.length() > 0
+ && r.charAt(r.length() - 1) == ' ') {
+ r.insert(lineStart, '"');
+ inquote = true;
+ }
+ r.append(' ');
+ break;
+
+ default:
+ r.append(c);
+ break;
+ }
+ }
+ if (inquote) {
+ r.append('"');
+ }
+ return r.toString();
+ }
+
+ /**
+ * Obtain an integer value from the configuration.
+ *
+ * @param section
+ * section the key is grouped within.
+ * @param name
+ * name of the key to get.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return an integer value from the configuration, or defaultValue.
+ */
+ public int getInt(final String section, final String name,
+ final int defaultValue) {
+ return getInt(section, null, name, defaultValue);
+ }
+
+ /**
+ * Obtain an integer value from the configuration.
+ *
+ * @param section
+ * section the key is grouped within.
+ * @param subsection
+ * subsection name, such a remote or branch name.
+ * @param name
+ * name of the key to get.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return an integer value from the configuration, or defaultValue.
+ */
+ public int getInt(final String section, String subsection,
+ final String name, final int defaultValue) {
+ final long val = getLong(section, subsection, name, defaultValue);
+ if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE)
+ return (int) val;
+ throw new IllegalArgumentException("Integer value " + section + "."
+ + name + " out of range");
+ }
+
+ /**
+ * Obtain an integer value from the configuration.
+ *
+ * @param section
+ * section the key is grouped within.
+ * @param subsection
+ * subsection name, such a remote or branch name.
+ * @param name
+ * name of the key to get.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return an integer value from the configuration, or defaultValue.
+ */
+ public long getLong(final String section, String subsection,
+ final String name, final long defaultValue) {
+ final String str = getString(section, subsection, name);
+ if (str == null)
+ return defaultValue;
+
+ String n = str.trim();
+ if (n.length() == 0)
+ return defaultValue;
+
+ long mul = 1;
+ switch (StringUtils.toLowerCase(n.charAt(n.length() - 1))) {
+ case 'g':
+ mul = GiB;
+ break;
+ case 'm':
+ mul = MiB;
+ break;
+ case 'k':
+ mul = KiB;
+ break;
+ }
+ if (mul > 1)
+ n = n.substring(0, n.length() - 1).trim();
+ if (n.length() == 0)
+ return defaultValue;
+
+ try {
+ return mul * Long.parseLong(n);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException("Invalid integer value: "
+ + section + "." + name + "=" + str);
+ }
+ }
+
+ /**
+ * Get a boolean value from the git config
+ *
+ * @param section
+ * section the key is grouped within.
+ * @param name
+ * name of the key to get.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return true if any value or defaultValue is true, false for missing or
+ * explicit false
+ */
+ public boolean getBoolean(final String section, final String name,
+ final boolean defaultValue) {
+ return getBoolean(section, null, name, defaultValue);
+ }
+
+ /**
+ * Get a boolean value from the git config
+ *
+ * @param section
+ * section the key is grouped within.
+ * @param subsection
+ * subsection name, such a remote or branch name.
+ * @param name
+ * name of the key to get.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return true if any value or defaultValue is true, false for missing or
+ * explicit false
+ */
+ public boolean getBoolean(final String section, String subsection,
+ final String name, final boolean defaultValue) {
+ String n = getRawString(section, subsection, name);
+ if (n == null)
+ return defaultValue;
+
+ if (MAGIC_EMPTY_VALUE == n || StringUtils.equalsIgnoreCase("yes", n)
+ || StringUtils.equalsIgnoreCase("true", n)
+ || StringUtils.equalsIgnoreCase("1", n)
+ || StringUtils.equalsIgnoreCase("on", n)) {
+ return true;
+
+ } else if (StringUtils.equalsIgnoreCase("no", n)
+ || StringUtils.equalsIgnoreCase("false", n)
+ || StringUtils.equalsIgnoreCase("0", n)
+ || StringUtils.equalsIgnoreCase("off", n)) {
+ return false;
+
+ } else {
+ throw new IllegalArgumentException("Invalid boolean value: "
+ + section + "." + name + "=" + n);
+ }
+ }
+
+ /**
+ * Get string value
+ *
+ * @param section
+ * the section
+ * @param subsection
+ * the subsection for the value
+ * @param name
+ * the key name
+ * @return a String value from git config.
+ */
+ public String getString(final String section, String subsection,
+ final String name) {
+ return getRawString(section, subsection, name);
+ }
+
+ /**
+ * Get a list of string values
+ * <p>
+ * If this instance was created with a base, the base's values are returned
+ * first (if any).
+ *
+ * @param section
+ * the section
+ * @param subsection
+ * the subsection for the value
+ * @param name
+ * the key name
+ * @return array of zero or more values from the configuration.
+ */
+ public String[] getStringList(final String section, String subsection,
+ final String name) {
+ final String[] baseList;
+ if (baseConfig != null)
+ baseList = baseConfig.getStringList(section, subsection, name);
+ else
+ baseList = EMPTY_STRING_ARRAY;
+
+ final List<String> lst = getRawStringList(section, subsection, name);
+ if (lst != null) {
+ final String[] res = new String[baseList.length + lst.size()];
+ int idx = baseList.length;
+ System.arraycopy(baseList, 0, res, 0, idx);
+ for (final String val : lst)
+ res[idx++] = val;
+ return res;
+ }
+ return baseList;
+ }
+
+ /**
+ * @param section
+ * section to search for.
+ * @return set of all subsections of specified section within this
+ * configuration and its base configuration; may be empty if no
+ * subsection exists.
+ */
+ public Set<String> getSubsections(final String section) {
+ return get(new SubsectionNames(section));
+ }
+
+ /**
+ * Obtain a handle to a parsed set of configuration values.
+ *
+ * @param <T>
+ * type of configuration model to return.
+ * @param parser
+ * parser which can create the model if it is not already
+ * available in this configuration file. The parser is also used
+ * as the key into a cache and must obey the hashCode and equals
+ * contract in order to reuse a parsed model.
+ * @return the parsed object instance, which is cached inside this config.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T get(final SectionParser<T> parser) {
+ final State myState = getState();
+ T obj = (T) myState.cache.get(parser);
+ if (obj == null) {
+ obj = parser.parse(this);
+ myState.cache.put(parser, obj);
+ }
+ return obj;
+ }
+
+ /**
+ * Remove a cached configuration object.
+ * <p>
+ * If the associated configuration object has not yet been cached, this
+ * method has no effect.
+ *
+ * @param parser
+ * parser used to obtain the configuration object.
+ * @see #get(SectionParser)
+ */
+ public void uncache(final SectionParser<?> parser) {
+ state.get().cache.remove(parser);
+ }
+
+ private String getRawString(final String section, final String subsection,
+ final String name) {
+ final List<String> lst = getRawStringList(section, subsection, name);
+ if (lst != null)
+ return lst.get(0);
+ else if (baseConfig != null)
+ return baseConfig.getRawString(section, subsection, name);
+ else
+ return null;
+ }
+
+ private List<String> getRawStringList(final String section,
+ final String subsection, final String name) {
+ List<String> r = null;
+ for (final Entry e : state.get().entryList) {
+ if (e.match(section, subsection, name))
+ r = add(r, e.value);
+ }
+ return r;
+ }
+
+ private static List<String> add(final List<String> curr, final String value) {
+ if (curr == null)
+ return Collections.singletonList(value);
+ if (curr.size() == 1) {
+ final List<String> r = new ArrayList<String>(2);
+ r.add(curr.get(0));
+ r.add(value);
+ return r;
+ }
+ curr.add(value);
+ return curr;
+ }
+
+ private State getState() {
+ State cur, upd;
+ do {
+ cur = state.get();
+ final State base = getBaseState();
+ if (cur.baseState == base)
+ return cur;
+ upd = new State(cur.entryList, base);
+ } while (!state.compareAndSet(cur, upd));
+ return upd;
+ }
+
+ private State getBaseState() {
+ return baseConfig != null ? baseConfig.getState() : null;
+ }
+
+ /**
+ * Add or modify a configuration value. The parameters will result in a
+ * configuration entry like this.
+ *
+ * <pre>
+ * [section &quot;subsection&quot;]
+ * name = value
+ * </pre>
+ *
+ * @param section
+ * section name, e.g "branch"
+ * @param subsection
+ * optional subsection value, e.g. a branch name
+ * @param name
+ * parameter name, e.g. "filemode"
+ * @param value
+ * parameter value
+ */
+ public void setInt(final String section, final String subsection,
+ final String name, final int value) {
+ setLong(section, subsection, name, value);
+ }
+
+ /**
+ * Add or modify a configuration value. The parameters will result in a
+ * configuration entry like this.
+ *
+ * <pre>
+ * [section &quot;subsection&quot;]
+ * name = value
+ * </pre>
+ *
+ * @param section
+ * section name, e.g "branch"
+ * @param subsection
+ * optional subsection value, e.g. a branch name
+ * @param name
+ * parameter name, e.g. "filemode"
+ * @param value
+ * parameter value
+ */
+ public void setLong(final String section, final String subsection,
+ final String name, final long value) {
+ final String s;
+
+ if (value >= GiB && (value % GiB) == 0)
+ s = String.valueOf(value / GiB) + " g";
+ else if (value >= MiB && (value % MiB) == 0)
+ s = String.valueOf(value / MiB) + " m";
+ else if (value >= KiB && (value % KiB) == 0)
+ s = String.valueOf(value / KiB) + " k";
+ else
+ s = String.valueOf(value);
+
+ setString(section, subsection, name, s);
+ }
+
+ /**
+ * Add or modify a configuration value. The parameters will result in a
+ * configuration entry like this.
+ *
+ * <pre>
+ * [section &quot;subsection&quot;]
+ * name = value
+ * </pre>
+ *
+ * @param section
+ * section name, e.g "branch"
+ * @param subsection
+ * optional subsection value, e.g. a branch name
+ * @param name
+ * parameter name, e.g. "filemode"
+ * @param value
+ * parameter value
+ */
+ public void setBoolean(final String section, final String subsection,
+ final String name, final boolean value) {
+ setString(section, subsection, name, value ? "true" : "false");
+ }
+
+ /**
+ * Add or modify a configuration value. The parameters will result in a
+ * configuration entry like this.
+ *
+ * <pre>
+ * [section &quot;subsection&quot;]
+ * name = value
+ * </pre>
+ *
+ * @param section
+ * section name, e.g "branch"
+ * @param subsection
+ * optional subsection value, e.g. a branch name
+ * @param name
+ * parameter name, e.g. "filemode"
+ * @param value
+ * parameter value, e.g. "true"
+ */
+ public void setString(final String section, final String subsection,
+ final String name, final String value) {
+ setStringList(section, subsection, name, Collections
+ .singletonList(value));
+ }
+
+ /**
+ * Remove a configuration value.
+ *
+ * @param section
+ * section name, e.g "branch"
+ * @param subsection
+ * optional subsection value, e.g. a branch name
+ * @param name
+ * parameter name, e.g. "filemode"
+ */
+ public void unset(final String section, final String subsection,
+ final String name) {
+ setStringList(section, subsection, name, Collections
+ .<String> emptyList());
+ }
+
+ /**
+ * Set a configuration value.
+ *
+ * <pre>
+ * [section &quot;subsection&quot;]
+ * name = value
+ * </pre>
+ *
+ * @param section
+ * section name, e.g "branch"
+ * @param subsection
+ * optional subsection value, e.g. a branch name
+ * @param name
+ * parameter name, e.g. "filemode"
+ * @param values
+ * list of zero or more values for this key.
+ */
+ public void setStringList(final String section, final String subsection,
+ final String name, final List<String> values) {
+ State src, res;
+ do {
+ src = state.get();
+ res = replaceStringList(src, section, subsection, name, values);
+ } while (!state.compareAndSet(src, res));
+ }
+
+ private State replaceStringList(final State srcState,
+ final String section, final String subsection, final String name,
+ final List<String> values) {
+ final List<Entry> entries = copy(srcState, values);
+ int entryIndex = 0;
+ int valueIndex = 0;
+ int insertPosition = -1;
+
+ // Reset the first n Entry objects that match this input name.
+ //
+ while (entryIndex < entries.size() && valueIndex < values.size()) {
+ final Entry e = entries.get(entryIndex);
+ if (e.match(section, subsection, name)) {
+ entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
+ insertPosition = entryIndex + 1;
+ }
+ entryIndex++;
+ }
+
+ // Remove any extra Entry objects that we no longer need.
+ //
+ if (valueIndex == values.size() && entryIndex < entries.size()) {
+ while (entryIndex < entries.size()) {
+ final Entry e = entries.get(entryIndex++);
+ if (e.match(section, subsection, name))
+ entries.remove(--entryIndex);
+ }
+ }
+
+ // Insert new Entry objects for additional/new values.
+ //
+ if (valueIndex < values.size() && entryIndex == entries.size()) {
+ if (insertPosition < 0) {
+ // We didn't find a matching key above, but maybe there
+ // is already a section available that matches. Insert
+ // after the last key of that section.
+ //
+ insertPosition = findSectionEnd(entries, section, subsection);
+ }
+ if (insertPosition < 0) {
+ // We didn't find any matching section header for this key,
+ // so we must create a new section header at the end.
+ //
+ final Entry e = new Entry();
+ e.section = section;
+ e.subsection = subsection;
+ entries.add(e);
+ insertPosition = entries.size();
+ }
+ while (valueIndex < values.size()) {
+ final Entry e = new Entry();
+ e.section = section;
+ e.subsection = subsection;
+ e.name = name;
+ e.value = values.get(valueIndex++);
+ entries.add(insertPosition++, e);
+ }
+ }
+
+ return newState(entries);
+ }
+
+ private static List<Entry> copy(final State src, final List<String> values) {
+ // At worst we need to insert 1 line for each value, plus 1 line
+ // for a new section header. Assume that and allocate the space.
+ //
+ final int max = src.entryList.size() + values.size() + 1;
+ final ArrayList<Entry> r = new ArrayList<Entry>(max);
+ r.addAll(src.entryList);
+ return r;
+ }
+
+ private static int findSectionEnd(final List<Entry> entries,
+ final String section, final String subsection) {
+ for (int i = 0; i < entries.size(); i++) {
+ Entry e = entries.get(i);
+ if (e.match(section, subsection, null)) {
+ i++;
+ while (i < entries.size()) {
+ e = entries.get(i);
+ if (e.match(section, subsection, e.name))
+ i++;
+ else
+ break;
+ }
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @return this configuration, formatted as a Git style text file.
+ */
+ public String toText() {
+ final StringBuilder out = new StringBuilder();
+ for (final Entry e : state.get().entryList) {
+ if (e.prefix != null)
+ out.append(e.prefix);
+ if (e.section != null && e.name == null) {
+ out.append('[');
+ out.append(e.section);
+ if (e.subsection != null) {
+ out.append(' ');
+ out.append('"');
+ out.append(escapeValue(e.subsection));
+ out.append('"');
+ }
+ out.append(']');
+ } else if (e.section != null && e.name != null) {
+ if (e.prefix == null || "".equals(e.prefix))
+ out.append('\t');
+ out.append(e.name);
+ if (e.value != null) {
+ if (MAGIC_EMPTY_VALUE != e.value) {
+ out.append(" = ");
+ out.append(escapeValue(e.value));
+ }
+ }
+ if (e.suffix != null)
+ out.append(' ');
+ }
+ if (e.suffix != null)
+ out.append(e.suffix);
+ out.append('\n');
+ }
+ return out.toString();
+ }
+
+ /**
+ * Clear this configuration and reset to the contents of the parsed string.
+ *
+ * @param text
+ * Git style text file listing configuration properties.
+ * @throws ConfigInvalidException
+ * the text supplied is not formatted correctly. No changes were
+ * made to {@code this}.
+ */
+ public void fromText(final String text) throws ConfigInvalidException {
+ final List<Entry> newEntries = new ArrayList<Entry>();
+ final StringReader in = new StringReader(text);
+ Entry last = null;
+ Entry e = new Entry();
+ for (;;) {
+ int input = in.read();
+ if (-1 == input)
+ break;
+
+ final char c = (char) input;
+ if ('\n' == c) {
+ // End of this entry.
+ newEntries.add(e);
+ if (e.section != null)
+ last = e;
+ e = new Entry();
+
+ } else if (e.suffix != null) {
+ // Everything up until the end-of-line is in the suffix.
+ e.suffix += c;
+
+ } else if (';' == c || '#' == c) {
+ // The rest of this line is a comment; put into suffix.
+ e.suffix = String.valueOf(c);
+
+ } else if (e.section == null && Character.isWhitespace(c)) {
+ // Save the leading whitespace (if any).
+ if (e.prefix == null)
+ e.prefix = "";
+ e.prefix += c;
+
+ } else if ('[' == c) {
+ // This is a section header.
+ e.section = readSectionName(in);
+ input = in.read();
+ if ('"' == input) {
+ e.subsection = readValue(in, true, '"');
+ input = in.read();
+ }
+ if (']' != input)
+ throw new ConfigInvalidException("Bad group header");
+ e.suffix = "";
+
+ } else if (last != null) {
+ // Read a value.
+ e.section = last.section;
+ e.subsection = last.subsection;
+ in.reset();
+ e.name = readKeyName(in);
+ if (e.name.endsWith("\n")) {
+ e.name = e.name.substring(0, e.name.length() - 1);
+ e.value = MAGIC_EMPTY_VALUE;
+ } else
+ e.value = readValue(in, false, -1);
+
+ } else
+ throw new ConfigInvalidException("Invalid line in config file");
+ }
+
+ state.set(newState(newEntries));
+ }
+
+ private State newState() {
+ return new State(Collections.<Entry> emptyList(), getBaseState());
+ }
+
+ private State newState(final List<Entry> entries) {
+ return new State(Collections.unmodifiableList(entries), getBaseState());
+ }
+
+ /**
+ * Clear the configuration file
+ */
+ protected void clear() {
+ state.set(newState());
+ }
+
+ private static String readSectionName(final StringReader in)
+ throws ConfigInvalidException {
+ final StringBuilder name = new StringBuilder();
+ for (;;) {
+ int c = in.read();
+ if (c < 0)
+ throw new ConfigInvalidException("Unexpected end of config file");
+
+ if (']' == c) {
+ in.reset();
+ break;
+ }
+
+ if (' ' == c || '\t' == c) {
+ for (;;) {
+ c = in.read();
+ if (c < 0)
+ throw new ConfigInvalidException("Unexpected end of config file");
+
+ if ('"' == c) {
+ in.reset();
+ break;
+ }
+
+ if (' ' == c || '\t' == c)
+ continue; // Skipped...
+ throw new ConfigInvalidException("Bad section entry: " + name);
+ }
+ break;
+ }
+
+ if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
+ name.append((char) c);
+ else
+ throw new ConfigInvalidException("Bad section entry: " + name);
+ }
+ return name.toString();
+ }
+
+ private static String readKeyName(final StringReader in)
+ throws ConfigInvalidException {
+ final StringBuffer name = new StringBuffer();
+ for (;;) {
+ int c = in.read();
+ if (c < 0)
+ throw new ConfigInvalidException("Unexpected end of config file");
+
+ if ('=' == c)
+ break;
+
+ if (' ' == c || '\t' == c) {
+ for (;;) {
+ c = in.read();
+ if (c < 0)
+ throw new ConfigInvalidException("Unexpected end of config file");
+
+ if ('=' == c)
+ break;
+
+ if (';' == c || '#' == c || '\n' == c) {
+ in.reset();
+ break;
+ }
+
+ if (' ' == c || '\t' == c)
+ continue; // Skipped...
+ throw new ConfigInvalidException("Bad entry delimiter");
+ }
+ break;
+ }
+
+ if (Character.isLetterOrDigit((char) c) || c == '-') {
+ // From the git-config man page:
+ // The variable names are case-insensitive and only
+ // alphanumeric characters and - are allowed.
+ name.append((char) c);
+ } else if ('\n' == c) {
+ in.reset();
+ name.append((char) c);
+ break;
+ } else
+ throw new ConfigInvalidException("Bad entry name: " + name);
+ }
+ return name.toString();
+ }
+
+ private static String readValue(final StringReader in, boolean quote,
+ final int eol) throws ConfigInvalidException {
+ final StringBuffer value = new StringBuffer();
+ boolean space = false;
+ for (;;) {
+ int c = in.read();
+ if (c < 0) {
+ if (value.length() == 0)
+ throw new ConfigInvalidException("Unexpected end of config file");
+ break;
+ }
+
+ if ('\n' == c) {
+ if (quote)
+ throw new ConfigInvalidException("Newline in quotes not allowed");
+ in.reset();
+ break;
+ }
+
+ if (eol == c)
+ break;
+
+ if (!quote) {
+ if (Character.isWhitespace((char) c)) {
+ space = true;
+ continue;
+ }
+ if (';' == c || '#' == c) {
+ in.reset();
+ break;
+ }
+ }
+
+ if (space) {
+ if (value.length() > 0)
+ value.append(' ');
+ space = false;
+ }
+
+ if ('\\' == c) {
+ c = in.read();
+ switch (c) {
+ case -1:
+ throw new ConfigInvalidException("End of file in escape");
+ case '\n':
+ continue;
+ case 't':
+ value.append('\t');
+ continue;
+ case 'b':
+ value.append('\b');
+ continue;
+ case 'n':
+ value.append('\n');
+ continue;
+ case '\\':
+ value.append('\\');
+ continue;
+ case '"':
+ value.append('"');
+ continue;
+ default:
+ throw new ConfigInvalidException("Bad escape: " + ((char) c));
+ }
+ }
+
+ if ('"' == c) {
+ quote = !quote;
+ continue;
+ }
+
+ value.append((char) c);
+ }
+ return value.length() > 0 ? value.toString() : null;
+ }
+
+ /**
+ * Parses a section of the configuration into an application model object.
+ * <p>
+ * Instances must implement hashCode and equals such that model objects can
+ * be cached by using the {@code SectionParser} as a key of a HashMap.
+ * <p>
+ * As the {@code SectionParser} itself is used as the key of the internal
+ * HashMap applications should be careful to ensure the SectionParser key
+ * does not retain unnecessary application state which may cause memory to
+ * be held longer than expected.
+ *
+ * @param <T>
+ * type of the application model created by the parser.
+ */
+ public static interface SectionParser<T> {
+ /**
+ * Create a model object from a configuration.
+ *
+ * @param cfg
+ * the configuration to read values from.
+ * @return the application model instance.
+ */
+ T parse(Config cfg);
+ }
+
+ private static class SubsectionNames implements SectionParser<Set<String>> {
+ private final String section;
+
+ SubsectionNames(final String sectionName) {
+ section = sectionName;
+ }
+
+ public int hashCode() {
+ return section.hashCode();
+ }
+
+ public boolean equals(Object other) {
+ if (other instanceof SubsectionNames) {
+ return section.equals(((SubsectionNames) other).section);
+ }
+ return false;
+ }
+
+ public Set<String> parse(Config cfg) {
+ final Set<String> result = new HashSet<String>();
+ while (cfg != null) {
+ for (final Entry e : cfg.state.get().entryList) {
+ if (e.subsection != null && e.name == null
+ && StringUtils.equalsIgnoreCase(section, e.section))
+ result.add(e.subsection);
+ }
+ cfg = cfg.baseConfig;
+ }
+ return Collections.unmodifiableSet(result);
+ }
+ }
+
+ private static class State {
+ final List<Entry> entryList;
+
+ final Map<Object, Object> cache;
+
+ final State baseState;
+
+ State(List<Entry> entries, State base) {
+ entryList = entries;
+ cache = new ConcurrentHashMap<Object, Object>(16, 0.75f, 1);
+ baseState = base;
+ }
+ }
+
+ /**
+ * The configuration file entry
+ */
+ private static class Entry {
+ /**
+ * The text content before entry
+ */
+ String prefix;
+
+ /**
+ * The section name for the entry
+ */
+ String section;
+
+ /**
+ * Subsection name
+ */
+ String subsection;
+
+ /**
+ * The key name
+ */
+ String name;
+
+ /**
+ * The value
+ */
+ String value;
+
+ /**
+ * The text content after entry
+ */
+ String suffix;
+
+ Entry forValue(final String newValue) {
+ final Entry e = new Entry();
+ e.prefix = prefix;
+ e.section = section;
+ e.subsection = subsection;
+ e.name = name;
+ e.value = newValue;
+ e.suffix = suffix;
+ return e;
+ }
+
+ boolean match(final String aSection, final String aSubsection,
+ final String aKey) {
+ return eqIgnoreCase(section, aSection)
+ && eqSameCase(subsection, aSubsection)
+ && eqIgnoreCase(name, aKey);
+ }
+
+ private static boolean eqIgnoreCase(final String a, final String b) {
+ if (a == null && b == null)
+ return true;
+ if (a == null || b == null)
+ return false;
+ return StringUtils.equalsIgnoreCase(a, b);
+ }
+
+ private static boolean eqSameCase(final String a, final String b) {
+ if (a == null && b == null)
+ return true;
+ if (a == null || b == null)
+ return false;
+ return a.equals(b);
+ }
+ }
+
+ private static class StringReader {
+ private final char[] buf;
+
+ private int pos;
+
+ StringReader(final String in) {
+ buf = in.toCharArray();
+ }
+
+ int read() {
+ try {
+ return buf[pos++];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ pos = buf.length;
+ return -1;
+ }
+ }
+
+ void reset() {
+ pos--;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
new file mode 100644
index 0000000000..403d0dbeed
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.util.MutableInteger;
+
+/** Misc. constants used throughout JGit. */
+public final class Constants {
+ /** Hash function used natively by Git for all objects. */
+ private static final String HASH_FUNCTION = "SHA-1";
+
+ /** Length of an object hash. */
+ public static final int OBJECT_ID_LENGTH = 20;
+
+ /** Special name for the "HEAD" symbolic-ref. */
+ public static final String HEAD = "HEAD";
+
+ /**
+ * Text string that identifies an object as a commit.
+ * <p>
+ * Commits connect trees into a string of project histories, where each
+ * commit is an assertion that the best way to continue is to use this other
+ * tree (set of files).
+ */
+ public static final String TYPE_COMMIT = "commit";
+
+ /**
+ * Text string that identifies an object as a blob.
+ * <p>
+ * Blobs store whole file revisions. They are used for any user file, as
+ * well as for symlinks. Blobs form the bulk of any project's storage space.
+ */
+ public static final String TYPE_BLOB = "blob";
+
+ /**
+ * Text string that identifies an object as a tree.
+ * <p>
+ * Trees attach object ids (hashes) to names and file modes. The normal use
+ * for a tree is to store a version of a directory and its contents.
+ */
+ public static final String TYPE_TREE = "tree";
+
+ /**
+ * Text string that identifies an object as an annotated tag.
+ * <p>
+ * Annotated tags store a pointer to any other object, and an additional
+ * message. It is most commonly used to record a stable release of the
+ * project.
+ */
+ public static final String TYPE_TAG = "tag";
+
+ private static final byte[] ENCODED_TYPE_COMMIT = encodeASCII(TYPE_COMMIT);
+
+ private static final byte[] ENCODED_TYPE_BLOB = encodeASCII(TYPE_BLOB);
+
+ private static final byte[] ENCODED_TYPE_TREE = encodeASCII(TYPE_TREE);
+
+ private static final byte[] ENCODED_TYPE_TAG = encodeASCII(TYPE_TAG);
+
+ /** An unknown or invalid object type code. */
+ public static final int OBJ_BAD = -1;
+
+ /**
+ * In-pack object type: extended types.
+ * <p>
+ * This header code is reserved for future expansion. It is currently
+ * undefined/unsupported.
+ */
+ public static final int OBJ_EXT = 0;
+
+ /**
+ * In-pack object type: commit.
+ * <p>
+ * Indicates the associated object is a commit.
+ * <p>
+ * <b>This constant is fixed and is defined by the Git packfile format.</b>
+ *
+ * @see #TYPE_COMMIT
+ */
+ public static final int OBJ_COMMIT = 1;
+
+ /**
+ * In-pack object type: tree.
+ * <p>
+ * Indicates the associated object is a tree.
+ * <p>
+ * <b>This constant is fixed and is defined by the Git packfile format.</b>
+ *
+ * @see #TYPE_BLOB
+ */
+ public static final int OBJ_TREE = 2;
+
+ /**
+ * In-pack object type: blob.
+ * <p>
+ * Indicates the associated object is a blob.
+ * <p>
+ * <b>This constant is fixed and is defined by the Git packfile format.</b>
+ *
+ * @see #TYPE_BLOB
+ */
+ public static final int OBJ_BLOB = 3;
+
+ /**
+ * In-pack object type: annotated tag.
+ * <p>
+ * Indicates the associated object is an annotated tag.
+ * <p>
+ * <b>This constant is fixed and is defined by the Git packfile format.</b>
+ *
+ * @see #TYPE_TAG
+ */
+ public static final int OBJ_TAG = 4;
+
+ /** In-pack object type: reserved for future use. */
+ public static final int OBJ_TYPE_5 = 5;
+
+ /**
+ * In-pack object type: offset delta
+ * <p>
+ * Objects stored with this type actually have a different type which must
+ * be obtained from their delta base object. Delta objects store only the
+ * changes needed to apply to the base object in order to recover the
+ * original object.
+ * <p>
+ * An offset delta uses a negative offset from the start of this object to
+ * refer to its delta base. The base object must exist in this packfile
+ * (even in the case of a thin pack).
+ * <p>
+ * <b>This constant is fixed and is defined by the Git packfile format.</b>
+ */
+ public static final int OBJ_OFS_DELTA = 6;
+
+ /**
+ * In-pack object type: reference delta
+ * <p>
+ * Objects stored with this type actually have a different type which must
+ * be obtained from their delta base object. Delta objects store only the
+ * changes needed to apply to the base object in order to recover the
+ * original object.
+ * <p>
+ * A reference delta uses a full object id (hash) to reference the delta
+ * base. The base object is allowed to be omitted from the packfile, but
+ * only in the case of a thin pack being transferred over the network.
+ * <p>
+ * <b>This constant is fixed and is defined by the Git packfile format.</b>
+ */
+ public static final int OBJ_REF_DELTA = 7;
+
+ /**
+ * Pack file signature that occurs at file header - identifies file as Git
+ * packfile formatted.
+ * <p>
+ * <b>This constant is fixed and is defined by the Git packfile format.</b>
+ */
+ public static final byte[] PACK_SIGNATURE = { 'P', 'A', 'C', 'K' };
+
+ /** Native character encoding for commit messages, file names... */
+ public static final String CHARACTER_ENCODING = "UTF-8";
+
+ /** Native character encoding for commit messages, file names... */
+ public static final Charset CHARSET;
+
+ /** Default main branch name */
+ public static final String MASTER = "master";
+
+ /** Prefix for branch refs */
+ public static final String R_HEADS = "refs/heads/";
+
+ /** Prefix for remotes refs */
+ public static final String R_REMOTES = "refs/remotes/";
+
+ /** Prefix for tag refs */
+ public static final String R_TAGS = "refs/tags/";
+
+ /** Prefix for any ref */
+ public static final String R_REFS = "refs/";
+
+ /** Logs folder name */
+ public static final String LOGS = "logs";
+
+ /** Info refs folder */
+ public static final String INFO_REFS = "info/refs";
+
+ /** Packed refs file */
+ public static final String PACKED_REFS = "packed-refs";
+
+ /** The environment variable that contains the system user name */
+ public static final String OS_USER_NAME_KEY = "user.name";
+
+ /** The environment variable that contains the author's name */
+ public static final String GIT_AUTHOR_NAME_KEY = "GIT_AUTHOR_NAME";
+
+ /** The environment variable that contains the author's email */
+ public static final String GIT_AUTHOR_EMAIL_KEY = "GIT_AUTHOR_EMAIL";
+
+ /** The environment variable that contains the commiter's name */
+ public static final String GIT_COMMITTER_NAME_KEY = "GIT_COMMITTER_NAME";
+
+ /** The environment variable that contains the commiter's email */
+ public static final String GIT_COMMITTER_EMAIL_KEY = "GIT_COMMITTER_EMAIL";
+
+ /** Default value for the user name if no other information is available */
+ public static final String UNKNOWN_USER_DEFAULT = "unknown-user";
+
+ /** Beginning of the common "Signed-off-by: " commit message line */
+ public static final String SIGNED_OFF_BY_TAG = "Signed-off-by: ";
+
+ /**
+ * Create a new digest function for objects.
+ *
+ * @return a new digest object.
+ * @throws RuntimeException
+ * this Java virtual machine does not support the required hash
+ * function. Very unlikely given that JGit uses a hash function
+ * that is in the Java reference specification.
+ */
+ public static MessageDigest newMessageDigest() {
+ try {
+ return MessageDigest.getInstance(HASH_FUNCTION);
+ } catch (NoSuchAlgorithmException nsae) {
+ throw new RuntimeException("Required hash function "
+ + HASH_FUNCTION + " not available.", nsae);
+ }
+ }
+
+ /**
+ * Convert an OBJ_* type constant to a TYPE_* type constant.
+ *
+ * @param typeCode the type code, from a pack representation.
+ * @return the canonical string name of this type.
+ */
+ public static String typeString(final int typeCode) {
+ switch (typeCode) {
+ case OBJ_COMMIT:
+ return TYPE_COMMIT;
+ case OBJ_TREE:
+ return TYPE_TREE;
+ case OBJ_BLOB:
+ return TYPE_BLOB;
+ case OBJ_TAG:
+ return TYPE_TAG;
+ default:
+ throw new IllegalArgumentException("Bad object type: " + typeCode);
+ }
+ }
+
+ /**
+ * Convert an OBJ_* type constant to an ASCII encoded string constant.
+ * <p>
+ * The ASCII encoded string is often the canonical representation of
+ * the type within a loose object header, or within a tag header.
+ *
+ * @param typeCode the type code, from a pack representation.
+ * @return the canonical ASCII encoded name of this type.
+ */
+ public static byte[] encodedTypeString(final int typeCode) {
+ switch (typeCode) {
+ case OBJ_COMMIT:
+ return ENCODED_TYPE_COMMIT;
+ case OBJ_TREE:
+ return ENCODED_TYPE_TREE;
+ case OBJ_BLOB:
+ return ENCODED_TYPE_BLOB;
+ case OBJ_TAG:
+ return ENCODED_TYPE_TAG;
+ default:
+ throw new IllegalArgumentException("Bad object type: " + typeCode);
+ }
+ }
+
+ /**
+ * Parse an encoded type string into a type constant.
+ *
+ * @param id
+ * object id this type string came from; may be null if that is
+ * not known at the time the parse is occurring.
+ * @param typeString
+ * string version of the type code.
+ * @param endMark
+ * character immediately following the type string. Usually ' '
+ * (space) or '\n' (line feed).
+ * @param offset
+ * position within <code>typeString</code> where the parse
+ * should start. Updated with the new position (just past
+ * <code>endMark</code> when the parse is successful.
+ * @return a type code constant (one of {@link #OBJ_BLOB},
+ * {@link #OBJ_COMMIT}, {@link #OBJ_TAG}, {@link #OBJ_TREE}.
+ * @throws CorruptObjectException
+ * there is no valid type identified by <code>typeString</code>.
+ */
+ public static int decodeTypeString(final AnyObjectId id,
+ final byte[] typeString, final byte endMark,
+ final MutableInteger offset) throws CorruptObjectException {
+ try {
+ int position = offset.value;
+ switch (typeString[position]) {
+ case 'b':
+ if (typeString[position + 1] != 'l'
+ || typeString[position + 2] != 'o'
+ || typeString[position + 3] != 'b'
+ || typeString[position + 4] != endMark)
+ throw new CorruptObjectException(id, "invalid type");
+ offset.value = position + 5;
+ return Constants.OBJ_BLOB;
+
+ case 'c':
+ if (typeString[position + 1] != 'o'
+ || typeString[position + 2] != 'm'
+ || typeString[position + 3] != 'm'
+ || typeString[position + 4] != 'i'
+ || typeString[position + 5] != 't'
+ || typeString[position + 6] != endMark)
+ throw new CorruptObjectException(id, "invalid type");
+ offset.value = position + 7;
+ return Constants.OBJ_COMMIT;
+
+ case 't':
+ switch (typeString[position + 1]) {
+ case 'a':
+ if (typeString[position + 2] != 'g'
+ || typeString[position + 3] != endMark)
+ throw new CorruptObjectException(id, "invalid type");
+ offset.value = position + 4;
+ return Constants.OBJ_TAG;
+
+ case 'r':
+ if (typeString[position + 2] != 'e'
+ || typeString[position + 3] != 'e'
+ || typeString[position + 4] != endMark)
+ throw new CorruptObjectException(id, "invalid type");
+ offset.value = position + 5;
+ return Constants.OBJ_TREE;
+
+ default:
+ throw new CorruptObjectException(id, "invalid type");
+ }
+
+ default:
+ throw new CorruptObjectException(id, "invalid type");
+ }
+ } catch (ArrayIndexOutOfBoundsException bad) {
+ throw new CorruptObjectException(id, "invalid type");
+ }
+ }
+
+ /**
+ * Convert an integer into its decimal representation.
+ *
+ * @param s
+ * the integer to convert.
+ * @return a decimal representation of the input integer. The returned array
+ * is the smallest array that will hold the value.
+ */
+ public static byte[] encodeASCII(final long s) {
+ return encodeASCII(Long.toString(s));
+ }
+
+ /**
+ * Convert a string to US-ASCII encoding.
+ *
+ * @param s
+ * the string to convert. Must not contain any characters over
+ * 127 (outside of 7-bit ASCII).
+ * @return a byte array of the same length as the input string, holding the
+ * same characters, in the same order.
+ * @throws IllegalArgumentException
+ * the input string contains one or more characters outside of
+ * the 7-bit ASCII character space.
+ */
+ public static byte[] encodeASCII(final String s) {
+ final byte[] r = new byte[s.length()];
+ for (int k = r.length - 1; k >= 0; k--) {
+ final char c = s.charAt(k);
+ if (c > 127)
+ throw new IllegalArgumentException("Not ASCII string: " + s);
+ r[k] = (byte) c;
+ }
+ return r;
+ }
+
+ /**
+ * Convert a string to a byte array in the standard character encoding.
+ *
+ * @param str
+ * the string to convert. May contain any Unicode characters.
+ * @return a byte array representing the requested string, encoded using the
+ * default character encoding (UTF-8).
+ * @see #CHARACTER_ENCODING
+ */
+ public static byte[] encode(final String str) {
+ final ByteBuffer bb = Constants.CHARSET.encode(str);
+ final int len = bb.limit();
+ if (bb.hasArray() && bb.arrayOffset() == 0) {
+ final byte[] arr = bb.array();
+ if (arr.length == len)
+ return arr;
+ }
+
+ final byte[] arr = new byte[len];
+ bb.get(arr);
+ return arr;
+ }
+
+ static {
+ if (OBJECT_ID_LENGTH != newMessageDigest().getDigestLength())
+ throw new LinkageError("Incorrect OBJECT_ID_LENGTH.");
+ CHARSET = Charset.forName(CHARACTER_ENCODING);
+ }
+
+ private Constants() {
+ // Hide the default constructor
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
new file mode 100644
index 0000000000..4d4c3a731f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import static java.util.zip.Deflater.DEFAULT_COMPRESSION;
+
+import org.eclipse.jgit.lib.Config.SectionParser;
+
+/**
+ * This class keeps git repository core parameters.
+ */
+public class CoreConfig {
+ /** Key for {@link Config#get(SectionParser)}. */
+ public static final Config.SectionParser<CoreConfig> KEY = new SectionParser<CoreConfig>() {
+ public CoreConfig parse(final Config cfg) {
+ return new CoreConfig(cfg);
+ }
+ };
+
+ private final int compression;
+
+ private final int packIndexVersion;
+
+ private CoreConfig(final Config rc) {
+ compression = rc.getInt("core", "compression", DEFAULT_COMPRESSION);
+ packIndexVersion = rc.getInt("pack", "indexversion", 2);
+ }
+
+ /**
+ * @see ObjectWriter
+ * @return The compression level to use when storing loose objects
+ */
+ public int getCompression() {
+ return compression;
+ }
+
+ /**
+ * @return the preferred pack index file format; 0 for oldest possible.
+ * @see org.eclipse.jgit.transport.IndexPack
+ */
+ public int getPackIndexVersion() {
+ return packIndexVersion;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java
new file mode 100644
index 0000000000..9e1b3da198
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+
+/** Reads a deltified object which uses an offset to find its base. */
+class DeltaOfsPackedObjectLoader extends DeltaPackedObjectLoader {
+ private final long deltaBase;
+
+ DeltaOfsPackedObjectLoader(final PackFile pr,
+ final long dataOffset, final long objectOffset, final int deltaSz,
+ final long base) {
+ super(pr, dataOffset, objectOffset, deltaSz);
+ deltaBase = base;
+ }
+
+ protected PackedObjectLoader getBaseLoader(final WindowCursor curs)
+ throws IOException {
+ return pack.resolveBase(curs, deltaBase);
+ }
+
+ @Override
+ public int getRawType() {
+ return Constants.OBJ_OFS_DELTA;
+ }
+
+ @Override
+ public ObjectId getDeltaBase() throws IOException {
+ final ObjectId id = pack.findObjectForOffset(deltaBase);
+ if (id == null)
+ throw new CorruptObjectException(
+ "Offset-written delta base for object not found in a pack");
+ return id;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java
new file mode 100644
index 0000000000..867aadfb29
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+
+/** Reader for a deltified object stored in a pack file. */
+abstract class DeltaPackedObjectLoader extends PackedObjectLoader {
+ private static final int OBJ_COMMIT = Constants.OBJ_COMMIT;
+
+ private final int deltaSize;
+
+ DeltaPackedObjectLoader(final PackFile pr, final long dataOffset,
+ final long objectOffset, final int deltaSz) {
+ super(pr, dataOffset, objectOffset);
+ objectType = -1;
+ deltaSize = deltaSz;
+ }
+
+ @Override
+ public void materialize(final WindowCursor curs) throws IOException {
+ if (cachedBytes != null) {
+ return;
+ }
+
+ if (objectType != OBJ_COMMIT) {
+ final UnpackedObjectCache.Entry cache = pack.readCache(dataOffset);
+ if (cache != null) {
+ curs.release();
+ objectType = cache.type;
+ objectSize = cache.data.length;
+ cachedBytes = cache.data;
+ return;
+ }
+ }
+
+ try {
+ final PackedObjectLoader baseLoader = getBaseLoader(curs);
+ baseLoader.materialize(curs);
+ cachedBytes = BinaryDelta.apply(baseLoader.getCachedBytes(),
+ pack.decompress(dataOffset, deltaSize, curs));
+ curs.release();
+ objectType = baseLoader.getType();
+ objectSize = cachedBytes.length;
+ if (objectType != OBJ_COMMIT)
+ pack.saveCache(dataOffset, cachedBytes, objectType);
+ } catch (DataFormatException dfe) {
+ final CorruptObjectException coe;
+ coe = new CorruptObjectException("Object at " + dataOffset + " in "
+ + pack.getPackFile() + " has bad zlib stream");
+ coe.initCause(dfe);
+ throw coe;
+ }
+ }
+
+ @Override
+ public long getRawSize() {
+ return deltaSize;
+ }
+
+ /**
+ * @param curs
+ * temporary thread storage during data access.
+ * @return the object loader for the base object
+ * @throws IOException
+ */
+ protected abstract PackedObjectLoader getBaseLoader(WindowCursor curs)
+ throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java
new file mode 100644
index 0000000000..3616c41072
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/** Reads a deltified object which uses an {@link ObjectId} to find its base. */
+class DeltaRefPackedObjectLoader extends DeltaPackedObjectLoader {
+ private final ObjectId deltaBase;
+
+ DeltaRefPackedObjectLoader(final PackFile pr,
+ final long dataOffset, final long objectOffset, final int deltaSz,
+ final ObjectId base) {
+ super(pr, dataOffset, objectOffset, deltaSz);
+ deltaBase = base;
+ }
+
+ protected PackedObjectLoader getBaseLoader(final WindowCursor curs)
+ throws IOException {
+ final PackedObjectLoader or = pack.get(curs, deltaBase);
+ if (or == null)
+ throw new MissingObjectException(deltaBase, "delta base");
+ return or;
+ }
+
+ @Override
+ public int getRawType() {
+ return Constants.OBJ_REF_DELTA;
+ }
+
+ @Override
+ public ObjectId getDeltaBase() throws IOException {
+ return deltaBase;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java
new file mode 100644
index 0000000000..a1843d1189
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * The configuration file that is stored in the file of the file system.
+ */
+public class FileBasedConfig extends Config {
+ private final File configFile;
+
+ /**
+ * Create a configuration with no default fallback.
+ *
+ * @param cfgLocation
+ * the location of the configuration file on the file system
+ */
+ public FileBasedConfig(File cfgLocation) {
+ this(null, cfgLocation);
+ }
+
+ /**
+ * The constructor
+ *
+ * @param base
+ * the base configuration file
+ * @param cfgLocation
+ * the location of the configuration file on the file system
+ */
+ public FileBasedConfig(Config base, File cfgLocation) {
+ super(base);
+ configFile = cfgLocation;
+ }
+
+ /** @return location of the configuration file on disk */
+ public final File getFile() {
+ return configFile;
+ }
+
+ /**
+ * Load the configuration as a Git text style configuration file.
+ * <p>
+ * If the file does not exist, this configuration is cleared, and thus
+ * behaves the same as though the file exists, but is empty.
+ *
+ * @throws IOException
+ * the file could not be read (but does exist).
+ * @throws ConfigInvalidException
+ * the file is not a properly formatted configuration file.
+ */
+ public void load() throws IOException, ConfigInvalidException {
+ try {
+ fromText(RawParseUtils.decode(NB.readFully(getFile())));
+ } catch (FileNotFoundException noFile) {
+ clear();
+ } catch (IOException e) {
+ final IOException e2 = new IOException("Cannot read " + getFile());
+ e2.initCause(e);
+ throw e2;
+ } catch (ConfigInvalidException e) {
+ throw new ConfigInvalidException("Cannot read " + getFile(), e);
+ }
+ }
+
+ /**
+ * Save the configuration as a Git text style configuration file.
+ * <p>
+ * <b>Warning:</b> Although this method uses the traditional Git file
+ * locking approach to protect against concurrent writes of the
+ * configuration file, it does not ensure that the file has not been
+ * modified since the last read, which means updates performed by other
+ * objects accessing the same backing file may be lost.
+ *
+ * @throws IOException
+ * the file could not be written.
+ */
+ public void save() throws IOException {
+ final byte[] out = Constants.encode(toText());
+ final LockFile lf = new LockFile(getFile());
+ if (!lf.lock())
+ throw new IOException("Cannot lock " + getFile());
+ try {
+ lf.write(out);
+ if (!lf.commit())
+ throw new IOException("Cannot commit write to " + getFile());
+ } finally {
+ lf.unlock();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[" + getFile().getPath() + "]";
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
new file mode 100644
index 0000000000..4c3fb6b597
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Constants describing various file modes recognized by GIT.
+ * <p>
+ * GIT uses a subset of the available UNIX file permission bits. The
+ * <code>FileMode</code> class provides access to constants defining the modes
+ * actually used by GIT.
+ * </p>
+ */
+public abstract class FileMode {
+ /**
+ * Mask to apply to a file mode to obtain its type bits.
+ *
+ * @see #TYPE_TREE
+ * @see #TYPE_SYMLINK
+ * @see #TYPE_FILE
+ * @see #TYPE_GITLINK
+ * @see #TYPE_MISSING
+ */
+ public static final int TYPE_MASK = 0170000;
+
+ /** Bit pattern for {@link #TYPE_MASK} matching {@link #TREE}. */
+ public static final int TYPE_TREE = 0040000;
+
+ /** Bit pattern for {@link #TYPE_MASK} matching {@link #SYMLINK}. */
+ public static final int TYPE_SYMLINK = 0120000;
+
+ /** Bit pattern for {@link #TYPE_MASK} matching {@link #REGULAR_FILE}. */
+ public static final int TYPE_FILE = 0100000;
+
+ /** Bit pattern for {@link #TYPE_MASK} matching {@link #GITLINK}. */
+ public static final int TYPE_GITLINK = 0160000;
+
+ /** Bit pattern for {@link #TYPE_MASK} matching {@link #MISSING}. */
+ public static final int TYPE_MISSING = 0000000;
+
+ /** Mode indicating an entry is a {@link Tree}. */
+ @SuppressWarnings("synthetic-access")
+ public static final FileMode TREE = new FileMode(TYPE_TREE,
+ Constants.OBJ_TREE) {
+ public boolean equals(final int modeBits) {
+ return (modeBits & TYPE_MASK) == TYPE_TREE;
+ }
+ };
+
+ /** Mode indicating an entry is a {@link SymlinkTreeEntry}. */
+ @SuppressWarnings("synthetic-access")
+ public static final FileMode SYMLINK = new FileMode(TYPE_SYMLINK,
+ Constants.OBJ_BLOB) {
+ public boolean equals(final int modeBits) {
+ return (modeBits & TYPE_MASK) == TYPE_SYMLINK;
+ }
+ };
+
+ /** Mode indicating an entry is a non-executable {@link FileTreeEntry}. */
+ @SuppressWarnings("synthetic-access")
+ public static final FileMode REGULAR_FILE = new FileMode(0100644,
+ Constants.OBJ_BLOB) {
+ public boolean equals(final int modeBits) {
+ return (modeBits & TYPE_MASK) == TYPE_FILE && (modeBits & 0111) == 0;
+ }
+ };
+
+ /** Mode indicating an entry is an executable {@link FileTreeEntry}. */
+ @SuppressWarnings("synthetic-access")
+ public static final FileMode EXECUTABLE_FILE = new FileMode(0100755,
+ Constants.OBJ_BLOB) {
+ public boolean equals(final int modeBits) {
+ return (modeBits & TYPE_MASK) == TYPE_FILE && (modeBits & 0111) != 0;
+ }
+ };
+
+ /** Mode indicating an entry is a submodule commit in another repository. */
+ @SuppressWarnings("synthetic-access")
+ public static final FileMode GITLINK = new FileMode(TYPE_GITLINK,
+ Constants.OBJ_COMMIT) {
+ public boolean equals(final int modeBits) {
+ return (modeBits & TYPE_MASK) == TYPE_GITLINK;
+ }
+ };
+
+ /** Mode indicating an entry is missing during parallel walks. */
+ @SuppressWarnings("synthetic-access")
+ public static final FileMode MISSING = new FileMode(TYPE_MISSING,
+ Constants.OBJ_BAD) {
+ public boolean equals(final int modeBits) {
+ return modeBits == 0;
+ }
+ };
+
+ /**
+ * Convert a set of mode bits into a FileMode enumerated value.
+ *
+ * @param bits
+ * the mode bits the caller has somehow obtained.
+ * @return the FileMode instance that represents the given bits.
+ */
+ public static final FileMode fromBits(final int bits) {
+ switch (bits & TYPE_MASK) {
+ case TYPE_MISSING:
+ if (bits == 0)
+ return MISSING;
+ break;
+ case TYPE_TREE:
+ return TREE;
+ case TYPE_FILE:
+ if ((bits & 0111) != 0)
+ return EXECUTABLE_FILE;
+ return REGULAR_FILE;
+ case TYPE_SYMLINK:
+ return SYMLINK;
+ case TYPE_GITLINK:
+ return GITLINK;
+ }
+
+ return new FileMode(bits, Constants.OBJ_BAD) {
+ @Override
+ public boolean equals(final int a) {
+ return bits == a;
+ }
+ };
+ }
+
+ private final byte[] octalBytes;
+
+ private final int modeBits;
+
+ private final int objectType;
+
+ private FileMode(int mode, final int expType) {
+ modeBits = mode;
+ objectType = expType;
+ if (mode != 0) {
+ final byte[] tmp = new byte[10];
+ int p = tmp.length;
+
+ while (mode != 0) {
+ tmp[--p] = (byte) ('0' + (mode & 07));
+ mode >>= 3;
+ }
+
+ octalBytes = new byte[tmp.length - p];
+ for (int k = 0; k < octalBytes.length; k++) {
+ octalBytes[k] = tmp[p + k];
+ }
+ } else {
+ octalBytes = new byte[] { '0' };
+ }
+ }
+
+ /**
+ * Test a file mode for equality with this {@link FileMode} object.
+ *
+ * @param modebits
+ * @return true if the mode bits represent the same mode as this object
+ */
+ public abstract boolean equals(final int modebits);
+
+ /**
+ * Copy this mode as a sequence of octal US-ASCII bytes.
+ * <p>
+ * The mode is copied as a sequence of octal digits using the US-ASCII
+ * character encoding. The sequence does not use a leading '0' prefix to
+ * indicate octal notation. This method is suitable for generation of a mode
+ * string within a GIT tree object.
+ * </p>
+ *
+ * @param os
+ * stream to copy the mode to.
+ * @throws IOException
+ * the stream encountered an error during the copy.
+ */
+ public void copyTo(final OutputStream os) throws IOException {
+ os.write(octalBytes);
+ }
+
+ /**
+ * @return the number of bytes written by {@link #copyTo(OutputStream)}.
+ */
+ public int copyToLength() {
+ return octalBytes.length;
+ }
+
+ /**
+ * Get the object type that should appear for this type of mode.
+ * <p>
+ * See the object type constants in {@link Constants}.
+ *
+ * @return one of the well known object type constants.
+ */
+ public int getObjectType() {
+ return objectType;
+ }
+
+ /** Format this mode as an octal string (for debugging only). */
+ public String toString() {
+ return Integer.toOctalString(modeBits);
+ }
+
+ /**
+ * @return The mode bits as an integer.
+ */
+ public int getBits() {
+ return modeBits;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
new file mode 100644
index 0000000000..9ff4deca35
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+/**
+ * A representation of a file (blob) object in a {@link Tree}.
+ */
+public class FileTreeEntry extends TreeEntry {
+ private FileMode mode;
+
+ /**
+ * Constructor for a File (blob) object.
+ *
+ * @param parent
+ * The {@link Tree} holding this object (or null)
+ * @param id
+ * the SHA-1 of the blob (or null for a yet unhashed file)
+ * @param nameUTF8
+ * raw object name in the parent tree
+ * @param execute
+ * true if the executable flag is set
+ */
+ public FileTreeEntry(final Tree parent, final ObjectId id,
+ final byte[] nameUTF8, final boolean execute) {
+ super(parent, id, nameUTF8);
+ setExecutable(execute);
+ }
+
+ public FileMode getMode() {
+ return mode;
+ }
+
+ /**
+ * @return true if this file is executable
+ */
+ public boolean isExecutable() {
+ return getMode().equals(FileMode.EXECUTABLE_FILE);
+ }
+
+ /**
+ * @param execute set/reset the executable flag
+ */
+ public void setExecutable(final boolean execute) {
+ mode = execute ? FileMode.EXECUTABLE_FILE : FileMode.REGULAR_FILE;
+ }
+
+ /**
+ * @return an {@link ObjectLoader} that will return the data
+ * @throws IOException
+ */
+ public ObjectLoader openReader() throws IOException {
+ return getRepository().openBlob(getId());
+ }
+
+ public void accept(final TreeVisitor tv, final int flags)
+ throws IOException {
+ if ((MODIFIED_ONLY & flags) == MODIFIED_ONLY && !isModified()) {
+ return;
+ }
+
+ tv.visitFile(this);
+ }
+
+ public String toString() {
+ final StringBuffer r = new StringBuffer();
+ r.append(ObjectId.toString(getId()));
+ r.append(' ');
+ r.append(isExecutable() ? 'X' : 'F');
+ r.append(' ');
+ r.append(getFullName());
+ return r.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ForceModified.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ForceModified.java
new file mode 100644
index 0000000000..6e341d604b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ForceModified.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2009, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+/**
+ * Visitor for marking all nodes of a tree as modified.
+ */
+public class ForceModified implements TreeVisitor {
+ public void startVisitTree(final Tree t) throws IOException {
+ t.setModified();
+ }
+
+ public void endVisitTree(final Tree t) throws IOException {
+ // Nothing to do.
+ }
+
+ public void visitFile(final FileTreeEntry f) throws IOException {
+ f.setModified();
+ }
+
+ public void visitSymlink(final SymlinkTreeEntry s) throws IOException {
+ // TODO: handle symlinks. Only problem is that JGit is independent of
+ // Eclipse
+ // and Pure Java does not know what to do about symbolic links.
+ }
+
+ public void visitGitlink(GitlinkTreeEntry s) throws IOException {
+ // TODO: handle gitlinks.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java
new file mode 100644
index 0000000000..d62c9df045
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java
@@ -0,0 +1,976 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
+ * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.security.MessageDigest;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Stack;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * A representation of the Git index.
+ *
+ * The index points to the objects currently checked out or in the process of
+ * being prepared for committing or objects involved in an unfinished merge.
+ *
+ * The abstract format is:<br/> path stage flags statdata SHA-1
+ * <ul>
+ * <li>Path is the relative path in the workdir</li>
+ * <li>stage is 0 (normally), but when
+ * merging 1 is the common ancestor version, 2 is 'our' version and 3 is 'their'
+ * version. A fully resolved merge only contains stage 0.</li>
+ * <li>flags is the object type and information of validity</li>
+ * <li>statdata is the size of this object and some other file system specifics,
+ * some of it ignored by JGit</li>
+ * <li>SHA-1 represents the content of the references object</li>
+ * </ul>
+ *
+ * An index can also contain a tree cache which we ignore for now. We drop the
+ * tree cache when writing the index.
+ *
+ * @deprecated Use {@link DirCache} instead.
+ */
+public class GitIndex {
+
+ /** Stage 0 represents merged entries. */
+ public static final int STAGE_0 = 0;
+
+ private RandomAccessFile cache;
+
+ private File cacheFile;
+
+ // Index is modified
+ private boolean changed;
+
+ // Stat information updated
+ private boolean statDirty;
+
+ private Header header;
+
+ private long lastCacheTime;
+
+ private final Repository db;
+
+ private Map<byte[], Entry> entries = new TreeMap<byte[], Entry>(new Comparator<byte[]>() {
+ public int compare(byte[] o1, byte[] o2) {
+ for (int i = 0; i < o1.length && i < o2.length; ++i) {
+ int c = (o1[i] & 0xff) - (o2[i] & 0xff);
+ if (c != 0)
+ return c;
+ }
+ if (o1.length < o2.length)
+ return -1;
+ else if (o1.length > o2.length)
+ return 1;
+ return 0;
+ }
+ });
+
+ /**
+ * Construct a Git index representation.
+ * @param db
+ */
+ public GitIndex(Repository db) {
+ this.db = db;
+ this.cacheFile = new File(db.getDirectory(), "index");
+ }
+
+ /**
+ * @return true if we have modified the index in memory since reading it from disk
+ */
+ public boolean isChanged() {
+ return changed || statDirty;
+ }
+
+ /**
+ * Reread index data from disk if the index file has been changed
+ * @throws IOException
+ */
+ public void rereadIfNecessary() throws IOException {
+ if (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) {
+ read();
+ db.fireIndexChanged();
+ }
+ }
+
+ /**
+ * Add the content of a file to the index.
+ *
+ * @param wd workdir
+ * @param f the file
+ * @return a new or updated index entry for the path represented by f
+ * @throws IOException
+ */
+ public Entry add(File wd, File f) throws IOException {
+ byte[] key = makeKey(wd, f);
+ Entry e = entries.get(key);
+ if (e == null) {
+ e = new Entry(key, f, 0);
+ entries.put(key, e);
+ } else {
+ e.update(f);
+ }
+ return e;
+ }
+
+ /**
+ * Add the content of a file to the index.
+ *
+ * @param wd
+ * workdir
+ * @param f
+ * the file
+ * @param content
+ * content of the file
+ * @return a new or updated index entry for the path represented by f
+ * @throws IOException
+ */
+ public Entry add(File wd, File f, byte[] content) throws IOException {
+ byte[] key = makeKey(wd, f);
+ Entry e = entries.get(key);
+ if (e == null) {
+ e = new Entry(key, f, 0, content);
+ entries.put(key, e);
+ } else {
+ e.update(f, content);
+ }
+ return e;
+ }
+
+ /**
+ * Remove a path from the index.
+ *
+ * @param wd
+ * workdir
+ * @param f
+ * the file whose path shall be removed.
+ * @return true if such a path was found (and thus removed)
+ * @throws IOException
+ */
+ public boolean remove(File wd, File f) throws IOException {
+ byte[] key = makeKey(wd, f);
+ return entries.remove(key) != null;
+ }
+
+ /**
+ * Read the cache file into memory.
+ *
+ * @throws IOException
+ */
+ public void read() throws IOException {
+ changed = false;
+ statDirty = false;
+ if (!cacheFile.exists()) {
+ header = null;
+ entries.clear();
+ lastCacheTime = 0;
+ return;
+ }
+ cache = new RandomAccessFile(cacheFile, "r");
+ try {
+ FileChannel channel = cache.getChannel();
+ ByteBuffer buffer = ByteBuffer.allocateDirect((int) cacheFile.length());
+ buffer.order(ByteOrder.BIG_ENDIAN);
+ int j = channel.read(buffer);
+ if (j != buffer.capacity())
+ throw new IOException("Could not read index in one go, only "+j+" out of "+buffer.capacity()+" read");
+ buffer.flip();
+ header = new Header(buffer);
+ entries.clear();
+ for (int i = 0; i < header.entries; ++i) {
+ Entry entry = new Entry(buffer);
+ entries.put(entry.name, entry);
+ }
+ lastCacheTime = cacheFile.lastModified();
+ } finally {
+ cache.close();
+ }
+ }
+
+ /**
+ * Write content of index to disk.
+ *
+ * @throws IOException
+ */
+ public void write() throws IOException {
+ checkWriteOk();
+ File tmpIndex = new File(cacheFile.getAbsoluteFile() + ".tmp");
+ File lock = new File(cacheFile.getAbsoluteFile() + ".lock");
+ if (!lock.createNewFile())
+ throw new IOException("Index file is in use");
+ try {
+ FileOutputStream fileOutputStream = new FileOutputStream(tmpIndex);
+ FileChannel fc = fileOutputStream.getChannel();
+ ByteBuffer buf = ByteBuffer.allocate(4096);
+ MessageDigest newMessageDigest = Constants.newMessageDigest();
+ header = new Header(entries);
+ header.write(buf);
+ buf.flip();
+ newMessageDigest
+ .update(buf.array(), buf.arrayOffset(), buf.limit());
+ fc.write(buf);
+ buf.flip();
+ buf.clear();
+ for (Iterator i = entries.values().iterator(); i.hasNext();) {
+ Entry e = (Entry) i.next();
+ e.write(buf);
+ buf.flip();
+ newMessageDigest.update(buf.array(), buf.arrayOffset(), buf
+ .limit());
+ fc.write(buf);
+ buf.flip();
+ buf.clear();
+ }
+ buf.put(newMessageDigest.digest());
+ buf.flip();
+ fc.write(buf);
+ fc.close();
+ fileOutputStream.close();
+ if (cacheFile.exists())
+ if (!cacheFile.delete())
+ throw new IOException(
+ "Could not rename delete old index");
+ if (!tmpIndex.renameTo(cacheFile))
+ throw new IOException(
+ "Could not rename temporary index file to index");
+ changed = false;
+ statDirty = false;
+ lastCacheTime = cacheFile.lastModified();
+ db.fireIndexChanged();
+ } finally {
+ if (!lock.delete())
+ throw new IOException(
+ "Could not delete lock file. Should not happen");
+ if (tmpIndex.exists() && !tmpIndex.delete())
+ throw new IOException(
+ "Could not delete temporary index file. Should not happen");
+ }
+ }
+
+ private void checkWriteOk() throws IOException {
+ for (Iterator i = entries.values().iterator(); i.hasNext();) {
+ Entry e = (Entry) i.next();
+ if (e.getStage() != 0) {
+ throw new NotSupportedException("Cannot work with other stages than zero right now. Won't write corrupt index.");
+ }
+ }
+ }
+
+ static boolean File_canExecute( File f){
+ return FS.INSTANCE.canExecute(f);
+ }
+
+ static boolean File_setExecute(File f, boolean value) {
+ return FS.INSTANCE.setExecute(f, value);
+ }
+
+ static boolean File_hasExecute() {
+ return FS.INSTANCE.supportsExecute();
+ }
+
+ static byte[] makeKey(File wd, File f) {
+ if (!f.getPath().startsWith(wd.getPath()))
+ throw new Error("Path is not in working dir");
+ String relName = Repository.stripWorkDir(wd, f);
+ return Constants.encode(relName);
+ }
+
+ Boolean filemode;
+ private boolean config_filemode() {
+ // temporary til we can actually set parameters. We need to be able
+ // to change this for testing.
+ if (filemode != null)
+ return filemode.booleanValue();
+ RepositoryConfig config = db.getConfig();
+ return config.getBoolean("core", null, "filemode", true);
+ }
+
+ /** An index entry */
+ public class Entry {
+ private long ctime;
+
+ private long mtime;
+
+ private int dev;
+
+ private int ino;
+
+ private int mode;
+
+ private int uid;
+
+ private int gid;
+
+ private int size;
+
+ private ObjectId sha1;
+
+ private short flags;
+
+ private byte[] name;
+
+ Entry(byte[] key, File f, int stage)
+ throws IOException {
+ ctime = f.lastModified() * 1000000L;
+ mtime = ctime; // we use same here
+ dev = -1;
+ ino = -1;
+ if (config_filemode() && File_canExecute(f))
+ mode = FileMode.EXECUTABLE_FILE.getBits();
+ else
+ mode = FileMode.REGULAR_FILE.getBits();
+ uid = -1;
+ gid = -1;
+ size = (int) f.length();
+ ObjectWriter writer = new ObjectWriter(db);
+ sha1 = writer.writeBlob(f);
+ name = key;
+ flags = (short) ((stage << 12) | name.length); // TODO: fix flags
+ }
+
+ Entry(byte[] key, File f, int stage, byte[] newContent)
+ throws IOException {
+ ctime = f.lastModified() * 1000000L;
+ mtime = ctime; // we use same here
+ dev = -1;
+ ino = -1;
+ if (config_filemode() && File_canExecute(f))
+ mode = FileMode.EXECUTABLE_FILE.getBits();
+ else
+ mode = FileMode.REGULAR_FILE.getBits();
+ uid = -1;
+ gid = -1;
+ size = newContent.length;
+ ObjectWriter writer = new ObjectWriter(db);
+ sha1 = writer.writeBlob(newContent);
+ name = key;
+ flags = (short) ((stage << 12) | name.length); // TODO: fix flags
+ }
+
+ Entry(TreeEntry f, int stage) {
+ ctime = -1; // hmm
+ mtime = -1;
+ dev = -1;
+ ino = -1;
+ mode = f.getMode().getBits();
+ uid = -1;
+ gid = -1;
+ try {
+ size = (int) db.openBlob(f.getId()).getSize();
+ } catch (IOException e) {
+ e.printStackTrace();
+ size = -1;
+ }
+ sha1 = f.getId();
+ name = Constants.encode(f.getFullName());
+ flags = (short) ((stage << 12) | name.length); // TODO: fix flags
+ }
+
+ Entry(ByteBuffer b) {
+ int startposition = b.position();
+ ctime = b.getInt() * 1000000000L + (b.getInt() % 1000000000L);
+ mtime = b.getInt() * 1000000000L + (b.getInt() % 1000000000L);
+ dev = b.getInt();
+ ino = b.getInt();
+ mode = b.getInt();
+ uid = b.getInt();
+ gid = b.getInt();
+ size = b.getInt();
+ byte[] sha1bytes = new byte[Constants.OBJECT_ID_LENGTH];
+ b.get(sha1bytes);
+ sha1 = ObjectId.fromRaw(sha1bytes);
+ flags = b.getShort();
+ name = new byte[flags & 0xFFF];
+ b.get(name);
+ b
+ .position(startposition
+ + ((8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 20 + 2
+ + name.length + 8) & ~7));
+ }
+
+ /**
+ * Update this index entry with stat and SHA-1 information if it looks
+ * like the file has been modified in the workdir.
+ *
+ * @param f
+ * file in work dir
+ * @return true if a change occurred
+ * @throws IOException
+ */
+ public boolean update(File f) throws IOException {
+ long lm = f.lastModified() * 1000000L;
+ boolean modified = mtime != lm;
+ mtime = lm;
+ if (size != f.length())
+ modified = true;
+ if (config_filemode()) {
+ if (File_canExecute(f) != FileMode.EXECUTABLE_FILE.equals(mode)) {
+ mode = FileMode.EXECUTABLE_FILE.getBits();
+ modified = true;
+ }
+ }
+ if (modified) {
+ size = (int) f.length();
+ ObjectWriter writer = new ObjectWriter(db);
+ ObjectId newsha1 = writer.writeBlob(f);
+ if (!newsha1.equals(sha1))
+ modified = true;
+ sha1 = newsha1;
+ }
+ return modified;
+ }
+
+ /**
+ * Update this index entry with stat and SHA-1 information if it looks
+ * like the file has been modified in the workdir.
+ *
+ * @param f
+ * file in work dir
+ * @param newContent
+ * the new content of the file
+ * @return true if a change occurred
+ * @throws IOException
+ */
+ public boolean update(File f, byte[] newContent) throws IOException {
+ boolean modified = false;
+ size = newContent.length;
+ ObjectWriter writer = new ObjectWriter(db);
+ ObjectId newsha1 = writer.writeBlob(newContent);
+ if (!newsha1.equals(sha1))
+ modified = true;
+ sha1 = newsha1;
+ return modified;
+ }
+
+ void write(ByteBuffer buf) {
+ int startposition = buf.position();
+ buf.putInt((int) (ctime / 1000000000L));
+ buf.putInt((int) (ctime % 1000000000L));
+ buf.putInt((int) (mtime / 1000000000L));
+ buf.putInt((int) (mtime % 1000000000L));
+ buf.putInt(dev);
+ buf.putInt(ino);
+ buf.putInt(mode);
+ buf.putInt(uid);
+ buf.putInt(gid);
+ buf.putInt(size);
+ sha1.copyRawTo(buf);
+ buf.putShort(flags);
+ buf.put(name);
+ int end = startposition
+ + ((8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 20 + 2 + name.length + 8) & ~7);
+ int remain = end - buf.position();
+ while (remain-- > 0)
+ buf.put((byte) 0);
+ }
+
+ /**
+ * Check if an entry's content is different from the cache,
+ *
+ * File status information is used and status is same we
+ * consider the file identical to the state in the working
+ * directory. Native git uses more stat fields than we
+ * have accessible in Java.
+ *
+ * @param wd working directory to compare content with
+ * @return true if content is most likely different.
+ */
+ public boolean isModified(File wd) {
+ return isModified(wd, false);
+ }
+
+ /**
+ * Check if an entry's content is different from the cache,
+ *
+ * File status information is used and status is same we
+ * consider the file identical to the state in the working
+ * directory. Native git uses more stat fields than we
+ * have accessible in Java.
+ *
+ * @param wd working directory to compare content with
+ * @param forceContentCheck True if the actual file content
+ * should be checked if modification time differs.
+ *
+ * @return true if content is most likely different.
+ */
+ public boolean isModified(File wd, boolean forceContentCheck) {
+
+ if (isAssumedValid())
+ return false;
+
+ if (isUpdateNeeded())
+ return true;
+
+ File file = getFile(wd);
+ if (!file.exists())
+ return true;
+
+ // JDK1.6 has file.canExecute
+ // if (file.canExecute() != FileMode.EXECUTABLE_FILE.equals(mode))
+ // return true;
+ final int exebits = FileMode.EXECUTABLE_FILE.getBits()
+ ^ FileMode.REGULAR_FILE.getBits();
+
+ if (config_filemode() && FileMode.EXECUTABLE_FILE.equals(mode)) {
+ if (!File_canExecute(file)&& File_hasExecute())
+ return true;
+ } else {
+ if (FileMode.REGULAR_FILE.equals(mode&~exebits)) {
+ if (!file.isFile())
+ return true;
+ if (config_filemode() && File_canExecute(file) && File_hasExecute())
+ return true;
+ } else {
+ if (FileMode.SYMLINK.equals(mode)) {
+ return true;
+ } else {
+ if (FileMode.TREE.equals(mode)) {
+ if (!file.isDirectory())
+ return true;
+ } else {
+ System.out.println("Does not handle mode "+mode+" ("+file+")");
+ return true;
+ }
+ }
+ }
+ }
+
+ if (file.length() != size)
+ return true;
+
+ // Git under windows only stores seconds so we round the timestamp
+ // Java gives us if it looks like the timestamp in index is seconds
+ // only. Otherwise we compare the timestamp at millisecond prevision.
+ long javamtime = mtime / 1000000L;
+ long lastm = file.lastModified();
+ if (javamtime % 1000 == 0)
+ lastm = lastm - lastm % 1000;
+ if (lastm != javamtime) {
+ if (!forceContentCheck)
+ return true;
+
+ try {
+ InputStream is = new FileInputStream(file);
+ try {
+ ObjectWriter objectWriter = new ObjectWriter(db);
+ ObjectId newId = objectWriter.computeBlobSha1(file
+ .length(), is);
+ boolean ret = !newId.equals(sha1);
+ return ret;
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // can't happen, but if it does we ignore it
+ e.printStackTrace();
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // should not happen because we already checked this
+ e.printStackTrace();
+ throw new Error(e);
+ }
+ }
+ return false;
+ }
+
+ // for testing
+ void forceRecheck() {
+ mtime = -1;
+ }
+
+ private File getFile(File wd) {
+ return new File(wd, getName());
+ }
+
+ public String toString() {
+ return getName() + "/SHA-1(" + sha1.name() + ")/M:"
+ + new Date(ctime / 1000000L) + "/C:"
+ + new Date(mtime / 1000000L) + "/d" + dev + "/i" + ino
+ + "/m" + Integer.toString(mode, 8) + "/u" + uid + "/g"
+ + gid + "/s" + size + "/f" + flags + "/@" + getStage();
+ }
+
+ /**
+ * @return path name for this entry
+ */
+ public String getName() {
+ return RawParseUtils.decode(name);
+ }
+
+ /**
+ * @return path name for this entry as byte array, hopefully UTF-8 encoded
+ */
+ public byte[] getNameUTF8() {
+ return name;
+ }
+
+ /**
+ * @return SHA-1 of the entry managed by this index
+ */
+ public ObjectId getObjectId() {
+ return sha1;
+ }
+
+ /**
+ * @return the stage this entry is in
+ */
+ public int getStage() {
+ return (flags & 0x3000) >> 12;
+ }
+
+ /**
+ * @return size of disk object
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * @return true if this entry shall be assumed valid
+ */
+ public boolean isAssumedValid() {
+ return (flags & 0x8000) != 0;
+ }
+
+ /**
+ * @return true if this entry should be checked for changes
+ */
+ public boolean isUpdateNeeded() {
+ return (flags & 0x4000) != 0;
+ }
+
+ /**
+ * Set whether to always assume this entry valid
+ *
+ * @param assumeValid true to ignore changes
+ */
+ public void setAssumeValid(boolean assumeValid) {
+ if (assumeValid)
+ flags |= 0x8000;
+ else
+ flags &= ~0x8000;
+ }
+
+ /**
+ * Set whether this entry must be checked
+ *
+ * @param updateNeeded
+ */
+ public void setUpdateNeeded(boolean updateNeeded) {
+ if (updateNeeded)
+ flags |= 0x4000;
+ else
+ flags &= ~0x4000;
+ }
+
+ /**
+ * Return raw file mode bits. See {@link FileMode}
+ * @return file mode bits
+ */
+ public int getModeBits() {
+ return mode;
+ }
+ }
+
+ static class Header {
+ private int signature;
+
+ private int version;
+
+ int entries;
+
+ Header(ByteBuffer map) throws CorruptObjectException {
+ read(map);
+ }
+
+ private void read(ByteBuffer buf) throws CorruptObjectException {
+ signature = buf.getInt();
+ version = buf.getInt();
+ entries = buf.getInt();
+ if (signature != 0x44495243)
+ throw new CorruptObjectException("Index signature is invalid: "
+ + signature);
+ if (version != 2)
+ throw new CorruptObjectException(
+ "Unknown index version (or corrupt index):" + version);
+ }
+
+ void write(ByteBuffer buf) {
+ buf.order(ByteOrder.BIG_ENDIAN);
+ buf.putInt(signature);
+ buf.putInt(version);
+ buf.putInt(entries);
+ }
+
+ Header(Map entryset) {
+ signature = 0x44495243;
+ version = 2;
+ entries = entryset.size();
+ }
+ }
+
+ /**
+ * Read a Tree recursively into the index
+ *
+ * @param t The tree to read
+ *
+ * @throws IOException
+ */
+ public void readTree(Tree t) throws IOException {
+ entries.clear();
+ readTree("", t);
+ }
+
+ void readTree(String prefix, Tree t) throws IOException {
+ TreeEntry[] members = t.members();
+ for (int i = 0; i < members.length; ++i) {
+ TreeEntry te = members[i];
+ String name;
+ if (prefix.length() > 0)
+ name = prefix + "/" + te.getName();
+ else
+ name = te.getName();
+ if (te instanceof Tree) {
+ readTree(name, (Tree) te);
+ } else {
+ Entry e = new Entry(te, 0);
+ entries.put(Constants.encode(name), e);
+ }
+ }
+ }
+
+ /**
+ * Add tree entry to index
+ * @param te tree entry
+ * @return new or modified index entry
+ * @throws IOException
+ */
+ public Entry addEntry(TreeEntry te) throws IOException {
+ byte[] key = Constants.encode(te.getFullName());
+ Entry e = new Entry(te, 0);
+ entries.put(key, e);
+ return e;
+ }
+
+ /**
+ * Check out content of the content represented by the index
+ *
+ * @param wd
+ * workdir
+ * @throws IOException
+ */
+ public void checkout(File wd) throws IOException {
+ for (Entry e : entries.values()) {
+ if (e.getStage() != 0)
+ continue;
+ checkoutEntry(wd, e);
+ }
+ }
+
+ /**
+ * Check out content of the specified index entry
+ *
+ * @param wd workdir
+ * @param e index entry
+ * @throws IOException
+ */
+ public void checkoutEntry(File wd, Entry e) throws IOException {
+ ObjectLoader ol = db.openBlob(e.sha1);
+ byte[] bytes = ol.getBytes();
+ File file = new File(wd, e.getName());
+ file.delete();
+ file.getParentFile().mkdirs();
+ FileChannel channel = new FileOutputStream(file).getChannel();
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ int j = channel.write(buffer);
+ if (j != bytes.length)
+ throw new IOException("Could not write file " + file);
+ channel.close();
+ if (config_filemode() && File_hasExecute()) {
+ if (FileMode.EXECUTABLE_FILE.equals(e.mode)) {
+ if (!File_canExecute(file))
+ File_setExecute(file, true);
+ } else {
+ if (File_canExecute(file))
+ File_setExecute(file, false);
+ }
+ }
+ e.mtime = file.lastModified() * 1000000L;
+ e.ctime = e.mtime;
+ }
+
+ /**
+ * Construct and write tree out of index.
+ *
+ * @return SHA-1 of the constructed tree
+ *
+ * @throws IOException
+ */
+ public ObjectId writeTree() throws IOException {
+ checkWriteOk();
+ ObjectWriter writer = new ObjectWriter(db);
+ Tree current = new Tree(db);
+ Stack<Tree> trees = new Stack<Tree>();
+ trees.push(current);
+ String[] prevName = new String[0];
+ for (Entry e : entries.values()) {
+ if (e.getStage() != 0)
+ continue;
+ String[] newName = splitDirPath(e.getName());
+ int c = longestCommonPath(prevName, newName);
+ while (c < trees.size() - 1) {
+ current.setId(writer.writeTree(current));
+ trees.pop();
+ current = trees.isEmpty() ? null : (Tree) trees.peek();
+ }
+ while (trees.size() < newName.length) {
+ if (!current.existsTree(newName[trees.size() - 1])) {
+ current = new Tree(current, Constants.encode(newName[trees.size() - 1]));
+ current.getParent().addEntry(current);
+ trees.push(current);
+ } else {
+ current = (Tree) current.findTreeMember(newName[trees
+ .size() - 1]);
+ trees.push(current);
+ }
+ }
+ FileTreeEntry ne = new FileTreeEntry(current, e.sha1,
+ Constants.encode(newName[newName.length - 1]),
+ (e.mode & FileMode.EXECUTABLE_FILE.getBits()) == FileMode.EXECUTABLE_FILE.getBits());
+ current.addEntry(ne);
+ }
+ while (!trees.isEmpty()) {
+ current.setId(writer.writeTree(current));
+ trees.pop();
+ if (!trees.isEmpty())
+ current = trees.peek();
+ }
+ return current.getTreeId();
+ }
+
+ String[] splitDirPath(String name) {
+ String[] tmp = new String[name.length() / 2 + 1];
+ int p0 = -1;
+ int p1;
+ int c = 0;
+ while ((p1 = name.indexOf('/', p0 + 1)) != -1) {
+ tmp[c++] = name.substring(p0 + 1, p1);
+ p0 = p1;
+ }
+ tmp[c++] = name.substring(p0 + 1);
+ String[] ret = new String[c];
+ for (int i = 0; i < c; ++i) {
+ ret[i] = tmp[i];
+ }
+ return ret;
+ }
+
+ int longestCommonPath(String[] a, String[] b) {
+ int i;
+ for (i = 0; i < a.length && i < b.length; ++i)
+ if (!a[i].equals(b[i]))
+ return i;
+ return i;
+ }
+
+ /**
+ * Return the members of the index sorted by the unsigned byte
+ * values of the path names.
+ *
+ * Small beware: Unaccounted for are unmerged entries. You may want
+ * to abort if members with stage != 0 are found if you are doing
+ * any updating operations. All stages will be found after one another
+ * here later. Currently only one stage per name is returned.
+ *
+ * @return The index entries sorted
+ */
+ public Entry[] getMembers() {
+ return entries.values().toArray(new Entry[entries.size()]);
+ }
+
+ /**
+ * Look up an entry with the specified path.
+ *
+ * @param path
+ * @return index entry for the path or null if not in index.
+ * @throws UnsupportedEncodingException
+ */
+ public Entry getEntry(String path) throws UnsupportedEncodingException {
+ return entries.get(Repository.gitInternalSlash(Constants.encode(path)));
+ }
+
+ /**
+ * @return The repository holding this index.
+ */
+ public Repository getRepository() {
+ return db;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java
new file mode 100644
index 0000000000..a000759128
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2009, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+/**
+ * A tree entry representing a gitlink entry used for submodules.
+ *
+ * Note. Java cannot really handle these as file system objects.
+ */
+public class GitlinkTreeEntry extends TreeEntry {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a {@link GitlinkTreeEntry} with the specified name and SHA-1 in
+ * the specified parent
+ *
+ * @param parent
+ * @param id
+ * @param nameUTF8
+ */
+ public GitlinkTreeEntry(final Tree parent, final ObjectId id,
+ final byte[] nameUTF8) {
+ super(parent, id, nameUTF8);
+ }
+
+ public FileMode getMode() {
+ return FileMode.GITLINK;
+ }
+
+ public void accept(final TreeVisitor tv, final int flags)
+ throws IOException {
+ if ((MODIFIED_ONLY & flags) == MODIFIED_ONLY && !isModified()) {
+ return;
+ }
+
+ tv.visitGitlink(this);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer r = new StringBuffer();
+ r.append(ObjectId.toString(getId()));
+ r.append(" G ");
+ r.append(getFullName());
+ return r.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java
new file mode 100644
index 0000000000..3c41e92c40
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/**
+ * This class passes information about a changed Git index to a
+ * {@link RepositoryListener}
+ *
+ * Currently only a reference to the repository is passed.
+ */
+public class IndexChangedEvent extends RepositoryChangedEvent {
+ IndexChangedEvent(final Repository repository) {
+ super(repository);
+ }
+
+ @Override
+ public String toString() {
+ return "IndexChangedEvent[" + getRepository() + "]";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
new file mode 100644
index 0000000000..bbcd328b65
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+
+import org.eclipse.jgit.lib.GitIndex.Entry;
+
+/**
+ * Compares the Index, a Tree, and the working directory
+ */
+public class IndexDiff {
+ private GitIndex index;
+ private Tree tree;
+
+ /**
+ * Construct an indexdiff for diffing the workdir against
+ * the index.
+ *
+ * @param repository
+ * @throws IOException
+ */
+ public IndexDiff(Repository repository) throws IOException {
+ this.tree = repository.mapTree(Constants.HEAD);
+ this.index = repository.getIndex();
+ }
+
+ /**
+ * Construct an indexdiff for diffing the workdir against both
+ * the index and a tree.
+ *
+ * @param tree
+ * @param index
+ */
+ public IndexDiff(Tree tree, GitIndex index) {
+ this.tree = tree;
+ this.index = index;
+ }
+
+ boolean anyChanges = false;
+
+ /**
+ * Run the diff operation. Until this is called, all lists will be empty
+ * @return if anything is different between index, tree, and workdir
+ * @throws IOException
+ */
+ public boolean diff() throws IOException {
+ final File root = index.getRepository().getWorkDir();
+ new IndexTreeWalker(index, tree, root, new AbstractIndexTreeVisitor() {
+ public void visitEntry(TreeEntry treeEntry, Entry indexEntry, File file) {
+ if (treeEntry == null) {
+ added.add(indexEntry.getName());
+ anyChanges = true;
+ } else if (indexEntry == null) {
+ if (!(treeEntry instanceof Tree))
+ removed.add(treeEntry.getFullName());
+ anyChanges = true;
+ } else {
+ if (!treeEntry.getId().equals(indexEntry.getObjectId())) {
+ changed.add(indexEntry.getName());
+ anyChanges = true;
+ }
+ }
+
+ if (indexEntry != null) {
+ if (!file.exists()) {
+ missing.add(indexEntry.getName());
+ anyChanges = true;
+ } else {
+ if (indexEntry.isModified(root, true)) {
+ modified.add(indexEntry.getName());
+ anyChanges = true;
+ }
+ }
+ }
+ }
+ }).walk();
+
+ return anyChanges;
+ }
+
+ HashSet<String> added = new HashSet<String>();
+ HashSet<String> changed = new HashSet<String>();
+ HashSet<String> removed = new HashSet<String>();
+ HashSet<String> missing = new HashSet<String>();
+ HashSet<String> modified = new HashSet<String>();
+
+ /**
+ * @return list of files added to the index, not in the tree
+ */
+ public HashSet<String> getAdded() {
+ return added;
+ }
+
+ /**
+ * @return list of files changed from tree to index
+ */
+ public HashSet<String> getChanged() {
+ return changed;
+ }
+
+ /**
+ * @return list of files removed from index, but in tree
+ */
+ public HashSet<String> getRemoved() {
+ return removed;
+ }
+
+ /**
+ * @return list of files in index, but not filesystem
+ */
+ public HashSet<String> getMissing() {
+ return missing;
+ }
+
+ /**
+ * @return list of files on modified on disk relative to the index
+ */
+ public HashSet<String> getModified() {
+ return modified;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeVisitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeVisitor.java
new file mode 100644
index 0000000000..0835b0e52b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeVisitor.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.GitIndex.Entry;
+
+/**
+ * Visitor interface for traversing the index and two trees in parallel.
+ *
+ * When merging we deal with up to two tree nodes and a base node. Then
+ * we figure out what to do.
+ *
+ * A File argument is supplied to allow us to check for modifications in
+ * a work tree or update the file.
+ */
+public interface IndexTreeVisitor {
+ /**
+ * Visit a blob, and corresponding tree and index entries.
+ *
+ * @param treeEntry
+ * @param indexEntry
+ * @param file
+ * @throws IOException
+ */
+ public void visitEntry(TreeEntry treeEntry, Entry indexEntry, File file) throws IOException;
+
+ /**
+ * Visit a blob, and corresponding tree nodes and associated index entry.
+ *
+ * @param treeEntry
+ * @param auxEntry
+ * @param indexEntry
+ * @param file
+ * @throws IOException
+ */
+ public void visitEntry(TreeEntry treeEntry, TreeEntry auxEntry, Entry indexEntry, File file) throws IOException;
+
+ /**
+ * Invoked after handling all child nodes of a tree, during a three way merge
+ *
+ * @param tree
+ * @param auxTree
+ * @param curDir
+ * @throws IOException
+ */
+ public void finishVisitTree(Tree tree, Tree auxTree, String curDir) throws IOException;
+
+ /**
+ * Invoked after handling all child nodes of a tree, during two way merge.
+ *
+ * @param tree
+ * @param i
+ * @param curDir
+ * @throws IOException
+ */
+ public void finishVisitTree(Tree tree, int i, String curDir) throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeWalker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeWalker.java
new file mode 100644
index 0000000000..22d9584344
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeWalker.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.GitIndex.Entry;
+
+/**
+ * A class for traversing the index and one or two trees.
+ *
+ * A visitor is invoked for executing actions, like figuring out how to merge.
+ */
+public class IndexTreeWalker {
+ private final Tree mainTree;
+ private final Tree newTree;
+ private final File root;
+ private final IndexTreeVisitor visitor;
+ private boolean threeTrees;
+
+ /**
+ * Construct a walker for the index and one tree.
+ *
+ * @param index
+ * @param tree
+ * @param root
+ * @param visitor
+ */
+ public IndexTreeWalker(GitIndex index, Tree tree, File root, IndexTreeVisitor visitor) {
+ this.mainTree = tree;
+ this.root = root;
+ this.visitor = visitor;
+ this.newTree = null;
+
+ threeTrees = false;
+
+ indexMembers = index.getMembers();
+ }
+
+ /**
+ * Construct a walker for the index and two trees.
+ *
+ * @param index
+ * @param mainTree
+ * @param newTree
+ * @param root
+ * @param visitor
+ */
+ public IndexTreeWalker(GitIndex index, Tree mainTree, Tree newTree, File root, IndexTreeVisitor visitor) {
+ this.mainTree = mainTree;
+ this.newTree = newTree;
+ this.root = root;
+ this.visitor = visitor;
+
+ threeTrees = true;
+
+ indexMembers = index.getMembers();
+ }
+
+ Entry[] indexMembers;
+ int indexCounter = 0;
+
+ /**
+ * Actually walk the index tree
+ *
+ * @throws IOException
+ */
+ public void walk() throws IOException {
+ walk(mainTree, newTree);
+ }
+
+ private void walk(Tree tree, Tree auxTree) throws IOException {
+ TreeIterator mi = new TreeIterator(tree, TreeIterator.Order.POSTORDER);
+ TreeIterator ai = new TreeIterator(auxTree, TreeIterator.Order.POSTORDER);
+ TreeEntry m = mi.hasNext() ? mi.next() : null;
+ TreeEntry a = ai.hasNext() ? ai.next() : null;
+ int curIndexPos = indexCounter;
+ Entry i = indexCounter < indexMembers.length ? indexMembers[indexCounter++] : null;
+ while (m != null || a != null || i != null) {
+ int cmpma = compare(m, a);
+ int cmpmi = compare(m, i);
+ int cmpai = compare(a, i);
+
+ TreeEntry pm = cmpma <= 0 && cmpmi <= 0 ? m : null;
+ TreeEntry pa = cmpma >= 0 && cmpai <= 0 ? a : null;
+ Entry pi = cmpmi >= 0 && cmpai >= 0 ? i : null;
+
+ if (pi != null)
+ visitEntry(pm, pa, pi);
+ else
+ finishVisitTree(pm, pa, curIndexPos);
+
+ if (pm != null) m = mi.hasNext() ? mi.next() : null;
+ if (pa != null) a = ai.hasNext() ? ai.next() : null;
+ if (pi != null) i = indexCounter < indexMembers.length ? indexMembers[indexCounter++] : null;
+ }
+ }
+
+ private void visitEntry(TreeEntry t1, TreeEntry t2,
+ Entry i) throws IOException {
+
+ assert t1 != null || t2 != null || i != null : "Needs at least one entry";
+ assert root != null : "Needs workdir";
+
+ if (t1 != null && t1.getParent() == null)
+ t1 = null;
+ if (t2 != null && t2.getParent() == null)
+ t2 = null;
+
+ File f = null;
+ if (i != null)
+ f = new File(root, i.getName());
+ else if (t1 != null)
+ f = new File(root, t1.getFullName());
+ else if (t2 != null)
+ f = new File(root, t2.getFullName());
+
+ if (t1 != null || t2 != null || i != null)
+ if (threeTrees)
+ visitor.visitEntry(t1, t2, i, f);
+ else
+ visitor.visitEntry(t1, i, f);
+ }
+
+ private void finishVisitTree(TreeEntry t1, TreeEntry t2, int curIndexPos)
+ throws IOException {
+
+ assert t1 != null || t2 != null : "Needs at least one entry";
+ assert root != null : "Needs workdir";
+
+ if (t1 != null && t1.getParent() == null)
+ t1 = null;
+ if (t2 != null && t2.getParent() == null)
+ t2 = null;
+
+ File f = null;
+ String c= null;
+ if (t1 != null) {
+ c = t1.getFullName();
+ f = new File(root, c);
+ } else if (t2 != null) {
+ c = t2.getFullName();
+ f = new File(root, c);
+ }
+ if (t1 instanceof Tree || t2 instanceof Tree)
+ if (threeTrees)
+ visitor.finishVisitTree((Tree)t1, (Tree)t2, c);
+ else
+ visitor.finishVisitTree((Tree)t1, indexCounter - curIndexPos, c);
+ else if (t1 != null || t2 != null)
+ if (threeTrees)
+ visitor.visitEntry(t1, t2, null, f);
+ else
+ visitor.visitEntry(t1, null, f);
+ }
+
+ static boolean lt(TreeEntry h, Entry i) {
+ return compare(h, i) < 0;
+ }
+
+ static boolean lt(Entry i, TreeEntry t) {
+ return compare(t, i) > 0;
+ }
+
+ static boolean lt(TreeEntry h, TreeEntry m) {
+ return compare(h, m) < 0;
+ }
+
+ static boolean eq(TreeEntry t1, TreeEntry t2) {
+ return compare(t1, t2) == 0;
+ }
+
+ static boolean eq(TreeEntry t1, Entry e) {
+ return compare(t1, e) == 0;
+ }
+
+ static int compare(TreeEntry t, Entry i) {
+ if (t == null && i == null)
+ return 0;
+ if (t == null)
+ return 1;
+ if (i == null)
+ return -1;
+ return Tree.compareNames(t.getFullNameUTF8(), i.getNameUTF8(), TreeEntry.lastChar(t), TreeEntry.lastChar(i));
+ }
+
+ static int compare(TreeEntry t1, TreeEntry t2) {
+ if (t1 != null && t1.getParent() == null && t2 != null && t2.getParent() == null)
+ return 0;
+ if (t1 != null && t1.getParent() == null)
+ return -1;
+ if (t2 != null && t2.getParent() == null)
+ return 1;
+
+ if (t1 == null && t2 == null)
+ return 0;
+ if (t1 == null)
+ return 1;
+ if (t2 == null)
+ return -1;
+ return Tree.compareNames(t1.getFullNameUTF8(), t2.getFullNameUTF8(), TreeEntry.lastChar(t1), TreeEntry.lastChar(t2));
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java
new file mode 100644
index 0000000000..9705898842
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.util.zip.Inflater;
+
+/** Creates zlib based inflaters as necessary for object decompression. */
+public class InflaterCache {
+ private static final int SZ = 4;
+
+ private static final Inflater[] inflaterCache;
+
+ private static int openInflaterCount;
+
+ static {
+ inflaterCache = new Inflater[SZ];
+ }
+
+ /**
+ * Obtain an Inflater for decompression.
+ * <p>
+ * Inflaters obtained through this cache should be returned (if possible) by
+ * {@link #release(Inflater)} to avoid garbage collection and reallocation.
+ *
+ * @return an available inflater. Never null.
+ */
+ public static Inflater get() {
+ final Inflater r = getImpl();
+ return r != null ? r : new Inflater(false);
+ }
+
+ private synchronized static Inflater getImpl() {
+ if (openInflaterCount > 0) {
+ final Inflater r = inflaterCache[--openInflaterCount];
+ inflaterCache[openInflaterCount] = null;
+ return r;
+ }
+ return null;
+ }
+
+ /**
+ * Release an inflater previously obtained from this cache.
+ *
+ * @param i
+ * the inflater to return. May be null, in which case this method
+ * does nothing.
+ */
+ public static void release(final Inflater i) {
+ if (i != null) {
+ i.reset();
+ if (releaseImpl(i))
+ i.end();
+ }
+ }
+
+ private static synchronized boolean releaseImpl(final Inflater i) {
+ if (openInflaterCount < SZ) {
+ inflaterCache[openInflaterCount++] = i;
+ return false;
+ }
+ return true;
+ }
+
+ private InflaterCache() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
new file mode 100644
index 0000000000..bf0036beec
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+
+/**
+ * Git style file locking and replacement.
+ * <p>
+ * To modify a ref file Git tries to use an atomic update approach: we write the
+ * new data into a brand new file, then rename it in place over the old name.
+ * This way we can just delete the temporary file if anything goes wrong, and
+ * nothing has been damaged. To coordinate access from multiple processes at
+ * once Git tries to atomically create the new temporary file under a well-known
+ * name.
+ */
+public class LockFile {
+ private final File ref;
+
+ private final File lck;
+
+ private FileLock fLck;
+
+ private boolean haveLck;
+
+ private FileOutputStream os;
+
+ private boolean needStatInformation;
+
+ private long commitLastModified;
+
+ /**
+ * Create a new lock for any file.
+ *
+ * @param f
+ * the file that will be locked.
+ */
+ public LockFile(final File f) {
+ ref = f;
+ lck = new File(ref.getParentFile(), ref.getName() + ".lock");
+ }
+
+ /**
+ * Try to establish the lock.
+ *
+ * @return true if the lock is now held by the caller; false if it is held
+ * by someone else.
+ * @throws IOException
+ * the temporary output file could not be created. The caller
+ * does not hold the lock.
+ */
+ public boolean lock() throws IOException {
+ lck.getParentFile().mkdirs();
+ if (lck.createNewFile()) {
+ haveLck = true;
+ try {
+ os = new FileOutputStream(lck);
+ try {
+ fLck = os.getChannel().tryLock();
+ if (fLck == null)
+ throw new OverlappingFileLockException();
+ } catch (OverlappingFileLockException ofle) {
+ // We cannot use unlock() here as this file is not
+ // held by us, but we thought we created it. We must
+ // not delete it, as it belongs to some other process.
+ //
+ haveLck = false;
+ try {
+ os.close();
+ } catch (IOException ioe) {
+ // Fail by returning haveLck = false.
+ }
+ os = null;
+ }
+ } catch (IOException ioe) {
+ unlock();
+ throw ioe;
+ }
+ }
+ return haveLck;
+ }
+
+ /**
+ * Try to establish the lock for appending.
+ *
+ * @return true if the lock is now held by the caller; false if it is held
+ * by someone else.
+ * @throws IOException
+ * the temporary output file could not be created. The caller
+ * does not hold the lock.
+ */
+ public boolean lockForAppend() throws IOException {
+ if (!lock())
+ return false;
+ copyCurrentContent();
+ return true;
+ }
+
+ /**
+ * Copy the current file content into the temporary file.
+ * <p>
+ * This method saves the current file content by inserting it into the
+ * temporary file, so that the caller can safely append rather than replace
+ * the primary file.
+ * <p>
+ * This method does nothing if the current file does not exist, or exists
+ * but is empty.
+ *
+ * @throws IOException
+ * the temporary file could not be written, or a read error
+ * occurred while reading from the current file. The lock is
+ * released before throwing the underlying IO exception to the
+ * caller.
+ * @throws RuntimeException
+ * the temporary file could not be written. The lock is released
+ * before throwing the underlying exception to the caller.
+ */
+ public void copyCurrentContent() throws IOException {
+ requireLock();
+ try {
+ final FileInputStream fis = new FileInputStream(ref);
+ try {
+ final byte[] buf = new byte[2048];
+ int r;
+ while ((r = fis.read(buf)) >= 0)
+ os.write(buf, 0, r);
+ } finally {
+ fis.close();
+ }
+ } catch (FileNotFoundException fnfe) {
+ // Don't worry about a file that doesn't exist yet, it
+ // conceptually has no current content to copy.
+ //
+ } catch (IOException ioe) {
+ unlock();
+ throw ioe;
+ } catch (RuntimeException ioe) {
+ unlock();
+ throw ioe;
+ } catch (Error ioe) {
+ unlock();
+ throw ioe;
+ }
+ }
+
+ /**
+ * Write an ObjectId and LF to the temporary file.
+ *
+ * @param id
+ * the id to store in the file. The id will be written in hex,
+ * followed by a sole LF.
+ * @throws IOException
+ * the temporary file could not be written. The lock is released
+ * before throwing the underlying IO exception to the caller.
+ * @throws RuntimeException
+ * the temporary file could not be written. The lock is released
+ * before throwing the underlying exception to the caller.
+ */
+ public void write(final ObjectId id) throws IOException {
+ requireLock();
+ try {
+ final BufferedOutputStream b;
+ b = new BufferedOutputStream(os, Constants.OBJECT_ID_LENGTH * 2 + 1);
+ id.copyTo(b);
+ b.write('\n');
+ b.flush();
+ fLck.release();
+ b.close();
+ os = null;
+ } catch (IOException ioe) {
+ unlock();
+ throw ioe;
+ } catch (RuntimeException ioe) {
+ unlock();
+ throw ioe;
+ } catch (Error ioe) {
+ unlock();
+ throw ioe;
+ }
+ }
+
+ /**
+ * Write arbitrary data to the temporary file.
+ *
+ * @param content
+ * the bytes to store in the temporary file. No additional bytes
+ * are added, so if the file must end with an LF it must appear
+ * at the end of the byte array.
+ * @throws IOException
+ * the temporary file could not be written. The lock is released
+ * before throwing the underlying IO exception to the caller.
+ * @throws RuntimeException
+ * the temporary file could not be written. The lock is released
+ * before throwing the underlying exception to the caller.
+ */
+ public void write(final byte[] content) throws IOException {
+ requireLock();
+ try {
+ os.write(content);
+ os.flush();
+ fLck.release();
+ os.close();
+ os = null;
+ } catch (IOException ioe) {
+ unlock();
+ throw ioe;
+ } catch (RuntimeException ioe) {
+ unlock();
+ throw ioe;
+ } catch (Error ioe) {
+ unlock();
+ throw ioe;
+ }
+ }
+
+ /**
+ * Obtain the direct output stream for this lock.
+ * <p>
+ * The stream may only be accessed once, and only after {@link #lock()} has
+ * been successfully invoked and returned true. Callers must close the
+ * stream prior to calling {@link #commit()} to commit the change.
+ *
+ * @return a stream to write to the new file. The stream is unbuffered.
+ */
+ public OutputStream getOutputStream() {
+ requireLock();
+ return new OutputStream() {
+ @Override
+ public void write(final byte[] b, final int o, final int n)
+ throws IOException {
+ os.write(b, o, n);
+ }
+
+ @Override
+ public void write(final byte[] b) throws IOException {
+ os.write(b);
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ os.write(b);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ os.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ os.flush();
+ fLck.release();
+ os.close();
+ os = null;
+ } catch (IOException ioe) {
+ unlock();
+ throw ioe;
+ } catch (RuntimeException ioe) {
+ unlock();
+ throw ioe;
+ } catch (Error ioe) {
+ unlock();
+ throw ioe;
+ }
+ }
+ };
+ }
+
+ private void requireLock() {
+ if (os == null) {
+ unlock();
+ throw new IllegalStateException("Lock on " + ref + " not held.");
+ }
+ }
+
+ /**
+ * Request that {@link #commit()} remember modification time.
+ *
+ * @param on
+ * true if the commit method must remember the modification time.
+ */
+ public void setNeedStatInformation(final boolean on) {
+ needStatInformation = on;
+ }
+
+ /**
+ * Commit this change and release the lock.
+ * <p>
+ * If this method fails (returns false) the lock is still released.
+ *
+ * @return true if the commit was successful and the file contains the new
+ * data; false if the commit failed and the file remains with the
+ * old data.
+ * @throws IllegalStateException
+ * the lock is not held.
+ */
+ public boolean commit() {
+ if (os != null) {
+ unlock();
+ throw new IllegalStateException("Lock on " + ref + " not closed.");
+ }
+
+ saveStatInformation();
+ if (lck.renameTo(ref))
+ return true;
+ if (!ref.exists() || ref.delete())
+ if (lck.renameTo(ref))
+ return true;
+ unlock();
+ return false;
+ }
+
+ private void saveStatInformation() {
+ if (needStatInformation)
+ commitLastModified = lck.lastModified();
+ }
+
+ /**
+ * Get the modification time of the output file when it was committed.
+ *
+ * @return modification time of the lock file right before we committed it.
+ */
+ public long getCommitLastModified() {
+ return commitLastModified;
+ }
+
+ /**
+ * Unlock this file and abort this change.
+ * <p>
+ * The temporary file (if created) is deleted before returning.
+ */
+ public void unlock() {
+ if (os != null) {
+ if (fLck != null) {
+ try {
+ fLck.release();
+ } catch (IOException ioe) {
+ // Huh?
+ }
+ fLck = null;
+ }
+ try {
+ os.close();
+ } catch (IOException ioe) {
+ // Ignore this
+ }
+ os = null;
+ }
+
+ if (haveLck) {
+ haveLck = false;
+ lck.delete();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "LockFile[" + lck + ", haveLck=" + haveLck + "]";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java
new file mode 100644
index 0000000000..478f8ba618
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2009, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import org.eclipse.jgit.errors.InvalidObjectIdException;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * A mutable SHA-1 abstraction.
+ */
+public class MutableObjectId extends AnyObjectId {
+ /**
+ * Empty constructor. Initialize object with default (zeros) value.
+ */
+ public MutableObjectId() {
+ super();
+ }
+
+ /**
+ * Copying constructor.
+ *
+ * @param src
+ * original entry, to copy id from
+ */
+ MutableObjectId(MutableObjectId src) {
+ this.w1 = src.w1;
+ this.w2 = src.w2;
+ this.w3 = src.w3;
+ this.w4 = src.w4;
+ this.w5 = src.w5;
+ }
+
+ /** Make this id match {@link ObjectId#zeroId()}. */
+ public void clear() {
+ w1 = 0;
+ w2 = 0;
+ w3 = 0;
+ w4 = 0;
+ w5 = 0;
+ }
+
+ /**
+ * Convert an ObjectId from raw binary representation.
+ *
+ * @param bs
+ * the raw byte buffer to read from. At least 20 bytes must be
+ * available within this byte array.
+ */
+ public void fromRaw(final byte[] bs) {
+ fromRaw(bs, 0);
+ }
+
+ /**
+ * Convert an ObjectId from raw binary representation.
+ *
+ * @param bs
+ * the raw byte buffer to read from. At least 20 bytes after p
+ * must be available within this byte array.
+ * @param p
+ * position to read the first byte of data from.
+ */
+ public void fromRaw(final byte[] bs, final int p) {
+ w1 = NB.decodeInt32(bs, p);
+ w2 = NB.decodeInt32(bs, p + 4);
+ w3 = NB.decodeInt32(bs, p + 8);
+ w4 = NB.decodeInt32(bs, p + 12);
+ w5 = NB.decodeInt32(bs, p + 16);
+ }
+
+ /**
+ * Convert an ObjectId from binary representation expressed in integers.
+ *
+ * @param ints
+ * the raw int buffer to read from. At least 5 integers must be
+ * available within this integers array.
+ */
+ public void fromRaw(final int[] ints) {
+ fromRaw(ints, 0);
+ }
+
+ /**
+ * Convert an ObjectId from binary representation expressed in integers.
+ *
+ * @param ints
+ * the raw int buffer to read from. At least 5 integers after p
+ * must be available within this integers array.
+ * @param p
+ * position to read the first integer of data from.
+ *
+ */
+ public void fromRaw(final int[] ints, final int p) {
+ w1 = ints[p];
+ w2 = ints[p + 1];
+ w3 = ints[p + 2];
+ w4 = ints[p + 3];
+ w5 = ints[p + 4];
+ }
+
+ /**
+ * Convert an ObjectId from hex characters (US-ASCII).
+ *
+ * @param buf
+ * the US-ASCII buffer to read from. At least 40 bytes after
+ * offset must be available within this byte array.
+ * @param offset
+ * position to read the first character from.
+ */
+ public void fromString(final byte[] buf, final int offset) {
+ fromHexString(buf, offset);
+ }
+
+ /**
+ * Convert an ObjectId from hex characters.
+ *
+ * @param str
+ * the string to read from. Must be 40 characters long.
+ */
+ public void fromString(final String str) {
+ if (str.length() != STR_LEN)
+ throw new IllegalArgumentException("Invalid id: " + str);
+ fromHexString(Constants.encodeASCII(str), 0);
+ }
+
+ private void fromHexString(final byte[] bs, int p) {
+ try {
+ w1 = RawParseUtils.parseHexInt32(bs, p);
+ w2 = RawParseUtils.parseHexInt32(bs, p + 8);
+ w3 = RawParseUtils.parseHexInt32(bs, p + 16);
+ w4 = RawParseUtils.parseHexInt32(bs, p + 24);
+ w5 = RawParseUtils.parseHexInt32(bs, p + 32);
+ } catch (ArrayIndexOutOfBoundsException e1) {
+ throw new InvalidObjectIdException(bs, p, STR_LEN);
+ }
+ }
+
+ @Override
+ public ObjectId toObjectId() {
+ return new ObjectId(this);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java
new file mode 100644
index 0000000000..d05c8c6b01
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2009, Alex Blewitt <alex.blewitt@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/**
+ * A NullProgressMonitor does not report progress anywhere.
+ */
+public class NullProgressMonitor implements ProgressMonitor {
+ /** Immutable instance of a null progress monitor. */
+ public static final NullProgressMonitor INSTANCE = new NullProgressMonitor();
+
+ private NullProgressMonitor() {
+ // Do not let others instantiate
+ }
+
+ public void start(int totalTasks) {
+ // Do not report.
+ }
+
+ public void beginTask(String title, int totalWork) {
+ // Do not report.
+ }
+
+ public void update(int completed) {
+ // Do not report.
+ }
+
+ public boolean isCancelled() {
+ return false;
+ }
+
+ public void endTask() {
+ // Do not report.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
new file mode 100644
index 0000000000..9cf1643db5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import static org.eclipse.jgit.util.RawParseUtils.match;
+import static org.eclipse.jgit.util.RawParseUtils.nextLF;
+import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.util.MutableInteger;
+
+/**
+ * Verifies that an object is formatted correctly.
+ * <p>
+ * Verifications made by this class only check that the fields of an object are
+ * formatted correctly. The ObjectId checksum of the object is not verified, and
+ * connectivity links between objects are also not verified. Its assumed that
+ * the caller can provide both of these validations on its own.
+ * <p>
+ * Instances of this class are not thread safe, but they may be reused to
+ * perform multiple object validations.
+ */
+public class ObjectChecker {
+ /** Header "tree " */
+ public static final byte[] tree = Constants.encodeASCII("tree ");
+
+ /** Header "parent " */
+ public static final byte[] parent = Constants.encodeASCII("parent ");
+
+ /** Header "author " */
+ public static final byte[] author = Constants.encodeASCII("author ");
+
+ /** Header "committer " */
+ public static final byte[] committer = Constants.encodeASCII("committer ");
+
+ /** Header "encoding " */
+ public static final byte[] encoding = Constants.encodeASCII("encoding ");
+
+ /** Header "object " */
+ public static final byte[] object = Constants.encodeASCII("object ");
+
+ /** Header "type " */
+ public static final byte[] type = Constants.encodeASCII("type ");
+
+ /** Header "tag " */
+ public static final byte[] tag = Constants.encodeASCII("tag ");
+
+ /** Header "tagger " */
+ public static final byte[] tagger = Constants.encodeASCII("tagger ");
+
+ private final MutableObjectId tempId = new MutableObjectId();
+
+ private final MutableInteger ptrout = new MutableInteger();
+
+ /**
+ * Check an object for parsing errors.
+ *
+ * @param objType
+ * type of the object. Must be a valid object type code in
+ * {@link Constants}.
+ * @param raw
+ * the raw data which comprises the object. This should be in the
+ * canonical format (that is the format used to generate the
+ * ObjectId of the object). The array is never modified.
+ * @throws CorruptObjectException
+ * if an error is identified.
+ */
+ public void check(final int objType, final byte[] raw)
+ throws CorruptObjectException {
+ switch (objType) {
+ case Constants.OBJ_COMMIT:
+ checkCommit(raw);
+ break;
+ case Constants.OBJ_TAG:
+ checkTag(raw);
+ break;
+ case Constants.OBJ_TREE:
+ checkTree(raw);
+ break;
+ case Constants.OBJ_BLOB:
+ checkBlob(raw);
+ break;
+ default:
+ throw new CorruptObjectException("Invalid object type: " + objType);
+ }
+ }
+
+ private int id(final byte[] raw, final int ptr) {
+ try {
+ tempId.fromString(raw, ptr);
+ return ptr + AnyObjectId.STR_LEN;
+ } catch (IllegalArgumentException e) {
+ return -1;
+ }
+ }
+
+ private int personIdent(final byte[] raw, int ptr) {
+ final int emailB = nextLF(raw, ptr, '<');
+ if (emailB == ptr || raw[emailB - 1] != '<')
+ return -1;
+
+ final int emailE = nextLF(raw, emailB, '>');
+ if (emailE == emailB || raw[emailE - 1] != '>')
+ return -1;
+ if (emailE == raw.length || raw[emailE] != ' ')
+ return -1;
+
+ parseBase10(raw, emailE + 1, ptrout); // when
+ ptr = ptrout.value;
+ if (emailE + 1 == ptr)
+ return -1;
+ if (ptr == raw.length || raw[ptr] != ' ')
+ return -1;
+
+ parseBase10(raw, ptr + 1, ptrout); // tz offset
+ if (ptr + 1 == ptrout.value)
+ return -1;
+ return ptrout.value;
+ }
+
+ /**
+ * Check a commit for errors.
+ *
+ * @param raw
+ * the commit data. The array is never modified.
+ * @throws CorruptObjectException
+ * if any error was detected.
+ */
+ public void checkCommit(final byte[] raw) throws CorruptObjectException {
+ int ptr = 0;
+
+ if ((ptr = match(raw, ptr, tree)) < 0)
+ throw new CorruptObjectException("no tree header");
+ if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
+ throw new CorruptObjectException("invalid tree");
+
+ while (match(raw, ptr, parent) >= 0) {
+ ptr += parent.length;
+ if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
+ throw new CorruptObjectException("invalid parent");
+ }
+
+ if ((ptr = match(raw, ptr, author)) < 0)
+ throw new CorruptObjectException("no author");
+ if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
+ throw new CorruptObjectException("invalid author");
+
+ if ((ptr = match(raw, ptr, committer)) < 0)
+ throw new CorruptObjectException("no committer");
+ if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
+ throw new CorruptObjectException("invalid committer");
+ }
+
+ /**
+ * Check an annotated tag for errors.
+ *
+ * @param raw
+ * the tag data. The array is never modified.
+ * @throws CorruptObjectException
+ * if any error was detected.
+ */
+ public void checkTag(final byte[] raw) throws CorruptObjectException {
+ int ptr = 0;
+
+ if ((ptr = match(raw, ptr, object)) < 0)
+ throw new CorruptObjectException("no object header");
+ if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
+ throw new CorruptObjectException("invalid object");
+
+ if ((ptr = match(raw, ptr, type)) < 0)
+ throw new CorruptObjectException("no type header");
+ ptr = nextLF(raw, ptr);
+
+ if ((ptr = match(raw, ptr, tag)) < 0)
+ throw new CorruptObjectException("no tag header");
+ ptr = nextLF(raw, ptr);
+
+ if ((ptr = match(raw, ptr, tagger)) < 0)
+ throw new CorruptObjectException("no tagger header");
+ if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
+ throw new CorruptObjectException("invalid tagger");
+ }
+
+ private static int lastPathChar(final int mode) {
+ return FileMode.TREE.equals(mode) ? '/' : '\0';
+ }
+
+ private static int pathCompare(final byte[] raw, int aPos, final int aEnd,
+ final int aMode, int bPos, final int bEnd, final int bMode) {
+ while (aPos < aEnd && bPos < bEnd) {
+ final int cmp = (raw[aPos++] & 0xff) - (raw[bPos++] & 0xff);
+ if (cmp != 0)
+ return cmp;
+ }
+
+ if (aPos < aEnd)
+ return (raw[aPos] & 0xff) - lastPathChar(bMode);
+ if (bPos < bEnd)
+ return lastPathChar(aMode) - (raw[bPos] & 0xff);
+ return 0;
+ }
+
+ private static boolean duplicateName(final byte[] raw,
+ final int thisNamePos, final int thisNameEnd) {
+ final int sz = raw.length;
+ int nextPtr = thisNameEnd + 1 + Constants.OBJECT_ID_LENGTH;
+ for (;;) {
+ int nextMode = 0;
+ for (;;) {
+ if (nextPtr >= sz)
+ return false;
+ final byte c = raw[nextPtr++];
+ if (' ' == c)
+ break;
+ nextMode <<= 3;
+ nextMode += c - '0';
+ }
+
+ final int nextNamePos = nextPtr;
+ for (;;) {
+ if (nextPtr == sz)
+ return false;
+ final byte c = raw[nextPtr++];
+ if (c == 0)
+ break;
+ }
+ if (nextNamePos + 1 == nextPtr)
+ return false;
+
+ final int cmp = pathCompare(raw, thisNamePos, thisNameEnd,
+ FileMode.TREE.getBits(), nextNamePos, nextPtr - 1, nextMode);
+ if (cmp < 0)
+ return false;
+ else if (cmp == 0)
+ return true;
+
+ nextPtr += Constants.OBJECT_ID_LENGTH;
+ }
+ }
+
+ /**
+ * Check a canonical formatted tree for errors.
+ *
+ * @param raw
+ * the raw tree data. The array is never modified.
+ * @throws CorruptObjectException
+ * if any error was detected.
+ */
+ public void checkTree(final byte[] raw) throws CorruptObjectException {
+ final int sz = raw.length;
+ int ptr = 0;
+ int lastNameB = 0, lastNameE = 0, lastMode = 0;
+
+ while (ptr < sz) {
+ int thisMode = 0;
+ for (;;) {
+ if (ptr == sz)
+ throw new CorruptObjectException("truncated in mode");
+ final byte c = raw[ptr++];
+ if (' ' == c)
+ break;
+ if (c < '0' || c > '7')
+ throw new CorruptObjectException("invalid mode character");
+ if (thisMode == 0 && c == '0')
+ throw new CorruptObjectException("mode starts with '0'");
+ thisMode <<= 3;
+ thisMode += c - '0';
+ }
+
+ if (FileMode.fromBits(thisMode).getObjectType() == Constants.OBJ_BAD)
+ throw new CorruptObjectException("invalid mode " + thisMode);
+
+ final int thisNameB = ptr;
+ for (;;) {
+ if (ptr == sz)
+ throw new CorruptObjectException("truncated in name");
+ final byte c = raw[ptr++];
+ if (c == 0)
+ break;
+ if (c == '/')
+ throw new CorruptObjectException("name contains '/'");
+ }
+ if (thisNameB + 1 == ptr)
+ throw new CorruptObjectException("zero length name");
+ if (raw[thisNameB] == '.') {
+ final int nameLen = (ptr - 1) - thisNameB;
+ if (nameLen == 1)
+ throw new CorruptObjectException("invalid name '.'");
+ if (nameLen == 2 && raw[thisNameB + 1] == '.')
+ throw new CorruptObjectException("invalid name '..'");
+ }
+ if (duplicateName(raw, thisNameB, ptr - 1))
+ throw new CorruptObjectException("duplicate entry names");
+
+ if (lastNameB != 0) {
+ final int cmp = pathCompare(raw, lastNameB, lastNameE,
+ lastMode, thisNameB, ptr - 1, thisMode);
+ if (cmp > 0)
+ throw new CorruptObjectException("incorrectly sorted");
+ }
+
+ lastNameB = thisNameB;
+ lastNameE = ptr - 1;
+ lastMode = thisMode;
+
+ ptr += Constants.OBJECT_ID_LENGTH;
+ if (ptr > sz)
+ throw new CorruptObjectException("truncated in object id");
+ }
+ }
+
+ /**
+ * Check a blob for errors.
+ *
+ * @param raw
+ * the blob data. The array is never modified.
+ * @throws CorruptObjectException
+ * if any error was detected.
+ */
+ public void checkBlob(final byte[] raw) throws CorruptObjectException {
+ // We can always assume the blob is valid.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
new file mode 100644
index 0000000000..21b7b9dc93
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Abstraction of arbitrary object storage.
+ * <p>
+ * An object database stores one or more Git objects, indexed by their unique
+ * {@link ObjectId}. Optionally an object database can reference one or more
+ * alternates; other ObjectDatabase instances that are searched in addition to
+ * the current database.
+ * <p>
+ * Databases are usually divided into two halves: a half that is considered to
+ * be fast to search, and a half that is considered to be slow to search. When
+ * alternates are present the fast half is fully searched (recursively through
+ * all alternates) before the slow half is considered.
+ */
+public abstract class ObjectDatabase {
+ /** Constant indicating no alternate databases exist. */
+ protected static final ObjectDatabase[] NO_ALTERNATES = {};
+
+ private final AtomicReference<ObjectDatabase[]> alternates;
+
+ /** Initialize a new database instance for access. */
+ protected ObjectDatabase() {
+ alternates = new AtomicReference<ObjectDatabase[]>();
+ }
+
+ /**
+ * Does this database exist yet?
+ *
+ * @return true if this database is already created; false if the caller
+ * should invoke {@link #create()} to create this database location.
+ */
+ public boolean exists() {
+ return true;
+ }
+
+ /**
+ * Initialize a new object database at this location.
+ *
+ * @throws IOException
+ * the database could not be created.
+ */
+ public void create() throws IOException {
+ // Assume no action is required.
+ }
+
+ /**
+ * Close any resources held by this database and its active alternates.
+ */
+ public final void close() {
+ closeSelf();
+ closeAlternates();
+ }
+
+ /**
+ * Close any resources held by this database only; ignoring alternates.
+ * <p>
+ * To fully close this database and its referenced alternates, the caller
+ * should instead invoke {@link #close()}.
+ */
+ public void closeSelf() {
+ // Assume no action is required.
+ }
+
+ /** Fully close all loaded alternates and clear the alternate list. */
+ public final void closeAlternates() {
+ ObjectDatabase[] alt = alternates.get();
+ if (alt != null) {
+ alternates.set(null);
+ closeAlternates(alt);
+ }
+ }
+
+ /**
+ * Does the requested object exist in this database?
+ * <p>
+ * Alternates (if present) are searched automatically.
+ *
+ * @param objectId
+ * identity of the object to test for existence of.
+ * @return true if the specified object is stored in this database, or any
+ * of the alternate databases.
+ */
+ public final boolean hasObject(final AnyObjectId objectId) {
+ return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name());
+ }
+
+ private final boolean hasObjectImpl1(final AnyObjectId objectId) {
+ if (hasObject1(objectId)) {
+ return true;
+ }
+ for (final ObjectDatabase alt : getAlternates()) {
+ if (alt.hasObjectImpl1(objectId)) {
+ return true;
+ }
+ }
+ return tryAgain1() && hasObject1(objectId);
+ }
+
+ private final boolean hasObjectImpl2(final String objectId) {
+ if (hasObject2(objectId)) {
+ return true;
+ }
+ for (final ObjectDatabase alt : getAlternates()) {
+ if (alt.hasObjectImpl2(objectId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Fast half of {@link #hasObject(AnyObjectId)}.
+ *
+ * @param objectId
+ * identity of the object to test for existence of.
+ * @return true if the specified object is stored in this database.
+ */
+ protected abstract boolean hasObject1(AnyObjectId objectId);
+
+ /**
+ * Slow half of {@link #hasObject(AnyObjectId)}.
+ *
+ * @param objectName
+ * identity of the object to test for existence of.
+ * @return true if the specified object is stored in this database.
+ */
+ protected boolean hasObject2(String objectName) {
+ // Assume the search took place during hasObject1.
+ return false;
+ }
+
+ /**
+ * Open an object from this database.
+ * <p>
+ * Alternates (if present) are searched automatically.
+ *
+ * @param curs
+ * temporary working space associated with the calling thread.
+ * @param objectId
+ * identity of the object to open.
+ * @return a {@link ObjectLoader} for accessing the data of the named
+ * object, or null if the object does not exist.
+ * @throws IOException
+ */
+ public final ObjectLoader openObject(final WindowCursor curs,
+ final AnyObjectId objectId) throws IOException {
+ ObjectLoader ldr;
+
+ ldr = openObjectImpl1(curs, objectId);
+ if (ldr != null) {
+ return ldr;
+ }
+
+ ldr = openObjectImpl2(curs, objectId.name(), objectId);
+ if (ldr != null) {
+ return ldr;
+ }
+ return null;
+ }
+
+ private ObjectLoader openObjectImpl1(final WindowCursor curs,
+ final AnyObjectId objectId) throws IOException {
+ ObjectLoader ldr;
+
+ ldr = openObject1(curs, objectId);
+ if (ldr != null) {
+ return ldr;
+ }
+ for (final ObjectDatabase alt : getAlternates()) {
+ ldr = alt.openObjectImpl1(curs, objectId);
+ if (ldr != null) {
+ return ldr;
+ }
+ }
+ if (tryAgain1()) {
+ ldr = openObject1(curs, objectId);
+ if (ldr != null) {
+ return ldr;
+ }
+ }
+ return null;
+ }
+
+ private ObjectLoader openObjectImpl2(final WindowCursor curs,
+ final String objectName, final AnyObjectId objectId)
+ throws IOException {
+ ObjectLoader ldr;
+
+ ldr = openObject2(curs, objectName, objectId);
+ if (ldr != null) {
+ return ldr;
+ }
+ for (final ObjectDatabase alt : getAlternates()) {
+ ldr = alt.openObjectImpl2(curs, objectName, objectId);
+ if (ldr != null) {
+ return ldr;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Fast half of {@link #openObject(WindowCursor, AnyObjectId)}.
+ *
+ * @param curs
+ * temporary working space associated with the calling thread.
+ * @param objectId
+ * identity of the object to open.
+ * @return a {@link ObjectLoader} for accessing the data of the named
+ * object, or null if the object does not exist.
+ * @throws IOException
+ */
+ protected abstract ObjectLoader openObject1(WindowCursor curs,
+ AnyObjectId objectId) throws IOException;
+
+ /**
+ * Slow half of {@link #openObject(WindowCursor, AnyObjectId)}.
+ *
+ * @param curs
+ * temporary working space associated with the calling thread.
+ * @param objectName
+ * name of the object to open.
+ * @param objectId
+ * identity of the object to open.
+ * @return a {@link ObjectLoader} for accessing the data of the named
+ * object, or null if the object does not exist.
+ * @throws IOException
+ */
+ protected ObjectLoader openObject2(WindowCursor curs, String objectName,
+ AnyObjectId objectId) throws IOException {
+ // Assume the search took place during openObject1.
+ return null;
+ }
+
+ /**
+ * Open the object from all packs containing it.
+ * <p>
+ * If any alternates are present, their packs are also considered.
+ *
+ * @param out
+ * result collection of loaders for this object, filled with
+ * loaders from all packs containing specified object
+ * @param curs
+ * temporary working space associated with the calling thread.
+ * @param objectId
+ * id of object to search for
+ * @throws IOException
+ */
+ final void openObjectInAllPacks(final Collection<PackedObjectLoader> out,
+ final WindowCursor curs, final AnyObjectId objectId)
+ throws IOException {
+ openObjectInAllPacks1(out, curs, objectId);
+ for (final ObjectDatabase alt : getAlternates()) {
+ alt.openObjectInAllPacks1(out, curs, objectId);
+ }
+ }
+
+ /**
+ * Open the object from all packs containing it.
+ *
+ * @param out
+ * result collection of loaders for this object, filled with
+ * loaders from all packs containing specified object
+ * @param curs
+ * temporary working space associated with the calling thread.
+ * @param objectId
+ * id of object to search for
+ * @throws IOException
+ */
+ void openObjectInAllPacks1(Collection<PackedObjectLoader> out,
+ WindowCursor curs, AnyObjectId objectId) throws IOException {
+ // Assume no pack support
+ }
+
+ /**
+ * @return true if the fast-half search should be tried again.
+ */
+ protected boolean tryAgain1() {
+ return false;
+ }
+
+ /**
+ * Get the alternate databases known to this database.
+ *
+ * @return the alternate list. Never null, but may be an empty array.
+ */
+ public final ObjectDatabase[] getAlternates() {
+ ObjectDatabase[] r = alternates.get();
+ if (r == null) {
+ synchronized (alternates) {
+ r = alternates.get();
+ if (r == null) {
+ try {
+ r = loadAlternates();
+ } catch (IOException e) {
+ r = NO_ALTERNATES;
+ }
+ alternates.set(r);
+ }
+ }
+ }
+ return r;
+ }
+
+ /**
+ * Load the list of alternate databases into memory.
+ * <p>
+ * This method is invoked by {@link #getAlternates()} if the alternate list
+ * has not yet been populated, or if {@link #closeAlternates()} has been
+ * called on this instance and the alternate list is needed again.
+ * <p>
+ * If the alternate array is empty, implementors should consider using the
+ * constant {@link #NO_ALTERNATES}.
+ *
+ * @return the alternate list for this database.
+ * @throws IOException
+ * the alternate list could not be accessed. The empty alternate
+ * array {@link #NO_ALTERNATES} will be assumed by the caller.
+ */
+ protected ObjectDatabase[] loadAlternates() throws IOException {
+ return NO_ALTERNATES;
+ }
+
+ /**
+ * Close the list of alternates returned by {@link #loadAlternates()}.
+ *
+ * @param alt
+ * the alternate list, from {@link #loadAlternates()}.
+ */
+ protected void closeAlternates(ObjectDatabase[] alt) {
+ for (final ObjectDatabase d : alt) {
+ d.close();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java
new file mode 100644
index 0000000000..297d85f83e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java
@@ -0,0 +1,517 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jgit.errors.PackMismatchException;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.util.FS;
+
+/**
+ * Traditional file system based {@link ObjectDatabase}.
+ * <p>
+ * This is the classical object database representation for a Git repository,
+ * where objects are stored loose by hashing them into directories by their
+ * {@link ObjectId}, or are stored in compressed containers known as
+ * {@link PackFile}s.
+ */
+public class ObjectDirectory extends ObjectDatabase {
+ private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]);
+
+ private final File objects;
+
+ private final File infoDirectory;
+
+ private final File packDirectory;
+
+ private final File alternatesFile;
+
+ private final AtomicReference<PackList> packList;
+
+ /**
+ * Initialize a reference to an on-disk object directory.
+ *
+ * @param dir
+ * the location of the <code>objects</code> directory.
+ */
+ public ObjectDirectory(final File dir) {
+ objects = dir;
+ infoDirectory = new File(objects, "info");
+ packDirectory = new File(objects, "pack");
+ alternatesFile = new File(infoDirectory, "alternates");
+ packList = new AtomicReference<PackList>(NO_PACKS);
+ }
+
+ /**
+ * @return the location of the <code>objects</code> directory.
+ */
+ public final File getDirectory() {
+ return objects;
+ }
+
+ @Override
+ public boolean exists() {
+ return objects.exists();
+ }
+
+ @Override
+ public void create() throws IOException {
+ objects.mkdirs();
+ infoDirectory.mkdir();
+ packDirectory.mkdir();
+ }
+
+ @Override
+ public void closeSelf() {
+ final PackList packs = packList.get();
+ packList.set(NO_PACKS);
+ for (final PackFile p : packs.packs)
+ p.close();
+ }
+
+ /**
+ * Compute the location of a loose object file.
+ *
+ * @param objectId
+ * identity of the loose object to map to the directory.
+ * @return location of the object, if it were to exist as a loose object.
+ */
+ public File fileFor(final AnyObjectId objectId) {
+ return fileFor(objectId.name());
+ }
+
+ private File fileFor(final String objectName) {
+ final String d = objectName.substring(0, 2);
+ final String f = objectName.substring(2);
+ return new File(new File(objects, d), f);
+ }
+
+ /**
+ * Add a single existing pack to the list of available pack files.
+ *
+ * @param pack
+ * path of the pack file to open.
+ * @param idx
+ * path of the corresponding index file.
+ * @throws IOException
+ * index file could not be opened, read, or is not recognized as
+ * a Git pack file index.
+ */
+ public void openPack(final File pack, final File idx) throws IOException {
+ final String p = pack.getName();
+ final String i = idx.getName();
+
+ if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack"))
+ throw new IOException("Not a valid pack " + pack);
+
+ if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx"))
+ throw new IOException("Not a valid pack " + idx);
+
+ if (!p.substring(0, 45).equals(i.substring(0, 45)))
+ throw new IOException("Pack " + pack + "does not match index");
+
+ insertPack(new PackFile(idx, pack));
+ }
+
+ @Override
+ public String toString() {
+ return "ObjectDirectory[" + getDirectory() + "]";
+ }
+
+ @Override
+ protected boolean hasObject1(final AnyObjectId objectId) {
+ for (final PackFile p : packList.get().packs) {
+ try {
+ if (p.hasObject(objectId)) {
+ return true;
+ }
+ } catch (IOException e) {
+ // The hasObject call should have only touched the index,
+ // so any failure here indicates the index is unreadable
+ // by this process, and the pack is likewise not readable.
+ //
+ removePack(p);
+ continue;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected ObjectLoader openObject1(final WindowCursor curs,
+ final AnyObjectId objectId) throws IOException {
+ PackList pList = packList.get();
+ SEARCH: for (;;) {
+ for (final PackFile p : pList.packs) {
+ try {
+ final PackedObjectLoader ldr = p.get(curs, objectId);
+ if (ldr != null) {
+ ldr.materialize(curs);
+ return ldr;
+ }
+ } catch (PackMismatchException e) {
+ // Pack was modified; refresh the entire pack list.
+ //
+ pList = scanPacks(pList);
+ continue SEARCH;
+ } catch (IOException e) {
+ // Assume the pack is corrupted.
+ //
+ removePack(p);
+ }
+ }
+ return null;
+ }
+ }
+
+ @Override
+ void openObjectInAllPacks1(final Collection<PackedObjectLoader> out,
+ final WindowCursor curs, final AnyObjectId objectId)
+ throws IOException {
+ PackList pList = packList.get();
+ SEARCH: for (;;) {
+ for (final PackFile p : pList.packs) {
+ try {
+ final PackedObjectLoader ldr = p.get(curs, objectId);
+ if (ldr != null) {
+ out.add(ldr);
+ }
+ } catch (PackMismatchException e) {
+ // Pack was modified; refresh the entire pack list.
+ //
+ pList = scanPacks(pList);
+ continue SEARCH;
+ } catch (IOException e) {
+ // Assume the pack is corrupted.
+ //
+ removePack(p);
+ }
+ }
+ break SEARCH;
+ }
+ }
+
+ @Override
+ protected boolean hasObject2(final String objectName) {
+ return fileFor(objectName).exists();
+ }
+
+ @Override
+ protected ObjectLoader openObject2(final WindowCursor curs,
+ final String objectName, final AnyObjectId objectId)
+ throws IOException {
+ try {
+ return new UnpackedObjectLoader(fileFor(objectName), objectId);
+ } catch (FileNotFoundException noFile) {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean tryAgain1() {
+ final PackList old = packList.get();
+ if (old.tryAgain(packDirectory.lastModified()))
+ return old != scanPacks(old);
+ return false;
+ }
+
+ private void insertPack(final PackFile pf) {
+ PackList o, n;
+ do {
+ o = packList.get();
+ final PackFile[] oldList = o.packs;
+ final PackFile[] newList = new PackFile[1 + oldList.length];
+ newList[0] = pf;
+ System.arraycopy(oldList, 0, newList, 1, oldList.length);
+ n = new PackList(o.lastRead, o.lastModified, newList);
+ } while (!packList.compareAndSet(o, n));
+ }
+
+ private void removePack(final PackFile deadPack) {
+ PackList o, n;
+ do {
+ o = packList.get();
+
+ final PackFile[] oldList = o.packs;
+ final int j = indexOf(oldList, deadPack);
+ if (j < 0)
+ break;
+
+ final PackFile[] newList = new PackFile[oldList.length - 1];
+ System.arraycopy(oldList, 0, newList, 0, j);
+ System.arraycopy(oldList, j + 1, newList, j, newList.length - j);
+ n = new PackList(o.lastRead, o.lastModified, newList);
+ } while (!packList.compareAndSet(o, n));
+ deadPack.close();
+ }
+
+ private static int indexOf(final PackFile[] list, final PackFile pack) {
+ for (int i = 0; i < list.length; i++) {
+ if (list[i] == pack)
+ return i;
+ }
+ return -1;
+ }
+
+ private PackList scanPacks(final PackList original) {
+ synchronized (packList) {
+ PackList o, n;
+ do {
+ o = packList.get();
+ if (o != original) {
+ // Another thread did the scan for us, while we
+ // were blocked on the monitor above.
+ //
+ return o;
+ }
+ n = scanPacksImpl(o);
+ if (n == o)
+ return n;
+ } while (!packList.compareAndSet(o, n));
+ return n;
+ }
+ }
+
+ private PackList scanPacksImpl(final PackList old) {
+ final Map<String, PackFile> forReuse = reuseMap(old);
+ final long lastRead = System.currentTimeMillis();
+ final long lastModified = packDirectory.lastModified();
+ final Set<String> names = listPackDirectory();
+ final List<PackFile> list = new ArrayList<PackFile>(names.size() >> 2);
+ boolean foundNew = false;
+ for (final String indexName : names) {
+ // Must match "pack-[0-9a-f]{40}.idx" to be an index.
+ //
+ if (indexName.length() != 49 || !indexName.endsWith(".idx"))
+ continue;
+
+ final String base = indexName.substring(0, indexName.length() - 4);
+ final String packName = base + ".pack";
+ if (!names.contains(packName)) {
+ // Sometimes C Git's HTTP fetch transport leaves a
+ // .idx file behind and does not download the .pack.
+ // We have to skip over such useless indexes.
+ //
+ continue;
+ }
+
+ final PackFile oldPack = forReuse.remove(packName);
+ if (oldPack != null) {
+ list.add(oldPack);
+ continue;
+ }
+
+ final File packFile = new File(packDirectory, packName);
+ final File idxFile = new File(packDirectory, indexName);
+ list.add(new PackFile(idxFile, packFile));
+ foundNew = true;
+ }
+
+ // If we did not discover any new files, the modification time was not
+ // changed, and we did not remove any files, then the set of files is
+ // the same as the set we were given. Instead of building a new object
+ // return the same collection.
+ //
+ if (!foundNew && lastModified == old.lastModified && forReuse.isEmpty())
+ return old.updateLastRead(lastRead);
+
+ for (final PackFile p : forReuse.values()) {
+ p.close();
+ }
+
+ if (list.isEmpty())
+ return new PackList(lastRead, lastModified, NO_PACKS.packs);
+
+ final PackFile[] r = list.toArray(new PackFile[list.size()]);
+ Arrays.sort(r, PackFile.SORT);
+ return new PackList(lastRead, lastModified, r);
+ }
+
+ private static Map<String, PackFile> reuseMap(final PackList old) {
+ final Map<String, PackFile> forReuse = new HashMap<String, PackFile>();
+ for (final PackFile p : old.packs) {
+ if (p.invalid()) {
+ // The pack instance is corrupted, and cannot be safely used
+ // again. Do not include it in our reuse map.
+ //
+ p.close();
+ continue;
+ }
+
+ final PackFile prior = forReuse.put(p.getPackFile().getName(), p);
+ if (prior != null) {
+ // This should never occur. It should be impossible for us
+ // to have two pack files with the same name, as all of them
+ // came out of the same directory. If it does, we promised to
+ // close any PackFiles we did not reuse, so close the one we
+ // just evicted out of the reuse map.
+ //
+ prior.close();
+ }
+ }
+ return forReuse;
+ }
+
+ private Set<String> listPackDirectory() {
+ final String[] nameList = packDirectory.list();
+ if (nameList == null)
+ return Collections.emptySet();
+ final Set<String> nameSet = new HashSet<String>(nameList.length << 1);
+ for (final String name : nameList) {
+ if (name.startsWith("pack-"))
+ nameSet.add(name);
+ }
+ return nameSet;
+ }
+
+ @Override
+ protected ObjectDatabase[] loadAlternates() throws IOException {
+ final BufferedReader br = open(alternatesFile);
+ final List<ObjectDatabase> l = new ArrayList<ObjectDatabase>(4);
+ try {
+ String line;
+ while ((line = br.readLine()) != null) {
+ l.add(openAlternate(line));
+ }
+ } finally {
+ br.close();
+ }
+
+ if (l.isEmpty()) {
+ return NO_ALTERNATES;
+ }
+ return l.toArray(new ObjectDatabase[l.size()]);
+ }
+
+ private static BufferedReader open(final File f)
+ throws FileNotFoundException {
+ return new BufferedReader(new FileReader(f));
+ }
+
+ private ObjectDatabase openAlternate(final String location)
+ throws IOException {
+ final File objdir = FS.resolve(objects, location);
+ final File parent = objdir.getParentFile();
+ if (FileKey.isGitRepository(parent)) {
+ final Repository db = RepositoryCache.open(FileKey.exact(parent));
+ return new AlternateRepositoryDatabase(db);
+ }
+ return new ObjectDirectory(objdir);
+ }
+
+ private static final class PackList {
+ /** Last wall-clock time the directory was read. */
+ volatile long lastRead;
+
+ /** Last modification time of {@link ObjectDirectory#packDirectory}. */
+ final long lastModified;
+
+ /** All known packs, sorted by {@link PackFile#SORT}. */
+ final PackFile[] packs;
+
+ private boolean cannotBeRacilyClean;
+
+ PackList(final long lastRead, final long lastModified,
+ final PackFile[] packs) {
+ this.lastRead = lastRead;
+ this.lastModified = lastModified;
+ this.packs = packs;
+ this.cannotBeRacilyClean = notRacyClean(lastRead);
+ }
+
+ private boolean notRacyClean(final long read) {
+ return read - lastModified > 2 * 60 * 1000L;
+ }
+
+ PackList updateLastRead(final long now) {
+ if (notRacyClean(now))
+ cannotBeRacilyClean = true;
+ lastRead = now;
+ return this;
+ }
+
+ boolean tryAgain(final long currLastModified) {
+ // Any difference indicates the directory was modified.
+ //
+ if (lastModified != currLastModified)
+ return true;
+
+ // We have already determined the last read was far enough
+ // after the last modification that any new modifications
+ // are certain to change the last modified time.
+ //
+ if (cannotBeRacilyClean)
+ return false;
+
+ if (notRacyClean(lastRead)) {
+ // Our last read should have marked cannotBeRacilyClean,
+ // but this thread may not have seen the change. The read
+ // of the volatile field lastRead should have fixed that.
+ //
+ return false;
+ }
+
+ // We last read this directory too close to its last observed
+ // modification time. We may have missed a modification. Scan
+ // the directory again, to ensure we still see the same state.
+ //
+ return true;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
new file mode 100644
index 0000000000..2e3506619f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import org.eclipse.jgit.errors.InvalidObjectIdException;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * A SHA-1 abstraction.
+ */
+public class ObjectId extends AnyObjectId {
+ private static final ObjectId ZEROID;
+
+ private static final String ZEROID_STR;
+
+ static {
+ ZEROID = new ObjectId(0, 0, 0, 0, 0);
+ ZEROID_STR = ZEROID.name();
+ }
+
+ /**
+ * Get the special all-null ObjectId.
+ *
+ * @return the all-null ObjectId, often used to stand-in for no object.
+ */
+ public static final ObjectId zeroId() {
+ return ZEROID;
+ }
+
+ /**
+ * Test a string of characters to verify it is a hex format.
+ * <p>
+ * If true the string can be parsed with {@link #fromString(String)}.
+ *
+ * @param id
+ * the string to test.
+ * @return true if the string can converted into an ObjectId.
+ */
+ public static final boolean isId(final String id) {
+ if (id.length() != STR_LEN)
+ return false;
+ try {
+ for (int i = 0; i < STR_LEN; i++) {
+ RawParseUtils.parseHexInt4((byte) id.charAt(i));
+ }
+ return true;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Convert an ObjectId into a hex string representation.
+ *
+ * @param i
+ * the id to convert. May be null.
+ * @return the hex string conversion of this id's content.
+ */
+ public static final String toString(final ObjectId i) {
+ return i != null ? i.name() : ZEROID_STR;
+ }
+
+ /**
+ * Compare to object identifier byte sequences for equality.
+ *
+ * @param firstBuffer
+ * the first buffer to compare against. Must have at least 20
+ * bytes from position ai through the end of the buffer.
+ * @param fi
+ * first offset within firstBuffer to begin testing.
+ * @param secondBuffer
+ * the second buffer to compare against. Must have at least 2
+ * bytes from position bi through the end of the buffer.
+ * @param si
+ * first offset within secondBuffer to begin testing.
+ * @return true if the two identifiers are the same.
+ */
+ public static boolean equals(final byte[] firstBuffer, final int fi,
+ final byte[] secondBuffer, final int si) {
+ return firstBuffer[fi] == secondBuffer[si]
+ && firstBuffer[fi + 1] == secondBuffer[si + 1]
+ && firstBuffer[fi + 2] == secondBuffer[si + 2]
+ && firstBuffer[fi + 3] == secondBuffer[si + 3]
+ && firstBuffer[fi + 4] == secondBuffer[si + 4]
+ && firstBuffer[fi + 5] == secondBuffer[si + 5]
+ && firstBuffer[fi + 6] == secondBuffer[si + 6]
+ && firstBuffer[fi + 7] == secondBuffer[si + 7]
+ && firstBuffer[fi + 8] == secondBuffer[si + 8]
+ && firstBuffer[fi + 9] == secondBuffer[si + 9]
+ && firstBuffer[fi + 10] == secondBuffer[si + 10]
+ && firstBuffer[fi + 11] == secondBuffer[si + 11]
+ && firstBuffer[fi + 12] == secondBuffer[si + 12]
+ && firstBuffer[fi + 13] == secondBuffer[si + 13]
+ && firstBuffer[fi + 14] == secondBuffer[si + 14]
+ && firstBuffer[fi + 15] == secondBuffer[si + 15]
+ && firstBuffer[fi + 16] == secondBuffer[si + 16]
+ && firstBuffer[fi + 17] == secondBuffer[si + 17]
+ && firstBuffer[fi + 18] == secondBuffer[si + 18]
+ && firstBuffer[fi + 19] == secondBuffer[si + 19];
+ }
+
+ /**
+ * Convert an ObjectId from raw binary representation.
+ *
+ * @param bs
+ * the raw byte buffer to read from. At least 20 bytes must be
+ * available within this byte array.
+ * @return the converted object id.
+ */
+ public static final ObjectId fromRaw(final byte[] bs) {
+ return fromRaw(bs, 0);
+ }
+
+ /**
+ * Convert an ObjectId from raw binary representation.
+ *
+ * @param bs
+ * the raw byte buffer to read from. At least 20 bytes after p
+ * must be available within this byte array.
+ * @param p
+ * position to read the first byte of data from.
+ * @return the converted object id.
+ */
+ public static final ObjectId fromRaw(final byte[] bs, final int p) {
+ final int a = NB.decodeInt32(bs, p);
+ final int b = NB.decodeInt32(bs, p + 4);
+ final int c = NB.decodeInt32(bs, p + 8);
+ final int d = NB.decodeInt32(bs, p + 12);
+ final int e = NB.decodeInt32(bs, p + 16);
+ return new ObjectId(a, b, c, d, e);
+ }
+
+ /**
+ * Convert an ObjectId from raw binary representation.
+ *
+ * @param is
+ * the raw integers buffer to read from. At least 5 integers must
+ * be available within this int array.
+ * @return the converted object id.
+ */
+ public static final ObjectId fromRaw(final int[] is) {
+ return fromRaw(is, 0);
+ }
+
+ /**
+ * Convert an ObjectId from raw binary representation.
+ *
+ * @param is
+ * the raw integers buffer to read from. At least 5 integers
+ * after p must be available within this int array.
+ * @param p
+ * position to read the first integer of data from.
+ * @return the converted object id.
+ */
+ public static final ObjectId fromRaw(final int[] is, final int p) {
+ return new ObjectId(is[p], is[p + 1], is[p + 2], is[p + 3], is[p + 4]);
+ }
+
+ /**
+ * Convert an ObjectId from hex characters (US-ASCII).
+ *
+ * @param buf
+ * the US-ASCII buffer to read from. At least 40 bytes after
+ * offset must be available within this byte array.
+ * @param offset
+ * position to read the first character from.
+ * @return the converted object id.
+ */
+ public static final ObjectId fromString(final byte[] buf, final int offset) {
+ return fromHexString(buf, offset);
+ }
+
+ /**
+ * Convert an ObjectId from hex characters.
+ *
+ * @param str
+ * the string to read from. Must be 40 characters long.
+ * @return the converted object id.
+ */
+ public static final ObjectId fromString(final String str) {
+ if (str.length() != STR_LEN)
+ throw new IllegalArgumentException("Invalid id: " + str);
+ return fromHexString(Constants.encodeASCII(str), 0);
+ }
+
+ private static final ObjectId fromHexString(final byte[] bs, int p) {
+ try {
+ final int a = RawParseUtils.parseHexInt32(bs, p);
+ final int b = RawParseUtils.parseHexInt32(bs, p + 8);
+ final int c = RawParseUtils.parseHexInt32(bs, p + 16);
+ final int d = RawParseUtils.parseHexInt32(bs, p + 24);
+ final int e = RawParseUtils.parseHexInt32(bs, p + 32);
+ return new ObjectId(a, b, c, d, e);
+ } catch (ArrayIndexOutOfBoundsException e1) {
+ throw new InvalidObjectIdException(bs, p, STR_LEN);
+ }
+ }
+
+ ObjectId(final int new_1, final int new_2, final int new_3,
+ final int new_4, final int new_5) {
+ w1 = new_1;
+ w2 = new_2;
+ w3 = new_3;
+ w4 = new_4;
+ w5 = new_5;
+ }
+
+ /**
+ * Initialize this instance by copying another existing ObjectId.
+ * <p>
+ * This constructor is mostly useful for subclasses who want to extend an
+ * ObjectId with more properties, but initialize from an existing ObjectId
+ * instance acquired by other means.
+ *
+ * @param src
+ * another already parsed ObjectId to copy the value out of.
+ */
+ protected ObjectId(final AnyObjectId src) {
+ w1 = src.w1;
+ w2 = src.w2;
+ w3 = src.w3;
+ w4 = src.w4;
+ w5 = src.w5;
+ }
+
+ @Override
+ public ObjectId toObjectId() {
+ return this;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
new file mode 100644
index 0000000000..bc4072f607
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.util.Iterator;
+
+/**
+ * Fast, efficient map specifically for {@link ObjectId} subclasses.
+ * <p>
+ * This map provides an efficient translation from any ObjectId instance to a
+ * cached subclass of ObjectId that has the same value.
+ * <p>
+ * Raw value equality is tested when comparing two ObjectIds (or subclasses),
+ * not reference equality and not <code>.equals(Object)</code> equality. This
+ * allows subclasses to override <code>equals</code> to supply their own
+ * extended semantics.
+ *
+ * @param <V>
+ * type of subclass of ObjectId that will be stored in the map.
+ */
+public class ObjectIdSubclassMap<V extends ObjectId> implements Iterable<V> {
+ private int size;
+
+ private V[] obj_hash;
+
+ /** Create an empty map. */
+ public ObjectIdSubclassMap() {
+ obj_hash = createArray(32);
+ }
+
+ /** Remove all entries from this map. */
+ public void clear() {
+ size = 0;
+ obj_hash = createArray(32);
+ }
+
+ /**
+ * Lookup an existing mapping.
+ *
+ * @param toFind
+ * the object identifier to find.
+ * @return the instance mapped to toFind, or null if no mapping exists.
+ */
+ public V get(final AnyObjectId toFind) {
+ int i = index(toFind);
+ V obj;
+
+ while ((obj = obj_hash[i]) != null) {
+ if (AnyObjectId.equals(obj, toFind))
+ return obj;
+ if (++i == obj_hash.length)
+ i = 0;
+ }
+ return null;
+ }
+
+ /**
+ * Store an object for future lookup.
+ * <p>
+ * An existing mapping for <b>must not</b> be in this map. Callers must
+ * first call {@link #get(AnyObjectId)} to verify there is no current
+ * mapping prior to adding a new mapping.
+ *
+ * @param newValue
+ * the object to store.
+ * @param
+ * <Q>
+ * type of instance to store.
+ */
+ public <Q extends V> void add(final Q newValue) {
+ if (obj_hash.length - 1 <= size * 2)
+ grow();
+ insert(newValue);
+ size++;
+ }
+
+ /**
+ * @return number of objects in map
+ */
+ public int size() {
+ return size;
+ }
+
+ public Iterator<V> iterator() {
+ return new Iterator<V>() {
+ private int found;
+
+ private int i;
+
+ public boolean hasNext() {
+ return found < size;
+ }
+
+ public V next() {
+ while (i < obj_hash.length) {
+ final V v = obj_hash[i++];
+ if (v != null) {
+ found++;
+ return v;
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ private final int index(final AnyObjectId id) {
+ return (id.w1 >>> 1) % obj_hash.length;
+ }
+
+ private void insert(final V newValue) {
+ int j = index(newValue);
+ while (obj_hash[j] != null) {
+ if (++j >= obj_hash.length)
+ j = 0;
+ }
+ obj_hash[j] = newValue;
+ }
+
+ private void grow() {
+ final V[] old_hash = obj_hash;
+ final int old_hash_size = obj_hash.length;
+
+ obj_hash = createArray(2 * old_hash_size);
+ for (int i = 0; i < old_hash_size; i++) {
+ final V obj = old_hash[i];
+ if (obj != null)
+ insert(obj);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private final V[] createArray(final int sz) {
+ return (V[]) new ObjectId[sz];
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java
new file mode 100644
index 0000000000..97b3b769aa
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+
+/**
+ * Base class for a set of loaders for different representations of Git objects.
+ * New loaders are constructed for every object.
+ */
+public abstract class ObjectLoader {
+ /**
+ * @return Git in pack object type, see {@link Constants}.
+ */
+ public abstract int getType();
+
+ /**
+ * @return size of object in bytes
+ */
+ public abstract long getSize();
+
+ /**
+ * Obtain a copy of the bytes of this object.
+ * <p>
+ * Unlike {@link #getCachedBytes()} this method returns an array that might
+ * be modified by the caller.
+ *
+ * @return the bytes of this object.
+ */
+ public final byte[] getBytes() {
+ final byte[] data = getCachedBytes();
+ final byte[] copy = new byte[data.length];
+ System.arraycopy(data, 0, copy, 0, data.length);
+ return copy;
+ }
+
+ /**
+ * Obtain a reference to the (possibly cached) bytes of this object.
+ * <p>
+ * This method offers direct access to the internal caches, potentially
+ * saving on data copies between the internal cache and higher level code.
+ * Callers who receive this reference <b>must not</b> modify its contents.
+ * Changes (if made) will affect the cache but not the repository itself.
+ *
+ * @return the cached bytes of this object. Do not modify it.
+ */
+ public abstract byte[] getCachedBytes();
+
+ /**
+ * @return raw object type from object header, as stored in storage (pack,
+ * loose file). This may be different from {@link #getType()} result
+ * for packs (see {@link Constants}).
+ */
+ public abstract int getRawType();
+
+ /**
+ * @return raw size of object from object header (pack, loose file).
+ * Interpretation of this value depends on {@link #getRawType()}.
+ */
+ public abstract long getRawSize();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java
new file mode 100644
index 0000000000..60e85eb57f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.security.MessageDigest;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import org.eclipse.jgit.errors.ObjectWritingException;
+
+/**
+ * A class for writing loose objects.
+ */
+public class ObjectWriter {
+ private static final byte[] htree = Constants.encodeASCII("tree");
+
+ private static final byte[] hparent = Constants.encodeASCII("parent");
+
+ private static final byte[] hauthor = Constants.encodeASCII("author");
+
+ private static final byte[] hcommitter = Constants.encodeASCII("committer");
+
+ private static final byte[] hencoding = Constants.encodeASCII("encoding");
+
+ private final Repository r;
+
+ private final byte[] buf;
+
+ private final MessageDigest md;
+
+ private final Deflater def;
+
+ /**
+ * Construct an Object writer for the specified repository
+ * @param d
+ */
+ public ObjectWriter(final Repository d) {
+ r = d;
+ buf = new byte[8192];
+ md = Constants.newMessageDigest();
+ def = new Deflater(r.getConfig().getCore().getCompression());
+ }
+
+ /**
+ * Write a blob with the specified data
+ *
+ * @param b bytes of the blob
+ * @return SHA-1 of the blob
+ * @throws IOException
+ */
+ public ObjectId writeBlob(final byte[] b) throws IOException {
+ return writeBlob(b.length, new ByteArrayInputStream(b));
+ }
+
+ /**
+ * Write a blob with the data in the specified file
+ *
+ * @param f
+ * a file containing blob data
+ * @return SHA-1 of the blob
+ * @throws IOException
+ */
+ public ObjectId writeBlob(final File f) throws IOException {
+ final FileInputStream is = new FileInputStream(f);
+ try {
+ return writeBlob(f.length(), is);
+ } finally {
+ is.close();
+ }
+ }
+
+ /**
+ * Write a blob with data from a stream
+ *
+ * @param len
+ * number of bytes to consume from the stream
+ * @param is
+ * stream with blob data
+ * @return SHA-1 of the blob
+ * @throws IOException
+ */
+ public ObjectId writeBlob(final long len, final InputStream is)
+ throws IOException {
+ return writeObject(Constants.OBJ_BLOB, len, is, true);
+ }
+
+ /**
+ * Write a Tree to the object database.
+ *
+ * @param t
+ * Tree
+ * @return SHA-1 of the tree
+ * @throws IOException
+ */
+ public ObjectId writeTree(final Tree t) throws IOException {
+ final ByteArrayOutputStream o = new ByteArrayOutputStream();
+ final TreeEntry[] items = t.members();
+ for (int k = 0; k < items.length; k++) {
+ final TreeEntry e = items[k];
+ final ObjectId id = e.getId();
+
+ if (id == null)
+ throw new ObjectWritingException("Object at path \""
+ + e.getFullName() + "\" does not have an id assigned."
+ + " All object ids must be assigned prior"
+ + " to writing a tree.");
+
+ e.getMode().copyTo(o);
+ o.write(' ');
+ o.write(e.getNameUTF8());
+ o.write(0);
+ id.copyRawTo(o);
+ }
+ return writeCanonicalTree(o.toByteArray());
+ }
+
+ /**
+ * Write a canonical tree to the object database.
+ *
+ * @param b
+ * the canonical encoding of the tree object.
+ * @return SHA-1 of the tree
+ * @throws IOException
+ */
+ public ObjectId writeCanonicalTree(final byte[] b) throws IOException {
+ return writeTree(b.length, new ByteArrayInputStream(b));
+ }
+
+ private ObjectId writeTree(final long len, final InputStream is)
+ throws IOException {
+ return writeObject(Constants.OBJ_TREE, len, is, true);
+ }
+
+ /**
+ * Write a Commit to the object database
+ *
+ * @param c
+ * Commit to store
+ * @return SHA-1 of the commit
+ * @throws IOException
+ */
+ public ObjectId writeCommit(final Commit c) throws IOException {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ String encoding = c.getEncoding();
+ if (encoding == null)
+ encoding = Constants.CHARACTER_ENCODING;
+ final OutputStreamWriter w = new OutputStreamWriter(os, encoding);
+
+ os.write(htree);
+ os.write(' ');
+ c.getTreeId().copyTo(os);
+ os.write('\n');
+
+ ObjectId[] ps = c.getParentIds();
+ for (int i=0; i<ps.length; ++i) {
+ os.write(hparent);
+ os.write(' ');
+ ps[i].copyTo(os);
+ os.write('\n');
+ }
+
+ os.write(hauthor);
+ os.write(' ');
+ w.write(c.getAuthor().toExternalString());
+ w.flush();
+ os.write('\n');
+
+ os.write(hcommitter);
+ os.write(' ');
+ w.write(c.getCommitter().toExternalString());
+ w.flush();
+ os.write('\n');
+
+ if (!encoding.equals(Constants.CHARACTER_ENCODING)) {
+ os.write(hencoding);
+ os.write(' ');
+ os.write(Constants.encodeASCII(encoding));
+ os.write('\n');
+ }
+
+ os.write('\n');
+ w.write(c.getMessage());
+ w.flush();
+
+ return writeCommit(os.toByteArray());
+ }
+
+ private ObjectId writeTag(final byte[] b) throws IOException {
+ return writeTag(b.length, new ByteArrayInputStream(b));
+ }
+
+ /**
+ * Write an annotated Tag to the object database
+ *
+ * @param c
+ * Tag
+ * @return SHA-1 of the tag
+ * @throws IOException
+ */
+ public ObjectId writeTag(final Tag c) throws IOException {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ final OutputStreamWriter w = new OutputStreamWriter(os,
+ Constants.CHARSET);
+
+ w.write("object ");
+ c.getObjId().copyTo(w);
+ w.write('\n');
+
+ w.write("type ");
+ w.write(c.getType());
+ w.write("\n");
+
+ w.write("tag ");
+ w.write(c.getTag());
+ w.write("\n");
+
+ w.write("tagger ");
+ w.write(c.getAuthor().toExternalString());
+ w.write('\n');
+
+ w.write('\n');
+ w.write(c.getMessage());
+ w.close();
+
+ return writeTag(os.toByteArray());
+ }
+
+ private ObjectId writeCommit(final byte[] b) throws IOException {
+ return writeCommit(b.length, new ByteArrayInputStream(b));
+ }
+
+ private ObjectId writeCommit(final long len, final InputStream is)
+ throws IOException {
+ return writeObject(Constants.OBJ_COMMIT, len, is, true);
+ }
+
+ private ObjectId writeTag(final long len, final InputStream is)
+ throws IOException {
+ return writeObject(Constants.OBJ_TAG, len, is, true);
+ }
+
+ /**
+ * Compute the SHA-1 of a blob without creating an object. This is for
+ * figuring out if we already have a blob or not.
+ *
+ * @param len number of bytes to consume
+ * @param is stream for read blob data from
+ * @return SHA-1 of a looked for blob
+ * @throws IOException
+ */
+ public ObjectId computeBlobSha1(final long len, final InputStream is)
+ throws IOException {
+ return writeObject(Constants.OBJ_BLOB, len, is, false);
+ }
+
+ ObjectId writeObject(final int type, long len, final InputStream is,
+ boolean store) throws IOException {
+ final File t;
+ final DeflaterOutputStream deflateStream;
+ final FileOutputStream fileStream;
+ ObjectId id = null;
+
+ if (store) {
+ t = File.createTempFile("noz", null, r.getObjectsDirectory());
+ fileStream = new FileOutputStream(t);
+ } else {
+ t = null;
+ fileStream = null;
+ }
+
+ md.reset();
+ if (store) {
+ def.reset();
+ deflateStream = new DeflaterOutputStream(fileStream, def);
+ } else
+ deflateStream = null;
+
+ try {
+ byte[] header;
+ int n;
+
+ header = Constants.encodedTypeString(type);
+ md.update(header);
+ if (deflateStream != null)
+ deflateStream.write(header);
+
+ md.update((byte) ' ');
+ if (deflateStream != null)
+ deflateStream.write((byte) ' ');
+
+ header = Constants.encodeASCII(len);
+ md.update(header);
+ if (deflateStream != null)
+ deflateStream.write(header);
+
+ md.update((byte) 0);
+ if (deflateStream != null)
+ deflateStream.write((byte) 0);
+
+ while (len > 0
+ && (n = is.read(buf, 0, (int) Math.min(len, buf.length))) > 0) {
+ md.update(buf, 0, n);
+ if (deflateStream != null)
+ deflateStream.write(buf, 0, n);
+ len -= n;
+ }
+
+ if (len != 0)
+ throw new IOException("Input did not match supplied length. "
+ + len + " bytes are missing.");
+
+ if (deflateStream != null ) {
+ deflateStream.close();
+ if (t != null)
+ t.setReadOnly();
+ }
+
+ id = ObjectId.fromRaw(md.digest());
+ } finally {
+ if (id == null && deflateStream != null) {
+ try {
+ deflateStream.close();
+ } finally {
+ t.delete();
+ }
+ }
+ }
+
+ if (t == null)
+ return id;
+
+ if (r.hasObject(id)) {
+ // Object is already in the repository so remove
+ // the temporary file.
+ //
+ t.delete();
+ } else {
+ final File o = r.toFile(id);
+ if (!t.renameTo(o)) {
+ // Maybe the directory doesn't exist yet as the object
+ // directories are always lazily created. Note that we
+ // try the rename first as the directory likely does exist.
+ //
+ o.getParentFile().mkdir();
+ if (!t.renameTo(o)) {
+ if (!r.hasObject(id)) {
+ // The object failed to be renamed into its proper
+ // location and it doesn't exist in the repository
+ // either. We really don't know what went wrong, so
+ // fail.
+ //
+ t.delete();
+ throw new ObjectWritingException("Unable to"
+ + " create new object: " + o);
+ }
+ }
+ }
+ }
+
+ return id;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/OffsetCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/OffsetCache.java
new file mode 100644
index 0000000000..747f6f122e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/OffsetCache.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Least frequently used cache for objects specified by PackFile positions.
+ * <p>
+ * This cache maps a <code>({@link PackFile},position)</code> tuple to an Object.
+ * <p>
+ * This cache is suitable for objects that are "relative expensive" to compute
+ * from the underlying PackFile, given some known position in that file.
+ * <p>
+ * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by
+ * exactly one thread for the given <code>(PackFile,position)</code> key tuple.
+ * This is ensured by an array of locks, with the tuple hashed to a lock
+ * instance.
+ * <p>
+ * During a miss, older entries are evicted from the cache so long as
+ * {@link #isFull()} returns true.
+ * <p>
+ * Its too expensive during object access to be 100% accurate with a least
+ * recently used (LRU) algorithm. Strictly ordering every read is a lot of
+ * overhead that typically doesn't yield a corresponding benefit to the
+ * application.
+ * <p>
+ * This cache implements a loose LRU policy by randomly picking a window
+ * comprised of roughly 10% of the cache, and evicting the oldest accessed entry
+ * within that window.
+ * <p>
+ * Entities created by the cache are held under SoftReferences, permitting the
+ * Java runtime's garbage collector to evict entries when heap memory gets low.
+ * Most JREs implement a loose least recently used algorithm for this eviction.
+ * <p>
+ * The internal hash table does not expand at runtime, instead it is fixed in
+ * size at cache creation time. The internal lock table used to gate load
+ * invocations is also fixed in size.
+ * <p>
+ * The key tuple is passed through to methods as a pair of parameters rather
+ * than as a single Object, thus reducing the transient memory allocations of
+ * callers. It is more efficient to avoid the allocation, as we can't be 100%
+ * sure that a JIT would be able to stack-allocate a key tuple.
+ * <p>
+ * This cache has an implementation rule such that:
+ * <ul>
+ * <li>{@link #load(PackFile, long)} is invoked by at most one thread at a time
+ * for a given <code>(PackFile,position)</code> tuple.</li>
+ * <li>For every <code>load()</code> invocation there is exactly one
+ * {@link #createRef(PackFile, long, Object)} invocation to wrap a SoftReference
+ * around the cached entity.</li>
+ * <li>For every Reference created by <code>createRef()</code> there will be
+ * exactly one call to {@link #clear(Ref)} to cleanup any resources associated
+ * with the (now expired) cached entity.</li>
+ * </ul>
+ * <p>
+ * Therefore, it is safe to perform resource accounting increments during the
+ * {@link #load(PackFile, long)} or {@link #createRef(PackFile, long, Object)}
+ * methods, and matching decrements during {@link #clear(Ref)}. Implementors may
+ * need to override {@link #createRef(PackFile, long, Object)} in order to embed
+ * additional accounting information into an implementation specific
+ * {@link OffsetCache.Ref} subclass, as the cached entity may have already been
+ * evicted by the JRE's garbage collector.
+ * <p>
+ * To maintain higher concurrency workloads, during eviction only one thread
+ * performs the eviction work, while other threads can continue to insert new
+ * objects in parallel. This means that the cache can be temporarily over limit,
+ * especially if the nominated eviction thread is being starved relative to the
+ * other threads.
+ *
+ * @param <V>
+ * type of value stored in the cache.
+ * @param <R>
+ * type of {@link OffsetCache.Ref} subclass used by the cache.
+ */
+abstract class OffsetCache<V, R extends OffsetCache.Ref<V>> {
+ private static final Random rng = new Random();
+
+ /** ReferenceQueue that {@link #createRef(PackFile, long, Object)} must use. */
+ protected final ReferenceQueue<V> queue;
+
+ /** Number of entries in {@link #table}. */
+ private final int tableSize;
+
+ /** Access clock for loose LRU. */
+ private final AtomicLong clock;
+
+ /** Hash bucket directory; entries are chained below. */
+ private final AtomicReferenceArray<Entry<V>> table;
+
+ /** Locks to prevent concurrent loads for same (PackFile,position). */
+ private final Lock[] locks;
+
+ /** Lock to elect the eviction thread after a load occurs. */
+ private final ReentrantLock evictLock;
+
+ /** Number of {@link #table} buckets to scan for an eviction window. */
+ private final int evictBatch;
+
+ /**
+ * Create a new cache with a fixed size entry table and lock table.
+ *
+ * @param tSize
+ * number of entries in the entry hash table.
+ * @param lockCount
+ * number of entries in the lock table. This is the maximum
+ * concurrency rate for creation of new objects through
+ * {@link #load(PackFile, long)} invocations.
+ */
+ OffsetCache(final int tSize, final int lockCount) {
+ if (tSize < 1)
+ throw new IllegalArgumentException("tSize must be >= 1");
+ if (lockCount < 1)
+ throw new IllegalArgumentException("lockCount must be >= 1");
+
+ queue = new ReferenceQueue<V>();
+ tableSize = tSize;
+ clock = new AtomicLong(1);
+ table = new AtomicReferenceArray<Entry<V>>(tableSize);
+ locks = new Lock[lockCount];
+ for (int i = 0; i < locks.length; i++)
+ locks[i] = new Lock();
+ evictLock = new ReentrantLock();
+
+ int eb = (int) (tableSize * .1);
+ if (64 < eb)
+ eb = 64;
+ else if (eb < 4)
+ eb = 4;
+ if (tableSize < eb)
+ eb = tableSize;
+ evictBatch = eb;
+ }
+
+ /**
+ * Lookup a cached object, creating and loading it if it doesn't exist.
+ *
+ * @param pack
+ * the pack that "contains" the cached object.
+ * @param position
+ * offset within <code>pack</code> of the object.
+ * @return the object reference.
+ * @throws IOException
+ * the object reference was not in the cache and could not be
+ * obtained by {@link #load(PackFile, long)}.
+ */
+ V getOrLoad(final PackFile pack, final long position) throws IOException {
+ final int slot = slot(pack, position);
+ final Entry<V> e1 = table.get(slot);
+ V v = scan(e1, pack, position);
+ if (v != null)
+ return v;
+
+ synchronized (lock(pack, position)) {
+ Entry<V> e2 = table.get(slot);
+ if (e2 != e1) {
+ v = scan(e2, pack, position);
+ if (v != null)
+ return v;
+ }
+
+ v = load(pack, position);
+ final Ref<V> ref = createRef(pack, position, v);
+ hit(ref);
+ for (;;) {
+ final Entry<V> n = new Entry<V>(clean(e2), ref);
+ if (table.compareAndSet(slot, e2, n))
+ break;
+ e2 = table.get(slot);
+ }
+ }
+
+ if (evictLock.tryLock()) {
+ try {
+ gc();
+ evict();
+ } finally {
+ evictLock.unlock();
+ }
+ }
+
+ return v;
+ }
+
+ private V scan(Entry<V> n, final PackFile pack, final long position) {
+ for (; n != null; n = n.next) {
+ final Ref<V> r = n.ref;
+ if (r.pack == pack && r.position == position) {
+ final V v = r.get();
+ if (v != null) {
+ hit(r);
+ return v;
+ }
+ n.kill();
+ break;
+ }
+ }
+ return null;
+ }
+
+ private void hit(final Ref<V> r) {
+ // We don't need to be 100% accurate here. Its sufficient that at least
+ // one thread performs the increment. Any other concurrent access at
+ // exactly the same time can simply use the same clock value.
+ //
+ // Consequently we attempt the set, but we don't try to recover should
+ // it fail. This is why we don't use getAndIncrement() here.
+ //
+ final long c = clock.get();
+ clock.compareAndSet(c, c + 1);
+ r.lastAccess = c;
+ }
+
+ private void evict() {
+ while (isFull()) {
+ int ptr = rng.nextInt(tableSize);
+ Entry<V> old = null;
+ int slot = 0;
+ for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
+ if (tableSize <= ptr)
+ ptr = 0;
+ for (Entry<V> e = table.get(ptr); e != null; e = e.next) {
+ if (e.dead)
+ continue;
+ if (old == null || e.ref.lastAccess < old.ref.lastAccess) {
+ old = e;
+ slot = ptr;
+ }
+ }
+ }
+ if (old != null) {
+ old.kill();
+ gc();
+ final Entry<V> e1 = table.get(slot);
+ table.compareAndSet(slot, e1, clean(e1));
+ }
+ }
+ }
+
+ /**
+ * Clear every entry from the cache.
+ *<p>
+ * This is a last-ditch effort to clear out the cache, such as before it
+ * gets replaced by another cache that is configured differently. This
+ * method tries to force every cached entry through {@link #clear(Ref)} to
+ * ensure that resources are correctly accounted for and cleaned up by the
+ * subclass. A concurrent reader loading entries while this method is
+ * running may cause resource accounting failures.
+ */
+ void removeAll() {
+ for (int s = 0; s < tableSize; s++) {
+ Entry<V> e1;
+ do {
+ e1 = table.get(s);
+ for (Entry<V> e = e1; e != null; e = e.next)
+ e.kill();
+ } while (!table.compareAndSet(s, e1, null));
+ }
+ gc();
+ }
+
+ /**
+ * Clear all entries related to a single file.
+ * <p>
+ * Typically this method is invoked during {@link PackFile#close()}, when we
+ * know the pack is never going to be useful to us again (for example, it no
+ * longer exists on disk). A concurrent reader loading an entry from this
+ * same pack may cause the pack to become stuck in the cache anyway.
+ *
+ * @param pack
+ * the file to purge all entries of.
+ */
+ void removeAll(final PackFile pack) {
+ for (int s = 0; s < tableSize; s++) {
+ final Entry<V> e1 = table.get(s);
+ boolean hasDead = false;
+ for (Entry<V> e = e1; e != null; e = e.next) {
+ if (e.ref.pack == pack) {
+ e.kill();
+ hasDead = true;
+ } else if (e.dead)
+ hasDead = true;
+ }
+ if (hasDead)
+ table.compareAndSet(s, e1, clean(e1));
+ }
+ gc();
+ }
+
+ /**
+ * Materialize an object that doesn't yet exist in the cache.
+ * <p>
+ * This method is invoked by {@link #getOrLoad(PackFile, long)} when the
+ * specified entity does not yet exist in the cache. Internal locking
+ * ensures that at most one thread can call this method for each unique
+ * <code>(pack,position)</code>, but multiple threads can call this method
+ * concurrently for different <code>(pack,position)</code> tuples.
+ *
+ * @param pack
+ * the file to materialize the entry from.
+ * @param position
+ * offset within the file of the entry.
+ * @return the materialized object. Must never be null.
+ * @throws IOException
+ * the method was unable to materialize the object for this
+ * input pair. The usual reasons would be file corruption, file
+ * not found, out of file descriptors, etc.
+ */
+ protected abstract V load(PackFile pack, long position) throws IOException;
+
+ /**
+ * Construct a Ref (SoftReference) around a cached entity.
+ * <p>
+ * Implementing this is only necessary if the subclass is performing
+ * resource accounting during {@link #load(PackFile, long)} and
+ * {@link #clear(Ref)} requires some information to update the accounting.
+ * <p>
+ * Implementors <b>MUST</b> ensure that the returned reference uses the
+ * {@link #queue} ReferenceQueue, otherwise {@link #clear(Ref)} will not be
+ * invoked at the proper time.
+ *
+ * @param pack
+ * the file to materialize the entry from.
+ * @param position
+ * offset within the file of the entry.
+ * @param v
+ * the object returned by {@link #load(PackFile, long)}.
+ * @return a soft reference subclass wrapped around <code>v</code>.
+ */
+ @SuppressWarnings("unchecked")
+ protected R createRef(final PackFile pack, final long position, final V v) {
+ return (R) new Ref<V>(pack, position, v, queue);
+ }
+
+ /**
+ * Update accounting information now that an object has left the cache.
+ * <p>
+ * This method is invoked exactly once for the combined
+ * {@link #load(PackFile, long)} and
+ * {@link #createRef(PackFile, long, Object)} invocation pair that was used
+ * to construct and insert an object into the cache.
+ *
+ * @param ref
+ * the reference wrapped around the object. Implementations must
+ * be prepared for <code>ref.get()</code> to return null.
+ */
+ protected void clear(final R ref) {
+ // Do nothing by default.
+ }
+
+ /**
+ * Determine if the cache is full and requires eviction of entries.
+ * <p>
+ * By default this method returns false. Implementors may override to
+ * consult with the accounting updated by {@link #load(PackFile, long)},
+ * {@link #createRef(PackFile, long, Object)} and {@link #clear(Ref)}.
+ *
+ * @return true if the cache is still over-limit and requires eviction of
+ * more entries.
+ */
+ protected boolean isFull() {
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void gc() {
+ R r;
+ while ((r = (R) queue.poll()) != null) {
+ // Sun's Java 5 and 6 implementation have a bug where a Reference
+ // can be enqueued and dequeued twice on the same reference queue
+ // due to a race condition within ReferenceQueue.enqueue(Reference).
+ //
+ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6837858
+ //
+ // We CANNOT permit a Reference to come through us twice, as it will
+ // skew the resource counters we maintain. Our canClear() check here
+ // provides a way to skip the redundant dequeues, if any.
+ //
+ if (r.canClear()) {
+ clear(r);
+
+ boolean found = false;
+ final int s = slot(r.pack, r.position);
+ final Entry<V> e1 = table.get(s);
+ for (Entry<V> n = e1; n != null; n = n.next) {
+ if (n.ref == r) {
+ n.dead = true;
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ table.compareAndSet(s, e1, clean(e1));
+ }
+ }
+ }
+
+ /**
+ * Compute the hash code value for a <code>(PackFile,position)</code> tuple.
+ * <p>
+ * For example, <code>return packHash + (int) (position >>> 4)</code>.
+ * Implementors must override with a suitable hash (for example, a different
+ * right shift on the position).
+ *
+ * @param packHash
+ * hash code for the file being accessed.
+ * @param position
+ * position within the file being accessed.
+ * @return a reasonable hash code mixing the two values.
+ */
+ protected abstract int hash(int packHash, long position);
+
+ private int slot(final PackFile pack, final long position) {
+ return (hash(pack.hash, position) >>> 1) % tableSize;
+ }
+
+ private Lock lock(final PackFile pack, final long position) {
+ return locks[(hash(pack.hash, position) >>> 1) % locks.length];
+ }
+
+ private static <V> Entry<V> clean(Entry<V> top) {
+ while (top != null && top.dead) {
+ top.ref.enqueue();
+ top = top.next;
+ }
+ if (top == null)
+ return null;
+ final Entry<V> n = clean(top.next);
+ return n == top.next ? top : new Entry<V>(n, top.ref);
+ }
+
+ private static class Entry<V> {
+ /** Next entry in the hash table's chain list. */
+ final Entry<V> next;
+
+ /** The referenced object. */
+ final Ref<V> ref;
+
+ /**
+ * Marked true when ref.get() returns null and the ref is dead.
+ * <p>
+ * A true here indicates that the ref is no longer accessible, and that
+ * we therefore need to eventually purge this Entry object out of the
+ * bucket's chain.
+ */
+ volatile boolean dead;
+
+ Entry(final Entry<V> n, final Ref<V> r) {
+ next = n;
+ ref = r;
+ }
+
+ final void kill() {
+ dead = true;
+ ref.enqueue();
+ }
+ }
+
+ /**
+ * A soft reference wrapped around a cached object.
+ *
+ * @param <V>
+ * type of the cached object.
+ */
+ protected static class Ref<V> extends SoftReference<V> {
+ final PackFile pack;
+
+ final long position;
+
+ long lastAccess;
+
+ private boolean cleared;
+
+ protected Ref(final PackFile pack, final long position, final V v,
+ final ReferenceQueue<V> queue) {
+ super(v, queue);
+ this.pack = pack;
+ this.position = position;
+ }
+
+ final synchronized boolean canClear() {
+ if (cleared)
+ return false;
+ cleared = true;
+ return true;
+ }
+ }
+
+ private static final class Lock {
+ // Used only for its implicit monitor.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java
new file mode 100644
index 0000000000..9defcad918
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel.MapMode;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.zip.CRC32;
+import java.util.zip.CheckedOutputStream;
+import java.util.zip.DataFormatException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.PackInvalidException;
+import org.eclipse.jgit.errors.PackMismatchException;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * A Git version 2 pack file representation. A pack file contains Git objects in
+ * delta packed format yielding high compression of lots of object where some
+ * objects are similar.
+ */
+public class PackFile implements Iterable<PackIndex.MutableEntry> {
+ /** Sorts PackFiles to be most recently created to least recently created. */
+ public static Comparator<PackFile> SORT = new Comparator<PackFile>() {
+ public int compare(final PackFile a, final PackFile b) {
+ return b.packLastModified - a.packLastModified;
+ }
+ };
+
+ private final File idxFile;
+
+ private final File packFile;
+
+ final int hash;
+
+ private RandomAccessFile fd;
+
+ long length;
+
+ private int activeWindows;
+
+ private int activeCopyRawData;
+
+ private int packLastModified;
+
+ private volatile boolean invalid;
+
+ private byte[] packChecksum;
+
+ private PackIndex loadedIdx;
+
+ private PackReverseIndex reverseIdx;
+
+ /**
+ * Construct a reader for an existing, pre-indexed packfile.
+ *
+ * @param idxFile
+ * path of the <code>.idx</code> file listing the contents.
+ * @param packFile
+ * path of the <code>.pack</code> file holding the data.
+ */
+ public PackFile(final File idxFile, final File packFile) {
+ this.idxFile = idxFile;
+ this.packFile = packFile;
+ this.packLastModified = (int) (packFile.lastModified() >> 10);
+
+ // Multiply by 31 here so we can more directly combine with another
+ // value in WindowCache.hash(), without doing the multiply there.
+ //
+ hash = System.identityHashCode(this) * 31;
+ length = Long.MAX_VALUE;
+ }
+
+ private synchronized PackIndex idx() throws IOException {
+ if (loadedIdx == null) {
+ if (invalid)
+ throw new PackInvalidException(packFile);
+
+ try {
+ final PackIndex idx = PackIndex.open(idxFile);
+
+ if (packChecksum == null)
+ packChecksum = idx.packChecksum;
+ else if (!Arrays.equals(packChecksum, idx.packChecksum))
+ throw new PackMismatchException("Pack checksum mismatch");
+
+ loadedIdx = idx;
+ } catch (IOException e) {
+ invalid = true;
+ throw e;
+ }
+ }
+ return loadedIdx;
+ }
+
+ final PackedObjectLoader resolveBase(final WindowCursor curs, final long ofs)
+ throws IOException {
+ return reader(curs, ofs);
+ }
+
+ /** @return the File object which locates this pack on disk. */
+ public File getPackFile() {
+ return packFile;
+ }
+
+ /**
+ * Determine if an object is contained within the pack file.
+ * <p>
+ * For performance reasons only the index file is searched; the main pack
+ * content is ignored entirely.
+ * </p>
+ *
+ * @param id
+ * the object to look for. Must not be null.
+ * @return true if the object is in this pack; false otherwise.
+ * @throws IOException
+ * the index file cannot be loaded into memory.
+ */
+ public boolean hasObject(final AnyObjectId id) throws IOException {
+ return idx().hasObject(id);
+ }
+
+ /**
+ * Get an object from this pack.
+ *
+ * @param curs
+ * temporary working space associated with the calling thread.
+ * @param id
+ * the object to obtain from the pack. Must not be null.
+ * @return the object loader for the requested object if it is contained in
+ * this pack; null if the object was not found.
+ * @throws IOException
+ * the pack file or the index could not be read.
+ */
+ public PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id)
+ throws IOException {
+ final long offset = idx().findOffset(id);
+ return 0 < offset ? reader(curs, offset) : null;
+ }
+
+ /**
+ * Close the resources utilized by this repository
+ */
+ public void close() {
+ UnpackedObjectCache.purge(this);
+ WindowCache.purge(this);
+ synchronized (this) {
+ loadedIdx = null;
+ reverseIdx = null;
+ }
+ }
+
+ /**
+ * Provide iterator over entries in associated pack index, that should also
+ * exist in this pack file. Objects returned by such iterator are mutable
+ * during iteration.
+ * <p>
+ * Iterator returns objects in SHA-1 lexicographical order.
+ * </p>
+ *
+ * @return iterator over entries of associated pack index
+ *
+ * @see PackIndex#iterator()
+ */
+ public Iterator<PackIndex.MutableEntry> iterator() {
+ try {
+ return idx().iterator();
+ } catch (IOException e) {
+ return Collections.<PackIndex.MutableEntry> emptyList().iterator();
+ }
+ }
+
+ /**
+ * Obtain the total number of objects available in this pack. This method
+ * relies on pack index, giving number of effectively available objects.
+ *
+ * @return number of objects in index of this pack, likewise in this pack
+ * @throws IOException
+ * the index file cannot be loaded into memory.
+ */
+ long getObjectCount() throws IOException {
+ return idx().getObjectCount();
+ }
+
+ /**
+ * Search for object id with the specified start offset in associated pack
+ * (reverse) index.
+ *
+ * @param offset
+ * start offset of object to find
+ * @return object id for this offset, or null if no object was found
+ * @throws IOException
+ * the index file cannot be loaded into memory.
+ */
+ ObjectId findObjectForOffset(final long offset) throws IOException {
+ return getReverseIdx().findObject(offset);
+ }
+
+ final UnpackedObjectCache.Entry readCache(final long position) {
+ return UnpackedObjectCache.get(this, position);
+ }
+
+ final void saveCache(final long position, final byte[] data, final int type) {
+ UnpackedObjectCache.store(this, position, data, type);
+ }
+
+ final byte[] decompress(final long position, final int totalSize,
+ final WindowCursor curs) throws DataFormatException, IOException {
+ final byte[] dstbuf = new byte[totalSize];
+ if (curs.inflate(this, position, dstbuf, 0) != totalSize)
+ throw new EOFException("Short compressed stream at " + position);
+ return dstbuf;
+ }
+
+ final void copyRawData(final PackedObjectLoader loader,
+ final OutputStream out, final byte buf[], final WindowCursor curs)
+ throws IOException {
+ final long objectOffset = loader.objectOffset;
+ final long dataOffset = loader.dataOffset;
+ final int cnt = (int) (findEndOffset(objectOffset) - dataOffset);
+ final PackIndex idx = idx();
+
+ if (idx.hasCRC32Support()) {
+ final CRC32 crc = new CRC32();
+ int headerCnt = (int) (dataOffset - objectOffset);
+ while (headerCnt > 0) {
+ final int toRead = Math.min(headerCnt, buf.length);
+ readFully(objectOffset, buf, 0, toRead, curs);
+ crc.update(buf, 0, toRead);
+ headerCnt -= toRead;
+ }
+ final CheckedOutputStream crcOut = new CheckedOutputStream(out, crc);
+ copyToStream(dataOffset, buf, cnt, crcOut, curs);
+ final long computed = crc.getValue();
+
+ final ObjectId id = findObjectForOffset(objectOffset);
+ final long expected = idx.findCRC32(id);
+ if (computed != expected)
+ throw new CorruptObjectException("Object at " + dataOffset
+ + " in " + getPackFile() + " has bad zlib stream");
+ } else {
+ try {
+ curs.inflateVerify(this, dataOffset);
+ } catch (DataFormatException dfe) {
+ final CorruptObjectException coe;
+ coe = new CorruptObjectException("Object at " + dataOffset
+ + " in " + getPackFile() + " has bad zlib stream");
+ coe.initCause(dfe);
+ throw coe;
+ }
+ copyToStream(dataOffset, buf, cnt, out, curs);
+ }
+ }
+
+ boolean supportsFastCopyRawData() throws IOException {
+ return idx().hasCRC32Support();
+ }
+
+ boolean invalid() {
+ return invalid;
+ }
+
+ private void readFully(final long position, final byte[] dstbuf,
+ int dstoff, final int cnt, final WindowCursor curs)
+ throws IOException {
+ if (curs.copy(this, position, dstbuf, dstoff, cnt) != cnt)
+ throw new EOFException();
+ }
+
+ private void copyToStream(long position, final byte[] buf, long cnt,
+ final OutputStream out, final WindowCursor curs)
+ throws IOException, EOFException {
+ while (cnt > 0) {
+ final int toRead = (int) Math.min(cnt, buf.length);
+ readFully(position, buf, 0, toRead, curs);
+ position += toRead;
+ cnt -= toRead;
+ out.write(buf, 0, toRead);
+ }
+ }
+
+ synchronized void beginCopyRawData() throws IOException {
+ if (++activeCopyRawData == 1 && activeWindows == 0)
+ doOpen();
+ }
+
+ synchronized void endCopyRawData() {
+ if (--activeCopyRawData == 0 && activeWindows == 0)
+ doClose();
+ }
+
+ synchronized boolean beginWindowCache() throws IOException {
+ if (++activeWindows == 1) {
+ if (activeCopyRawData == 0)
+ doOpen();
+ return true;
+ }
+ return false;
+ }
+
+ synchronized boolean endWindowCache() {
+ final boolean r = --activeWindows == 0;
+ if (r && activeCopyRawData == 0)
+ doClose();
+ return r;
+ }
+
+ private void doOpen() throws IOException {
+ try {
+ if (invalid)
+ throw new PackInvalidException(packFile);
+ fd = new RandomAccessFile(packFile, "r");
+ length = fd.length();
+ onOpenPack();
+ } catch (IOException ioe) {
+ openFail();
+ throw ioe;
+ } catch (RuntimeException re) {
+ openFail();
+ throw re;
+ } catch (Error re) {
+ openFail();
+ throw re;
+ }
+ }
+
+ private void openFail() {
+ activeWindows = 0;
+ activeCopyRawData = 0;
+ invalid = true;
+ doClose();
+ }
+
+ private void doClose() {
+ if (fd != null) {
+ try {
+ fd.close();
+ } catch (IOException err) {
+ // Ignore a close event. We had it open only for reading.
+ // There should not be errors related to network buffers
+ // not flushed, etc.
+ }
+ fd = null;
+ }
+ }
+
+ ByteArrayWindow read(final long pos, int size) throws IOException {
+ if (length < pos + size)
+ size = (int) (length - pos);
+ final byte[] buf = new byte[size];
+ NB.readFully(fd.getChannel(), pos, buf, 0, size);
+ return new ByteArrayWindow(this, pos, buf);
+ }
+
+ ByteWindow mmap(final long pos, int size) throws IOException {
+ if (length < pos + size)
+ size = (int) (length - pos);
+
+ MappedByteBuffer map;
+ try {
+ map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
+ } catch (IOException ioe1) {
+ // The most likely reason this failed is the JVM has run out
+ // of virtual memory. We need to discard quickly, and try to
+ // force the GC to finalize and release any existing mappings.
+ //
+ System.gc();
+ System.runFinalization();
+ map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
+ }
+
+ if (map.hasArray())
+ return new ByteArrayWindow(this, pos, map.array());
+ return new ByteBufferWindow(this, pos, map);
+ }
+
+ private void onOpenPack() throws IOException {
+ final PackIndex idx = idx();
+ final byte[] buf = new byte[20];
+
+ NB.readFully(fd.getChannel(), 0, buf, 0, 12);
+ if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4)
+ throw new IOException("Not a PACK file.");
+ final long vers = NB.decodeUInt32(buf, 4);
+ final long packCnt = NB.decodeUInt32(buf, 8);
+ if (vers != 2 && vers != 3)
+ throw new IOException("Unsupported pack version " + vers + ".");
+
+ if (packCnt != idx.getObjectCount())
+ throw new PackMismatchException("Pack object count mismatch:"
+ + " pack " + packCnt
+ + " index " + idx.getObjectCount()
+ + ": " + getPackFile());
+
+ NB.readFully(fd.getChannel(), length - 20, buf, 0, 20);
+ if (!Arrays.equals(buf, packChecksum))
+ throw new PackMismatchException("Pack checksum mismatch:"
+ + " pack " + ObjectId.fromRaw(buf).name()
+ + " index " + ObjectId.fromRaw(idx.packChecksum).name()
+ + ": " + getPackFile());
+ }
+
+ private PackedObjectLoader reader(final WindowCursor curs,
+ final long objOffset) throws IOException {
+ long pos = objOffset;
+ int p = 0;
+ final byte[] ib = curs.tempId;
+ readFully(pos, ib, 0, 20, curs);
+ int c = ib[p++] & 0xff;
+ final int typeCode = (c >> 4) & 7;
+ long dataSize = c & 15;
+ int shift = 4;
+ while ((c & 0x80) != 0) {
+ c = ib[p++] & 0xff;
+ dataSize += (c & 0x7f) << shift;
+ shift += 7;
+ }
+ pos += p;
+
+ switch (typeCode) {
+ case Constants.OBJ_COMMIT:
+ case Constants.OBJ_TREE:
+ case Constants.OBJ_BLOB:
+ case Constants.OBJ_TAG:
+ return new WholePackedObjectLoader(this, pos, objOffset, typeCode,
+ (int) dataSize);
+
+ case Constants.OBJ_OFS_DELTA: {
+ readFully(pos, ib, 0, 20, curs);
+ p = 0;
+ c = ib[p++] & 0xff;
+ long ofs = c & 127;
+ while ((c & 128) != 0) {
+ ofs += 1;
+ c = ib[p++] & 0xff;
+ ofs <<= 7;
+ ofs += (c & 127);
+ }
+ return new DeltaOfsPackedObjectLoader(this, pos + p, objOffset,
+ (int) dataSize, objOffset - ofs);
+ }
+ case Constants.OBJ_REF_DELTA: {
+ readFully(pos, ib, 0, 20, curs);
+ return new DeltaRefPackedObjectLoader(this, pos + ib.length,
+ objOffset, (int) dataSize, ObjectId.fromRaw(ib));
+ }
+ default:
+ throw new IOException("Unknown object type " + typeCode + ".");
+ }
+ }
+
+ private long findEndOffset(final long startOffset)
+ throws IOException, CorruptObjectException {
+ final long maxOffset = length - 20;
+ return getReverseIdx().findNextOffset(startOffset, maxOffset);
+ }
+
+ private synchronized PackReverseIndex getReverseIdx() throws IOException {
+ if (reverseIdx == null)
+ reverseIdx = new PackReverseIndex(idx());
+ return reverseIdx;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java
new file mode 100644
index 0000000000..733834e5be
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Access path to locate objects by {@link ObjectId} in a {@link PackFile}.
+ * <p>
+ * Indexes are strictly redundant information in that we can rebuild all of the
+ * data held in the index file from the on disk representation of the pack file
+ * itself, but it is faster to access for random requests because data is stored
+ * by ObjectId.
+ * </p>
+ */
+public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> {
+ /**
+ * Open an existing pack <code>.idx</code> file for reading.
+ * <p>
+ * The format of the file will be automatically detected and a proper access
+ * implementation for that format will be constructed and returned to the
+ * caller. The file may or may not be held open by the returned instance.
+ * </p>
+ *
+ * @param idxFile
+ * existing pack .idx to read.
+ * @return access implementation for the requested file.
+ * @throws FileNotFoundException
+ * the file does not exist.
+ * @throws IOException
+ * the file exists but could not be read due to security errors,
+ * unrecognized data version, or unexpected data corruption.
+ */
+ public static PackIndex open(final File idxFile) throws IOException {
+ final FileInputStream fd = new FileInputStream(idxFile);
+ try {
+ final byte[] hdr = new byte[8];
+ NB.readFully(fd, hdr, 0, hdr.length);
+ if (isTOC(hdr)) {
+ final int v = NB.decodeInt32(hdr, 4);
+ switch (v) {
+ case 2:
+ return new PackIndexV2(fd);
+ default:
+ throw new IOException("Unsupported pack index version " + v);
+ }
+ }
+ return new PackIndexV1(fd, hdr);
+ } catch (IOException ioe) {
+ final String path = idxFile.getAbsolutePath();
+ final IOException err;
+ err = new IOException("Unreadable pack index: " + path);
+ err.initCause(ioe);
+ throw err;
+ } finally {
+ try {
+ fd.close();
+ } catch (IOException err2) {
+ // ignore
+ }
+ }
+ }
+
+ private static boolean isTOC(final byte[] h) {
+ final byte[] toc = PackIndexWriter.TOC;
+ for (int i = 0; i < toc.length; i++)
+ if (h[i] != toc[i])
+ return false;
+ return true;
+ }
+
+ /** Footer checksum applied on the bottom of the pack file. */
+ protected byte[] packChecksum;
+
+ /**
+ * Determine if an object is contained within the pack file.
+ *
+ * @param id
+ * the object to look for. Must not be null.
+ * @return true if the object is listed in this index; false otherwise.
+ */
+ public boolean hasObject(final AnyObjectId id) {
+ return findOffset(id) != -1;
+ }
+
+ /**
+ * Provide iterator that gives access to index entries. Note, that iterator
+ * returns reference to mutable object, the same reference in each call -
+ * for performance reason. If client needs immutable objects, it must copy
+ * returned object on its own.
+ * <p>
+ * Iterator returns objects in SHA-1 lexicographical order.
+ * </p>
+ *
+ * @return iterator over pack index entries
+ */
+ public abstract Iterator<MutableEntry> iterator();
+
+ /**
+ * Obtain the total number of objects described by this index.
+ *
+ * @return number of objects in this index, and likewise in the associated
+ * pack that this index was generated from.
+ */
+ abstract long getObjectCount();
+
+ /**
+ * Obtain the total number of objects needing 64 bit offsets.
+ *
+ * @return number of objects in this index using a 64 bit offset; that is an
+ * object positioned after the 2 GB position within the file.
+ */
+ abstract long getOffset64Count();
+
+ /**
+ * Get ObjectId for the n-th object entry returned by {@link #iterator()}.
+ * <p>
+ * This method is a constant-time replacement for the following loop:
+ *
+ * <pre>
+ * Iterator&lt;MutableEntry&gt; eItr = index.iterator();
+ * int curPosition = 0;
+ * while (eItr.hasNext() &amp;&amp; curPosition++ &lt; nthPosition)
+ * eItr.next();
+ * ObjectId result = eItr.next().toObjectId();
+ * </pre>
+ *
+ * @param nthPosition
+ * position within the traversal of {@link #iterator()} that the
+ * caller needs the object for. The first returned
+ * {@link MutableEntry} is 0, the second is 1, etc.
+ * @return the ObjectId for the corresponding entry.
+ */
+ abstract ObjectId getObjectId(long nthPosition);
+
+ /**
+ * Get ObjectId for the n-th object entry returned by {@link #iterator()}.
+ * <p>
+ * This method is a constant-time replacement for the following loop:
+ *
+ * <pre>
+ * Iterator&lt;MutableEntry&gt; eItr = index.iterator();
+ * int curPosition = 0;
+ * while (eItr.hasNext() &amp;&amp; curPosition++ &lt; nthPosition)
+ * eItr.next();
+ * ObjectId result = eItr.next().toObjectId();
+ * </pre>
+ *
+ * @param nthPosition
+ * unsigned 32 bit position within the traversal of
+ * {@link #iterator()} that the caller needs the object for. The
+ * first returned {@link MutableEntry} is 0, the second is 1,
+ * etc. Positions past 2**31-1 are negative, but still valid.
+ * @return the ObjectId for the corresponding entry.
+ */
+ final ObjectId getObjectId(final int nthPosition) {
+ if (nthPosition >= 0)
+ return getObjectId((long) nthPosition);
+ final int u31 = nthPosition >>> 1;
+ final int one = nthPosition & 1;
+ return getObjectId(((long) u31) << 1 | one);
+ }
+
+ /**
+ * Locate the file offset position for the requested object.
+ *
+ * @param objId
+ * name of the object to locate within the pack.
+ * @return offset of the object's header and compressed content; -1 if the
+ * object does not exist in this index and is thus not stored in the
+ * associated pack.
+ */
+ abstract long findOffset(AnyObjectId objId);
+
+ /**
+ * Retrieve stored CRC32 checksum of the requested object raw-data
+ * (including header).
+ *
+ * @param objId
+ * id of object to look for
+ * @return CRC32 checksum of specified object (at 32 less significant bits)
+ * @throws MissingObjectException
+ * when requested ObjectId was not found in this index
+ * @throws UnsupportedOperationException
+ * when this index doesn't support CRC32 checksum
+ */
+ abstract long findCRC32(AnyObjectId objId) throws MissingObjectException,
+ UnsupportedOperationException;
+
+ /**
+ * Check whether this index supports (has) CRC32 checksums for objects.
+ *
+ * @return true if CRC32 is stored, false otherwise
+ */
+ abstract boolean hasCRC32Support();
+
+ /**
+ * Represent mutable entry of pack index consisting of object id and offset
+ * in pack (both mutable).
+ *
+ */
+ public static class MutableEntry {
+ final MutableObjectId idBuffer = new MutableObjectId();
+
+ long offset;
+
+ /**
+ * Returns offset for this index object entry
+ *
+ * @return offset of this object in a pack file
+ */
+ public long getOffset() {
+ return offset;
+ }
+
+ /** @return hex string describing the object id of this entry. */
+ public String name() {
+ ensureId();
+ return idBuffer.name();
+ }
+
+ /** @return a copy of the object id. */
+ public ObjectId toObjectId() {
+ ensureId();
+ return idBuffer.toObjectId();
+ }
+
+ /** @return a complete copy of this entry, that won't modify */
+ public MutableEntry cloneEntry() {
+ final MutableEntry r = new MutableEntry();
+ ensureId();
+ r.idBuffer.w1 = idBuffer.w1;
+ r.idBuffer.w2 = idBuffer.w2;
+ r.idBuffer.w3 = idBuffer.w3;
+ r.idBuffer.w4 = idBuffer.w4;
+ r.idBuffer.w5 = idBuffer.w5;
+ r.offset = offset;
+ return r;
+ }
+
+ void ensureId() {
+ // Override in implementations.
+ }
+ }
+
+ abstract class EntriesIterator implements Iterator<MutableEntry> {
+ protected final MutableEntry entry = initEntry();
+
+ protected long returnedNumber = 0;
+
+ protected abstract MutableEntry initEntry();
+
+ public boolean hasNext() {
+ return returnedNumber < getObjectCount();
+ }
+
+ /**
+ * Implementation must update {@link #returnedNumber} before returning
+ * element.
+ */
+ public abstract MutableEntry next();
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java
new file mode 100644
index 0000000000..a7bf99e2fe
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.util.NB;
+
+class PackIndexV1 extends PackIndex {
+ private static final int IDX_HDR_LEN = 256 * 4;
+
+ private final long[] idxHeader;
+
+ private byte[][] idxdata;
+
+ private long objectCnt;
+
+ PackIndexV1(final InputStream fd, final byte[] hdr)
+ throws CorruptObjectException, IOException {
+ final byte[] fanoutTable = new byte[IDX_HDR_LEN];
+ System.arraycopy(hdr, 0, fanoutTable, 0, hdr.length);
+ NB.readFully(fd, fanoutTable, hdr.length, IDX_HDR_LEN - hdr.length);
+
+ idxHeader = new long[256]; // really unsigned 32-bit...
+ for (int k = 0; k < idxHeader.length; k++)
+ idxHeader[k] = NB.decodeUInt32(fanoutTable, k * 4);
+ idxdata = new byte[idxHeader.length][];
+ for (int k = 0; k < idxHeader.length; k++) {
+ int n;
+ if (k == 0) {
+ n = (int) (idxHeader[k]);
+ } else {
+ n = (int) (idxHeader[k] - idxHeader[k - 1]);
+ }
+ if (n > 0) {
+ idxdata[k] = new byte[n * (Constants.OBJECT_ID_LENGTH + 4)];
+ NB.readFully(fd, idxdata[k], 0, idxdata[k].length);
+ }
+ }
+ objectCnt = idxHeader[255];
+
+ packChecksum = new byte[20];
+ NB.readFully(fd, packChecksum, 0, packChecksum.length);
+ }
+
+ long getObjectCount() {
+ return objectCnt;
+ }
+
+ @Override
+ long getOffset64Count() {
+ long n64 = 0;
+ for (final MutableEntry e : this) {
+ if (e.getOffset() >= Integer.MAX_VALUE)
+ n64++;
+ }
+ return n64;
+ }
+
+ @Override
+ ObjectId getObjectId(final long nthPosition) {
+ int levelOne = Arrays.binarySearch(idxHeader, nthPosition + 1);
+ long base;
+ if (levelOne >= 0) {
+ // If we hit the bucket exactly the item is in the bucket, or
+ // any bucket before it which has the same object count.
+ //
+ base = idxHeader[levelOne];
+ while (levelOne > 0 && base == idxHeader[levelOne - 1])
+ levelOne--;
+ } else {
+ // The item is in the bucket we would insert it into.
+ //
+ levelOne = -(levelOne + 1);
+ }
+
+ base = levelOne > 0 ? idxHeader[levelOne - 1] : 0;
+ final int p = (int) (nthPosition - base);
+ final int dataIdx = ((4 + Constants.OBJECT_ID_LENGTH) * p) + 4;
+ return ObjectId.fromRaw(idxdata[levelOne], dataIdx);
+ }
+
+ long findOffset(final AnyObjectId objId) {
+ final int levelOne = objId.getFirstByte();
+ byte[] data = idxdata[levelOne];
+ if (data == null)
+ return -1;
+ int high = data.length / (4 + Constants.OBJECT_ID_LENGTH);
+ int low = 0;
+ do {
+ final int mid = (low + high) >>> 1;
+ final int pos = ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4;
+ final int cmp = objId.compareTo(data, pos);
+ if (cmp < 0)
+ high = mid;
+ else if (cmp == 0) {
+ int b0 = data[pos - 4] & 0xff;
+ int b1 = data[pos - 3] & 0xff;
+ int b2 = data[pos - 2] & 0xff;
+ int b3 = data[pos - 1] & 0xff;
+ return (((long) b0) << 24) | (b1 << 16) | (b2 << 8) | (b3);
+ } else
+ low = mid + 1;
+ } while (low < high);
+ return -1;
+ }
+
+ @Override
+ long findCRC32(AnyObjectId objId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ boolean hasCRC32Support() {
+ return false;
+ }
+
+ public Iterator<MutableEntry> iterator() {
+ return new IndexV1Iterator();
+ }
+
+ private class IndexV1Iterator extends EntriesIterator {
+ private int levelOne;
+
+ private int levelTwo;
+
+ @Override
+ protected MutableEntry initEntry() {
+ return new MutableEntry() {
+ protected void ensureId() {
+ idBuffer.fromRaw(idxdata[levelOne], levelTwo
+ - Constants.OBJECT_ID_LENGTH);
+ }
+ };
+ }
+
+ public MutableEntry next() {
+ for (; levelOne < idxdata.length; levelOne++) {
+ if (idxdata[levelOne] == null)
+ continue;
+ if (levelTwo < idxdata[levelOne].length) {
+ entry.offset = NB.decodeUInt32(idxdata[levelOne], levelTwo);
+ levelTwo += Constants.OBJECT_ID_LENGTH + 4;
+ returnedNumber++;
+ return entry;
+ }
+ levelTwo = 0;
+ }
+ throw new NoSuchElementException();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java
new file mode 100644
index 0000000000..c37ce646de
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.util.NB;
+
+/** Support for the pack index v2 format. */
+class PackIndexV2 extends PackIndex {
+ private static final long IS_O64 = 1L << 31;
+
+ private static final int FANOUT = 256;
+
+ private static final int[] NO_INTS = {};
+
+ private static final byte[] NO_BYTES = {};
+
+ private long objectCnt;
+
+ private final long[] fanoutTable;
+
+ /** 256 arrays of contiguous object names. */
+ private int[][] names;
+
+ /** 256 arrays of the 32 bit offset data, matching {@link #names}. */
+ private byte[][] offset32;
+
+ /** 256 arrays of the CRC-32 of objects, matching {@link #names}. */
+ private byte[][] crc32;
+
+ /** 64 bit offset table. */
+ private byte[] offset64;
+
+ PackIndexV2(final InputStream fd) throws IOException {
+ final byte[] fanoutRaw = new byte[4 * FANOUT];
+ NB.readFully(fd, fanoutRaw, 0, fanoutRaw.length);
+ fanoutTable = new long[FANOUT];
+ for (int k = 0; k < FANOUT; k++)
+ fanoutTable[k] = NB.decodeUInt32(fanoutRaw, k * 4);
+ objectCnt = fanoutTable[FANOUT - 1];
+
+ names = new int[FANOUT][];
+ offset32 = new byte[FANOUT][];
+ crc32 = new byte[FANOUT][];
+
+ // Object name table. The size we can permit per fan-out bucket
+ // is limited to Java's 2 GB per byte array limitation. That is
+ // no more than 107,374,182 objects per fan-out.
+ //
+ for (int k = 0; k < FANOUT; k++) {
+ final long bucketCnt;
+ if (k == 0)
+ bucketCnt = fanoutTable[k];
+ else
+ bucketCnt = fanoutTable[k] - fanoutTable[k - 1];
+
+ if (bucketCnt == 0) {
+ names[k] = NO_INTS;
+ offset32[k] = NO_BYTES;
+ crc32[k] = NO_BYTES;
+ continue;
+ }
+
+ final long nameLen = bucketCnt * Constants.OBJECT_ID_LENGTH;
+ if (nameLen > Integer.MAX_VALUE)
+ throw new IOException("Index file is too large for jgit");
+
+ final int intNameLen = (int) nameLen;
+ final byte[] raw = new byte[intNameLen];
+ final int[] bin = new int[intNameLen >>> 2];
+ NB.readFully(fd, raw, 0, raw.length);
+ for (int i = 0; i < bin.length; i++)
+ bin[i] = NB.decodeInt32(raw, i << 2);
+
+ names[k] = bin;
+ offset32[k] = new byte[(int) (bucketCnt * 4)];
+ crc32[k] = new byte[(int) (bucketCnt * 4)];
+ }
+
+ // CRC32 table.
+ for (int k = 0; k < FANOUT; k++)
+ NB.readFully(fd, crc32[k], 0, crc32[k].length);
+
+ // 32 bit offset table. Any entries with the most significant bit
+ // set require a 64 bit offset entry in another table.
+ //
+ int o64cnt = 0;
+ for (int k = 0; k < FANOUT; k++) {
+ final byte[] ofs = offset32[k];
+ NB.readFully(fd, ofs, 0, ofs.length);
+ for (int p = 0; p < ofs.length; p += 4)
+ if (ofs[p] < 0)
+ o64cnt++;
+ }
+
+ // 64 bit offset table. Most objects should not require an entry.
+ //
+ if (o64cnt > 0) {
+ offset64 = new byte[o64cnt * 8];
+ NB.readFully(fd, offset64, 0, offset64.length);
+ } else {
+ offset64 = NO_BYTES;
+ }
+
+ packChecksum = new byte[20];
+ NB.readFully(fd, packChecksum, 0, packChecksum.length);
+ }
+
+ @Override
+ long getObjectCount() {
+ return objectCnt;
+ }
+
+ @Override
+ long getOffset64Count() {
+ return offset64.length / 8;
+ }
+
+ @Override
+ ObjectId getObjectId(final long nthPosition) {
+ int levelOne = Arrays.binarySearch(fanoutTable, nthPosition + 1);
+ long base;
+ if (levelOne >= 0) {
+ // If we hit the bucket exactly the item is in the bucket, or
+ // any bucket before it which has the same object count.
+ //
+ base = fanoutTable[levelOne];
+ while (levelOne > 0 && base == fanoutTable[levelOne - 1])
+ levelOne--;
+ } else {
+ // The item is in the bucket we would insert it into.
+ //
+ levelOne = -(levelOne + 1);
+ }
+
+ base = levelOne > 0 ? fanoutTable[levelOne - 1] : 0;
+ final int p = (int) (nthPosition - base);
+ final int p4 = p << 2;
+ return ObjectId.fromRaw(names[levelOne], p4 + p); // p * 5
+ }
+
+ @Override
+ long findOffset(final AnyObjectId objId) {
+ final int levelOne = objId.getFirstByte();
+ final int levelTwo = binarySearchLevelTwo(objId, levelOne);
+ if (levelTwo == -1)
+ return -1;
+ final long p = NB.decodeUInt32(offset32[levelOne], levelTwo << 2);
+ if ((p & IS_O64) != 0)
+ return NB.decodeUInt64(offset64, (8 * (int) (p & ~IS_O64)));
+ return p;
+ }
+
+ @Override
+ long findCRC32(AnyObjectId objId) throws MissingObjectException {
+ final int levelOne = objId.getFirstByte();
+ final int levelTwo = binarySearchLevelTwo(objId, levelOne);
+ if (levelTwo == -1)
+ throw new MissingObjectException(objId.copy(), "unknown");
+ return NB.decodeUInt32(crc32[levelOne], levelTwo << 2);
+ }
+
+ @Override
+ boolean hasCRC32Support() {
+ return true;
+ }
+
+ public Iterator<MutableEntry> iterator() {
+ return new EntriesIteratorV2();
+ }
+
+ private int binarySearchLevelTwo(final AnyObjectId objId, final int levelOne) {
+ final int[] data = names[levelOne];
+ int high = offset32[levelOne].length >>> 2;
+ if (high == 0)
+ return -1;
+ int low = 0;
+ do {
+ final int mid = (low + high) >>> 1;
+ final int mid4 = mid << 2;
+ final int cmp;
+
+ cmp = objId.compareTo(data, mid4 + mid); // mid * 5
+ if (cmp < 0)
+ high = mid;
+ else if (cmp == 0) {
+ return mid;
+ } else
+ low = mid + 1;
+ } while (low < high);
+ return -1;
+ }
+
+ private class EntriesIteratorV2 extends EntriesIterator {
+ private int levelOne;
+
+ private int levelTwo;
+
+ @Override
+ protected MutableEntry initEntry() {
+ return new MutableEntry() {
+ protected void ensureId() {
+ idBuffer.fromRaw(names[levelOne], levelTwo
+ - Constants.OBJECT_ID_LENGTH / 4);
+ }
+ };
+ }
+
+ public MutableEntry next() {
+ for (; levelOne < names.length; levelOne++) {
+ if (levelTwo < names[levelOne].length) {
+ int idx = levelTwo / (Constants.OBJECT_ID_LENGTH / 4) * 4;
+ long offset = NB.decodeUInt32(offset32[levelOne], idx);
+ if ((offset & IS_O64) != 0) {
+ idx = (8 * (int) (offset & ~IS_O64));
+ offset = NB.decodeUInt64(offset64, idx);
+ }
+ entry.offset = offset;
+
+ levelTwo += Constants.OBJECT_ID_LENGTH / 4;
+ returnedNumber++;
+ return entry;
+ }
+ levelTwo = 0;
+ }
+ throw new NoSuchElementException();
+ }
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java
new file mode 100644
index 0000000000..5fcf71a781
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.DigestOutputStream;
+import java.util.List;
+
+import org.eclipse.jgit.transport.PackedObjectInfo;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Creates a table of contents to support random access by {@link PackFile}.
+ * <p>
+ * Pack index files (the <code>.idx</code> suffix in a pack file pair)
+ * provides random access to any object in the pack by associating an ObjectId
+ * to the byte offset within the pack where the object's data can be read.
+ */
+public abstract class PackIndexWriter {
+ /** Magic constant indicating post-version 1 format. */
+ protected static final byte[] TOC = { -1, 't', 'O', 'c' };
+
+ /**
+ * Create a new writer for the oldest (most widely understood) format.
+ * <p>
+ * This method selects an index format that can accurate describe the
+ * supplied objects and that will be the most compatible format with older
+ * Git implementations.
+ * <p>
+ * Index version 1 is widely recognized by all Git implementations, but
+ * index version 2 (and later) is not as well recognized as it was
+ * introduced more than a year later. Index version 1 can only be used if
+ * the resulting pack file is under 4 gigabytes in size; packs larger than
+ * that limit must use index version 2.
+ *
+ * @param dst
+ * the stream the index data will be written to. If not already
+ * buffered it will be automatically wrapped in a buffered
+ * stream. Callers are always responsible for closing the stream.
+ * @param objs
+ * the objects the caller needs to store in the index. Entries
+ * will be examined until a format can be conclusively selected.
+ * @return a new writer to output an index file of the requested format to
+ * the supplied stream.
+ * @throws IllegalArgumentException
+ * no recognized pack index version can support the supplied
+ * objects. This is likely a bug in the implementation.
+ */
+ @SuppressWarnings("fallthrough")
+ public static PackIndexWriter createOldestPossible(final OutputStream dst,
+ final List<? extends PackedObjectInfo> objs) {
+ int version = 1;
+ LOOP: for (final PackedObjectInfo oe : objs) {
+ switch (version) {
+ case 1:
+ if (PackIndexWriterV1.canStore(oe))
+ continue;
+ version = 2;
+ case 2:
+ break LOOP;
+ }
+ }
+ return createVersion(dst, version);
+ }
+
+ /**
+ * Create a new writer instance for a specific index format version.
+ *
+ * @param dst
+ * the stream the index data will be written to. If not already
+ * buffered it will be automatically wrapped in a buffered
+ * stream. Callers are always responsible for closing the stream.
+ * @param version
+ * index format version number required by the caller. Exactly
+ * this formatted version will be written.
+ * @return a new writer to output an index file of the requested format to
+ * the supplied stream.
+ * @throws IllegalArgumentException
+ * the version requested is not supported by this
+ * implementation.
+ */
+ public static PackIndexWriter createVersion(final OutputStream dst,
+ final int version) {
+ switch (version) {
+ case 1:
+ return new PackIndexWriterV1(dst);
+ case 2:
+ return new PackIndexWriterV2(dst);
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported pack index version " + version);
+ }
+ }
+
+ /** The index data stream we are responsible for creating. */
+ protected final DigestOutputStream out;
+
+ /** A temporary buffer for use during IO to {link #out}. */
+ protected final byte[] tmp;
+
+ /** The entries this writer must pack. */
+ protected List<? extends PackedObjectInfo> entries;
+
+ /** SHA-1 checksum for the entire pack data. */
+ protected byte[] packChecksum;
+
+ /**
+ * Create a new writer instance.
+ *
+ * @param dst
+ * the stream this instance outputs to. If not already buffered
+ * it will be automatically wrapped in a buffered stream.
+ */
+ protected PackIndexWriter(final OutputStream dst) {
+ out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst
+ : new BufferedOutputStream(dst), Constants.newMessageDigest());
+ tmp = new byte[4 + Constants.OBJECT_ID_LENGTH];
+ }
+
+ /**
+ * Write all object entries to the index stream.
+ * <p>
+ * After writing the stream passed to the factory is flushed but remains
+ * open. Callers are always responsible for closing the output stream.
+ *
+ * @param toStore
+ * sorted list of objects to store in the index. The caller must
+ * have previously sorted the list using {@link PackedObjectInfo}'s
+ * native {@link Comparable} implementation.
+ * @param packDataChecksum
+ * checksum signature of the entire pack data content. This is
+ * traditionally the last 20 bytes of the pack file's own stream.
+ * @throws IOException
+ * an error occurred while writing to the output stream, or this
+ * index format cannot store the object data supplied.
+ */
+ public void write(final List<? extends PackedObjectInfo> toStore,
+ final byte[] packDataChecksum) throws IOException {
+ entries = toStore;
+ packChecksum = packDataChecksum;
+ writeImpl();
+ out.flush();
+ }
+
+ /**
+ * Writes the index file to {@link #out}.
+ * <p>
+ * Implementations should go something like:
+ *
+ * <pre>
+ * writeFanOutTable();
+ * for (final PackedObjectInfo po : entries)
+ * writeOneEntry(po);
+ * writeChecksumFooter();
+ * </pre>
+ *
+ * <p>
+ * Where the logic for <code>writeOneEntry</code> is specific to the index
+ * format in use. Additional headers/footers may be used if necessary and
+ * the {@link #entries} collection may be iterated over more than once if
+ * necessary. Implementors therefore have complete control over the data.
+ *
+ * @throws IOException
+ * an error occurred while writing to the output stream, or this
+ * index format cannot store the object data supplied.
+ */
+ protected abstract void writeImpl() throws IOException;
+
+ /**
+ * Output the version 2 (and later) TOC header, with version number.
+ * <p>
+ * Post version 1 all index files start with a TOC header that makes the
+ * file an invalid version 1 file, and then includes the version number.
+ * This header is necessary to recognize a version 1 from a version 2
+ * formatted index.
+ *
+ * @param version
+ * version number of this index format being written.
+ * @throws IOException
+ * an error occurred while writing to the output stream.
+ */
+ protected void writeTOC(final int version) throws IOException {
+ out.write(TOC);
+ NB.encodeInt32(tmp, 0, version);
+ out.write(tmp, 0, 4);
+ }
+
+ /**
+ * Output the standard 256 entry first-level fan-out table.
+ * <p>
+ * The fan-out table is 4 KB in size, holding 256 32-bit unsigned integer
+ * counts. Each count represents the number of objects within this index
+ * whose {@link ObjectId#getFirstByte()} matches the count's position in the
+ * fan-out table.
+ *
+ * @throws IOException
+ * an error occurred while writing to the output stream.
+ */
+ protected void writeFanOutTable() throws IOException {
+ final int[] fanout = new int[256];
+ for (final PackedObjectInfo po : entries)
+ fanout[po.getFirstByte() & 0xff]++;
+ for (int i = 1; i < 256; i++)
+ fanout[i] += fanout[i - 1];
+ for (final int n : fanout) {
+ NB.encodeInt32(tmp, 0, n);
+ out.write(tmp, 0, 4);
+ }
+ }
+
+ /**
+ * Output the standard two-checksum index footer.
+ * <p>
+ * The standard footer contains two checksums (20 byte SHA-1 values):
+ * <ol>
+ * <li>Pack data checksum - taken from the last 20 bytes of the pack file.</li>
+ * <li>Index data checksum - checksum of all index bytes written, including
+ * the pack data checksum above.</li>
+ * </ol>
+ *
+ * @throws IOException
+ * an error occurred while writing to the output stream.
+ */
+ protected void writeChecksumFooter() throws IOException {
+ out.write(packChecksum);
+ out.on(false);
+ out.write(out.getMessageDigest().digest());
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java
new file mode 100644
index 0000000000..b3be5480c9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.transport.PackedObjectInfo;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Creates the version 1 (old style) pack table of contents files.
+ *
+ * @see PackIndexWriter
+ * @see PackIndexV1
+ */
+class PackIndexWriterV1 extends PackIndexWriter {
+ static boolean canStore(final PackedObjectInfo oe) {
+ // We are limited to 4 GB per pack as offset is 32 bit unsigned int.
+ //
+ return oe.getOffset() >>> 1 < Integer.MAX_VALUE;
+ }
+
+ PackIndexWriterV1(final OutputStream dst) {
+ super(dst);
+ }
+
+ @Override
+ protected void writeImpl() throws IOException {
+ writeFanOutTable();
+
+ for (final PackedObjectInfo oe : entries) {
+ if (!canStore(oe))
+ throw new IOException("Pack too large for index version 1");
+ NB.encodeInt32(tmp, 0, (int) oe.getOffset());
+ oe.copyRawTo(tmp, 4);
+ out.write(tmp);
+ }
+
+ writeChecksumFooter();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java
new file mode 100644
index 0000000000..b6ac7b89e3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.transport.PackedObjectInfo;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Creates the version 2 pack table of contents files.
+ *
+ * @see PackIndexWriter
+ * @see PackIndexV2
+ */
+class PackIndexWriterV2 extends PackIndexWriter {
+ PackIndexWriterV2(final OutputStream dst) {
+ super(dst);
+ }
+
+ @Override
+ protected void writeImpl() throws IOException {
+ writeTOC(2);
+ writeFanOutTable();
+ writeObjectNames();
+ writeCRCs();
+ writeOffset32();
+ writeOffset64();
+ writeChecksumFooter();
+ }
+
+ private void writeObjectNames() throws IOException {
+ for (final PackedObjectInfo oe : entries)
+ oe.copyRawTo(out);
+ }
+
+ private void writeCRCs() throws IOException {
+ for (final PackedObjectInfo oe : entries) {
+ NB.encodeInt32(tmp, 0, oe.getCRC());
+ out.write(tmp, 0, 4);
+ }
+ }
+
+ private void writeOffset32() throws IOException {
+ int o64 = 0;
+ for (final PackedObjectInfo oe : entries) {
+ final long o = oe.getOffset();
+ if (o < Integer.MAX_VALUE)
+ NB.encodeInt32(tmp, 0, (int) o);
+ else
+ NB.encodeInt32(tmp, 0, (1 << 31) | o64++);
+ out.write(tmp, 0, 4);
+ }
+ }
+
+ private void writeOffset64() throws IOException {
+ for (final PackedObjectInfo oe : entries) {
+ final long o = oe.getOffset();
+ if (o > Integer.MAX_VALUE) {
+ NB.encodeInt64(tmp, 0, o);
+ out.write(tmp, 0, 8);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java
new file mode 100644
index 0000000000..de8e3fa637
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.IOException;
+
+/** Keeps track of a {@link PackFile}'s associated <code>.keep</code> file. */
+public class PackLock {
+ private final File keepFile;
+
+ /**
+ * Create a new lock for a pack file.
+ *
+ * @param packFile
+ * location of the <code>pack-*.pack</code> file.
+ */
+ public PackLock(final File packFile) {
+ final File p = packFile.getParentFile();
+ final String n = packFile.getName();
+ keepFile = new File(p, n.substring(0, n.length() - 5) + ".keep");
+ }
+
+ /**
+ * Create the <code>pack-*.keep</code> file, with the given message.
+ *
+ * @param msg
+ * message to store in the file.
+ * @return true if the keep file was successfully written; false otherwise.
+ * @throws IOException
+ * the keep file could not be written.
+ */
+ public boolean lock(String msg) throws IOException {
+ if (msg == null)
+ return false;
+ if (!msg.endsWith("\n"))
+ msg += "\n";
+ final LockFile lf = new LockFile(keepFile);
+ if (!lf.lock())
+ return false;
+ lf.write(Constants.encode(msg));
+ return lf.commit();
+ }
+
+ /** Remove the <code>.keep</code> file that holds this pack in place. */
+ public void unlock() {
+ keepFile.delete();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java
new file mode 100644
index 0000000000..a348f1e547
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.util.zip.CRC32;
+
+/** Custom output stream to support {@link PackWriter}. */
+final class PackOutputStream extends OutputStream {
+ private final OutputStream out;
+
+ private final CRC32 crc = new CRC32();
+
+ private final MessageDigest md = Constants.newMessageDigest();
+
+ private long count;
+
+ PackOutputStream(final OutputStream out) {
+ this.out = out;
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ out.write(b);
+ crc.update(b);
+ md.update((byte) b);
+ count++;
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len)
+ throws IOException {
+ out.write(b, off, len);
+ crc.update(b, off, len);
+ md.update(b, off, len);
+ count += len;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ /** @return total number of bytes written since stream start. */
+ long length() {
+ return count;
+ }
+
+ /** @return obtain the current CRC32 register. */
+ int getCRC32() {
+ return (int) crc.getValue();
+ }
+
+ /** Reinitialize the CRC32 register for a new region. */
+ void resetCRC32() {
+ crc.reset();
+ }
+
+ /** @return obtain the current SHA-1 digest. */
+ byte[] getDigest() {
+ return md.digest();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java
new file mode 100644
index 0000000000..c0ed7b29a6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.util.Arrays;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.lib.PackIndex.MutableEntry;
+
+/**
+ * <p>
+ * Reverse index for forward pack index. Provides operations based on offset
+ * instead of object id. Such offset-based reverse lookups are performed in
+ * O(log n) time.
+ * </p>
+ *
+ * @see PackIndex
+ * @see PackFile
+ */
+class PackReverseIndex {
+ /** Index we were created from, and that has our ObjectId data. */
+ private final PackIndex index;
+
+ /**
+ * (offset31, truly) Offsets accommodating in 31 bits.
+ */
+ private final int offsets32[];
+
+ /**
+ * Offsets not accommodating in 31 bits.
+ */
+ private final long offsets64[];
+
+ /** Position of the corresponding {@link #offsets32} in {@link #index}. */
+ private final int nth32[];
+
+ /** Position of the corresponding {@link #offsets64} in {@link #index}. */
+ private final int nth64[];
+
+ /**
+ * Create reverse index from straight/forward pack index, by indexing all
+ * its entries.
+ *
+ * @param packIndex
+ * forward index - entries to (reverse) index.
+ */
+ PackReverseIndex(final PackIndex packIndex) {
+ index = packIndex;
+
+ final long cnt = index.getObjectCount();
+ final long n64 = index.getOffset64Count();
+ final long n32 = cnt - n64;
+ if (n32 > Integer.MAX_VALUE || n64 > Integer.MAX_VALUE
+ || cnt > 0xffffffffL)
+ throw new IllegalArgumentException(
+ "Huge indexes are not supported by jgit, yet");
+
+ offsets32 = new int[(int) n32];
+ offsets64 = new long[(int) n64];
+ nth32 = new int[offsets32.length];
+ nth64 = new int[offsets64.length];
+
+ int i32 = 0;
+ int i64 = 0;
+ for (final MutableEntry me : index) {
+ final long o = me.getOffset();
+ if (o < Integer.MAX_VALUE)
+ offsets32[i32++] = (int) o;
+ else
+ offsets64[i64++] = o;
+ }
+
+ Arrays.sort(offsets32);
+ Arrays.sort(offsets64);
+
+ int nth = 0;
+ for (final MutableEntry me : index) {
+ final long o = me.getOffset();
+ if (o < Integer.MAX_VALUE)
+ nth32[Arrays.binarySearch(offsets32, (int) o)] = nth++;
+ else
+ nth64[Arrays.binarySearch(offsets64, o)] = nth++;
+ }
+ }
+
+ /**
+ * Search for object id with the specified start offset in this pack
+ * (reverse) index.
+ *
+ * @param offset
+ * start offset of object to find.
+ * @return object id for this offset, or null if no object was found.
+ */
+ ObjectId findObject(final long offset) {
+ if (offset <= Integer.MAX_VALUE) {
+ final int i32 = Arrays.binarySearch(offsets32, (int) offset);
+ if (i32 < 0)
+ return null;
+ return index.getObjectId(nth32[i32]);
+ } else {
+ final int i64 = Arrays.binarySearch(offsets64, offset);
+ if (i64 < 0)
+ return null;
+ return index.getObjectId(nth64[i64]);
+ }
+ }
+
+ /**
+ * Search for the next offset to the specified offset in this pack (reverse)
+ * index.
+ *
+ * @param offset
+ * start offset of previous object (must be valid-existing
+ * offset).
+ * @param maxOffset
+ * maximum offset in a pack (returned when there is no next
+ * offset).
+ * @return offset of the next object in a pack or maxOffset if provided
+ * offset was the last one.
+ * @throws CorruptObjectException
+ * when there is no object with the provided offset.
+ */
+ long findNextOffset(final long offset, final long maxOffset)
+ throws CorruptObjectException {
+ if (offset <= Integer.MAX_VALUE) {
+ final int i32 = Arrays.binarySearch(offsets32, (int) offset);
+ if (i32 < 0)
+ throw new CorruptObjectException(
+ "Can't find object in (reverse) pack index for the specified offset "
+ + offset);
+
+ if (i32 + 1 == offsets32.length) {
+ if (offsets64.length > 0)
+ return offsets64[0];
+ return maxOffset;
+ }
+ return offsets32[i32 + 1];
+ } else {
+ final int i64 = Arrays.binarySearch(offsets64, offset);
+ if (i64 < 0)
+ throw new CorruptObjectException(
+ "Can't find object in (reverse) pack index for the specified offset "
+ + offset);
+
+ if (i64 + 1 == offsets64.length)
+ return maxOffset;
+ return offsets64[i64 + 1];
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java
new file mode 100644
index 0000000000..6162deab7f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java
@@ -0,0 +1,1045 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.zip.Deflater;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.transport.PackedObjectInfo;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * <p>
+ * PackWriter class is responsible for generating pack files from specified set
+ * of objects from repository. This implementation produce pack files in format
+ * version 2.
+ * </p>
+ * <p>
+ * Source of objects may be specified in two ways:
+ * <ul>
+ * <li>(usually) by providing sets of interesting and uninteresting objects in
+ * repository - all interesting objects and their ancestors except uninteresting
+ * objects and their ancestors will be included in pack, or</li>
+ * <li>by providing iterator of {@link RevObject} specifying exact list and
+ * order of objects in pack</li>
+ * </ul>
+ * Typical usage consists of creating instance intended for some pack,
+ * configuring options, preparing the list of objects by calling
+ * {@link #preparePack(Iterator)} or
+ * {@link #preparePack(Collection, Collection)}, and finally
+ * producing the stream with {@link #writePack(OutputStream)}.
+ * </p>
+ * <p>
+ * Class provide set of configurable options and {@link ProgressMonitor}
+ * support, as operations may take a long time for big repositories. Deltas
+ * searching algorithm is <b>NOT IMPLEMENTED</b> yet - this implementation
+ * relies only on deltas and objects reuse.
+ * </p>
+ * <p>
+ * This class is not thread safe, it is intended to be used in one thread, with
+ * one instance per created pack. Subsequent calls to writePack result in
+ * undefined behavior.
+ * </p>
+ */
+
+public class PackWriter {
+ /**
+ * Title of {@link ProgressMonitor} task used during counting objects to
+ * pack.
+ *
+ * @see #preparePack(Collection, Collection)
+ */
+ public static final String COUNTING_OBJECTS_PROGRESS = "Counting objects";
+
+ /**
+ * Title of {@link ProgressMonitor} task used during searching for objects
+ * reuse or delta reuse.
+ *
+ * @see #writePack(OutputStream)
+ */
+ public static final String SEARCHING_REUSE_PROGRESS = "Compressing objects";
+
+ /**
+ * Title of {@link ProgressMonitor} task used during writing out pack
+ * (objects)
+ *
+ * @see #writePack(OutputStream)
+ */
+ public static final String WRITING_OBJECTS_PROGRESS = "Writing objects";
+
+ /**
+ * Default value of deltas reuse option.
+ *
+ * @see #setReuseDeltas(boolean)
+ */
+ public static final boolean DEFAULT_REUSE_DELTAS = true;
+
+ /**
+ * Default value of objects reuse option.
+ *
+ * @see #setReuseObjects(boolean)
+ */
+ public static final boolean DEFAULT_REUSE_OBJECTS = true;
+
+ /**
+ * Default value of delta base as offset option.
+ *
+ * @see #setDeltaBaseAsOffset(boolean)
+ */
+ public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET = false;
+
+ /**
+ * Default value of maximum delta chain depth.
+ *
+ * @see #setMaxDeltaDepth(int)
+ */
+ public static final int DEFAULT_MAX_DELTA_DEPTH = 50;
+
+ private static final int PACK_VERSION_GENERATED = 2;
+
+ @SuppressWarnings("unchecked")
+ private final List<ObjectToPack> objectsLists[] = new List[Constants.OBJ_TAG + 1];
+ {
+ objectsLists[0] = Collections.<ObjectToPack> emptyList();
+ objectsLists[Constants.OBJ_COMMIT] = new ArrayList<ObjectToPack>();
+ objectsLists[Constants.OBJ_TREE] = new ArrayList<ObjectToPack>();
+ objectsLists[Constants.OBJ_BLOB] = new ArrayList<ObjectToPack>();
+ objectsLists[Constants.OBJ_TAG] = new ArrayList<ObjectToPack>();
+ }
+
+ private final ObjectIdSubclassMap<ObjectToPack> objectsMap = new ObjectIdSubclassMap<ObjectToPack>();
+
+ // edge objects for thin packs
+ private final ObjectIdSubclassMap<ObjectId> edgeObjects = new ObjectIdSubclassMap<ObjectId>();
+
+ private final Repository db;
+
+ private PackOutputStream out;
+
+ private final Deflater deflater;
+
+ private ProgressMonitor initMonitor;
+
+ private ProgressMonitor writeMonitor;
+
+ private final byte[] buf = new byte[16384]; // 16 KB
+
+ private final WindowCursor windowCursor = new WindowCursor();
+
+ private List<ObjectToPack> sortedByName;
+
+ private byte packcsum[];
+
+ private boolean reuseDeltas = DEFAULT_REUSE_DELTAS;
+
+ private boolean reuseObjects = DEFAULT_REUSE_OBJECTS;
+
+ private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET;
+
+ private int maxDeltaDepth = DEFAULT_MAX_DELTA_DEPTH;
+
+ private int outputVersion;
+
+ private boolean thin;
+
+ private boolean ignoreMissingUninteresting = true;
+
+ /**
+ * Create writer for specified repository.
+ * <p>
+ * Objects for packing are specified in {@link #preparePack(Iterator)} or
+ * {@link #preparePack(Collection, Collection)}.
+ *
+ * @param repo
+ * repository where objects are stored.
+ * @param monitor
+ * operations progress monitor, used within
+ * {@link #preparePack(Iterator)},
+ * {@link #preparePack(Collection, Collection)}
+ * , or {@link #writePack(OutputStream)}.
+ */
+ public PackWriter(final Repository repo, final ProgressMonitor monitor) {
+ this(repo, monitor, monitor);
+ }
+
+ /**
+ * Create writer for specified repository.
+ * <p>
+ * Objects for packing are specified in {@link #preparePack(Iterator)} or
+ * {@link #preparePack(Collection, Collection)}.
+ *
+ * @param repo
+ * repository where objects are stored.
+ * @param imonitor
+ * operations progress monitor, used within
+ * {@link #preparePack(Iterator)},
+ * {@link #preparePack(Collection, Collection)}
+ * @param wmonitor
+ * operations progress monitor, used within
+ * {@link #writePack(OutputStream)}.
+ */
+ public PackWriter(final Repository repo, final ProgressMonitor imonitor,
+ final ProgressMonitor wmonitor) {
+ this.db = repo;
+ initMonitor = imonitor == null ? NullProgressMonitor.INSTANCE : imonitor;
+ writeMonitor = wmonitor == null ? NullProgressMonitor.INSTANCE : wmonitor;
+ this.deflater = new Deflater(db.getConfig().getCore().getCompression());
+ outputVersion = repo.getConfig().getCore().getPackIndexVersion();
+ }
+
+ /**
+ * Check whether object is configured to reuse deltas existing in
+ * repository.
+ * <p>
+ * Default setting: {@value #DEFAULT_REUSE_DELTAS}
+ * </p>
+ *
+ * @return true if object is configured to reuse deltas; false otherwise.
+ */
+ public boolean isReuseDeltas() {
+ return reuseDeltas;
+ }
+
+ /**
+ * Set reuse deltas configuration option for this writer. When enabled,
+ * writer will search for delta representation of object in repository and
+ * use it if possible. Normally, only deltas with base to another object
+ * existing in set of objects to pack will be used. Exception is however
+ * thin-pack (see
+ * {@link #preparePack(Collection, Collection)} and
+ * {@link #preparePack(Iterator)}) where base object must exist on other
+ * side machine.
+ * <p>
+ * When raw delta data is directly copied from a pack file, checksum is
+ * computed to verify data.
+ * </p>
+ * <p>
+ * Default setting: {@value #DEFAULT_REUSE_DELTAS}
+ * </p>
+ *
+ * @param reuseDeltas
+ * boolean indicating whether or not try to reuse deltas.
+ */
+ public void setReuseDeltas(boolean reuseDeltas) {
+ this.reuseDeltas = reuseDeltas;
+ }
+
+ /**
+ * Checks whether object is configured to reuse existing objects
+ * representation in repository.
+ * <p>
+ * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
+ * </p>
+ *
+ * @return true if writer is configured to reuse objects representation from
+ * pack; false otherwise.
+ */
+ public boolean isReuseObjects() {
+ return reuseObjects;
+ }
+
+ /**
+ * Set reuse objects configuration option for this writer. If enabled,
+ * writer searches for representation in a pack file. If possible,
+ * compressed data is directly copied from such a pack file. Data checksum
+ * is verified.
+ * <p>
+ * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
+ * </p>
+ *
+ * @param reuseObjects
+ * boolean indicating whether or not writer should reuse existing
+ * objects representation.
+ */
+ public void setReuseObjects(boolean reuseObjects) {
+ this.reuseObjects = reuseObjects;
+ }
+
+ /**
+ * Check whether writer can store delta base as an offset (new style
+ * reducing pack size) or should store it as an object id (legacy style,
+ * compatible with old readers).
+ * <p>
+ * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
+ * </p>
+ *
+ * @return true if delta base is stored as an offset; false if it is stored
+ * as an object id.
+ */
+ public boolean isDeltaBaseAsOffset() {
+ return deltaBaseAsOffset;
+ }
+
+ /**
+ * Set writer delta base format. Delta base can be written as an offset in a
+ * pack file (new approach reducing file size) or as an object id (legacy
+ * approach, compatible with old readers).
+ * <p>
+ * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
+ * </p>
+ *
+ * @param deltaBaseAsOffset
+ * boolean indicating whether delta base can be stored as an
+ * offset.
+ */
+ public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) {
+ this.deltaBaseAsOffset = deltaBaseAsOffset;
+ }
+
+ /**
+ * Get maximum depth of delta chain set up for this writer. Generated chains
+ * are not longer than this value.
+ * <p>
+ * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
+ * </p>
+ *
+ * @return maximum delta chain depth.
+ */
+ public int getMaxDeltaDepth() {
+ return maxDeltaDepth;
+ }
+
+ /**
+ * Set up maximum depth of delta chain for this writer. Generated chains are
+ * not longer than this value. Too low value causes low compression level,
+ * while too big makes unpacking (reading) longer.
+ * <p>
+ * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
+ * </p>
+ *
+ * @param maxDeltaDepth
+ * maximum delta chain depth.
+ */
+ public void setMaxDeltaDepth(int maxDeltaDepth) {
+ this.maxDeltaDepth = maxDeltaDepth;
+ }
+
+ /** @return true if this writer is producing a thin pack. */
+ public boolean isThin() {
+ return thin;
+ }
+
+ /**
+ * @param packthin
+ * a boolean indicating whether writer may pack objects with
+ * delta base object not within set of objects to pack, but
+ * belonging to party repository (uninteresting/boundary) as
+ * determined by set; this kind of pack is used only for
+ * transport; true - to produce thin pack, false - otherwise.
+ */
+ public void setThin(final boolean packthin) {
+ thin = packthin;
+ }
+
+ /**
+ * @return true to ignore objects that are uninteresting and also not found
+ * on local disk; false to throw a {@link MissingObjectException}
+ * out of {@link #preparePack(Collection, Collection)} if an
+ * uninteresting object is not in the source repository. By default,
+ * true, permitting gracefully ignoring of uninteresting objects.
+ */
+ public boolean isIgnoreMissingUninteresting() {
+ return ignoreMissingUninteresting;
+ }
+
+ /**
+ * @param ignore
+ * true if writer should ignore non existing uninteresting
+ * objects during construction set of objects to pack; false
+ * otherwise - non existing uninteresting objects may cause
+ * {@link MissingObjectException}
+ */
+ public void setIgnoreMissingUninteresting(final boolean ignore) {
+ ignoreMissingUninteresting = ignore;
+ }
+
+ /**
+ * Set the pack index file format version this instance will create.
+ *
+ * @param version
+ * the version to write. The special version 0 designates the
+ * oldest (most compatible) format available for the objects.
+ * @see PackIndexWriter
+ */
+ public void setIndexVersion(final int version) {
+ outputVersion = version;
+ }
+
+ /**
+ * Returns objects number in a pack file that was created by this writer.
+ *
+ * @return number of objects in pack.
+ */
+ public int getObjectsNumber() {
+ return objectsMap.size();
+ }
+
+ /**
+ * Prepare the list of objects to be written to the pack stream.
+ * <p>
+ * Iterator <b>exactly</b> determines which objects are included in a pack
+ * and order they appear in pack (except that objects order by type is not
+ * needed at input). This order should conform general rules of ordering
+ * objects in git - by recency and path (type and delta-base first is
+ * internally secured) and responsibility for guaranteeing this order is on
+ * a caller side. Iterator must return each id of object to write exactly
+ * once.
+ * </p>
+ * <p>
+ * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag,
+ * this object won't be included in an output pack. Instead, it is recorded
+ * as edge-object (known to remote repository) for thin-pack. In such a case
+ * writer may pack objects with delta base object not within set of objects
+ * to pack, but belonging to party repository - those marked with
+ * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for
+ * transport.
+ * </p>
+ *
+ * @param objectsSource
+ * iterator of object to store in a pack; order of objects within
+ * each type is important, ordering by type is not needed;
+ * allowed types for objects are {@link Constants#OBJ_COMMIT},
+ * {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and
+ * {@link Constants#OBJ_TAG}; objects returned by iterator may
+ * be later reused by caller as object id and type are internally
+ * copied in each iteration; if object returned by iterator has
+ * {@link RevFlag#UNINTERESTING} flag set, it won't be included
+ * in a pack, but is considered as edge-object for thin-pack.
+ * @throws IOException
+ * when some I/O problem occur during reading objects.
+ */
+ public void preparePack(final Iterator<RevObject> objectsSource)
+ throws IOException {
+ while (objectsSource.hasNext()) {
+ addObject(objectsSource.next());
+ }
+ }
+
+ /**
+ * Prepare the list of objects to be written to the pack stream.
+ * <p>
+ * Basing on these 2 sets, another set of objects to put in a pack file is
+ * created: this set consists of all objects reachable (ancestors) from
+ * interesting objects, except uninteresting objects and their ancestors.
+ * This method uses class {@link ObjectWalk} extensively to find out that
+ * appropriate set of output objects and their optimal order in output pack.
+ * Order is consistent with general git in-pack rules: sort by object type,
+ * recency, path and delta-base first.
+ * </p>
+ *
+ * @param interestingObjects
+ * collection of objects to be marked as interesting (start
+ * points of graph traversal).
+ * @param uninterestingObjects
+ * collection of objects to be marked as uninteresting (end
+ * points of graph traversal).
+ * @throws IOException
+ * when some I/O problem occur during reading objects.
+ */
+ public void preparePack(
+ final Collection<? extends ObjectId> interestingObjects,
+ final Collection<? extends ObjectId> uninterestingObjects)
+ throws IOException {
+ ObjectWalk walker = setUpWalker(interestingObjects,
+ uninterestingObjects);
+ findObjectsToPack(walker);
+ }
+
+ /**
+ * Determine if the pack file will contain the requested object.
+ *
+ * @param id
+ * the object to test the existence of.
+ * @return true if the object will appear in the output pack file.
+ */
+ public boolean willInclude(final AnyObjectId id) {
+ return objectsMap.get(id) != null;
+ }
+
+ /**
+ * Computes SHA-1 of lexicographically sorted objects ids written in this
+ * pack, as used to name a pack file in repository.
+ *
+ * @return ObjectId representing SHA-1 name of a pack that was created.
+ */
+ public ObjectId computeName() {
+ final MessageDigest md = Constants.newMessageDigest();
+ for (ObjectToPack otp : sortByName()) {
+ otp.copyRawTo(buf, 0);
+ md.update(buf, 0, Constants.OBJECT_ID_LENGTH);
+ }
+ return ObjectId.fromRaw(md.digest());
+ }
+
+ /**
+ * Create an index file to match the pack file just written.
+ * <p>
+ * This method can only be invoked after {@link #preparePack(Iterator)} or
+ * {@link #preparePack(Collection, Collection)} has been
+ * invoked and completed successfully. Writing a corresponding index is an
+ * optional feature that not all pack users may require.
+ *
+ * @param indexStream
+ * output for the index data. Caller is responsible for closing
+ * this stream.
+ * @throws IOException
+ * the index data could not be written to the supplied stream.
+ */
+ public void writeIndex(final OutputStream indexStream) throws IOException {
+ final List<ObjectToPack> list = sortByName();
+ final PackIndexWriter iw;
+ if (outputVersion <= 0)
+ iw = PackIndexWriter.createOldestPossible(indexStream, list);
+ else
+ iw = PackIndexWriter.createVersion(indexStream, outputVersion);
+ iw.write(list, packcsum);
+ }
+
+ private List<ObjectToPack> sortByName() {
+ if (sortedByName == null) {
+ sortedByName = new ArrayList<ObjectToPack>(objectsMap.size());
+ for (List<ObjectToPack> list : objectsLists) {
+ for (ObjectToPack otp : list)
+ sortedByName.add(otp);
+ }
+ Collections.sort(sortedByName);
+ }
+ return sortedByName;
+ }
+
+ /**
+ * Write the prepared pack to the supplied stream.
+ * <p>
+ * At first, this method collects and sorts objects to pack, then deltas
+ * search is performed if set up accordingly, finally pack stream is
+ * written. {@link ProgressMonitor} tasks {@value #SEARCHING_REUSE_PROGRESS}
+ * (only if reuseDeltas or reuseObjects is enabled) and
+ * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing.
+ * </p>
+ * <p>
+ * All reused objects data checksum (Adler32/CRC32) is computed and
+ * validated against existing checksum.
+ * </p>
+ *
+ * @param packStream
+ * output stream of pack data. If the stream is not buffered it
+ * will be buffered by the writer. Caller is responsible for
+ * closing the stream.
+ * @throws IOException
+ * an error occurred reading a local object's data to include in
+ * the pack, or writing compressed object data to the output
+ * stream.
+ */
+ public void writePack(OutputStream packStream) throws IOException {
+ if (reuseDeltas || reuseObjects)
+ searchForReuse();
+
+ if (!(packStream instanceof BufferedOutputStream))
+ packStream = new BufferedOutputStream(packStream);
+ out = new PackOutputStream(packStream);
+
+ writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber());
+ writeHeader();
+ writeObjects();
+ writeChecksum();
+
+ out.flush();
+ windowCursor.release();
+ writeMonitor.endTask();
+ }
+
+ private void searchForReuse() throws IOException {
+ initMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber());
+ final Collection<PackedObjectLoader> reuseLoaders = new ArrayList<PackedObjectLoader>();
+ for (List<ObjectToPack> list : objectsLists) {
+ for (ObjectToPack otp : list) {
+ if (initMonitor.isCancelled())
+ throw new IOException(
+ "Packing cancelled during objects writing");
+ reuseLoaders.clear();
+ searchForReuse(reuseLoaders, otp);
+ initMonitor.update(1);
+ }
+ }
+
+ initMonitor.endTask();
+ }
+
+ private void searchForReuse(
+ final Collection<PackedObjectLoader> reuseLoaders,
+ final ObjectToPack otp) throws IOException {
+ db.openObjectInAllPacks(otp, reuseLoaders, windowCursor);
+ if (reuseDeltas) {
+ selectDeltaReuseForObject(otp, reuseLoaders);
+ }
+ // delta reuse is preferred over object reuse
+ if (reuseObjects && !otp.hasReuseLoader()) {
+ selectObjectReuseForObject(otp, reuseLoaders);
+ }
+ }
+
+ private void selectDeltaReuseForObject(final ObjectToPack otp,
+ final Collection<PackedObjectLoader> loaders) throws IOException {
+ PackedObjectLoader bestLoader = null;
+ ObjectId bestBase = null;
+
+ for (PackedObjectLoader loader : loaders) {
+ ObjectId idBase = loader.getDeltaBase();
+ if (idBase == null)
+ continue;
+ ObjectToPack otpBase = objectsMap.get(idBase);
+
+ // only if base is in set of objects to write or thin-pack's edge
+ if ((otpBase != null || (thin && edgeObjects.get(idBase) != null))
+ // select smallest possible delta if > 1 available
+ && isBetterDeltaReuseLoader(bestLoader, loader)) {
+ bestLoader = loader;
+ bestBase = (otpBase != null ? otpBase : idBase);
+ }
+ }
+
+ if (bestLoader != null) {
+ otp.setReuseLoader(bestLoader);
+ otp.setDeltaBase(bestBase);
+ }
+ }
+
+ private static boolean isBetterDeltaReuseLoader(
+ PackedObjectLoader currentLoader, PackedObjectLoader loader)
+ throws IOException {
+ if (currentLoader == null)
+ return true;
+ if (loader.getRawSize() < currentLoader.getRawSize())
+ return true;
+ return (loader.getRawSize() == currentLoader.getRawSize()
+ && loader.supportsFastCopyRawData() && !currentLoader
+ .supportsFastCopyRawData());
+ }
+
+ private void selectObjectReuseForObject(final ObjectToPack otp,
+ final Collection<PackedObjectLoader> loaders) {
+ for (final PackedObjectLoader loader : loaders) {
+ if (loader instanceof WholePackedObjectLoader) {
+ otp.setReuseLoader(loader);
+ return;
+ }
+ }
+ }
+
+ private void writeHeader() throws IOException {
+ System.arraycopy(Constants.PACK_SIGNATURE, 0, buf, 0, 4);
+ NB.encodeInt32(buf, 4, PACK_VERSION_GENERATED);
+ NB.encodeInt32(buf, 8, getObjectsNumber());
+ out.write(buf, 0, 12);
+ }
+
+ private void writeObjects() throws IOException {
+ for (List<ObjectToPack> list : objectsLists) {
+ for (ObjectToPack otp : list) {
+ if (writeMonitor.isCancelled())
+ throw new IOException(
+ "Packing cancelled during objects writing");
+ if (!otp.isWritten())
+ writeObject(otp);
+ }
+ }
+ }
+
+ private void writeObject(final ObjectToPack otp) throws IOException {
+ otp.markWantWrite();
+ if (otp.isDeltaRepresentation()) {
+ ObjectToPack deltaBase = otp.getDeltaBase();
+ assert deltaBase != null || thin;
+ if (deltaBase != null && !deltaBase.isWritten()) {
+ if (deltaBase.wantWrite()) {
+ otp.clearDeltaBase(); // cycle detected
+ otp.disposeLoader();
+ } else {
+ writeObject(deltaBase);
+ }
+ }
+ }
+
+ assert !otp.isWritten();
+
+ out.resetCRC32();
+ otp.setOffset(out.length());
+
+ final PackedObjectLoader reuse = open(otp);
+ if (reuse != null) {
+ try {
+ if (otp.isDeltaRepresentation()) {
+ writeDeltaObjectReuse(otp, reuse);
+ } else {
+ writeObjectHeader(otp.getType(), reuse.getSize());
+ reuse.copyRawData(out, buf, windowCursor);
+ }
+ } finally {
+ reuse.endCopyRawData();
+ }
+ } else if (otp.isDeltaRepresentation()) {
+ throw new IOException("creating deltas is not implemented");
+ } else {
+ writeWholeObjectDeflate(otp);
+ }
+ otp.setCRC(out.getCRC32());
+
+ writeMonitor.update(1);
+ }
+
+ private PackedObjectLoader open(final ObjectToPack otp) throws IOException {
+ for (;;) {
+ PackedObjectLoader reuse = otp.useLoader();
+ if (reuse == null) {
+ return null;
+ }
+
+ try {
+ reuse.beginCopyRawData();
+ return reuse;
+ } catch (IOException err) {
+ // The pack we found the object in originally is gone, or
+ // it has been overwritten with a different layout.
+ //
+ otp.clearDeltaBase();
+ searchForReuse(new ArrayList<PackedObjectLoader>(), otp);
+ continue;
+ }
+ }
+ }
+
+ private void writeWholeObjectDeflate(final ObjectToPack otp)
+ throws IOException {
+ final ObjectLoader loader = db.openObject(windowCursor, otp);
+ final byte[] data = loader.getCachedBytes();
+ writeObjectHeader(otp.getType(), data.length);
+ deflater.reset();
+ deflater.setInput(data, 0, data.length);
+ deflater.finish();
+ do {
+ final int n = deflater.deflate(buf, 0, buf.length);
+ if (n > 0)
+ out.write(buf, 0, n);
+ } while (!deflater.finished());
+ }
+
+ private void writeDeltaObjectReuse(final ObjectToPack otp,
+ final PackedObjectLoader reuse) throws IOException {
+ if (deltaBaseAsOffset && otp.getDeltaBase() != null) {
+ writeObjectHeader(Constants.OBJ_OFS_DELTA, reuse.getRawSize());
+
+ final ObjectToPack deltaBase = otp.getDeltaBase();
+ long offsetDiff = otp.getOffset() - deltaBase.getOffset();
+ int pos = buf.length - 1;
+ buf[pos] = (byte) (offsetDiff & 0x7F);
+ while ((offsetDiff >>= 7) > 0) {
+ buf[--pos] = (byte) (0x80 | (--offsetDiff & 0x7F));
+ }
+
+ out.write(buf, pos, buf.length - pos);
+ } else {
+ writeObjectHeader(Constants.OBJ_REF_DELTA, reuse.getRawSize());
+ otp.getDeltaBaseId().copyRawTo(buf, 0);
+ out.write(buf, 0, Constants.OBJECT_ID_LENGTH);
+ }
+ reuse.copyRawData(out, buf, windowCursor);
+ }
+
+ private void writeObjectHeader(final int objectType, long dataLength)
+ throws IOException {
+ long nextLength = dataLength >>> 4;
+ int size = 0;
+ buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00)
+ | (objectType << 4) | (dataLength & 0x0F));
+ dataLength = nextLength;
+ while (dataLength > 0) {
+ nextLength >>>= 7;
+ buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F));
+ dataLength = nextLength;
+ }
+ out.write(buf, 0, size);
+ }
+
+ private void writeChecksum() throws IOException {
+ packcsum = out.getDigest();
+ out.write(packcsum);
+ }
+
+ private ObjectWalk setUpWalker(
+ final Collection<? extends ObjectId> interestingObjects,
+ final Collection<? extends ObjectId> uninterestingObjects)
+ throws MissingObjectException, IOException,
+ IncorrectObjectTypeException {
+ final ObjectWalk walker = new ObjectWalk(db);
+ walker.setRetainBody(false);
+ walker.sort(RevSort.TOPO);
+ walker.sort(RevSort.COMMIT_TIME_DESC, true);
+ if (thin)
+ walker.sort(RevSort.BOUNDARY, true);
+
+ for (ObjectId id : interestingObjects) {
+ RevObject o = walker.parseAny(id);
+ walker.markStart(o);
+ }
+ if (uninterestingObjects != null) {
+ for (ObjectId id : uninterestingObjects) {
+ final RevObject o;
+ try {
+ o = walker.parseAny(id);
+ } catch (MissingObjectException x) {
+ if (ignoreMissingUninteresting)
+ continue;
+ throw x;
+ }
+ walker.markUninteresting(o);
+ }
+ }
+ return walker;
+ }
+
+ private void findObjectsToPack(final ObjectWalk walker)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ initMonitor.beginTask(COUNTING_OBJECTS_PROGRESS,
+ ProgressMonitor.UNKNOWN);
+ RevObject o;
+
+ while ((o = walker.next()) != null) {
+ addObject(o);
+ initMonitor.update(1);
+ }
+ while ((o = walker.nextObject()) != null) {
+ addObject(o);
+ initMonitor.update(1);
+ }
+ initMonitor.endTask();
+ }
+
+ /**
+ * Include one object to the output file.
+ * <p>
+ * Objects are written in the order they are added. If the same object is
+ * added twice, it may be written twice, creating a larger than necessary
+ * file.
+ *
+ * @param object
+ * the object to add.
+ * @throws IncorrectObjectTypeException
+ * the object is an unsupported type.
+ */
+ public void addObject(final RevObject object)
+ throws IncorrectObjectTypeException {
+ if (object.has(RevFlag.UNINTERESTING)) {
+ edgeObjects.add(object);
+ thin = true;
+ return;
+ }
+
+ final ObjectToPack otp = new ObjectToPack(object, object.getType());
+ try {
+ objectsLists[object.getType()].add(otp);
+ } catch (ArrayIndexOutOfBoundsException x) {
+ throw new IncorrectObjectTypeException(object,
+ "COMMIT nor TREE nor BLOB nor TAG");
+ } catch (UnsupportedOperationException x) {
+ // index pointing to "dummy" empty list
+ throw new IncorrectObjectTypeException(object,
+ "COMMIT nor TREE nor BLOB nor TAG");
+ }
+ objectsMap.add(otp);
+ }
+
+ /**
+ * Class holding information about object that is going to be packed by
+ * {@link PackWriter}. Information include object representation in a
+ * pack-file and object status.
+ *
+ */
+ static class ObjectToPack extends PackedObjectInfo {
+ private ObjectId deltaBase;
+
+ private PackedObjectLoader reuseLoader;
+
+ /**
+ * Bit field, from bit 0 to bit 31:
+ * <ul>
+ * <li>1 bit: wantWrite</li>
+ * <li>3 bits: type</li>
+ * <li>28 bits: deltaDepth</li>
+ * </ul>
+ */
+ private int flags;
+
+ /**
+ * Construct object for specified object id. <br/> By default object is
+ * marked as not written and non-delta packed (as a whole object).
+ *
+ * @param src
+ * object id of object for packing
+ * @param type
+ * real type code of the object, not its in-pack type.
+ */
+ ObjectToPack(AnyObjectId src, final int type) {
+ super(src);
+ flags |= type << 1;
+ }
+
+ /**
+ * @return delta base object id if object is going to be packed in delta
+ * representation; null otherwise - if going to be packed as a
+ * whole object.
+ */
+ ObjectId getDeltaBaseId() {
+ return deltaBase;
+ }
+
+ /**
+ * @return delta base object to pack if object is going to be packed in
+ * delta representation and delta is specified as object to
+ * pack; null otherwise - if going to be packed as a whole
+ * object or delta base is specified only as id.
+ */
+ ObjectToPack getDeltaBase() {
+ if (deltaBase instanceof ObjectToPack)
+ return (ObjectToPack) deltaBase;
+ return null;
+ }
+
+ /**
+ * Set delta base for the object. Delta base set by this method is used
+ * by {@link PackWriter} to write object - determines its representation
+ * in a created pack.
+ *
+ * @param deltaBase
+ * delta base object or null if object should be packed as a
+ * whole object.
+ *
+ */
+ void setDeltaBase(ObjectId deltaBase) {
+ this.deltaBase = deltaBase;
+ }
+
+ void clearDeltaBase() {
+ this.deltaBase = null;
+ }
+
+ /**
+ * @return true if object is going to be written as delta; false
+ * otherwise.
+ */
+ boolean isDeltaRepresentation() {
+ return deltaBase != null;
+ }
+
+ /**
+ * Check if object is already written in a pack. This information is
+ * used to achieve delta-base precedence in a pack file.
+ *
+ * @return true if object is already written; false otherwise.
+ */
+ boolean isWritten() {
+ return getOffset() != 0;
+ }
+
+ PackedObjectLoader useLoader() {
+ final PackedObjectLoader r = reuseLoader;
+ reuseLoader = null;
+ return r;
+ }
+
+ boolean hasReuseLoader() {
+ return reuseLoader != null;
+ }
+
+ void setReuseLoader(PackedObjectLoader reuseLoader) {
+ this.reuseLoader = reuseLoader;
+ }
+
+ void disposeLoader() {
+ this.reuseLoader = null;
+ }
+
+ int getType() {
+ return (flags>>1) & 0x7;
+ }
+
+ int getDeltaDepth() {
+ return flags >>> 4;
+ }
+
+ void updateDeltaDepth() {
+ final int d;
+ if (deltaBase instanceof ObjectToPack)
+ d = ((ObjectToPack) deltaBase).getDeltaDepth() + 1;
+ else if (deltaBase != null)
+ d = 1;
+ else
+ d = 0;
+ flags = (d << 4) | flags & 0x15;
+ }
+
+ boolean wantWrite() {
+ return (flags & 1) == 1;
+ }
+
+ void markWantWrite() {
+ flags |= 1;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java
new file mode 100644
index 0000000000..4125579b22
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Base class for a set of object loader classes for packed objects.
+ */
+abstract class PackedObjectLoader extends ObjectLoader {
+ protected final PackFile pack;
+
+ protected final long dataOffset;
+
+ protected final long objectOffset;
+
+ protected int objectType;
+
+ protected int objectSize;
+
+ protected byte[] cachedBytes;
+
+ PackedObjectLoader(final PackFile pr, final long dataOffset,
+ final long objectOffset) {
+ pack = pr;
+ this.dataOffset = dataOffset;
+ this.objectOffset = objectOffset;
+ }
+
+ /**
+ * Force this object to be loaded into memory and pinned in this loader.
+ * <p>
+ * Once materialized, subsequent get operations for the following methods
+ * will always succeed without raising an exception, as all information is
+ * pinned in memory by this loader instance.
+ * <ul>
+ * <li>{@link #getType()}</li>
+ * <li>{@link #getSize()}</li>
+ * <li>{@link #getBytes()}, {@link #getCachedBytes}</li>
+ * <li>{@link #getRawSize()}</li>
+ * <li>{@link #getRawType()}</li>
+ * </ul>
+ *
+ * @param curs
+ * temporary thread storage during data access.
+ * @throws IOException
+ * the object cannot be read.
+ */
+ public abstract void materialize(WindowCursor curs) throws IOException;
+
+ public final int getType() {
+ return objectType;
+ }
+
+ public final long getSize() {
+ return objectSize;
+ }
+
+ @Override
+ public final byte[] getCachedBytes() {
+ return cachedBytes;
+ }
+
+ /**
+ * @return offset of object header within pack file
+ */
+ public final long getObjectOffset() {
+ return objectOffset;
+ }
+
+ /**
+ * @return offset of object data within pack file
+ */
+ public final long getDataOffset() {
+ return dataOffset;
+ }
+
+ /**
+ * Peg the pack file open to support data copying.
+ * <p>
+ * Applications trying to copy raw pack data should ensure the pack stays
+ * open and available throughout the entire copy. To do that use:
+ *
+ * <pre>
+ * loader.beginCopyRawData();
+ * try {
+ * loader.copyRawData(out, tmpbuf, curs);
+ * } finally {
+ * loader.endCopyRawData();
+ * }
+ * </pre>
+ *
+ * @throws IOException
+ * this loader contains stale information and cannot be used.
+ * The most likely cause is the underlying pack file has been
+ * deleted, and the object has moved to another pack file.
+ */
+ public void beginCopyRawData() throws IOException {
+ pack.beginCopyRawData();
+ }
+
+ /**
+ * Copy raw object representation from storage to provided output stream.
+ * <p>
+ * Copied data doesn't include object header. User must provide temporary
+ * buffer used during copying by underlying I/O layer.
+ * </p>
+ *
+ * @param out
+ * output stream when data is copied. No buffering is guaranteed.
+ * @param buf
+ * temporary buffer used during copying. Recommended size is at
+ * least few kB.
+ * @param curs
+ * temporary thread storage during data access.
+ * @throws IOException
+ * when the object cannot be read.
+ * @see #beginCopyRawData()
+ */
+ public void copyRawData(OutputStream out, byte buf[], WindowCursor curs)
+ throws IOException {
+ pack.copyRawData(this, out, buf, curs);
+ }
+
+ /** Release resources after {@link #beginCopyRawData()}. */
+ public void endCopyRawData() {
+ pack.endCopyRawData();
+ }
+
+ /**
+ * @return true if this loader is capable of fast raw-data copying basing on
+ * compressed data checksum; false if raw-data copying needs
+ * uncompressing and compressing data
+ * @throws IOException
+ * the index file format cannot be determined.
+ */
+ public boolean supportsFastCopyRawData() throws IOException {
+ return pack.supportsFastCopyRawData();
+ }
+
+ /**
+ * @return id of delta base object for this object representation. null if
+ * object is not stored as delta.
+ * @throws IOException
+ * when delta base cannot read.
+ */
+ public abstract ObjectId getDeltaBase() throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
new file mode 100644
index 0000000000..a9f520e8fc
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.eclipse.jgit.util.SystemReader;
+
+/**
+ * A combination of a person identity and time in Git.
+ *
+ * Git combines Name + email + time + time zone to specify who wrote or
+ * committed something.
+ */
+public class PersonIdent {
+ private final String name;
+
+ private final String emailAddress;
+
+ private final long when;
+
+ private final int tzOffset;
+
+ /**
+ * Creates new PersonIdent from config info in repository, with current time.
+ * This new PersonIdent gets the info from the default committer as available
+ * from the configuration.
+ *
+ * @param repo
+ */
+ public PersonIdent(final Repository repo) {
+ final RepositoryConfig config = repo.getConfig();
+ name = config.getCommitterName();
+ emailAddress = config.getCommitterEmail();
+ when = SystemReader.getInstance().getCurrentTime();
+ tzOffset = SystemReader.getInstance().getTimezone(when);
+ }
+
+ /**
+ * Copy a {@link PersonIdent}.
+ *
+ * @param pi
+ * Original {@link PersonIdent}
+ */
+ public PersonIdent(final PersonIdent pi) {
+ this(pi.getName(), pi.getEmailAddress());
+ }
+
+ /**
+ * Construct a new {@link PersonIdent} with current time.
+ *
+ * @param aName
+ * @param aEmailAddress
+ */
+ public PersonIdent(final String aName, final String aEmailAddress) {
+ this(aName, aEmailAddress, new Date(), TimeZone.getDefault());
+ }
+
+ /**
+ * Copy a PersonIdent, but alter the clone's time stamp
+ *
+ * @param pi
+ * original {@link PersonIdent}
+ * @param when
+ * local time
+ * @param tz
+ * time zone
+ */
+ public PersonIdent(final PersonIdent pi, final Date when, final TimeZone tz) {
+ this(pi.getName(), pi.getEmailAddress(), when, tz);
+ }
+
+ /**
+ * Copy a {@link PersonIdent}, but alter the clone's time stamp
+ *
+ * @param pi
+ * original {@link PersonIdent}
+ * @param aWhen
+ * local time
+ */
+ public PersonIdent(final PersonIdent pi, final Date aWhen) {
+ name = pi.getName();
+ emailAddress = pi.getEmailAddress();
+ when = aWhen.getTime();
+ tzOffset = pi.tzOffset;
+ }
+
+ /**
+ * Construct a PersonIdent from simple data
+ *
+ * @param aName
+ * @param aEmailAddress
+ * @param aWhen
+ * local time stamp
+ * @param aTZ
+ * time zone
+ */
+ public PersonIdent(final String aName, final String aEmailAddress,
+ final Date aWhen, final TimeZone aTZ) {
+ name = aName;
+ emailAddress = aEmailAddress;
+ when = aWhen.getTime();
+ tzOffset = aTZ.getOffset(when) / (60 * 1000);
+ }
+
+ /**
+ * Construct a {@link PersonIdent}
+ *
+ * @param aName
+ * @param aEmailAddress
+ * @param aWhen
+ * local time stamp
+ * @param aTZ
+ * time zone
+ */
+ public PersonIdent(final String aName, final String aEmailAddress,
+ final long aWhen, final int aTZ) {
+ name = aName;
+ emailAddress = aEmailAddress;
+ when = aWhen;
+ tzOffset = aTZ;
+ }
+
+ /**
+ * Copy a PersonIdent, but alter the clone's time stamp
+ *
+ * @param pi
+ * original {@link PersonIdent}
+ * @param aWhen
+ * local time stamp
+ * @param aTZ
+ * time zone
+ */
+ public PersonIdent(final PersonIdent pi, final long aWhen, final int aTZ) {
+ name = pi.getName();
+ emailAddress = pi.getEmailAddress();
+ when = aWhen;
+ tzOffset = aTZ;
+ }
+
+ /**
+ * Construct a PersonIdent from a string with full name, email, time time
+ * zone string. The input string must be valid.
+ *
+ * @param in
+ * a Git internal format author/committer string.
+ */
+ public PersonIdent(final String in) {
+ final int lt = in.indexOf('<');
+ if (lt == -1) {
+ throw new IllegalArgumentException("Malformed PersonIdent string"
+ + " (no < was found): " + in);
+ }
+ final int gt = in.indexOf('>', lt);
+ if (gt == -1) {
+ throw new IllegalArgumentException("Malformed PersonIdent string"
+ + " (no > was found): " + in);
+ }
+ final int sp = in.indexOf(' ', gt + 2);
+ if (sp == -1) {
+ when = 0;
+ tzOffset = -1;
+ } else {
+ final String tzHoursStr = in.substring(sp + 1, sp + 4).trim();
+ final int tzHours;
+ if (tzHoursStr.charAt(0) == '+') {
+ tzHours = Integer.parseInt(tzHoursStr.substring(1));
+ } else {
+ tzHours = Integer.parseInt(tzHoursStr);
+ }
+ final int tzMins = Integer.parseInt(in.substring(sp + 4).trim());
+ when = Long.parseLong(in.substring(gt + 1, sp).trim()) * 1000;
+ tzOffset = tzHours * 60 + tzMins;
+ }
+
+ name = in.substring(0, lt).trim();
+ emailAddress = in.substring(lt + 1, gt).trim();
+ }
+
+ /**
+ * @return Name of person
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return email address of person
+ */
+ public String getEmailAddress() {
+ return emailAddress;
+ }
+
+ /**
+ * @return timestamp
+ */
+ public Date getWhen() {
+ return new Date(when);
+ }
+
+ /**
+ * @return this person's declared time zone; null if time zone is unknown.
+ */
+ public TimeZone getTimeZone() {
+ StringBuffer tzId = new StringBuffer(8);
+ tzId.append("GMT");
+ appendTimezone(tzId);
+ return TimeZone.getTimeZone(tzId.toString());
+ }
+
+ /**
+ * @return this person's declared time zone as minutes east of UTC. If the
+ * timezone is to the west of UTC it is negative.
+ */
+ public int getTimeZoneOffset() {
+ return tzOffset;
+ }
+
+ public int hashCode() {
+ return getEmailAddress().hashCode() ^ (int) when;
+ }
+
+ public boolean equals(final Object o) {
+ if (o instanceof PersonIdent) {
+ final PersonIdent p = (PersonIdent) o;
+ return getName().equals(p.getName())
+ && getEmailAddress().equals(p.getEmailAddress())
+ && when == p.when;
+ }
+ return false;
+ }
+
+ /**
+ * Format for Git storage.
+ *
+ * @return a string in the git author format
+ */
+ public String toExternalString() {
+ final StringBuffer r = new StringBuffer();
+ r.append(getName());
+ r.append(" <");
+ r.append(getEmailAddress());
+ r.append("> ");
+ r.append(when / 1000);
+ r.append(' ');
+ appendTimezone(r);
+ return r.toString();
+ }
+
+ private void appendTimezone(final StringBuffer r) {
+ int offset = tzOffset;
+ final char sign;
+ final int offsetHours;
+ final int offsetMins;
+
+ if (offset < 0) {
+ sign = '-';
+ offset = -offset;
+ } else {
+ sign = '+';
+ }
+
+ offsetHours = offset / 60;
+ offsetMins = offset % 60;
+
+ r.append(sign);
+ if (offsetHours < 10) {
+ r.append('0');
+ }
+ r.append(offsetHours);
+ if (offsetMins < 10) {
+ r.append('0');
+ }
+ r.append(offsetMins);
+ }
+
+ public String toString() {
+ final StringBuffer r = new StringBuffer();
+ final SimpleDateFormat dtfmt;
+ dtfmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US);
+ dtfmt.setTimeZone(getTimeZone());
+
+ r.append("PersonIdent[");
+ r.append(getName());
+ r.append(", ");
+ r.append(getEmailAddress());
+ r.append(", ");
+ r.append(dtfmt.format(Long.valueOf(when)));
+ r.append("]");
+
+ return r.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java
new file mode 100644
index 0000000000..cab1b08584
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/** A progress reporting interface. */
+public interface ProgressMonitor {
+ /** Constant indicating the total work units cannot be predicted. */
+ public static final int UNKNOWN = 0;
+
+ /**
+ * Advise the monitor of the total number of subtasks.
+ * <p>
+ * This should be invoked at most once per progress monitor interface.
+ *
+ * @param totalTasks
+ * the total number of tasks the caller will need to complete
+ * their processing.
+ */
+ void start(int totalTasks);
+
+ /**
+ * Begin processing a single task.
+ *
+ * @param title
+ * title to describe the task. Callers should publish these as
+ * stable string constants that implementations could match
+ * against for translation support.
+ * @param totalWork
+ * total number of work units the application will perform;
+ * {@link #UNKNOWN} if it cannot be predicted in advance.
+ */
+ void beginTask(String title, int totalWork);
+
+ /**
+ * Denote that some work units have been completed.
+ * <p>
+ * This is an incremental update; if invoked once per work unit the correct
+ * value for our argument is <code>1</code>, to indicate a single unit of
+ * work has been finished by the caller.
+ *
+ * @param completed
+ * the number of work units completed since the last call.
+ */
+ void update(int completed);
+
+ /** Finish the current task, so the next can begin. */
+ void endTask();
+
+ /**
+ * Check for user task cancellation.
+ *
+ * @return true if the user asked the process to stop working.
+ */
+ boolean isCancelled();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
new file mode 100644
index 0000000000..b040e9bedd
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/**
+ * Pairing of a name and the {@link ObjectId} it currently has.
+ * <p>
+ * A ref in Git is (more or less) a variable that holds a single object
+ * identifier. The object identifier can be any valid Git object (blob, tree,
+ * commit, annotated tag, ...).
+ * <p>
+ * The ref name has the attributes of the ref that was asked for as well as
+ * the ref it was resolved to for symbolic refs plus the object id it points
+ * to and (for tags) the peeled target object id, i.e. the tag resolved
+ * recursively until a non-tag object is referenced.
+ */
+public class Ref {
+ /** Location where a {@link Ref} is stored. */
+ public static enum Storage {
+ /**
+ * The ref does not exist yet, updating it may create it.
+ * <p>
+ * Creation is likely to choose {@link #LOOSE} storage.
+ */
+ NEW(true, false),
+
+ /**
+ * The ref is stored in a file by itself.
+ * <p>
+ * Updating this ref affects only this ref.
+ */
+ LOOSE(true, false),
+
+ /**
+ * The ref is stored in the <code>packed-refs</code> file, with
+ * others.
+ * <p>
+ * Updating this ref requires rewriting the file, with perhaps many
+ * other refs being included at the same time.
+ */
+ PACKED(false, true),
+
+ /**
+ * The ref is both {@link #LOOSE} and {@link #PACKED}.
+ * <p>
+ * Updating this ref requires only updating the loose file, but deletion
+ * requires updating both the loose file and the packed refs file.
+ */
+ LOOSE_PACKED(true, true),
+
+ /**
+ * The ref came from a network advertisement and storage is unknown.
+ * <p>
+ * This ref cannot be updated without Git-aware support on the remote
+ * side, as Git-aware code consolidate the remote refs and reported them
+ * to this process.
+ */
+ NETWORK(false, false);
+
+ private final boolean loose;
+
+ private final boolean packed;
+
+ private Storage(final boolean l, final boolean p) {
+ loose = l;
+ packed = p;
+ }
+
+ /**
+ * @return true if this storage has a loose file.
+ */
+ public boolean isLoose() {
+ return loose;
+ }
+
+ /**
+ * @return true if this storage is inside the packed file.
+ */
+ public boolean isPacked() {
+ return packed;
+ }
+ }
+
+ private final Storage storage;
+
+ private final String name;
+
+ private ObjectId objectId;
+
+ private ObjectId peeledObjectId;
+
+ private final String origName;
+
+ private final boolean peeled;
+
+ /**
+ * Create a new ref pairing.
+ *
+ * @param st
+ * method used to store this ref.
+ * @param origName
+ * The name used to resolve this ref
+ * @param refName
+ * name of this ref.
+ * @param id
+ * current value of the ref. May be null to indicate a ref that
+ * does not exist yet.
+ */
+ public Ref(final Storage st, final String origName, final String refName, final ObjectId id) {
+ this(st, origName, refName, id, null, false);
+ }
+
+ /**
+ * Create a new ref pairing.
+ *
+ * @param st
+ * method used to store this ref.
+ * @param refName
+ * name of this ref.
+ * @param id
+ * current value of the ref. May be null to indicate a ref that
+ * does not exist yet.
+ */
+ public Ref(final Storage st, final String refName, final ObjectId id) {
+ this(st, refName, refName, id, null, false);
+ }
+
+ /**
+ * Create a new ref pairing.
+ *
+ * @param st
+ * method used to store this ref.
+ * @param origName
+ * The name used to resolve this ref
+ * @param refName
+ * name of this ref.
+ * @param id
+ * current value of the ref. May be null to indicate a ref that
+ * does not exist yet.
+ * @param peel
+ * peeled value of the ref's tag. May be null if this is not a
+ * tag or not yet peeled (in which case the next parameter should be null)
+ * @param peeled
+ * true if peel represents a the peeled value of the object
+ */
+ public Ref(final Storage st, final String origName, final String refName, final ObjectId id,
+ final ObjectId peel, final boolean peeled) {
+ storage = st;
+ this.origName = origName;
+ name = refName;
+ objectId = id;
+ peeledObjectId = peel;
+ this.peeled = peeled;
+ }
+
+ /**
+ * Create a new ref pairing.
+ *
+ * @param st
+ * method used to store this ref.
+ * @param refName
+ * name of this ref.
+ * @param id
+ * current value of the ref. May be null to indicate a ref that
+ * does not exist yet.
+ * @param peel
+ * peeled value of the ref's tag. May be null if this is not a
+ * tag or the peeled value is not known.
+ * @param peeled
+ * true if peel represents a the peeled value of the object
+ */
+ public Ref(final Storage st, final String refName, final ObjectId id,
+ final ObjectId peel, boolean peeled) {
+ this(st, refName, refName, id, peel, peeled);
+ }
+
+ /**
+ * What this ref is called within the repository.
+ *
+ * @return name of this ref.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return the originally resolved name
+ */
+ public String getOrigName() {
+ return origName;
+ }
+
+ /**
+ * Cached value of this ref.
+ *
+ * @return the value of this ref at the last time we read it.
+ */
+ public ObjectId getObjectId() {
+ return objectId;
+ }
+
+ /**
+ * Cached value of <code>ref^{}</code> (the ref peeled to commit).
+ *
+ * @return if this ref is an annotated tag the id of the commit (or tree or
+ * blob) that the annotated tag refers to; null if this ref does not
+ * refer to an annotated tag.
+ */
+ public ObjectId getPeeledObjectId() {
+ if (!peeled)
+ return null;
+ return peeledObjectId;
+ }
+
+ /**
+ * @return whether the Ref represents a peeled tag
+ */
+ public boolean isPeeled() {
+ return peeled;
+ }
+
+ /**
+ * How was this ref obtained?
+ * <p>
+ * The current storage model of a Ref may influence how the ref must be
+ * updated or deleted from the repository.
+ *
+ * @return type of ref.
+ */
+ public Storage getStorage() {
+ return storage;
+ }
+
+ public String toString() {
+ String o = "";
+ if (!origName.equals(name))
+ o = "(" + origName + ")";
+ return "Ref[" + o + name + "=" + ObjectId.toString(getObjectId()) + "]";
+ }
+
+ void setPeeledObjectId(final ObjectId id) {
+ peeledObjectId = id;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
new file mode 100644
index 0000000000..cbbc0a91cc
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Util for sorting (or comparing) Ref instances by name.
+ * <p>
+ * Useful for command line tools or writing out refs to file.
+ */
+public class RefComparator implements Comparator<Ref> {
+
+ /** Singleton instance of RefComparator */
+ public static final RefComparator INSTANCE = new RefComparator();
+
+ public int compare(final Ref o1, final Ref o2) {
+ return o1.getOrigName().compareTo(o2.getOrigName());
+ }
+
+ /**
+ * Sorts the collection of refs, returning a new collection.
+ *
+ * @param refs
+ * collection to be sorted
+ * @return sorted collection of refs
+ */
+ public static Collection<Ref> sort(final Collection<Ref> refs) {
+ final List<Ref> r = new ArrayList<Ref>(refs);
+ Collections.sort(r, INSTANCE);
+ return r;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
new file mode 100644
index 0000000000..2c68dbb6d4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.errors.ObjectWritingException;
+import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+class RefDatabase {
+ private static final String REFS_SLASH = "refs/";
+
+ private static final String[] refSearchPaths = { "", REFS_SLASH,
+ R_TAGS, Constants.R_HEADS, Constants.R_REMOTES };
+
+ private final Repository db;
+
+ private final File gitDir;
+
+ private final File refsDir;
+
+ private Map<String, Ref> looseRefs;
+ private Map<String, Long> looseRefsMTime;
+ private Map<String, String> looseSymRefs;
+
+ private final File packedRefsFile;
+
+ private Map<String, Ref> packedRefs;
+
+ private long packedRefsLastModified;
+
+ private long packedRefsLength;
+
+ int lastRefModification;
+
+ int lastNotifiedRefModification;
+
+ private int refModificationCounter;
+
+ RefDatabase(final Repository r) {
+ db = r;
+ gitDir = db.getDirectory();
+ refsDir = FS.resolve(gitDir, "refs");
+ packedRefsFile = FS.resolve(gitDir, Constants.PACKED_REFS);
+ clearCache();
+ }
+
+ synchronized void clearCache() {
+ looseRefs = new HashMap<String, Ref>();
+ looseRefsMTime = new HashMap<String, Long>();
+ packedRefs = new HashMap<String, Ref>();
+ looseSymRefs = new HashMap<String, String>();
+ packedRefsLastModified = 0;
+ packedRefsLength = 0;
+ }
+
+ Repository getRepository() {
+ return db;
+ }
+
+ void create() {
+ refsDir.mkdir();
+ new File(refsDir, "heads").mkdir();
+ new File(refsDir, "tags").mkdir();
+ }
+
+ ObjectId idOf(final String name) throws IOException {
+ refreshPackedRefs();
+ final Ref r = readRefBasic(name, 0);
+ return r != null ? r.getObjectId() : null;
+ }
+
+ /**
+ * Create a command to update, create or delete a ref in this repository.
+ *
+ * @param name
+ * name of the ref the caller wants to modify.
+ * @return an update command. The caller must finish populating this command
+ * and then invoke one of the update methods to actually make a
+ * change.
+ * @throws IOException
+ * a symbolic ref was passed in and could not be resolved back
+ * to the base ref, as the symbolic ref could not be read.
+ */
+ RefUpdate newUpdate(final String name) throws IOException {
+ refreshPackedRefs();
+ Ref r = readRefBasic(name, 0);
+ if (r == null)
+ r = new Ref(Ref.Storage.NEW, name, null);
+ return new RefUpdate(this, r, fileForRef(r.getName()));
+ }
+
+ void stored(final String origName, final String name, final ObjectId id, final long time) {
+ synchronized (this) {
+ looseRefs.put(name, new Ref(Ref.Storage.LOOSE, name, name, id));
+ looseRefsMTime.put(name, time);
+ setModified();
+ }
+ db.fireRefsMaybeChanged();
+ }
+
+ /**
+ * An set of update operations for renaming a ref
+ *
+ * @param fromRef Old ref name
+ * @param toRef New ref name
+ * @return a RefUpdate operation to rename a ref
+ * @throws IOException
+ */
+ RefRename newRename(String fromRef, String toRef) throws IOException {
+ refreshPackedRefs();
+ Ref f = readRefBasic(fromRef, 0);
+ Ref t = new Ref(Ref.Storage.NEW, toRef, null);
+ RefUpdate refUpdateFrom = new RefUpdate(this, f, fileForRef(f.getName()));
+ RefUpdate refUpdateTo = new RefUpdate(this, t, fileForRef(t.getName()));
+ return new RefRename(refUpdateTo, refUpdateFrom);
+ }
+
+ /**
+ * Writes a symref (e.g. HEAD) to disk
+ *
+ * @param name
+ * symref name
+ * @param target
+ * pointed to ref
+ * @throws IOException
+ */
+ void link(final String name, final String target) throws IOException {
+ final byte[] content = Constants.encode("ref: " + target + "\n");
+ lockAndWriteFile(fileForRef(name), content);
+ synchronized (this) {
+ looseSymRefs.remove(name);
+ setModified();
+ }
+ db.fireRefsMaybeChanged();
+ }
+
+ void uncacheSymRef(String name) {
+ synchronized(this) {
+ looseSymRefs.remove(name);
+ setModified();
+ }
+ }
+
+ void uncacheRef(String name) {
+ looseRefs.remove(name);
+ looseRefsMTime.remove(name);
+ packedRefs.remove(name);
+ }
+
+ private void setModified() {
+ lastRefModification = refModificationCounter++;
+ }
+
+ Ref readRef(final String partialName) throws IOException {
+ refreshPackedRefs();
+ for (int k = 0; k < refSearchPaths.length; k++) {
+ final Ref r = readRefBasic(refSearchPaths[k] + partialName, 0);
+ if (r != null && r.getObjectId() != null)
+ return r;
+ }
+ return null;
+ }
+
+ /**
+ * @return all known refs (heads, tags, remotes).
+ */
+ Map<String, Ref> getAllRefs() {
+ return readRefs();
+ }
+
+ /**
+ * @return all tags; key is short tag name ("v1.0") and value of the entry
+ * contains the ref with the full tag name ("refs/tags/v1.0").
+ */
+ Map<String, Ref> getTags() {
+ final Map<String, Ref> tags = new HashMap<String, Ref>();
+ for (final Ref r : readRefs().values()) {
+ if (r.getName().startsWith(R_TAGS))
+ tags.put(r.getName().substring(R_TAGS.length()), r);
+ }
+ return tags;
+ }
+
+ private Map<String, Ref> readRefs() {
+ final HashMap<String, Ref> avail = new HashMap<String, Ref>();
+ readPackedRefs(avail);
+ readLooseRefs(avail, REFS_SLASH, refsDir);
+ try {
+ final Ref r = readRefBasic(Constants.HEAD, 0);
+ if (r != null && r.getObjectId() != null)
+ avail.put(Constants.HEAD, r);
+ } catch (IOException e) {
+ // ignore here
+ }
+ db.fireRefsMaybeChanged();
+ return avail;
+ }
+
+ private synchronized void readPackedRefs(final Map<String, Ref> avail) {
+ refreshPackedRefs();
+ avail.putAll(packedRefs);
+ }
+
+ private void readLooseRefs(final Map<String, Ref> avail,
+ final String prefix, final File dir) {
+ final File[] entries = dir.listFiles();
+ if (entries == null)
+ return;
+
+ for (final File ent : entries) {
+ final String entName = ent.getName();
+ if (".".equals(entName) || "..".equals(entName))
+ continue;
+ if (ent.isDirectory()) {
+ readLooseRefs(avail, prefix + entName + "/", ent);
+ } else {
+ try {
+ final Ref ref = readRefBasic(prefix + entName, 0);
+ if (ref != null)
+ avail.put(ref.getOrigName(), ref);
+ } catch (IOException e) {
+ continue;
+ }
+ }
+ }
+ }
+
+ Ref peel(final Ref ref) {
+ if (ref.isPeeled())
+ return ref;
+ ObjectId peeled = null;
+ try {
+ Object target = db.mapObject(ref.getObjectId(), ref.getName());
+ while (target instanceof Tag) {
+ final Tag tag = (Tag)target;
+ peeled = tag.getObjId();
+ if (Constants.TYPE_TAG.equals(tag.getType()))
+ target = db.mapObject(tag.getObjId(), ref.getName());
+ else
+ break;
+ }
+ } catch (IOException e) {
+ // Ignore a read error.  Callers will also get the same error
+ // if they try to use the result of getPeeledObjectId.
+ }
+ return new Ref(ref.getStorage(), ref.getName(), ref.getObjectId(), peeled, true);
+
+ }
+
+ private File fileForRef(final String name) {
+ if (name.startsWith(REFS_SLASH))
+ return new File(refsDir, name.substring(REFS_SLASH.length()));
+ return new File(gitDir, name);
+ }
+
+ private Ref readRefBasic(final String name, final int depth) throws IOException {
+ return readRefBasic(name, name, depth);
+ }
+
+ private synchronized Ref readRefBasic(final String origName,
+ final String name, final int depth) throws IOException {
+ // Prefer loose ref to packed ref as the loose
+ // file can be more up-to-date than a packed one.
+ //
+ Ref ref = looseRefs.get(origName);
+ final File loose = fileForRef(name);
+ final long mtime = loose.lastModified();
+ if (ref != null) {
+ Long cachedlastModified = looseRefsMTime.get(name);
+ if (cachedlastModified != null && cachedlastModified == mtime) {
+ if (packedRefs.containsKey(origName))
+ return new Ref(Storage.LOOSE_PACKED, origName, ref
+ .getObjectId(), ref.getPeeledObjectId(), ref
+ .isPeeled());
+ else
+ return ref;
+ }
+ looseRefs.remove(origName);
+ looseRefsMTime.remove(origName);
+ }
+
+ if (mtime == 0) {
+ // If last modified is 0 the file does not exist.
+ // Try packed cache.
+ //
+ ref = packedRefs.get(name);
+ if (ref != null)
+ if (!ref.getOrigName().equals(origName))
+ ref = new Ref(Storage.LOOSE_PACKED, origName, name, ref.getObjectId());
+ return ref;
+ }
+
+ String line = null;
+ try {
+ Long cachedlastModified = looseRefsMTime.get(name);
+ if (cachedlastModified != null && cachedlastModified == mtime) {
+ line = looseSymRefs.get(name);
+ }
+ if (line == null) {
+ line = readLine(loose);
+ looseRefsMTime.put(name, mtime);
+ looseSymRefs.put(name, line);
+ }
+ } catch (FileNotFoundException notLoose) {
+ return packedRefs.get(name);
+ }
+
+ if (line == null || line.length() == 0) {
+ looseRefs.remove(origName);
+ looseRefsMTime.remove(origName);
+ return new Ref(Ref.Storage.LOOSE, origName, name, null);
+ }
+
+ if (line.startsWith("ref: ")) {
+ if (depth >= 5) {
+ throw new IOException("Exceeded maximum ref depth of " + depth
+ + " at " + name + ". Circular reference?");
+ }
+
+ final String target = line.substring("ref: ".length());
+ Ref r = readRefBasic(target, target, depth + 1);
+ Long cachedMtime = looseRefsMTime.get(name);
+ if (cachedMtime != null && cachedMtime != mtime)
+ setModified();
+ looseRefsMTime.put(name, mtime);
+ if (r == null)
+ return new Ref(Ref.Storage.LOOSE, origName, target, null);
+ if (!origName.equals(r.getName()))
+ r = new Ref(Ref.Storage.LOOSE_PACKED, origName, r.getName(), r.getObjectId(), r.getPeeledObjectId(), true);
+ return r;
+ }
+
+ setModified();
+
+ final ObjectId id;
+ try {
+ id = ObjectId.fromString(line);
+ } catch (IllegalArgumentException notRef) {
+ throw new IOException("Not a ref: " + name + ": " + line);
+ }
+
+ Storage storage;
+ if (packedRefs.containsKey(name))
+ storage = Ref.Storage.LOOSE_PACKED;
+ else
+ storage = Ref.Storage.LOOSE;
+ ref = new Ref(storage, name, id);
+ looseRefs.put(name, ref);
+ looseRefsMTime.put(name, mtime);
+
+ if (!origName.equals(name)) {
+ ref = new Ref(Ref.Storage.LOOSE, origName, name, id);
+ looseRefs.put(origName, ref);
+ }
+
+ return ref;
+ }
+
+ private synchronized void refreshPackedRefs() {
+ final long currTime = packedRefsFile.lastModified();
+ final long currLen = currTime == 0 ? 0 : packedRefsFile.length();
+ if (currTime == packedRefsLastModified && currLen == packedRefsLength)
+ return;
+ if (currTime == 0) {
+ packedRefsLastModified = 0;
+ packedRefsLength = 0;
+ packedRefs = new HashMap<String, Ref>();
+ return;
+ }
+
+ final Map<String, Ref> newPackedRefs = new HashMap<String, Ref>();
+ try {
+ final BufferedReader b = openReader(packedRefsFile);
+ try {
+ String p;
+ Ref last = null;
+ while ((p = b.readLine()) != null) {
+ if (p.charAt(0) == '#')
+ continue;
+
+ if (p.charAt(0) == '^') {
+ if (last == null)
+ throw new IOException("Peeled line before ref.");
+
+ final ObjectId id = ObjectId.fromString(p.substring(1));
+ last = new Ref(Ref.Storage.PACKED, last.getName(), last
+ .getName(), last.getObjectId(), id, true);
+ newPackedRefs.put(last.getName(), last);
+ continue;
+ }
+
+ final int sp = p.indexOf(' ');
+ final ObjectId id = ObjectId.fromString(p.substring(0, sp));
+ final String name = copy(p, sp + 1, p.length());
+ last = new Ref(Ref.Storage.PACKED, name, name, id);
+ newPackedRefs.put(last.getName(), last);
+ }
+ } finally {
+ b.close();
+ }
+ packedRefsLastModified = currTime;
+ packedRefsLength = currLen;
+ packedRefs = newPackedRefs;
+ setModified();
+ } catch (FileNotFoundException noPackedRefs) {
+ // Ignore it and leave the new map empty.
+ //
+ packedRefsLastModified = 0;
+ packedRefsLength = 0;
+ packedRefs = newPackedRefs;
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot read packed refs", e);
+ }
+ }
+
+ private static String copy(final String src, final int off, final int end) {
+ return new StringBuilder(end - off).append(src, off, end).toString();
+ }
+
+ private void lockAndWriteFile(File file, byte[] content) throws IOException {
+ String name = file.getName();
+ final LockFile lck = new LockFile(file);
+ if (!lck.lock())
+ throw new ObjectWritingException("Unable to lock " + name);
+ try {
+ lck.write(content);
+ } catch (IOException ioe) {
+ throw new ObjectWritingException("Unable to write " + name, ioe);
+ }
+ if (!lck.commit())
+ throw new ObjectWritingException("Unable to write " + name);
+ }
+
+ synchronized void removePackedRef(String name) throws IOException {
+ packedRefs.remove(name);
+ writePackedRefs();
+ }
+
+ private void writePackedRefs() throws IOException {
+ new RefWriter(packedRefs.values()) {
+ @Override
+ protected void writeFile(String name, byte[] content) throws IOException {
+ lockAndWriteFile(new File(db.getDirectory(), name), content);
+ }
+ }.writePackedRefs();
+ }
+
+ private static String readLine(final File file)
+ throws FileNotFoundException, IOException {
+ final byte[] buf = NB.readFully(file, 4096);
+ int n = buf.length;
+
+ // remove trailing whitespaces
+ while (n > 0 && Character.isWhitespace(buf[n - 1]))
+ n--;
+
+ if (n == 0)
+ return null;
+ return RawParseUtils.decode(buf, 0, n);
+ }
+
+ private static BufferedReader openReader(final File fileLocation)
+ throws FileNotFoundException {
+ return new BufferedReader(new InputStreamReader(new FileInputStream(
+ fileLocation), Constants.CHARSET));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java
new file mode 100644
index 0000000000..d41bbb644b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Utility class to work with reflog files
+ *
+ * @author Dave Watson
+ */
+public class RefLogWriter {
+ static void append(final RefUpdate u, final String msg) throws IOException {
+ final ObjectId oldId = u.getOldObjectId();
+ final ObjectId newId = u.getNewObjectId();
+ final Repository db = u.getRepository();
+ final PersonIdent ident = u.getRefLogIdent();
+
+ appendOneRecord(oldId, newId, ident, msg, db, u.getName());
+ if (!u.getName().equals(u.getOrigName()))
+ appendOneRecord(oldId, newId, ident, msg, db, u.getOrigName());
+ }
+
+ static void append(RefRename refRename, String logName, String msg) throws IOException {
+ final ObjectId id = refRename.getObjectId();
+ final Repository db = refRename.getRepository();
+ final PersonIdent ident = refRename.getRefLogIdent();
+ appendOneRecord(id, id, ident, msg, db, logName);
+ }
+
+ static void renameTo(final Repository db, final RefUpdate from,
+ final RefUpdate to) throws IOException {
+ final File logdir = new File(db.getDirectory(), Constants.LOGS);
+ final File reflogFrom = new File(logdir, from.getName());
+ if (!reflogFrom.exists())
+ return;
+ final File reflogTo = new File(logdir, to.getName());
+ final File reflogToDir = reflogTo.getParentFile();
+ File tmp = new File(logdir, "tmp-renamed-log.." + Thread.currentThread().getId());
+ if (!reflogFrom.renameTo(tmp)) {
+ throw new IOException("Cannot rename " + reflogFrom + " to (" + tmp
+ + ")" + reflogTo);
+ }
+ RefUpdate.deleteEmptyDir(reflogFrom, RefUpdate.count(from.getName(),
+ '/'));
+ if (!reflogToDir.exists() && !reflogToDir.mkdirs()) {
+ throw new IOException("Cannot create directory " + reflogToDir);
+ }
+ if (!tmp.renameTo(reflogTo)) {
+ throw new IOException("Cannot rename (" + tmp + ")" + reflogFrom
+ + " to " + reflogTo);
+ }
+ }
+
+ private static void appendOneRecord(final ObjectId oldId,
+ final ObjectId newId, PersonIdent ident, final String msg,
+ final Repository db, final String refName) throws IOException {
+ if (ident == null)
+ ident = new PersonIdent(db);
+ else
+ ident = new PersonIdent(ident);
+
+ final StringBuilder r = new StringBuilder();
+ r.append(ObjectId.toString(oldId));
+ r.append(' ');
+ r.append(ObjectId.toString(newId));
+ r.append(' ');
+ r.append(ident.toExternalString());
+ r.append('\t');
+ r.append(msg);
+ r.append('\n');
+
+ final byte[] rec = Constants.encode(r.toString());
+ final File logdir = new File(db.getDirectory(), Constants.LOGS);
+ final File reflog = new File(logdir, refName);
+ final File refdir = reflog.getParentFile();
+
+ if (!refdir.exists() && !refdir.mkdirs())
+ throw new IOException("Cannot create directory " + refdir);
+
+ final FileOutputStream out = new FileOutputStream(reflog, true);
+ try {
+ out.write(rec);
+ } finally {
+ out.close();
+ }
+ }
+
+ /**
+ * Writes reflog entry for ref specified by refName
+ *
+ * @param repo
+ * repository to use
+ * @param oldCommit
+ * previous commit
+ * @param commit
+ * new commit
+ * @param message
+ * reflog message
+ * @param refName
+ * full ref name
+ * @throws IOException
+ * @deprecated rely upon {@link RefUpdate}'s automatic logging instead.
+ */
+ public static void writeReflog(Repository repo, ObjectId oldCommit,
+ ObjectId commit, String message, String refName) throws IOException {
+ appendOneRecord(oldCommit, commit, null, message, repo, refName);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
new file mode 100644
index 0000000000..7e76ac58a0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2009, Robin Rosenberg
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.RefUpdate.Result;
+
+/**
+ * A RefUpdate combination for renaming a ref
+ */
+public class RefRename {
+ private RefUpdate newToUpdate;
+
+ private RefUpdate oldFromDelete;
+
+ private Result renameResult = Result.NOT_ATTEMPTED;
+
+ RefRename(final RefUpdate toUpdate, final RefUpdate fromUpdate) {
+ newToUpdate = toUpdate;
+ oldFromDelete = fromUpdate;
+ }
+
+ /**
+ * @return result of rename operation
+ */
+ public Result getResult() {
+ return renameResult;
+ }
+
+ /**
+ * @return the result of the new ref update
+ * @throws IOException
+ */
+ public Result rename() throws IOException {
+ Ref oldRef = oldFromDelete.db.readRef(Constants.HEAD);
+ boolean renameHEADtoo = oldRef != null
+ && oldRef.getName().equals(oldFromDelete.getName());
+ Repository db = oldFromDelete.getRepository();
+ try {
+ RefLogWriter.renameTo(db, oldFromDelete,
+ newToUpdate);
+ newToUpdate.setRefLogMessage(null, false);
+ String tmpRefName = "RENAMED-REF.." + Thread.currentThread().getId();
+ RefUpdate tmpUpdateRef = db.updateRef(tmpRefName);
+ if (renameHEADtoo) {
+ try {
+ oldFromDelete.db.link(Constants.HEAD, tmpRefName);
+ } catch (IOException e) {
+ RefLogWriter.renameTo(db,
+ newToUpdate, oldFromDelete);
+ return renameResult = Result.LOCK_FAILURE;
+ }
+ }
+ tmpUpdateRef.setNewObjectId(oldFromDelete.getOldObjectId());
+ tmpUpdateRef.setForceUpdate(true);
+ Result update = tmpUpdateRef.update();
+ if (update != Result.FORCED && update != Result.NEW && update != Result.NO_CHANGE) {
+ RefLogWriter.renameTo(db,
+ newToUpdate, oldFromDelete);
+ if (renameHEADtoo) {
+ oldFromDelete.db.link(Constants.HEAD, oldFromDelete.getName());
+ }
+ return renameResult = update;
+ }
+
+ oldFromDelete.setExpectedOldObjectId(oldFromDelete.getOldObjectId());
+ oldFromDelete.setForceUpdate(true);
+ Result delete = oldFromDelete.delete();
+ if (delete != Result.FORCED) {
+ if (db.getRef(
+ oldFromDelete.getName()) != null) {
+ RefLogWriter.renameTo(db,
+ newToUpdate, oldFromDelete);
+ if (renameHEADtoo) {
+ oldFromDelete.db.link(Constants.HEAD, oldFromDelete
+ .getName());
+ }
+ }
+ return renameResult = delete;
+ }
+
+ newToUpdate.setNewObjectId(tmpUpdateRef.getNewObjectId());
+ Result updateResult = newToUpdate.update();
+ if (updateResult != Result.NEW) {
+ RefLogWriter.renameTo(db, newToUpdate, oldFromDelete);
+ if (renameHEADtoo) {
+ oldFromDelete.db.link(Constants.HEAD, oldFromDelete.getName());
+ }
+ oldFromDelete.setExpectedOldObjectId(null);
+ oldFromDelete.setNewObjectId(oldFromDelete.getOldObjectId());
+ oldFromDelete.setForceUpdate(true);
+ oldFromDelete.setRefLogMessage(null, false);
+ Result undelete = oldFromDelete.update();
+ if (undelete != Result.NEW && undelete != Result.LOCK_FAILURE)
+ return renameResult = Result.IO_FAILURE;
+ return renameResult = Result.LOCK_FAILURE;
+ }
+
+ if (renameHEADtoo) {
+ oldFromDelete.db.link(Constants.HEAD, newToUpdate.getName());
+ } else {
+ db.fireRefsMaybeChanged();
+ }
+ RefLogWriter.append(this, newToUpdate.getName(), "Branch: renamed "
+ + db.shortenRefName(oldFromDelete.getName()) + " to "
+ + db.shortenRefName(newToUpdate.getName()));
+ if (renameHEADtoo)
+ RefLogWriter.append(this, Constants.HEAD, "Branch: renamed "
+ + db.shortenRefName(oldFromDelete.getName()) + " to "
+ + db.shortenRefName(newToUpdate.getName()));
+ return renameResult = Result.RENAMED;
+ } catch (RuntimeException e) {
+ throw e;
+ }
+ }
+
+ ObjectId getObjectId() {
+ return oldFromDelete.getOldObjectId();
+ }
+
+ Repository getRepository() {
+ return oldFromDelete.getRepository();
+ }
+
+ PersonIdent getRefLogIdent() {
+ return newToUpdate.getRefLogIdent();
+ }
+
+ String getToName() {
+ return newToUpdate.getName();
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
new file mode 100644
index 0000000000..18dc582c8a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * Updates any locally stored ref.
+ */
+public class RefUpdate {
+ /** Status of an update request. */
+ public static enum Result {
+ /** The ref update/delete has not been attempted by the caller. */
+ NOT_ATTEMPTED,
+
+ /**
+ * The ref could not be locked for update/delete.
+ * <p>
+ * This is generally a transient failure and is usually caused by
+ * another process trying to access the ref at the same time as this
+ * process was trying to update it. It is possible a future operation
+ * will be successful.
+ */
+ LOCK_FAILURE,
+
+ /**
+ * Same value already stored.
+ * <p>
+ * Both the old value and the new value are identical. No change was
+ * necessary for an update. For delete the branch is removed.
+ */
+ NO_CHANGE,
+
+ /**
+ * The ref was created locally for an update, but ignored for delete.
+ * <p>
+ * The ref did not exist when the update started, but it was created
+ * successfully with the new value.
+ */
+ NEW,
+
+ /**
+ * The ref had to be forcefully updated/deleted.
+ * <p>
+ * The ref already existed but its old value was not fully merged into
+ * the new value. The configuration permitted a forced update to take
+ * place, so ref now contains the new value. History associated with the
+ * objects not merged may no longer be reachable.
+ */
+ FORCED,
+
+ /**
+ * The ref was updated/deleted in a fast-forward way.
+ * <p>
+ * The tracking ref already existed and its old value was fully merged
+ * into the new value. No history was made unreachable.
+ */
+ FAST_FORWARD,
+
+ /**
+ * Not a fast-forward and not stored.
+ * <p>
+ * The tracking ref already existed but its old value was not fully
+ * merged into the new value. The configuration did not allow a forced
+ * update/delete to take place, so ref still contains the old value. No
+ * previous history was lost.
+ */
+ REJECTED,
+
+ /**
+ * Rejected because trying to delete the current branch.
+ * <p>
+ * Has no meaning for update.
+ */
+ REJECTED_CURRENT_BRANCH,
+
+ /**
+ * The ref was probably not updated/deleted because of I/O error.
+ * <p>
+ * Unexpected I/O error occurred when writing new ref. Such error may
+ * result in uncertain state, but most probably ref was not updated.
+ * <p>
+ * This kind of error doesn't include {@link #LOCK_FAILURE}, which is a
+ * different case.
+ */
+ IO_FAILURE,
+
+ /**
+ * The ref was renamed from another name
+ * <p>
+ */
+ RENAMED
+ }
+
+ /** Repository the ref is stored in. */
+ final RefDatabase db;
+
+ /** Location of the loose file holding the value of this ref. */
+ final File looseFile;
+
+ /** New value the caller wants this ref to have. */
+ private ObjectId newValue;
+
+ /** Does this specification ask for forced updated (rewind/reset)? */
+ private boolean force;
+
+ /** Identity to record action as within the reflog. */
+ private PersonIdent refLogIdent;
+
+ /** Message the caller wants included in the reflog. */
+ private String refLogMessage;
+
+ /** Should the Result value be appended to {@link #refLogMessage}. */
+ private boolean refLogIncludeResult;
+
+ /** Old value of the ref, obtained after we lock it. */
+ private ObjectId oldValue;
+
+ /** If non-null, the value {@link #oldValue} must have to continue. */
+ private ObjectId expValue;
+
+ /** Result of the update operation. */
+ Result result = Result.NOT_ATTEMPTED;
+
+ private final Ref ref;
+
+ RefUpdate(final RefDatabase r, final Ref ref, final File f) {
+ db = r;
+ this.ref = ref;
+ oldValue = ref.getObjectId();
+ looseFile = f;
+ refLogMessage = "";
+ }
+
+ /** @return the repository the updated ref resides in */
+ public Repository getRepository() {
+ return db.getRepository();
+ }
+
+ /**
+ * Get the name of the ref this update will operate on.
+ *
+ * @return name of underlying ref.
+ */
+ public String getName() {
+ return ref.getName();
+ }
+
+ /**
+ * Get the requested name of the ref thit update will operate on
+ *
+ * @return original (requested) name of the underlying ref.
+ */
+ public String getOrigName() {
+ return ref.getOrigName();
+ }
+
+ /**
+ * Get the new value the ref will be (or was) updated to.
+ *
+ * @return new value. Null if the caller has not configured it.
+ */
+ public ObjectId getNewObjectId() {
+ return newValue;
+ }
+
+ /**
+ * Set the new value the ref will update to.
+ *
+ * @param id
+ * the new value.
+ */
+ public void setNewObjectId(final AnyObjectId id) {
+ newValue = id.copy();
+ }
+
+ /**
+ * @return the expected value of the ref after the lock is taken, but before
+ * update occurs. Null to avoid the compare and swap test. Use
+ * {@link ObjectId#zeroId()} to indicate expectation of a
+ * non-existant ref.
+ */
+ public ObjectId getExpectedOldObjectId() {
+ return expValue;
+ }
+
+ /**
+ * @param id
+ * the expected value of the ref after the lock is taken, but
+ * before update occurs. Null to avoid the compare and swap test.
+ * Use {@link ObjectId#zeroId()} to indicate expectation of a
+ * non-existant ref.
+ */
+ public void setExpectedOldObjectId(final AnyObjectId id) {
+ expValue = id != null ? id.toObjectId() : null;
+ }
+
+ /**
+ * Check if this update wants to forcefully change the ref.
+ *
+ * @return true if this update should ignore merge tests.
+ */
+ public boolean isForceUpdate() {
+ return force;
+ }
+
+ /**
+ * Set if this update wants to forcefully change the ref.
+ *
+ * @param b
+ * true if this update should ignore merge tests.
+ */
+ public void setForceUpdate(final boolean b) {
+ force = b;
+ }
+
+ /** @return identity of the user making the change in the reflog. */
+ public PersonIdent getRefLogIdent() {
+ return refLogIdent;
+ }
+
+ /**
+ * Set the identity of the user appearing in the reflog.
+ * <p>
+ * The timestamp portion of the identity is ignored. A new identity with the
+ * current timestamp will be created automatically when the update occurs
+ * and the log record is written.
+ *
+ * @param pi
+ * identity of the user. If null the identity will be
+ * automatically determined based on the repository
+ * configuration.
+ */
+ public void setRefLogIdent(final PersonIdent pi) {
+ refLogIdent = pi;
+ }
+
+ /**
+ * Get the message to include in the reflog.
+ *
+ * @return message the caller wants to include in the reflog; null if the
+ * update should not be logged.
+ */
+ public String getRefLogMessage() {
+ return refLogMessage;
+ }
+
+ /**
+ * Set the message to include in the reflog.
+ *
+ * @param msg
+ * the message to describe this change. It may be null
+ * if appendStatus is null in order not to append to the reflog
+ * @param appendStatus
+ * true if the status of the ref change (fast-forward or
+ * forced-update) should be appended to the user supplied
+ * message.
+ */
+ public void setRefLogMessage(final String msg, final boolean appendStatus) {
+ if (msg == null && !appendStatus)
+ disableRefLog();
+ else if (msg == null && appendStatus) {
+ refLogMessage = "";
+ refLogIncludeResult = true;
+ } else {
+ refLogMessage = msg;
+ refLogIncludeResult = appendStatus;
+ }
+ }
+
+ /** Don't record this update in the ref's associated reflog. */
+ public void disableRefLog() {
+ refLogMessage = null;
+ refLogIncludeResult = false;
+ }
+
+ /**
+ * The old value of the ref, prior to the update being attempted.
+ * <p>
+ * This value may differ before and after the update method. Initially it is
+ * populated with the value of the ref before the lock is taken, but the old
+ * value may change if someone else modified the ref between the time we
+ * last read it and when the ref was locked for update.
+ *
+ * @return the value of the ref prior to the update being attempted; null if
+ * the updated has not been attempted yet.
+ */
+ public ObjectId getOldObjectId() {
+ return oldValue;
+ }
+
+ /**
+ * Get the status of this update.
+ * <p>
+ * The same value that was previously returned from an update method.
+ *
+ * @return the status of the update.
+ */
+ public Result getResult() {
+ return result;
+ }
+
+ private void requireCanDoUpdate() {
+ if (newValue == null)
+ throw new IllegalStateException("A NewObjectId is required.");
+ }
+
+ /**
+ * Force the ref to take the new value.
+ * <p>
+ * This is just a convenient helper for setting the force flag, and as such
+ * the merge test is performed.
+ *
+ * @return the result status of the update.
+ * @throws IOException
+ * an unexpected IO error occurred while writing changes.
+ */
+ public Result forceUpdate() throws IOException {
+ force = true;
+ return update();
+ }
+
+ /**
+ * Gracefully update the ref to the new value.
+ * <p>
+ * Merge test will be performed according to {@link #isForceUpdate()}.
+ * <p>
+ * This is the same as:
+ *
+ * <pre>
+ * return update(new RevWalk(repository));
+ * </pre>
+ *
+ * @return the result status of the update.
+ * @throws IOException
+ * an unexpected IO error occurred while writing changes.
+ */
+ public Result update() throws IOException {
+ return update(new RevWalk(db.getRepository()));
+ }
+
+ /**
+ * Gracefully update the ref to the new value.
+ * <p>
+ * Merge test will be performed according to {@link #isForceUpdate()}.
+ *
+ * @param walk
+ * a RevWalk instance this update command can borrow to perform
+ * the merge test. The walk will be reset to perform the test.
+ * @return the result status of the update.
+ * @throws IOException
+ * an unexpected IO error occurred while writing changes.
+ */
+ public Result update(final RevWalk walk) throws IOException {
+ requireCanDoUpdate();
+ try {
+ return result = updateImpl(walk, new UpdateStore());
+ } catch (IOException x) {
+ result = Result.IO_FAILURE;
+ throw x;
+ }
+ }
+
+ /**
+ * Delete the ref.
+ * <p>
+ * This is the same as:
+ *
+ * <pre>
+ * return delete(new RevWalk(repository));
+ * </pre>
+ *
+ * @return the result status of the delete.
+ * @throws IOException
+ */
+ public Result delete() throws IOException {
+ return delete(new RevWalk(db.getRepository()));
+ }
+
+ /**
+ * Delete the ref.
+ *
+ * @param walk
+ * a RevWalk instance this delete command can borrow to perform
+ * the merge test. The walk will be reset to perform the test.
+ * @return the result status of the delete.
+ * @throws IOException
+ */
+ public Result delete(final RevWalk walk) throws IOException {
+ if (getName().startsWith(Constants.R_HEADS)) {
+ final Ref head = db.readRef(Constants.HEAD);
+ if (head != null && getName().equals(head.getName()))
+ return result = Result.REJECTED_CURRENT_BRANCH;
+ }
+
+ try {
+ return result = updateImpl(walk, new DeleteStore());
+ } catch (IOException x) {
+ result = Result.IO_FAILURE;
+ throw x;
+ }
+ }
+
+ private Result updateImpl(final RevWalk walk, final Store store)
+ throws IOException {
+ final LockFile lock;
+ RevObject newObj;
+ RevObject oldObj;
+
+ if (isNameConflicting())
+ return Result.LOCK_FAILURE;
+ lock = new LockFile(looseFile);
+ if (!lock.lock())
+ return Result.LOCK_FAILURE;
+ try {
+ oldValue = db.idOf(getName());
+ if (expValue != null) {
+ final ObjectId o;
+ o = oldValue != null ? oldValue : ObjectId.zeroId();
+ if (!AnyObjectId.equals(expValue, o))
+ return Result.LOCK_FAILURE;
+ }
+ if (oldValue == null)
+ return store.store(lock, Result.NEW);
+
+ newObj = safeParse(walk, newValue);
+ oldObj = safeParse(walk, oldValue);
+ if (newObj == oldObj)
+ return store.store(lock, Result.NO_CHANGE);
+
+ if (newObj instanceof RevCommit && oldObj instanceof RevCommit) {
+ if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj))
+ return store.store(lock, Result.FAST_FORWARD);
+ }
+
+ if (isForceUpdate())
+ return store.store(lock, Result.FORCED);
+ return Result.REJECTED;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private boolean isNameConflicting() throws IOException {
+ final String myName = getName();
+ final int lastSlash = myName.lastIndexOf('/');
+ if (lastSlash > 0)
+ if (db.getRepository().getRef(myName.substring(0, lastSlash)) != null)
+ return true;
+
+ final String rName = myName + "/";
+ for (Ref r : db.getAllRefs().values()) {
+ if (r.getName().startsWith(rName))
+ return true;
+ }
+ return false;
+ }
+
+ private static RevObject safeParse(final RevWalk rw, final AnyObjectId id)
+ throws IOException {
+ try {
+ return id != null ? rw.parseAny(id) : null;
+ } catch (MissingObjectException e) {
+ // We can expect some objects to be missing, like if we are
+ // trying to force a deletion of a branch and the object it
+ // points to has been pruned from the database due to freak
+ // corruption accidents (it happens with 'git new-work-dir').
+ //
+ return null;
+ }
+ }
+
+ private Result updateStore(final LockFile lock, final Result status)
+ throws IOException {
+ if (status == Result.NO_CHANGE)
+ return status;
+ lock.setNeedStatInformation(true);
+ lock.write(newValue);
+ String msg = getRefLogMessage();
+ if (msg != null) {
+ if (refLogIncludeResult) {
+ String strResult = toResultString(status);
+ if (strResult != null) {
+ if (msg.length() > 0)
+ msg = msg + ": " + strResult;
+ else
+ msg = strResult;
+ }
+ }
+ RefLogWriter.append(this, msg);
+ }
+ if (!lock.commit())
+ return Result.LOCK_FAILURE;
+ db.stored(this.ref.getOrigName(), ref.getName(), newValue, lock.getCommitLastModified());
+ return status;
+ }
+
+ private static String toResultString(final Result status) {
+ switch (status) {
+ case FORCED:
+ return "forced-update";
+ case FAST_FORWARD:
+ return "fast forward";
+ case NEW:
+ return "created";
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Handle the abstraction of storing a ref update. This is because both
+ * updating and deleting of a ref have merge testing in common.
+ */
+ private abstract class Store {
+ abstract Result store(final LockFile lock, final Result status)
+ throws IOException;
+ }
+
+ class UpdateStore extends Store {
+
+ @Override
+ Result store(final LockFile lock, final Result status)
+ throws IOException {
+ return updateStore(lock, status);
+ }
+ }
+
+ class DeleteStore extends Store {
+
+ @Override
+ Result store(LockFile lock, Result status) throws IOException {
+ Storage storage = ref.getStorage();
+ if (storage == Storage.NEW)
+ return status;
+ if (storage.isPacked())
+ db.removePackedRef(ref.getName());
+
+ final int levels = count(ref.getName(), '/') - 2;
+
+ // Delete logs _before_ unlocking
+ final File gitDir = db.getRepository().getDirectory();
+ final File logDir = new File(gitDir, Constants.LOGS);
+ deleteFileAndEmptyDir(new File(logDir, ref.getName()), levels);
+
+ // We have to unlock before (maybe) deleting the parent directories
+ lock.unlock();
+ if (storage.isLoose())
+ deleteFileAndEmptyDir(looseFile, levels);
+ db.uncacheRef(ref.getName());
+ return status;
+ }
+
+ private void deleteFileAndEmptyDir(final File file, final int depth)
+ throws IOException {
+ if (file.isFile()) {
+ if (!file.delete())
+ throw new IOException("File cannot be deleted: " + file);
+ File dir = file.getParentFile();
+ for (int i = 0; i < depth; ++i) {
+ if (!dir.delete())
+ break; // ignore problem here
+ dir = dir.getParentFile();
+ }
+ }
+ }
+ }
+
+ UpdateStore newUpdateStore() {
+ return new UpdateStore();
+ }
+
+ DeleteStore newDeleteStore() {
+ return new DeleteStore();
+ }
+
+ static void deleteEmptyDir(File dir, int depth) {
+ for (; depth > 0 && dir != null; depth--) {
+ if (dir.exists() && !dir.delete())
+ break;
+ dir = dir.getParentFile();
+ }
+ }
+
+ static int count(final String s, final char c) {
+ int count = 0;
+ for (int p = s.indexOf(c); p >= 0; p = s.indexOf(c, p + 1)) {
+ count++;
+ }
+ return count;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
new file mode 100644
index 0000000000..34e73a3f7d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Collection;
+
+/**
+ * Writes out refs to the {@link Constants#INFO_REFS} and
+ * {@link Constants#PACKED_REFS} files.
+ *
+ * This class is abstract as the writing of the files must be handled by the
+ * caller. This is because it is used by transport classes as well.
+ */
+public abstract class RefWriter {
+
+ private final Collection<Ref> refs;
+
+ /**
+ * @param refs
+ * the complete set of references. This should have been computed
+ * by applying updates to the advertised refs already discovered.
+ */
+ public RefWriter(Collection<Ref> refs) {
+ this.refs = RefComparator.sort(refs);
+ }
+
+ /**
+ * Rebuild the {@link Constants#INFO_REFS}.
+ * <p>
+ * This method rebuilds the contents of the {@link Constants#INFO_REFS} file
+ * to match the passed list of references.
+ *
+ *
+ * @throws IOException
+ * writing is not supported, or attempting to write the file
+ * failed, possibly due to permissions or remote disk full, etc.
+ */
+ public void writeInfoRefs() throws IOException {
+ final StringWriter w = new StringWriter();
+ final char[] tmp = new char[Constants.OBJECT_ID_LENGTH * 2];
+ for (final Ref r : refs) {
+ if (Constants.HEAD.equals(r.getName())) {
+ // Historically HEAD has never been published through
+ // the INFO_REFS file. This is a mistake, but its the
+ // way things are.
+ //
+ continue;
+ }
+
+ r.getObjectId().copyTo(tmp, w);
+ w.write('\t');
+ w.write(r.getName());
+ w.write('\n');
+
+ if (r.getPeeledObjectId() != null) {
+ r.getPeeledObjectId().copyTo(tmp, w);
+ w.write('\t');
+ w.write(r.getName());
+ w.write("^{}\n");
+ }
+ }
+ writeFile(Constants.INFO_REFS, Constants.encode(w.toString()));
+ }
+
+ /**
+ * Rebuild the {@link Constants#PACKED_REFS} file.
+ * <p>
+ * This method rebuilds the contents of the {@link Constants#PACKED_REFS}
+ * file to match the passed list of references, including only those refs
+ * that have a storage type of {@link Ref.Storage#PACKED}.
+ *
+ * @throws IOException
+ * writing is not supported, or attempting to write the file
+ * failed, possibly due to permissions or remote disk full, etc.
+ */
+ public void writePackedRefs() throws IOException {
+ boolean peeled = false;
+
+ for (final Ref r : refs) {
+ if (r.getStorage() != Ref.Storage.PACKED)
+ continue;
+ if (r.getPeeledObjectId() != null)
+ peeled = true;
+ }
+
+ final StringWriter w = new StringWriter();
+ if (peeled) {
+ w.write("# pack-refs with:");
+ if (peeled)
+ w.write(" peeled");
+ w.write('\n');
+ }
+
+ final char[] tmp = new char[Constants.OBJECT_ID_LENGTH * 2];
+ for (final Ref r : refs) {
+ if (r.getStorage() != Ref.Storage.PACKED)
+ continue;
+
+ r.getObjectId().copyTo(tmp, w);
+ w.write(' ');
+ w.write(r.getName());
+ w.write('\n');
+
+ if (r.getPeeledObjectId() != null) {
+ w.write('^');
+ r.getPeeledObjectId().copyTo(tmp, w);
+ w.write('\n');
+ }
+ }
+ writeFile(Constants.PACKED_REFS, Constants.encode(w.toString()));
+ }
+
+ /**
+ * Handles actual writing of ref files to the git repository, which may
+ * differ slightly depending on the destination and transport.
+ *
+ * @param file
+ * path to ref file.
+ * @param content
+ * byte content of file to be written.
+ * @throws IOException
+ */
+ protected abstract void writeFile(String file, byte[] content)
+ throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
new file mode 100644
index 0000000000..a85eb0e4c6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2009, Robin Rosenberg
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Utility for reading reflog entries
+ */
+public class ReflogReader {
+ /**
+ * Parsed reflog entry
+ */
+ static public class Entry {
+ private ObjectId oldId;
+
+ private ObjectId newId;
+
+ private PersonIdent who;
+
+ private String comment;
+
+ Entry(byte[] raw, int pos) {
+ oldId = ObjectId.fromString(raw, pos);
+ pos += Constants.OBJECT_ID_LENGTH * 2;
+ if (raw[pos++] != ' ')
+ throw new IllegalArgumentException(
+ "Raw log message does not parse as log entry");
+ newId = ObjectId.fromString(raw, pos);
+ pos += Constants.OBJECT_ID_LENGTH * 2;
+ if (raw[pos++] != ' ') {
+ throw new IllegalArgumentException(
+ "Raw log message does not parse as log entry");
+ }
+ who = RawParseUtils.parsePersonIdentOnly(raw, pos);
+ int p0 = RawParseUtils.next(raw, pos, '\t'); // personident has no
+ // \t
+ if (p0 == -1) {
+ throw new IllegalArgumentException(
+ "Raw log message does not parse as log entry");
+ }
+ int p1 = RawParseUtils.nextLF(raw, p0);
+ if (p1 == -1) {
+ throw new IllegalArgumentException(
+ "Raw log message does not parse as log entry");
+ }
+ comment = RawParseUtils.decode(raw, p0, p1 - 1);
+ }
+
+ /**
+ * @return the commit id before the change
+ */
+ public ObjectId getOldId() {
+ return oldId;
+ }
+
+ /**
+ * @return the commit id after the change
+ */
+ public ObjectId getNewId() {
+ return newId;
+ }
+
+ /**
+ * @return user performin the change
+ */
+ public PersonIdent getWho() {
+ return who;
+ }
+
+ /**
+ * @return textual description of the change
+ */
+ public String getComment() {
+ return comment;
+ }
+
+ @Override
+ public String toString() {
+ return "Entry[" + oldId.name() + ", " + newId.name() + ", " + getWho() + ", "
+ + getComment() + "]";
+ }
+ }
+
+ private File logName;
+
+ ReflogReader(Repository db, String refname) {
+ logName = new File(db.getDirectory(), "logs/" + refname);
+ }
+
+ /**
+ * Get the last entry in the reflog
+ *
+ * @return the latest reflog entry, or null if no log
+ * @throws IOException
+ */
+ public Entry getLastEntry() throws IOException {
+ List<Entry> entries = getReverseEntries(1);
+ return entries.size() > 0 ? entries.get(0) : null;
+ }
+
+ /**
+ * @return all reflog entries in reverse order
+ * @throws IOException
+ */
+ public List<Entry> getReverseEntries() throws IOException {
+ return getReverseEntries(Integer.MAX_VALUE);
+ }
+
+ /**
+ * @param max
+ * max numer of entries to read
+ * @return all reflog entries in reverse order
+ * @throws IOException
+ */
+ public List<Entry> getReverseEntries(int max) throws IOException {
+ final byte[] log;
+ try {
+ log = NB.readFully(logName);
+ } catch (FileNotFoundException e) {
+ return Collections.emptyList();
+ }
+
+ int rs = RawParseUtils.prevLF(log, log.length);
+ List<Entry> ret = new ArrayList<Entry>();
+ while (rs >= 0 && max-- > 0) {
+ rs = RawParseUtils.prevLF(log, rs);
+ Entry entry = new Entry(log, rs < 0 ? 0 : rs + 2);
+ ret.add(entry);
+ }
+ return ret;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java
new file mode 100644
index 0000000000..7a7d99cd23
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/**
+ * This class passes information about a changed Git index to a
+ * {@link RepositoryListener}
+ *
+ * Currently only a reference to the repository is passed.
+ */
+public class RefsChangedEvent extends RepositoryChangedEvent {
+ RefsChangedEvent(final Repository repository) {
+ super(repository);
+ }
+
+ @Override
+ public String toString() {
+ return "RefsChangedEvent[" + getRepository() + "]";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
new file mode 100644
index 0000000000..181ca580f6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -0,0 +1,1176 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2006-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.RevisionSyntaxException;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.SystemReader;
+
+/**
+ * Represents a Git repository. A repository holds all objects and refs used for
+ * managing source code (could by any type of file, but source code is what
+ * SCM's are typically used for).
+ *
+ * In Git terms all data is stored in GIT_DIR, typically a directory called
+ * .git. A work tree is maintained unless the repository is a bare repository.
+ * Typically the .git directory is located at the root of the work dir.
+ *
+ * <ul>
+ * <li>GIT_DIR
+ * <ul>
+ * <li>objects/ - objects</li>
+ * <li>refs/ - tags and heads</li>
+ * <li>config - configuration</li>
+ * <li>info/ - more configurations</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * <p>
+ * This class is thread-safe.
+ * <p>
+ * This implementation only handles a subtly undocumented subset of git features.
+ *
+ */
+public class Repository {
+ private final AtomicInteger useCnt = new AtomicInteger(1);
+
+ private final File gitDir;
+
+ private final RepositoryConfig config;
+
+ private final RefDatabase refs;
+
+ private final ObjectDirectory objectDatabase;
+
+ private GitIndex index;
+
+ private final List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
+ static private final List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe
+
+ /**
+ * Construct a representation of a Git repository.
+ *
+ * @param d
+ * GIT_DIR (the location of the repository metadata).
+ * @throws IOException
+ * the repository appears to already exist but cannot be
+ * accessed.
+ */
+ public Repository(final File d) throws IOException {
+ gitDir = d.getAbsoluteFile();
+ refs = new RefDatabase(this);
+ objectDatabase = new ObjectDirectory(FS.resolve(gitDir, "objects"));
+
+ final FileBasedConfig userConfig;
+ userConfig = SystemReader.getInstance().openUserConfig();
+ try {
+ userConfig.load();
+ } catch (ConfigInvalidException e1) {
+ IOException e2 = new IOException("User config file "
+ + userConfig.getFile().getAbsolutePath() + " invalid: "
+ + e1);
+ e2.initCause(e1);
+ throw e2;
+ }
+ config = new RepositoryConfig(userConfig, FS.resolve(gitDir, "config"));
+
+ if (objectDatabase.exists()) {
+ try {
+ getConfig().load();
+ } catch (ConfigInvalidException e1) {
+ IOException e2 = new IOException("Unknown repository format");
+ e2.initCause(e1);
+ throw e2;
+ }
+ final String repositoryFormatVersion = getConfig().getString(
+ "core", null, "repositoryFormatVersion");
+ if (!"0".equals(repositoryFormatVersion)) {
+ throw new IOException("Unknown repository format \""
+ + repositoryFormatVersion + "\"; expected \"0\".");
+ }
+ }
+ }
+
+ /**
+ * Create a new Git repository initializing the necessary files and
+ * directories. Repository with working tree is created using this method.
+ *
+ * @throws IOException
+ * @see #create(boolean)
+ */
+ public synchronized void create() throws IOException {
+ create(false);
+ }
+
+ /**
+ * Create a new Git repository initializing the necessary files and
+ * directories.
+ *
+ * @param bare
+ * if true, a bare repository is created.
+ *
+ * @throws IOException
+ * in case of IO problem
+ */
+ public void create(boolean bare) throws IOException {
+ final RepositoryConfig cfg = getConfig();
+ if (cfg.getFile().exists()) {
+ throw new IllegalStateException("Repository already exists: "
+ + gitDir);
+ }
+ gitDir.mkdirs();
+ refs.create();
+ objectDatabase.create();
+
+ new File(gitDir, "branches").mkdir();
+ new File(gitDir, "remotes").mkdir();
+ final String master = Constants.R_HEADS + Constants.MASTER;
+ refs.link(Constants.HEAD, master);
+
+ cfg.setInt("core", null, "repositoryformatversion", 0);
+ cfg.setBoolean("core", null, "filemode", true);
+ if (bare)
+ cfg.setBoolean("core", null, "bare", true);
+ cfg.save();
+ }
+
+ /**
+ * @return GIT_DIR
+ */
+ public File getDirectory() {
+ return gitDir;
+ }
+
+ /**
+ * @return the directory containing the objects owned by this repository.
+ */
+ public File getObjectsDirectory() {
+ return objectDatabase.getDirectory();
+ }
+
+ /**
+ * @return the object database which stores this repository's data.
+ */
+ public ObjectDatabase getObjectDatabase() {
+ return objectDatabase;
+ }
+
+ /**
+ * @return the configuration of this repository
+ */
+ public RepositoryConfig getConfig() {
+ return config;
+ }
+
+ /**
+ * Construct a filename where the loose object having a specified SHA-1
+ * should be stored. If the object is stored in a shared repository the path
+ * to the alternative repo will be returned. If the object is not yet store
+ * a usable path in this repo will be returned. It is assumed that callers
+ * will look for objects in a pack first.
+ *
+ * @param objectId
+ * @return suggested file name
+ */
+ public File toFile(final AnyObjectId objectId) {
+ return objectDatabase.fileFor(objectId);
+ }
+
+ /**
+ * @param objectId
+ * @return true if the specified object is stored in this repo or any of the
+ * known shared repositories.
+ */
+ public boolean hasObject(final AnyObjectId objectId) {
+ return objectDatabase.hasObject(objectId);
+ }
+
+ /**
+ * @param id
+ * SHA-1 of an object.
+ *
+ * @return a {@link ObjectLoader} for accessing the data of the named
+ * object, or null if the object does not exist.
+ * @throws IOException
+ */
+ public ObjectLoader openObject(final AnyObjectId id)
+ throws IOException {
+ final WindowCursor wc = new WindowCursor();
+ try {
+ return openObject(wc, id);
+ } finally {
+ wc.release();
+ }
+ }
+
+ /**
+ * @param curs
+ * temporary working space associated with the calling thread.
+ * @param id
+ * SHA-1 of an object.
+ *
+ * @return a {@link ObjectLoader} for accessing the data of the named
+ * object, or null if the object does not exist.
+ * @throws IOException
+ */
+ public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
+ throws IOException {
+ return objectDatabase.openObject(curs, id);
+ }
+
+ /**
+ * Open object in all packs containing specified object.
+ *
+ * @param objectId
+ * id of object to search for
+ * @param curs
+ * temporary working space associated with the calling thread.
+ * @return collection of loaders for this object, from all packs containing
+ * this object
+ * @throws IOException
+ */
+ public Collection<PackedObjectLoader> openObjectInAllPacks(
+ final AnyObjectId objectId, final WindowCursor curs)
+ throws IOException {
+ Collection<PackedObjectLoader> result = new LinkedList<PackedObjectLoader>();
+ openObjectInAllPacks(objectId, result, curs);
+ return result;
+ }
+
+ /**
+ * Open object in all packs containing specified object.
+ *
+ * @param objectId
+ * id of object to search for
+ * @param resultLoaders
+ * result collection of loaders for this object, filled with
+ * loaders from all packs containing specified object
+ * @param curs
+ * temporary working space associated with the calling thread.
+ * @throws IOException
+ */
+ void openObjectInAllPacks(final AnyObjectId objectId,
+ final Collection<PackedObjectLoader> resultLoaders,
+ final WindowCursor curs) throws IOException {
+ objectDatabase.openObjectInAllPacks(resultLoaders, curs, objectId);
+ }
+
+ /**
+ * @param id
+ * SHA'1 of a blob
+ * @return an {@link ObjectLoader} for accessing the data of a named blob
+ * @throws IOException
+ */
+ public ObjectLoader openBlob(final ObjectId id) throws IOException {
+ return openObject(id);
+ }
+
+ /**
+ * @param id
+ * SHA'1 of a tree
+ * @return an {@link ObjectLoader} for accessing the data of a named tree
+ * @throws IOException
+ */
+ public ObjectLoader openTree(final ObjectId id) throws IOException {
+ return openObject(id);
+ }
+
+ /**
+ * Access a Commit object using a symbolic reference. This reference may
+ * be a SHA-1 or ref in combination with a number of symbols translating
+ * from one ref or SHA1-1 to another, such as HEAD^ etc.
+ *
+ * @param revstr a reference to a git commit object
+ * @return a Commit named by the specified string
+ * @throws IOException for I/O error or unexpected object type.
+ *
+ * @see #resolve(String)
+ */
+ public Commit mapCommit(final String revstr) throws IOException {
+ final ObjectId id = resolve(revstr);
+ return id != null ? mapCommit(id) : null;
+ }
+
+ /**
+ * Access any type of Git object by id and
+ *
+ * @param id
+ * SHA-1 of object to read
+ * @param refName optional, only relevant for simple tags
+ * @return The Git object if found or null
+ * @throws IOException
+ */
+ public Object mapObject(final ObjectId id, final String refName) throws IOException {
+ final ObjectLoader or = openObject(id);
+ if (or == null)
+ return null;
+ final byte[] raw = or.getBytes();
+ switch (or.getType()) {
+ case Constants.OBJ_TREE:
+ return makeTree(id, raw);
+
+ case Constants.OBJ_COMMIT:
+ return makeCommit(id, raw);
+
+ case Constants.OBJ_TAG:
+ return makeTag(id, refName, raw);
+
+ case Constants.OBJ_BLOB:
+ return raw;
+
+ default:
+ throw new IncorrectObjectTypeException(id,
+ "COMMIT nor TREE nor BLOB nor TAG");
+ }
+ }
+
+ /**
+ * Access a Commit by SHA'1 id.
+ * @param id
+ * @return Commit or null
+ * @throws IOException for I/O error or unexpected object type.
+ */
+ public Commit mapCommit(final ObjectId id) throws IOException {
+ final ObjectLoader or = openObject(id);
+ if (or == null)
+ return null;
+ final byte[] raw = or.getBytes();
+ if (Constants.OBJ_COMMIT == or.getType())
+ return new Commit(this, id, raw);
+ throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
+ }
+
+ private Commit makeCommit(final ObjectId id, final byte[] raw) {
+ Commit ret = new Commit(this, id, raw);
+ return ret;
+ }
+
+ /**
+ * Access a Tree object using a symbolic reference. This reference may
+ * be a SHA-1 or ref in combination with a number of symbols translating
+ * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
+ *
+ * @param revstr a reference to a git commit object
+ * @return a Tree named by the specified string
+ * @throws IOException
+ *
+ * @see #resolve(String)
+ */
+ public Tree mapTree(final String revstr) throws IOException {
+ final ObjectId id = resolve(revstr);
+ return id != null ? mapTree(id) : null;
+ }
+
+ /**
+ * Access a Tree by SHA'1 id.
+ * @param id
+ * @return Tree or null
+ * @throws IOException for I/O error or unexpected object type.
+ */
+ public Tree mapTree(final ObjectId id) throws IOException {
+ final ObjectLoader or = openObject(id);
+ if (or == null)
+ return null;
+ final byte[] raw = or.getBytes();
+ switch (or.getType()) {
+ case Constants.OBJ_TREE:
+ return new Tree(this, id, raw);
+
+ case Constants.OBJ_COMMIT:
+ return mapTree(ObjectId.fromString(raw, 5));
+
+ default:
+ throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
+ }
+ }
+
+ private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
+ Tree ret = new Tree(this, id, raw);
+ return ret;
+ }
+
+ private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
+ Tag ret = new Tag(this, id, refName, raw);
+ return ret;
+ }
+
+ /**
+ * Access a tag by symbolic name.
+ *
+ * @param revstr
+ * @return a Tag or null
+ * @throws IOException on I/O error or unexpected type
+ */
+ public Tag mapTag(String revstr) throws IOException {
+ final ObjectId id = resolve(revstr);
+ return id != null ? mapTag(revstr, id) : null;
+ }
+
+ /**
+ * Access a Tag by SHA'1 id
+ * @param refName
+ * @param id
+ * @return Commit or null
+ * @throws IOException for I/O error or unexpected object type.
+ */
+ public Tag mapTag(final String refName, final ObjectId id) throws IOException {
+ final ObjectLoader or = openObject(id);
+ if (or == null)
+ return null;
+ final byte[] raw = or.getBytes();
+ if (Constants.OBJ_TAG == or.getType())
+ return new Tag(this, id, refName, raw);
+ return new Tag(this, id, refName, null);
+ }
+
+ /**
+ * Create a command to update, create or delete a ref in this repository.
+ *
+ * @param ref
+ * name of the ref the caller wants to modify.
+ * @return an update command. The caller must finish populating this command
+ * and then invoke one of the update methods to actually make a
+ * change.
+ * @throws IOException
+ * a symbolic ref was passed in and could not be resolved back
+ * to the base ref, as the symbolic ref could not be read.
+ */
+ public RefUpdate updateRef(final String ref) throws IOException {
+ return refs.newUpdate(ref);
+ }
+
+ /**
+ * Create a command to rename a ref in this repository
+ *
+ * @param fromRef
+ * name of ref to rename from
+ * @param toRef
+ * name of ref to rename to
+ * @return an update command that knows how to rename a branch to another.
+ * @throws IOException
+ * the rename could not be performed.
+ *
+ */
+ public RefRename renameRef(final String fromRef, final String toRef) throws IOException {
+ return refs.newRename(fromRef, toRef);
+ }
+
+ /**
+ * Parse a git revision string and return an object id.
+ *
+ * Currently supported is combinations of these.
+ * <ul>
+ * <li>SHA-1 - a SHA-1</li>
+ * <li>refs/... - a ref name</li>
+ * <li>ref^n - nth parent reference</li>
+ * <li>ref~n - distance via parent reference</li>
+ * <li>ref@{n} - nth version of ref</li>
+ * <li>ref^{tree} - tree references by ref</li>
+ * <li>ref^{commit} - commit references by ref</li>
+ * </ul>
+ *
+ * Not supported is
+ * <ul>
+ * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
+ * <li>abbreviated SHA-1's</li>
+ * </ul>
+ *
+ * @param revstr A git object references expression
+ * @return an ObjectId or null if revstr can't be resolved to any ObjectId
+ * @throws IOException on serious errors
+ */
+ public ObjectId resolve(final String revstr) throws IOException {
+ char[] rev = revstr.toCharArray();
+ Object ref = null;
+ ObjectId refId = null;
+ for (int i = 0; i < rev.length; ++i) {
+ switch (rev[i]) {
+ case '^':
+ if (refId == null) {
+ String refstr = new String(rev,0,i);
+ refId = resolveSimple(refstr);
+ if (refId == null)
+ return null;
+ }
+ if (i + 1 < rev.length) {
+ switch (rev[i + 1]) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ int j;
+ ref = mapObject(refId, null);
+ while (ref instanceof Tag) {
+ Tag tag = (Tag)ref;
+ refId = tag.getObjId();
+ ref = mapObject(refId, null);
+ }
+ if (!(ref instanceof Commit))
+ throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
+ for (j=i+1; j<rev.length; ++j) {
+ if (!Character.isDigit(rev[j]))
+ break;
+ }
+ String parentnum = new String(rev, i+1, j-i-1);
+ int pnum;
+ try {
+ pnum = Integer.parseInt(parentnum);
+ } catch (NumberFormatException e) {
+ throw new RevisionSyntaxException(
+ "Invalid commit parent number",
+ revstr);
+ }
+ if (pnum != 0) {
+ final ObjectId parents[] = ((Commit) ref)
+ .getParentIds();
+ if (pnum > parents.length)
+ refId = null;
+ else
+ refId = parents[pnum - 1];
+ }
+ i = j - 1;
+ break;
+ case '{':
+ int k;
+ String item = null;
+ for (k=i+2; k<rev.length; ++k) {
+ if (rev[k] == '}') {
+ item = new String(rev, i+2, k-i-2);
+ break;
+ }
+ }
+ i = k;
+ if (item != null)
+ if (item.equals("tree")) {
+ ref = mapObject(refId, null);
+ while (ref instanceof Tag) {
+ Tag t = (Tag)ref;
+ refId = t.getObjId();
+ ref = mapObject(refId, null);
+ }
+ if (ref instanceof Treeish)
+ refId = ((Treeish)ref).getTreeId();
+ else
+ throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
+ }
+ else if (item.equals("commit")) {
+ ref = mapObject(refId, null);
+ while (ref instanceof Tag) {
+ Tag t = (Tag)ref;
+ refId = t.getObjId();
+ ref = mapObject(refId, null);
+ }
+ if (!(ref instanceof Commit))
+ throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
+ }
+ else if (item.equals("blob")) {
+ ref = mapObject(refId, null);
+ while (ref instanceof Tag) {
+ Tag t = (Tag)ref;
+ refId = t.getObjId();
+ ref = mapObject(refId, null);
+ }
+ if (!(ref instanceof byte[]))
+ throw new IncorrectObjectTypeException(refId, Constants.TYPE_BLOB);
+ }
+ else if (item.equals("")) {
+ ref = mapObject(refId, null);
+ while (ref instanceof Tag) {
+ Tag t = (Tag)ref;
+ refId = t.getObjId();
+ ref = mapObject(refId, null);
+ }
+ }
+ else
+ throw new RevisionSyntaxException(revstr);
+ else
+ throw new RevisionSyntaxException(revstr);
+ break;
+ default:
+ ref = mapObject(refId, null);
+ if (ref instanceof Commit) {
+ final ObjectId parents[] = ((Commit) ref)
+ .getParentIds();
+ if (parents.length == 0)
+ refId = null;
+ else
+ refId = parents[0];
+ } else
+ throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
+
+ }
+ } else {
+ ref = mapObject(refId, null);
+ while (ref instanceof Tag) {
+ Tag tag = (Tag)ref;
+ refId = tag.getObjId();
+ ref = mapObject(refId, null);
+ }
+ if (ref instanceof Commit) {
+ final ObjectId parents[] = ((Commit) ref)
+ .getParentIds();
+ if (parents.length == 0)
+ refId = null;
+ else
+ refId = parents[0];
+ } else
+ throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
+ }
+ break;
+ case '~':
+ if (ref == null) {
+ String refstr = new String(rev,0,i);
+ refId = resolveSimple(refstr);
+ if (refId == null)
+ return null;
+ ref = mapObject(refId, null);
+ }
+ while (ref instanceof Tag) {
+ Tag tag = (Tag)ref;
+ refId = tag.getObjId();
+ ref = mapObject(refId, null);
+ }
+ if (!(ref instanceof Commit))
+ throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
+ int l;
+ for (l = i + 1; l < rev.length; ++l) {
+ if (!Character.isDigit(rev[l]))
+ break;
+ }
+ String distnum = new String(rev, i+1, l-i-1);
+ int dist;
+ try {
+ dist = Integer.parseInt(distnum);
+ } catch (NumberFormatException e) {
+ throw new RevisionSyntaxException(
+ "Invalid ancestry length", revstr);
+ }
+ while (dist > 0) {
+ final ObjectId[] parents = ((Commit) ref).getParentIds();
+ if (parents.length == 0) {
+ refId = null;
+ break;
+ }
+ refId = parents[0];
+ ref = mapCommit(refId);
+ --dist;
+ }
+ i = l - 1;
+ break;
+ case '@':
+ int m;
+ String time = null;
+ for (m=i+2; m<rev.length; ++m) {
+ if (rev[m] == '}') {
+ time = new String(rev, i+2, m-i-2);
+ break;
+ }
+ }
+ if (time != null)
+ throw new RevisionSyntaxException("reflogs not yet supported by revision parser", revstr);
+ i = m - 1;
+ break;
+ default:
+ if (refId != null)
+ throw new RevisionSyntaxException(revstr);
+ }
+ }
+ if (refId == null)
+ refId = resolveSimple(revstr);
+ return refId;
+ }
+
+ private ObjectId resolveSimple(final String revstr) throws IOException {
+ if (ObjectId.isId(revstr))
+ return ObjectId.fromString(revstr);
+ final Ref r = refs.readRef(revstr);
+ return r != null ? r.getObjectId() : null;
+ }
+
+ /** Increment the use counter by one, requiring a matched {@link #close()}. */
+ public void incrementOpen() {
+ useCnt.incrementAndGet();
+ }
+
+ /**
+ * Close all resources used by this repository
+ */
+ public void close() {
+ if (useCnt.decrementAndGet() == 0)
+ objectDatabase.close();
+ }
+
+ /**
+ * Add a single existing pack to the list of available pack files.
+ *
+ * @param pack
+ * path of the pack file to open.
+ * @param idx
+ * path of the corresponding index file.
+ * @throws IOException
+ * index file could not be opened, read, or is not recognized as
+ * a Git pack file index.
+ */
+ public void openPack(final File pack, final File idx) throws IOException {
+ objectDatabase.openPack(pack, idx);
+ }
+
+ /**
+ * Writes a symref (e.g. HEAD) to disk
+ *
+ * @param name symref name
+ * @param target pointed to ref
+ * @throws IOException
+ */
+ public void writeSymref(final String name, final String target)
+ throws IOException {
+ refs.link(name, target);
+ }
+
+ public String toString() {
+ return "Repository[" + getDirectory() + "]";
+ }
+
+ /**
+ * @return name of current branch
+ * @throws IOException
+ */
+ public String getFullBranch() throws IOException {
+ final File ptr = new File(getDirectory(),Constants.HEAD);
+ final BufferedReader br = new BufferedReader(new FileReader(ptr));
+ String ref;
+ try {
+ ref = br.readLine();
+ } finally {
+ br.close();
+ }
+ if (ref.startsWith("ref: "))
+ ref = ref.substring(5);
+ return ref;
+ }
+
+ /**
+ * @return name of current branch.
+ * @throws IOException
+ */
+ public String getBranch() throws IOException {
+ try {
+ final File ptr = new File(getDirectory(), Constants.HEAD);
+ final BufferedReader br = new BufferedReader(new FileReader(ptr));
+ String ref;
+ try {
+ ref = br.readLine();
+ } finally {
+ br.close();
+ }
+ if (ref.startsWith("ref: "))
+ ref = ref.substring(5);
+ if (ref.startsWith("refs/heads/"))
+ ref = ref.substring(11);
+ return ref;
+ } catch (FileNotFoundException e) {
+ final File ptr = new File(getDirectory(),"head-name");
+ final BufferedReader br = new BufferedReader(new FileReader(ptr));
+ String ref;
+ try {
+ ref = br.readLine();
+ } finally {
+ br.close();
+ }
+ return ref;
+ }
+ }
+
+ /**
+ * Get a ref by name.
+ *
+ * @param name
+ * the name of the ref to lookup. May be a short-hand form, e.g.
+ * "master" which is is automatically expanded to
+ * "refs/heads/master" if "refs/heads/master" already exists.
+ * @return the Ref with the given name, or null if it does not exist
+ * @throws IOException
+ */
+ public Ref getRef(final String name) throws IOException {
+ return refs.readRef(name);
+ }
+
+ /**
+ * @return all known refs (heads, tags, remotes).
+ */
+ public Map<String, Ref> getAllRefs() {
+ return refs.getAllRefs();
+ }
+
+ /**
+ * @return all tags; key is short tag name ("v1.0") and value of the entry
+ * contains the ref with the full tag name ("refs/tags/v1.0").
+ */
+ public Map<String, Ref> getTags() {
+ return refs.getTags();
+ }
+
+ /**
+ * Peel a possibly unpeeled ref and updates it.
+ * <p>
+ * If the ref cannot be peeled (as it does not refer to an annotated tag)
+ * the peeled id stays null, but {@link Ref#isPeeled()} will be true.
+ *
+ * @param ref
+ * The ref to peel
+ * @return <code>ref</code> if <code>ref.isPeeled()</code> is true; else a
+ * new Ref object representing the same data as Ref, but isPeeled()
+ * will be true and getPeeledObjectId will contain the peeled object
+ * (or null).
+ */
+ public Ref peel(final Ref ref) {
+ return refs.peel(ref);
+ }
+
+ /**
+ * @return a map with all objects referenced by a peeled ref.
+ */
+ public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
+ Map<String, Ref> allRefs = getAllRefs();
+ Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size());
+ for (Ref ref : allRefs.values()) {
+ if (!ref.isPeeled())
+ ref = peel(ref);
+ AnyObjectId target = ref.getPeeledObjectId();
+ if (target == null)
+ target = ref.getObjectId();
+ // We assume most Sets here are singletons
+ Set<Ref> oset = ret.put(target, Collections.singleton(ref));
+ if (oset != null) {
+ // that was not the case (rare)
+ if (oset.size() == 1) {
+ // Was a read-only singleton, we must copy to a new Set
+ oset = new HashSet<Ref>(oset);
+ }
+ ret.put(target, oset);
+ oset.add(ref);
+ }
+ }
+ return ret;
+ }
+
+ /** Clean up stale caches */
+ public void refreshFromDisk() {
+ refs.clearCache();
+ }
+
+ /**
+ * @return a representation of the index associated with this repo
+ * @throws IOException
+ */
+ public GitIndex getIndex() throws IOException {
+ if (index == null) {
+ index = new GitIndex(this);
+ index.read();
+ } else {
+ index.rereadIfNecessary();
+ }
+ return index;
+ }
+
+ static byte[] gitInternalSlash(byte[] bytes) {
+ if (File.separatorChar == '/')
+ return bytes;
+ for (int i=0; i<bytes.length; ++i)
+ if (bytes[i] == File.separatorChar)
+ bytes[i] = '/';
+ return bytes;
+ }
+
+ /**
+ * @return an important state
+ */
+ public RepositoryState getRepositoryState() {
+ // Pre Git-1.6 logic
+ if (new File(getWorkDir(), ".dotest").exists())
+ return RepositoryState.REBASING;
+ if (new File(gitDir,".dotest-merge").exists())
+ return RepositoryState.REBASING_INTERACTIVE;
+
+ // From 1.6 onwards
+ if (new File(getDirectory(),"rebase-apply/rebasing").exists())
+ return RepositoryState.REBASING_REBASING;
+ if (new File(getDirectory(),"rebase-apply/applying").exists())
+ return RepositoryState.APPLY;
+ if (new File(getDirectory(),"rebase-apply").exists())
+ return RepositoryState.REBASING;
+
+ if (new File(getDirectory(),"rebase-merge/interactive").exists())
+ return RepositoryState.REBASING_INTERACTIVE;
+ if (new File(getDirectory(),"rebase-merge").exists())
+ return RepositoryState.REBASING_MERGE;
+
+ // Both versions
+ if (new File(gitDir,"MERGE_HEAD").exists())
+ return RepositoryState.MERGING;
+ if (new File(gitDir,"BISECT_LOG").exists())
+ return RepositoryState.BISECTING;
+
+ return RepositoryState.SAFE;
+ }
+
+ /**
+ * Check validity of a ref name. It must not contain character that has
+ * a special meaning in a Git object reference expression. Some other
+ * dangerous characters are also excluded.
+ *
+ * For portability reasons '\' is excluded
+ *
+ * @param refName
+ *
+ * @return true if refName is a valid ref name
+ */
+ public static boolean isValidRefName(final String refName) {
+ final int len = refName.length();
+ if (len == 0)
+ return false;
+ if (refName.endsWith(".lock"))
+ return false;
+
+ int components = 1;
+ char p = '\0';
+ for (int i = 0; i < len; i++) {
+ final char c = refName.charAt(i);
+ if (c <= ' ')
+ return false;
+ switch (c) {
+ case '.':
+ switch (p) {
+ case '\0': case '/': case '.':
+ return false;
+ }
+ if (i == len -1)
+ return false;
+ break;
+ case '/':
+ if (i == 0 || i == len - 1)
+ return false;
+ components++;
+ break;
+ case '{':
+ if (p == '@')
+ return false;
+ break;
+ case '~': case '^': case ':':
+ case '?': case '[': case '*':
+ case '\\':
+ return false;
+ }
+ p = c;
+ }
+ return components > 1;
+ }
+
+ /**
+ * Strip work dir and return normalized repository path.
+ *
+ * @param workDir Work dir
+ * @param file File whose path shall be stripped of its workdir
+ * @return normalized repository relative path or the empty
+ * string if the file is not relative to the work directory.
+ */
+ public static String stripWorkDir(File workDir, File file) {
+ final String filePath = file.getPath();
+ final String workDirPath = workDir.getPath();
+
+ if (filePath.length() <= workDirPath.length() ||
+ filePath.charAt(workDirPath.length()) != File.separatorChar ||
+ !filePath.startsWith(workDirPath)) {
+ File absWd = workDir.isAbsolute() ? workDir : workDir.getAbsoluteFile();
+ File absFile = file.isAbsolute() ? file : file.getAbsoluteFile();
+ if (absWd == workDir && absFile == file)
+ return "";
+ return stripWorkDir(absWd, absFile);
+ }
+
+ String relName = filePath.substring(workDirPath.length() + 1);
+ if (File.separatorChar != '/')
+ relName = relName.replace(File.separatorChar, '/');
+ return relName;
+ }
+
+ /**
+ * @return the workdir file, i.e. where the files are checked out
+ */
+ public File getWorkDir() {
+ return getDirectory().getParentFile();
+ }
+
+ /**
+ * Register a {@link RepositoryListener} which will be notified
+ * when ref changes are detected.
+ *
+ * @param l
+ */
+ public void addRepositoryChangedListener(final RepositoryListener l) {
+ listeners.add(l);
+ }
+
+ /**
+ * Remove a registered {@link RepositoryListener}
+ * @param l
+ */
+ public void removeRepositoryChangedListener(final RepositoryListener l) {
+ listeners.remove(l);
+ }
+
+ /**
+ * Register a global {@link RepositoryListener} which will be notified
+ * when a ref changes in any repository are detected.
+ *
+ * @param l
+ */
+ public static void addAnyRepositoryChangedListener(final RepositoryListener l) {
+ allListeners.add(l);
+ }
+
+ /**
+ * Remove a globally registered {@link RepositoryListener}
+ * @param l
+ */
+ public static void removeAnyRepositoryChangedListener(final RepositoryListener l) {
+ allListeners.remove(l);
+ }
+
+ void fireRefsMaybeChanged() {
+ if (refs.lastRefModification != refs.lastNotifiedRefModification) {
+ refs.lastNotifiedRefModification = refs.lastRefModification;
+ final RefsChangedEvent event = new RefsChangedEvent(this);
+ List<RepositoryListener> all;
+ synchronized (listeners) {
+ all = new ArrayList<RepositoryListener>(listeners);
+ }
+ synchronized (allListeners) {
+ all.addAll(allListeners);
+ }
+ for (final RepositoryListener l : all) {
+ l.refsChanged(event);
+ }
+ }
+ }
+
+ void fireIndexChanged() {
+ final IndexChangedEvent event = new IndexChangedEvent(this);
+ List<RepositoryListener> all;
+ synchronized (listeners) {
+ all = new ArrayList<RepositoryListener>(listeners);
+ }
+ synchronized (allListeners) {
+ all.addAll(allListeners);
+ }
+ for (final RepositoryListener l : all) {
+ l.indexChanged(event);
+ }
+ }
+
+ /**
+ * Force a scan for changed refs.
+ *
+ * @throws IOException
+ */
+ public void scanForRepoChanges() throws IOException {
+ getAllRefs(); // This will look for changes to refs
+ getIndex(); // This will detect changes in the index
+ }
+
+ /**
+ * @param refName
+ *
+ * @return a more user friendly ref name
+ */
+ public String shortenRefName(String refName) {
+ if (refName.startsWith(Constants.R_HEADS))
+ return refName.substring(Constants.R_HEADS.length());
+ if (refName.startsWith(Constants.R_TAGS))
+ return refName.substring(Constants.R_TAGS.length());
+ if (refName.startsWith(Constants.R_REMOTES))
+ return refName.substring(Constants.R_REMOTES.length());
+ return refName;
+ }
+
+ /**
+ * @param refName
+ * @return a {@link ReflogReader} for the supplied refname, or null if the
+ * named ref does not exist.
+ * @throws IOException the ref could not be accessed.
+ */
+ public ReflogReader getReflogReader(String refName) throws IOException {
+ Ref ref = getRef(refName);
+ if (ref != null)
+ return new ReflogReader(this, ref.getOrigName());
+ return null;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
new file mode 100644
index 0000000000..e43c33ad7d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/**
+ * A default {@link RepositoryListener} that does nothing except invoke an
+ * optional general method for any repository change.
+ */
+public class RepositoryAdapter implements RepositoryListener {
+
+ public void indexChanged(final IndexChangedEvent e) {
+ // Empty
+ }
+
+ public void refsChanged(final RefsChangedEvent e) {
+ // Empty
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
new file mode 100644
index 0000000000..e8630a3c6d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/** Cache of active {@link Repository} instances. */
+public class RepositoryCache {
+ private static final RepositoryCache cache = new RepositoryCache();
+
+ /**
+ * Open an existing repository, reusing a cached instance if possible.
+ * <p>
+ * When done with the repository, the caller must call
+ * {@link Repository#close()} to decrement the repository's usage counter.
+ *
+ * @param location
+ * where the local repository is. Typically a {@link FileKey}.
+ * @return the repository instance requested; caller must close when done.
+ * @throws IOException
+ * the repository could not be read (likely its core.version
+ * property is not supported).
+ * @throws RepositoryNotFoundException
+ * there is no repository at the given location.
+ */
+ public static Repository open(final Key location) throws IOException,
+ RepositoryNotFoundException {
+ return open(location, true);
+ }
+
+ /**
+ * Open a repository, reusing a cached instance if possible.
+ * <p>
+ * When done with the repository, the caller must call
+ * {@link Repository#close()} to decrement the repository's usage counter.
+ *
+ * @param location
+ * where the local repository is. Typically a {@link FileKey}.
+ * @param mustExist
+ * If true, and the repository is not found, throws {@code
+ * RepositoryNotFoundException}. If false, a repository instance
+ * is created and registered anyway.
+ * @return the repository instance requested; caller must close when done.
+ * @throws IOException
+ * the repository could not be read (likely its core.version
+ * property is not supported).
+ * @throws RepositoryNotFoundException
+ * There is no repository at the given location, only thrown if
+ * {@code mustExist} is true.
+ */
+ public static Repository open(final Key location, final boolean mustExist)
+ throws IOException {
+ return cache.openRepository(location, mustExist);
+ }
+
+ /**
+ * Register one repository into the cache.
+ * <p>
+ * During registration the cache automatically increments the usage counter,
+ * permitting it to retain the reference. A {@link FileKey} for the
+ * repository's {@link Repository#getDirectory()} is used to index the
+ * repository in the cache.
+ * <p>
+ * If another repository already is registered in the cache at this
+ * location, the other instance is closed.
+ *
+ * @param db
+ * repository to register.
+ */
+ public static void register(final Repository db) {
+ cache.registerRepository(FileKey.exact(db.getDirectory()), db);
+ }
+
+ /**
+ * Remove a repository from the cache.
+ * <p>
+ * Removes a repository from the cache, if it is still registered here,
+ * permitting it to close.
+ *
+ * @param db
+ * repository to unregister.
+ */
+ public static void close(final Repository db) {
+ cache.unregisterRepository(FileKey.exact(db.getDirectory()));
+ }
+
+ /** Unregister all repositories from the cache. */
+ public static void clear() {
+ cache.clearAll();
+ }
+
+ private final ConcurrentHashMap<Key, Reference<Repository>> cacheMap;
+
+ private final Lock[] openLocks;
+
+ private RepositoryCache() {
+ cacheMap = new ConcurrentHashMap<Key, Reference<Repository>>();
+ openLocks = new Lock[4];
+ for (int i = 0; i < openLocks.length; i++)
+ openLocks[i] = new Lock();
+ }
+
+ private Repository openRepository(final Key location,
+ final boolean mustExist) throws IOException {
+ Reference<Repository> ref = cacheMap.get(location);
+ Repository db = ref != null ? ref.get() : null;
+ if (db == null) {
+ synchronized (lockFor(location)) {
+ ref = cacheMap.get(location);
+ db = ref != null ? ref.get() : null;
+ if (db == null) {
+ db = location.open(mustExist);
+ ref = new SoftReference<Repository>(db);
+ cacheMap.put(location, ref);
+ }
+ }
+ }
+ db.incrementOpen();
+ return db;
+ }
+
+ private void registerRepository(final Key location, final Repository db) {
+ db.incrementOpen();
+ SoftReference<Repository> newRef = new SoftReference<Repository>(db);
+ Reference<Repository> oldRef = cacheMap.put(location, newRef);
+ Repository oldDb = oldRef != null ? oldRef.get() : null;
+ if (oldDb != null)
+ oldDb.close();
+ }
+
+ private void unregisterRepository(final Key location) {
+ Reference<Repository> oldRef = cacheMap.remove(location);
+ Repository oldDb = oldRef != null ? oldRef.get() : null;
+ if (oldDb != null)
+ oldDb.close();
+ }
+
+ private void clearAll() {
+ for (int stage = 0; stage < 2; stage++) {
+ for (Iterator<Map.Entry<Key, Reference<Repository>>> i = cacheMap
+ .entrySet().iterator(); i.hasNext();) {
+ final Map.Entry<Key, Reference<Repository>> e = i.next();
+ final Repository db = e.getValue().get();
+ if (db != null)
+ db.close();
+ i.remove();
+ }
+ }
+ }
+
+ private Lock lockFor(final Key location) {
+ return openLocks[(location.hashCode() >>> 1) % openLocks.length];
+ }
+
+ private static class Lock {
+ // Used only for its monitor.
+ }
+
+ /**
+ * Abstract hash key for {@link RepositoryCache} entries.
+ * <p>
+ * A Key instance should be lightweight, and implement hashCode() and
+ * equals() such that two Key instances are equal if they represent the same
+ * Repository location.
+ */
+ public static interface Key {
+ /**
+ * Called by {@link RepositoryCache#open(Key)} if it doesn't exist yet.
+ * <p>
+ * If a repository does not exist yet in the cache, the cache will call
+ * this method to acquire a handle to it.
+ *
+ * @param mustExist
+ * true if the repository must exist in order to be opened;
+ * false if a new non-existent repository is permitted to be
+ * created (the caller is responsible for calling create).
+ * @return the new repository instance.
+ * @throws IOException
+ * the repository could not be read (likely its core.version
+ * property is not supported).
+ * @throws RepositoryNotFoundException
+ * There is no repository at the given location, only thrown
+ * if {@code mustExist} is true.
+ */
+ Repository open(boolean mustExist) throws IOException,
+ RepositoryNotFoundException;
+ }
+
+ /** Location of a Repository, using the standard java.io.File API. */
+ public static class FileKey implements Key {
+ /**
+ * Obtain a pointer to an exact location on disk.
+ * <p>
+ * No guessing is performed, the given location is exactly the GIT_DIR
+ * directory of the repository.
+ *
+ * @param directory
+ * location where the repository database is.
+ * @return a key for the given directory.
+ * @see #lenient(File)
+ */
+ public static FileKey exact(final File directory) {
+ return new FileKey(directory);
+ }
+
+ /**
+ * Obtain a pointer to a location on disk.
+ * <p>
+ * The method performs some basic guessing to locate the repository.
+ * Searched paths are:
+ * <ol>
+ * <li>{@code directory} // assume exact match</li>
+ * <li>{@code directory} + "/.git" // assume working directory</li>
+ * <li>{@code directory} + ".git" // assume bare</li>
+ * </ol>
+ *
+ * @param directory
+ * location where the repository database might be.
+ * @return a key for the given directory.
+ * @see #exact(File)
+ */
+ public static FileKey lenient(final File directory) {
+ final File gitdir = resolve(directory);
+ return new FileKey(gitdir != null ? gitdir : directory);
+ }
+
+ private final File path;
+
+ /**
+ * @param directory
+ * exact location of the repository.
+ */
+ protected FileKey(final File directory) {
+ path = canonical(directory);
+ }
+
+ private static File canonical(final File path) {
+ try {
+ return path.getCanonicalFile();
+ } catch (IOException e) {
+ return path.getAbsoluteFile();
+ }
+ }
+
+ /** @return location supplied to the constructor. */
+ public final File getFile() {
+ return path;
+ }
+
+ public Repository open(final boolean mustExist) throws IOException {
+ if (mustExist && !isGitRepository(path))
+ throw new RepositoryNotFoundException(path);
+ return new Repository(path);
+ }
+
+ @Override
+ public int hashCode() {
+ return path.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ return o instanceof FileKey && path.equals(((FileKey) o).path);
+ }
+
+ @Override
+ public String toString() {
+ return path.toString();
+ }
+
+ /**
+ * Guess if a directory contains a Git repository.
+ * <p>
+ * This method guesses by looking for the existence of some key files
+ * and directories.
+ *
+ * @param dir
+ * the location of the directory to examine.
+ * @return true if the directory "looks like" a Git repository; false if
+ * it doesn't look enough like a Git directory to really be a
+ * Git directory.
+ */
+ public static boolean isGitRepository(final File dir) {
+ return FS.resolve(dir, "objects").exists()
+ && FS.resolve(dir, "refs").exists()
+ && isValidHead(new File(dir, Constants.HEAD));
+ }
+
+ private static boolean isValidHead(final File head) {
+ final String ref = readFirstLine(head);
+ return ref != null
+ && (ref.startsWith("ref: refs/") || ObjectId.isId(ref));
+ }
+
+ private static String readFirstLine(final File head) {
+ try {
+ final byte[] buf = NB.readFully(head, 4096);
+ int n = buf.length;
+ if (n == 0)
+ return null;
+ if (buf[n - 1] == '\n')
+ n--;
+ return RawParseUtils.decode(buf, 0, n);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Guess the proper path for a Git repository.
+ * <p>
+ * The method performs some basic guessing to locate the repository.
+ * Searched paths are:
+ * <ol>
+ * <li>{@code directory} // assume exact match</li>
+ * <li>{@code directory} + "/.git" // assume working directory</li>
+ * <li>{@code directory} + ".git" // assume bare</li>
+ * </ol>
+ *
+ * @param directory
+ * location to guess from. Several permutations are tried.
+ * @return the actual directory location if a better match is found;
+ * null if there is no suitable match.
+ */
+ public static File resolve(final File directory) {
+ if (isGitRepository(directory))
+ return directory;
+ if (isGitRepository(new File(directory, ".git")))
+ return new File(directory, ".git");
+
+ final String name = directory.getName();
+ final File parent = directory.getParentFile();
+ if (isGitRepository(new File(parent, name + ".git")))
+ return new File(parent, name + ".git");
+ return null;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java
new file mode 100644
index 0000000000..495049ce74
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/**
+ * This class passes information about changed refs to a
+ * {@link RepositoryListener}
+ *
+ * Currently only a reference to the repository is passed.
+ */
+public class RepositoryChangedEvent {
+ private final Repository repository;
+
+ RepositoryChangedEvent(final Repository repository) {
+ this.repository = repository;
+ }
+
+ /**
+ * @return the repository that was changed
+ */
+ public Repository getRepository() {
+ return repository;
+ }
+
+ @Override
+ public String toString() {
+ return "RepositoryChangedEvent[" + repository + "]";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java
new file mode 100644
index 0000000000..805975a8d1
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com>
+ * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+
+/**
+ * An object representing the Git config file.
+ *
+ * This can be either the repository specific file or the user global
+ * file depending on how it is instantiated.
+ */
+public class RepositoryConfig extends FileBasedConfig {
+ /** Section name for a branch configuration. */
+ public static final String BRANCH_SECTION = "branch";
+
+ /**
+ * Create a Git configuration file reader/writer/cache for a specific file.
+ *
+ * @param base
+ * configuration that provides default values if this file does
+ * not set/override a particular key. Often this is the user's
+ * global configuration file, or the system level configuration.
+ * @param cfgLocation
+ * path of the file to load (or save).
+ */
+ public RepositoryConfig(final Config base, final File cfgLocation) {
+ super(base, cfgLocation);
+ }
+
+ /**
+ * @return Core configuration values
+ */
+ public CoreConfig getCore() {
+ return get(CoreConfig.KEY);
+ }
+
+ /**
+ * @return transfer, fetch and receive configuration values
+ */
+ public TransferConfig getTransfer() {
+ return get(TransferConfig.KEY);
+ }
+
+ /** @return standard user configuration data */
+ public UserConfig getUserConfig() {
+ return get(UserConfig.KEY);
+ }
+
+ /**
+ * @return the author name as defined in the git variables
+ * and configurations. If no name could be found, try
+ * to use the system user name instead.
+ */
+ public String getAuthorName() {
+ return getUserConfig().getAuthorName();
+ }
+
+ /**
+ * @return the committer name as defined in the git variables
+ * and configurations. If no name could be found, try
+ * to use the system user name instead.
+ */
+ public String getCommitterName() {
+ return getUserConfig().getCommitterName();
+ }
+
+ /**
+ * @return the author email as defined in git variables and
+ * configurations. If no email could be found, try to
+ * propose one default with the user name and the
+ * host name.
+ */
+ public String getAuthorEmail() {
+ return getUserConfig().getAuthorEmail();
+ }
+
+ /**
+ * @return the committer email as defined in git variables and
+ * configurations. If no email could be found, try to
+ * propose one default with the user name and the
+ * host name.
+ */
+ public String getCommitterEmail() {
+ return getUserConfig().getCommitterEmail();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java
new file mode 100644
index 0000000000..0473093e20
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/**
+ * A RepositoryListener gets notification about changes in refs or repository.
+ * <p>
+ * It currently does <em>not</em> get notification about which items are
+ * changed.
+ */
+public interface RepositoryListener {
+ /**
+ * Invoked when a ref changes
+ *
+ * @param e
+ * information about the changes.
+ */
+ void refsChanged(RefsChangedEvent e);
+
+ /**
+ * Invoked when the index changes
+ *
+ * @param e
+ * information about the changes.
+ */
+ void indexChanged(IndexChangedEvent e);
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
new file mode 100644
index 0000000000..6159839b13
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2008, Mike Ralphson <mike@abacus.co.uk>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg.lists@dewire.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/**
+ * Important state of the repository that affects what can and cannot bed
+ * done. This is things like unhandled conflicted merges and unfinished rebase.
+ *
+ * The granularity and set of states are somewhat arbitrary. The methods
+ * on the state are the only supported means of deciding what to do.
+ */
+public enum RepositoryState {
+ /**
+ * A safe state for working normally
+ * */
+ SAFE {
+ public boolean canCheckout() { return true; }
+ public boolean canResetHead() { return true; }
+ public boolean canCommit() { return true; }
+ public String getDescription() { return "Normal"; }
+ },
+
+ /** An unfinished merge. Must resole or reset before continuing normally
+ */
+ MERGING {
+ public boolean canCheckout() { return false; }
+ public boolean canResetHead() { return false; }
+ public boolean canCommit() { return false; }
+ public String getDescription() { return "Conflicts"; }
+ },
+
+ /**
+ * An unfinished rebase or am. Must resolve, skip or abort before normal work can take place
+ */
+ REBASING {
+ public boolean canCheckout() { return false; }
+ public boolean canResetHead() { return false; }
+ public boolean canCommit() { return true; }
+ public String getDescription() { return "Rebase/Apply mailbox"; }
+ },
+
+ /**
+ * An unfinished rebase. Must resolve, skip or abort before normal work can take place
+ */
+ REBASING_REBASING {
+ public boolean canCheckout() { return false; }
+ public boolean canResetHead() { return false; }
+ public boolean canCommit() { return true; }
+ public String getDescription() { return "Rebase"; }
+ },
+
+ /**
+ * An unfinished apply. Must resolve, skip or abort before normal work can take place
+ */
+ APPLY {
+ public boolean canCheckout() { return false; }
+ public boolean canResetHead() { return false; }
+ public boolean canCommit() { return true; }
+ public String getDescription() { return "Apply mailbox"; }
+ },
+
+ /**
+ * An unfinished rebase with merge. Must resolve, skip or abort before normal work can take place
+ */
+ REBASING_MERGE {
+ public boolean canCheckout() { return false; }
+ public boolean canResetHead() { return false; }
+ public boolean canCommit() { return true; }
+ public String getDescription() { return "Rebase w/merge"; }
+ },
+
+ /**
+ * An unfinished interactive rebase. Must resolve, skip or abort before normal work can take place
+ */
+ REBASING_INTERACTIVE {
+ public boolean canCheckout() { return false; }
+ public boolean canResetHead() { return false; }
+ public boolean canCommit() { return true; }
+ public String getDescription() { return "Rebase interactive"; }
+ },
+
+ /**
+ * Bisecting being done. Normal work may continue but is discouraged
+ */
+ BISECTING {
+ /* Changing head is a normal operation when bisecting */
+ public boolean canCheckout() { return true; }
+
+ /* Do not reset, checkout instead */
+ public boolean canResetHead() { return false; }
+
+ /* Actually it may make sense, but for now we err on the side of caution */
+ public boolean canCommit() { return false; }
+
+ public String getDescription() { return "Bisecting"; }
+ };
+
+ /**
+ * @return true if changing HEAD is sane.
+ */
+ public abstract boolean canCheckout();
+
+ /**
+ * @return true if we can commit
+ */
+ public abstract boolean canCommit();
+
+ /**
+ * @return true if reset to another HEAD is considered SAFE
+ */
+ public abstract boolean canResetHead();
+
+ /**
+ * @return a human readable description of the state.
+ */
+ public abstract String getDescription();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
new file mode 100644
index 0000000000..81666be45d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+/**
+ * A tree entry representing a symbolic link.
+ *
+ * Note. Java cannot really handle these as file system objects.
+ */
+public class SymlinkTreeEntry extends TreeEntry {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
+ * the specified parent
+ *
+ * @param parent
+ * @param id
+ * @param nameUTF8
+ */
+ public SymlinkTreeEntry(final Tree parent, final ObjectId id,
+ final byte[] nameUTF8) {
+ super(parent, id, nameUTF8);
+ }
+
+ public FileMode getMode() {
+ return FileMode.SYMLINK;
+ }
+
+ public void accept(final TreeVisitor tv, final int flags)
+ throws IOException {
+ if ((MODIFIED_ONLY & flags) == MODIFIED_ONLY && !isModified()) {
+ return;
+ }
+
+ tv.visitSymlink(this);
+ }
+
+ public String toString() {
+ final StringBuffer r = new StringBuffer();
+ r.append(ObjectId.toString(getId()));
+ r.append(" S ");
+ r.append(getFullName());
+ return r.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java
new file mode 100644
index 0000000000..0e1c1651de
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2006-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.ObjectWritingException;
+
+/**
+ * Represents a named reference to another Git object of any type.
+ */
+public class Tag {
+ private final Repository objdb;
+
+ private ObjectId tagId;
+
+ private PersonIdent tagger;
+
+ private String message;
+
+ private byte[] raw;
+
+ private String type;
+
+ private String tag;
+
+ private ObjectId objId;
+
+ /**
+ * Construct a new, yet unnamed Tag.
+ *
+ * @param db
+ */
+ public Tag(final Repository db) {
+ objdb = db;
+ }
+
+ /**
+ * Construct a Tag representing an existing with a known name referencing an known object.
+ * This could be either a simple or annotated tag.
+ *
+ * @param db {@link Repository}
+ * @param id target id.
+ * @param refName tag name or null
+ * @param raw data of an annotated tag.
+ */
+ public Tag(final Repository db, final ObjectId id, String refName, final byte[] raw) {
+ objdb = db;
+ if (raw != null) {
+ tagId = id;
+ objId = ObjectId.fromString(raw, 7);
+ } else
+ objId = id;
+ if (refName != null && refName.startsWith("refs/tags/"))
+ refName = refName.substring(10);
+ tag = refName;
+ this.raw = raw;
+ }
+
+ /**
+ * @return tagger of a annotated tag or null
+ */
+ public PersonIdent getAuthor() {
+ decode();
+ return tagger;
+ }
+
+ /**
+ * Set author of an annotated tag.
+ * @param a author identifier as a {@link PersonIdent}
+ */
+ public void setAuthor(final PersonIdent a) {
+ tagger = a;
+ }
+
+ /**
+ * @return comment of an annotated tag, or null
+ */
+ public String getMessage() {
+ decode();
+ return message;
+ }
+
+ private void decode() {
+ // FIXME: handle I/O errors
+ if (raw != null) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(
+ new ByteArrayInputStream(raw)));
+ String n = br.readLine();
+ if (n == null || !n.startsWith("object ")) {
+ throw new CorruptObjectException(tagId, "no object");
+ }
+ objId = ObjectId.fromString(n.substring(7));
+ n = br.readLine();
+ if (n == null || !n.startsWith("type ")) {
+ throw new CorruptObjectException(tagId, "no type");
+ }
+ type = n.substring("type ".length());
+ n = br.readLine();
+
+ if (n == null || !n.startsWith("tag ")) {
+ throw new CorruptObjectException(tagId, "no tag name");
+ }
+ tag = n.substring("tag ".length());
+ n = br.readLine();
+
+ // We should see a "tagger" header here, but some repos have tags
+ // without it.
+ if (n == null)
+ throw new CorruptObjectException(tagId, "no tagger header");
+
+ if (n.length()>0)
+ if (n.startsWith("tagger "))
+ tagger = new PersonIdent(n.substring("tagger ".length()));
+ else
+ throw new CorruptObjectException(tagId, "no tagger/bad header");
+
+ // Message should start with an empty line, but
+ StringBuffer tempMessage = new StringBuffer();
+ char[] readBuf = new char[2048];
+ int readLen;
+ while ((readLen = br.read(readBuf)) > 0) {
+ tempMessage.append(readBuf, 0, readLen);
+ }
+ message = tempMessage.toString();
+ if (message.startsWith("\n"))
+ message = message.substring(1);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ raw = null;
+ }
+ }
+ }
+
+ /**
+ * Set the message of an annotated tag
+ * @param m
+ */
+ public void setMessage(final String m) {
+ message = m;
+ }
+
+ /**
+ * Store a tag.
+ * If author, message or type is set make the tag an annotated tag.
+ *
+ * @throws IOException
+ */
+ public void tag() throws IOException {
+ if (getTagId() != null)
+ throw new IllegalStateException("exists " + getTagId());
+ final ObjectId id;
+ final RefUpdate ru;
+
+ if (tagger!=null || message!=null || type!=null) {
+ ObjectId tagid = new ObjectWriter(objdb).writeTag(this);
+ setTagId(tagid);
+ id = tagid;
+ } else {
+ id = objId;
+ }
+
+ ru = objdb.updateRef(Constants.R_TAGS + getTag());
+ ru.setNewObjectId(id);
+ ru.setRefLogMessage("tagged " + getTag(), false);
+ if (ru.forceUpdate() == RefUpdate.Result.LOCK_FAILURE)
+ throw new ObjectWritingException("Unable to lock tag " + getTag());
+ }
+
+ public String toString() {
+ return "tag[" + getTag() + getType() + getObjId() + " " + getAuthor() + "]";
+ }
+
+ /**
+ * @return SHA-1 of this tag (if annotated and stored).
+ */
+ public ObjectId getTagId() {
+ return tagId;
+ }
+
+ /**
+ * Set SHA-1 of this tag. Used by writer.
+ *
+ * @param tagId
+ */
+ public void setTagId(ObjectId tagId) {
+ this.tagId = tagId;
+ }
+
+ /**
+ * @return creator of this tag.
+ */
+ public PersonIdent getTagger() {
+ decode();
+ return tagger;
+ }
+
+ /**
+ * Set the creator of this tag
+ *
+ * @param tagger
+ */
+ public void setTagger(PersonIdent tagger) {
+ this.tagger = tagger;
+ }
+
+ /**
+ * @return tag target type
+ */
+ public String getType() {
+ decode();
+ return type;
+ }
+
+ /**
+ * Set tag target type
+ * @param type
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * @return name of the tag.
+ */
+ public String getTag() {
+ return tag;
+ }
+
+ /**
+ * Set the name of this tag.
+ *
+ * @param tag
+ */
+ public void setTag(String tag) {
+ this.tag = tag;
+ }
+
+ /**
+ * @return the SHA'1 of the object this tag refers to.
+ */
+ public ObjectId getObjId() {
+ return objId;
+ }
+
+ /**
+ * Set the id of the object this tag refers to.
+ *
+ * @param objId
+ */
+ public void setObjId(ObjectId objId) {
+ this.objId = objId;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
new file mode 100644
index 0000000000..a668b11be8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/**
+ * A simple progress reporter printing on stderr
+ */
+public class TextProgressMonitor implements ProgressMonitor {
+ private boolean output;
+
+ private long taskBeganAt;
+
+ private String msg;
+
+ private int lastWorked;
+
+ private int totalWork;
+
+ /** Initialize a new progress monitor. */
+ public TextProgressMonitor() {
+ taskBeganAt = System.currentTimeMillis();
+ }
+
+ public void start(final int totalTasks) {
+ // Ignore the number of tasks.
+ taskBeganAt = System.currentTimeMillis();
+ }
+
+ public void beginTask(final String title, final int total) {
+ endTask();
+ msg = title;
+ lastWorked = 0;
+ totalWork = total;
+ }
+
+ public void update(final int completed) {
+ if (msg == null)
+ return;
+
+ final int cmp = lastWorked + completed;
+ if (!output && System.currentTimeMillis() - taskBeganAt < 500)
+ return;
+ if (totalWork == UNKNOWN) {
+ display(cmp);
+ System.err.flush();
+ } else {
+ if ((cmp * 100 / totalWork) != (lastWorked * 100) / totalWork) {
+ display(cmp);
+ System.err.flush();
+ }
+ }
+ lastWorked = cmp;
+ output = true;
+ }
+
+ private void display(final int cmp) {
+ final StringBuilder m = new StringBuilder();
+ m.append('\r');
+ m.append(msg);
+ m.append(": ");
+ while (m.length() < 25)
+ m.append(' ');
+
+ if (totalWork == UNKNOWN) {
+ m.append(cmp);
+ } else {
+ final String twstr = String.valueOf(totalWork);
+ String cmpstr = String.valueOf(cmp);
+ while (cmpstr.length() < twstr.length())
+ cmpstr = " " + cmpstr;
+ final int pcnt = (cmp * 100 / totalWork);
+ if (pcnt < 100)
+ m.append(' ');
+ if (pcnt < 10)
+ m.append(' ');
+ m.append(pcnt);
+ m.append("% (");
+ m.append(cmpstr);
+ m.append("/");
+ m.append(twstr);
+ m.append(")");
+ }
+
+ System.err.print(m);
+ }
+
+ public boolean isCancelled() {
+ return false;
+ }
+
+ public void endTask() {
+ if (output) {
+ if (totalWork != UNKNOWN)
+ display(totalWork);
+ System.err.println();
+ }
+ output = false;
+ msg = null;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TransferConfig.java
new file mode 100644
index 0000000000..a745cbecdf
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TransferConfig.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import org.eclipse.jgit.lib.Config.SectionParser;
+
+/**
+ * The standard "transfer", "fetch" and "receive" configuration parameters.
+ */
+public class TransferConfig {
+ /** Key for {@link Config#get(SectionParser)}. */
+ public static final Config.SectionParser<TransferConfig> KEY = new SectionParser<TransferConfig>() {
+ public TransferConfig parse(final Config cfg) {
+ return new TransferConfig(cfg);
+ }
+ };
+
+ private final boolean fsckObjects;
+
+ private TransferConfig(final Config rc) {
+ fsckObjects = rc.getBoolean("receive", "fsckobjects", false);
+ }
+
+ /**
+ * @return strictly verify received objects?
+ */
+ public boolean isFsckObjects() {
+ return fsckObjects;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
new file mode 100644
index 0000000000..2b8a0e7cf3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
+ * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.EntryExistsException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * A representation of a Git tree entry. A Tree is a directory in Git.
+ */
+public class Tree extends TreeEntry implements Treeish {
+ private static final TreeEntry[] EMPTY_TREE = {};
+
+ /**
+ * Compare two names represented as bytes. Since git treats names of trees and
+ * blobs differently we have one parameter that represents a '/' for trees. For
+ * other objects the value should be NUL. The names are compare by their positive
+ * byte value (0..255).
+ *
+ * A blob and a tree with the same name will not compare equal.
+ *
+ * @param a name
+ * @param b name
+ * @param lasta '/' if a is a tree, else NUL
+ * @param lastb '/' if b is a tree, else NUL
+ *
+ * @return < 0 if a is sorted before b, 0 if they are the same, else b
+ */
+ public static final int compareNames(final byte[] a, final byte[] b, final int lasta,final int lastb) {
+ return compareNames(a, b, 0, b.length, lasta, lastb);
+ }
+
+ private static final int compareNames(final byte[] a, final byte[] nameUTF8,
+ final int nameStart, final int nameEnd, final int lasta, int lastb) {
+ int j,k;
+ for (j = 0, k = nameStart; j < a.length && k < nameEnd; j++, k++) {
+ final int aj = a[j] & 0xff;
+ final int bk = nameUTF8[k] & 0xff;
+ if (aj < bk)
+ return -1;
+ else if (aj > bk)
+ return 1;
+ }
+ if (j < a.length) {
+ int aj = a[j]&0xff;
+ if (aj < lastb)
+ return -1;
+ else if (aj > lastb)
+ return 1;
+ else
+ if (j == a.length - 1)
+ return 0;
+ else
+ return -1;
+ }
+ if (k < nameEnd) {
+ int bk = nameUTF8[k] & 0xff;
+ if (lasta < bk)
+ return -1;
+ else if (lasta > bk)
+ return 1;
+ else
+ if (k == nameEnd - 1)
+ return 0;
+ else
+ return 1;
+ }
+ if (lasta < lastb)
+ return -1;
+ else if (lasta > lastb)
+ return 1;
+
+ final int namelength = nameEnd - nameStart;
+ if (a.length == namelength)
+ return 0;
+ else if (a.length < namelength)
+ return -1;
+ else
+ return 1;
+ }
+
+ private static final byte[] substring(final byte[] s, final int nameStart,
+ final int nameEnd) {
+ if (nameStart == 0 && nameStart == s.length)
+ return s;
+ final byte[] n = new byte[nameEnd - nameStart];
+ System.arraycopy(s, nameStart, n, 0, n.length);
+ return n;
+ }
+
+ private static final int binarySearch(final TreeEntry[] entries,
+ final byte[] nameUTF8, final int nameUTF8last, final int nameStart, final int nameEnd) {
+ if (entries.length == 0)
+ return -1;
+ int high = entries.length;
+ int low = 0;
+ do {
+ final int mid = (low + high) >>> 1;
+ final int cmp = compareNames(entries[mid].getNameUTF8(), nameUTF8,
+ nameStart, nameEnd, TreeEntry.lastChar(entries[mid]), nameUTF8last);
+ if (cmp < 0)
+ low = mid + 1;
+ else if (cmp == 0)
+ return mid;
+ else
+ high = mid;
+ } while (low < high);
+ return -(low + 1);
+ }
+
+ private final Repository db;
+
+ private TreeEntry[] contents;
+
+ /**
+ * Constructor for a new Tree
+ *
+ * @param repo The repository that owns the Tree.
+ */
+ public Tree(final Repository repo) {
+ super(null, null, null);
+ db = repo;
+ contents = EMPTY_TREE;
+ }
+
+ /**
+ * Construct a Tree object with known content and hash value
+ *
+ * @param repo
+ * @param myId
+ * @param raw
+ * @throws IOException
+ */
+ public Tree(final Repository repo, final ObjectId myId, final byte[] raw)
+ throws IOException {
+ super(null, myId, null);
+ db = repo;
+ readTree(raw);
+ }
+
+ /**
+ * Construct a new Tree under another Tree
+ *
+ * @param parent
+ * @param nameUTF8
+ */
+ public Tree(final Tree parent, final byte[] nameUTF8) {
+ super(parent, null, nameUTF8);
+ db = parent.getRepository();
+ contents = EMPTY_TREE;
+ }
+
+ /**
+ * Construct a Tree with a known SHA-1 under another tree. Data is not yet
+ * specified and will have to be loaded on demand.
+ *
+ * @param parent
+ * @param id
+ * @param nameUTF8
+ */
+ public Tree(final Tree parent, final ObjectId id, final byte[] nameUTF8) {
+ super(parent, id, nameUTF8);
+ db = parent.getRepository();
+ }
+
+ public FileMode getMode() {
+ return FileMode.TREE;
+ }
+
+ /**
+ * @return true if this Tree is the top level Tree.
+ */
+ public boolean isRoot() {
+ return getParent() == null;
+ }
+
+ public Repository getRepository() {
+ return db;
+ }
+
+ public final ObjectId getTreeId() {
+ return getId();
+ }
+
+ public final Tree getTree() {
+ return this;
+ }
+
+ /**
+ * @return true of the data of this Tree is loaded
+ */
+ public boolean isLoaded() {
+ return contents != null;
+ }
+
+ /**
+ * Forget the in-memory data for this tree.
+ */
+ public void unload() {
+ if (isModified())
+ throw new IllegalStateException("Cannot unload a modified tree.");
+ contents = null;
+ }
+
+ /**
+ * Adds a new or existing file with the specified name to this tree.
+ * Trees are added if necessary as the name may contain '/':s.
+ *
+ * @param name Name
+ * @return a {@link FileTreeEntry} for the added file.
+ * @throws IOException
+ */
+ public FileTreeEntry addFile(final String name) throws IOException {
+ return addFile(Repository.gitInternalSlash(Constants.encode(name)), 0);
+ }
+
+ /**
+ * Adds a new or existing file with the specified name to this tree.
+ * Trees are added if necessary as the name may contain '/':s.
+ *
+ * @param s an array containing the name
+ * @param offset when the name starts in the tree.
+ *
+ * @return a {@link FileTreeEntry} for the added file.
+ * @throws IOException
+ */
+ public FileTreeEntry addFile(final byte[] s, final int offset)
+ throws IOException {
+ int slash;
+ int p;
+
+ for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
+ // search for path component terminator
+ }
+
+ ensureLoaded();
+ byte xlast = slash<s.length ? (byte)'/' : 0;
+ p = binarySearch(contents, s, xlast, offset, slash);
+ if (p >= 0 && slash < s.length && contents[p] instanceof Tree)
+ return ((Tree) contents[p]).addFile(s, slash + 1);
+
+ final byte[] newName = substring(s, offset, slash);
+ if (p >= 0)
+ throw new EntryExistsException(RawParseUtils.decode(newName));
+ else if (slash < s.length) {
+ final Tree t = new Tree(this, newName);
+ insertEntry(p, t);
+ return t.addFile(s, slash + 1);
+ } else {
+ final FileTreeEntry f = new FileTreeEntry(this, null, newName,
+ false);
+ insertEntry(p, f);
+ return f;
+ }
+ }
+
+ /**
+ * Adds a new or existing Tree with the specified name to this tree.
+ * Trees are added if necessary as the name may contain '/':s.
+ *
+ * @param name Name
+ * @return a {@link FileTreeEntry} for the added tree.
+ * @throws IOException
+ */
+ public Tree addTree(final String name) throws IOException {
+ return addTree(Repository.gitInternalSlash(Constants.encode(name)), 0);
+ }
+
+ /**
+ * Adds a new or existing Tree with the specified name to this tree.
+ * Trees are added if necessary as the name may contain '/':s.
+ *
+ * @param s an array containing the name
+ * @param offset when the name starts in the tree.
+ *
+ * @return a {@link FileTreeEntry} for the added tree.
+ * @throws IOException
+ */
+ public Tree addTree(final byte[] s, final int offset) throws IOException {
+ int slash;
+ int p;
+
+ for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
+ // search for path component terminator
+ }
+
+ ensureLoaded();
+ p = binarySearch(contents, s, (byte)'/', offset, slash);
+ if (p >= 0 && slash < s.length && contents[p] instanceof Tree)
+ return ((Tree) contents[p]).addTree(s, slash + 1);
+
+ final byte[] newName = substring(s, offset, slash);
+ if (p >= 0)
+ throw new EntryExistsException(RawParseUtils.decode(newName));
+
+ final Tree t = new Tree(this, newName);
+ insertEntry(p, t);
+ return slash == s.length ? t : t.addTree(s, slash + 1);
+ }
+
+ /**
+ * Add the specified tree entry to this tree.
+ *
+ * @param e
+ * @throws IOException
+ */
+ public void addEntry(final TreeEntry e) throws IOException {
+ final int p;
+
+ ensureLoaded();
+ p = binarySearch(contents, e.getNameUTF8(), TreeEntry.lastChar(e), 0, e.getNameUTF8().length);
+ if (p < 0) {
+ e.attachParent(this);
+ insertEntry(p, e);
+ } else {
+ throw new EntryExistsException(e.getName());
+ }
+ }
+
+ private void insertEntry(int p, final TreeEntry e) {
+ final TreeEntry[] c = contents;
+ final TreeEntry[] n = new TreeEntry[c.length + 1];
+ p = -(p + 1);
+ for (int k = c.length - 1; k >= p; k--)
+ n[k + 1] = c[k];
+ n[p] = e;
+ for (int k = p - 1; k >= 0; k--)
+ n[k] = c[k];
+ contents = n;
+ setModified();
+ }
+
+ void removeEntry(final TreeEntry e) {
+ final TreeEntry[] c = contents;
+ final int p = binarySearch(c, e.getNameUTF8(), TreeEntry.lastChar(e), 0,
+ e.getNameUTF8().length);
+ if (p >= 0) {
+ final TreeEntry[] n = new TreeEntry[c.length - 1];
+ for (int k = c.length - 1; k > p; k--)
+ n[k - 1] = c[k];
+ for (int k = p - 1; k >= 0; k--)
+ n[k] = c[k];
+ contents = n;
+ setModified();
+ }
+ }
+
+ /**
+ * @return number of members in this tree
+ * @throws IOException
+ */
+ public int memberCount() throws IOException {
+ ensureLoaded();
+ return contents.length;
+ }
+
+ /**
+ * Return all members of the tree sorted in Git order.
+ *
+ * Entries are sorted by the numerical unsigned byte
+ * values with (sub)trees having an implicit '/'. An
+ * example of a tree with three entries. a:b is an
+ * actual file name here.
+ *
+ * <p>
+ * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.b
+ * 040000 tree 4277b6e69d25e5efa77c455340557b384a4c018a a
+ * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a:b
+ *
+ * @return all entries in this Tree, sorted.
+ * @throws IOException
+ */
+ public TreeEntry[] members() throws IOException {
+ ensureLoaded();
+ final TreeEntry[] c = contents;
+ if (c.length != 0) {
+ final TreeEntry[] r = new TreeEntry[c.length];
+ for (int k = c.length - 1; k >= 0; k--)
+ r[k] = c[k];
+ return r;
+ } else
+ return c;
+ }
+
+ private boolean exists(final String s, byte slast) throws IOException {
+ return findMember(s, slast) != null;
+ }
+
+ /**
+ * @param path to the tree.
+ * @return true if a tree with the specified path can be found under this
+ * tree.
+ * @throws IOException
+ */
+ public boolean existsTree(String path) throws IOException {
+ return exists(path,(byte)'/');
+ }
+
+ /**
+ * @param path of the non-tree entry.
+ * @return true if a blob, symlink, or gitlink with the specified name
+ * can be found under this tree.
+ * @throws IOException
+ */
+ public boolean existsBlob(String path) throws IOException {
+ return exists(path,(byte)0);
+ }
+
+ private TreeEntry findMember(final String s, byte slast) throws IOException {
+ return findMember(Repository.gitInternalSlash(Constants.encode(s)), slast, 0);
+ }
+
+ private TreeEntry findMember(final byte[] s, final byte slast, final int offset)
+ throws IOException {
+ int slash;
+ int p;
+
+ for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
+ // search for path component terminator
+ }
+
+ ensureLoaded();
+ byte xlast = slash<s.length ? (byte)'/' : slast;
+ p = binarySearch(contents, s, xlast, offset, slash);
+ if (p >= 0) {
+ final TreeEntry r = contents[p];
+ if (slash < s.length-1)
+ return r instanceof Tree ? ((Tree) r).findMember(s, slast, slash + 1)
+ : null;
+ return r;
+ }
+ return null;
+ }
+
+ /**
+ * @param s
+ * blob name
+ * @return a {@link TreeEntry} representing an object with the specified
+ * relative path.
+ * @throws IOException
+ */
+ public TreeEntry findBlobMember(String s) throws IOException {
+ return findMember(s,(byte)0);
+ }
+
+ /**
+ * @param s Tree Name
+ * @return a Tree with the name s or null
+ * @throws IOException
+ */
+ public TreeEntry findTreeMember(String s) throws IOException {
+ return findMember(s,(byte)'/');
+ }
+
+ public void accept(final TreeVisitor tv, final int flags)
+ throws IOException {
+ final TreeEntry[] c;
+
+ if ((MODIFIED_ONLY & flags) == MODIFIED_ONLY && !isModified())
+ return;
+
+ if ((LOADED_ONLY & flags) == LOADED_ONLY && !isLoaded()) {
+ tv.startVisitTree(this);
+ tv.endVisitTree(this);
+ return;
+ }
+
+ ensureLoaded();
+ tv.startVisitTree(this);
+
+ if ((CONCURRENT_MODIFICATION & flags) == CONCURRENT_MODIFICATION)
+ c = members();
+ else
+ c = contents;
+
+ for (int k = 0; k < c.length; k++)
+ c[k].accept(tv, flags);
+
+ tv.endVisitTree(this);
+ }
+
+ private void ensureLoaded() throws IOException, MissingObjectException {
+ if (!isLoaded()) {
+ final ObjectLoader or = db.openTree(getId());
+ if (or == null)
+ throw new MissingObjectException(getId(), Constants.TYPE_TREE);
+ readTree(or.getBytes());
+ }
+ }
+
+ private void readTree(final byte[] raw) throws IOException {
+ final int rawSize = raw.length;
+ int rawPtr = 0;
+ TreeEntry[] temp;
+ int nextIndex = 0;
+
+ while (rawPtr < rawSize) {
+ while (rawPtr < rawSize && raw[rawPtr] != 0)
+ rawPtr++;
+ rawPtr++;
+ rawPtr += Constants.OBJECT_ID_LENGTH;
+ nextIndex++;
+ }
+
+ temp = new TreeEntry[nextIndex];
+ rawPtr = 0;
+ nextIndex = 0;
+ while (rawPtr < rawSize) {
+ int c = raw[rawPtr++];
+ if (c < '0' || c > '7')
+ throw new CorruptObjectException(getId(), "invalid entry mode");
+ int mode = c - '0';
+ for (;;) {
+ c = raw[rawPtr++];
+ if (' ' == c)
+ break;
+ else if (c < '0' || c > '7')
+ throw new CorruptObjectException(getId(), "invalid mode");
+ mode <<= 3;
+ mode += c - '0';
+ }
+
+ int nameLen = 0;
+ while (raw[rawPtr + nameLen] != 0)
+ nameLen++;
+ final byte[] name = new byte[nameLen];
+ System.arraycopy(raw, rawPtr, name, 0, nameLen);
+ rawPtr += nameLen + 1;
+
+ final ObjectId id = ObjectId.fromRaw(raw, rawPtr);
+ rawPtr += Constants.OBJECT_ID_LENGTH;
+
+ final TreeEntry ent;
+ if (FileMode.REGULAR_FILE.equals(mode))
+ ent = new FileTreeEntry(this, id, name, false);
+ else if (FileMode.EXECUTABLE_FILE.equals(mode))
+ ent = new FileTreeEntry(this, id, name, true);
+ else if (FileMode.TREE.equals(mode))
+ ent = new Tree(this, id, name);
+ else if (FileMode.SYMLINK.equals(mode))
+ ent = new SymlinkTreeEntry(this, id, name);
+ else if (FileMode.GITLINK.equals(mode))
+ ent = new GitlinkTreeEntry(this, id, name);
+ else
+ throw new CorruptObjectException(getId(), "Invalid mode: "
+ + Integer.toOctalString(mode));
+ temp[nextIndex++] = ent;
+ }
+
+ contents = temp;
+ }
+
+ public String toString() {
+ final StringBuffer r = new StringBuffer();
+ r.append(ObjectId.toString(getId()));
+ r.append(" T ");
+ r.append(getFullName());
+ return r.toString();
+ }
+
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java
new file mode 100644
index 0000000000..b6dd9311ae
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.GitIndex.Entry;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * This class represents an entry in a tree, like a blob or another tree.
+ */
+public abstract class TreeEntry implements Comparable {
+ /**
+ * a flag for {@link TreeEntry#accept(TreeVisitor, int)} to visit only modified entries
+ */
+ public static final int MODIFIED_ONLY = 1 << 0;
+
+ /**
+ * a flag for {@link TreeEntry#accept(TreeVisitor, int)} to visit only loaded entries
+ */
+ public static final int LOADED_ONLY = 1 << 1;
+
+ /**
+ * a flag for {@link TreeEntry#accept(TreeVisitor, int)} obsolete?
+ */
+ public static final int CONCURRENT_MODIFICATION = 1 << 2;
+
+ private byte[] nameUTF8;
+
+ private Tree parent;
+
+ private ObjectId id;
+
+ /**
+ * Construct a named tree entry.
+ *
+ * @param myParent
+ * @param myId
+ * @param myNameUTF8
+ */
+ protected TreeEntry(final Tree myParent, final ObjectId myId,
+ final byte[] myNameUTF8) {
+ nameUTF8 = myNameUTF8;
+ parent = myParent;
+ id = myId;
+ }
+
+ /**
+ * @return parent of this tree.
+ */
+ public Tree getParent() {
+ return parent;
+ }
+
+ /**
+ * Delete this entry.
+ */
+ public void delete() {
+ getParent().removeEntry(this);
+ detachParent();
+ }
+
+ /**
+ * Detach this entry from it's parent.
+ */
+ public void detachParent() {
+ parent = null;
+ }
+
+ void attachParent(final Tree p) {
+ parent = p;
+ }
+
+ /**
+ * @return the repository owning this entry.
+ */
+ public Repository getRepository() {
+ return getParent().getRepository();
+ }
+
+ /**
+ * @return the raw byte name of this entry.
+ */
+ public byte[] getNameUTF8() {
+ return nameUTF8;
+ }
+
+ /**
+ * @return the name of this entry.
+ */
+ public String getName() {
+ if (nameUTF8 != null)
+ return RawParseUtils.decode(nameUTF8);
+ return null;
+ }
+
+ /**
+ * Rename this entry.
+ *
+ * @param n The new name
+ * @throws IOException
+ */
+ public void rename(final String n) throws IOException {
+ rename(Constants.encode(n));
+ }
+
+ /**
+ * Rename this entry.
+ *
+ * @param n The new name
+ * @throws IOException
+ */
+ public void rename(final byte[] n) throws IOException {
+ final Tree t = getParent();
+ if (t != null) {
+ delete();
+ }
+ nameUTF8 = n;
+ if (t != null) {
+ t.addEntry(this);
+ }
+ }
+
+ /**
+ * @return true if this entry is new or modified since being loaded.
+ */
+ public boolean isModified() {
+ return getId() == null;
+ }
+
+ /**
+ * Mark this entry as modified.
+ */
+ public void setModified() {
+ setId(null);
+ }
+
+ /**
+ * @return SHA-1 of this tree entry (null for new unhashed entries)
+ */
+ public ObjectId getId() {
+ return id;
+ }
+
+ /**
+ * Set (update) the SHA-1 of this entry. Invalidates the id's of all
+ * entries above this entry as they will have to be recomputed.
+ *
+ * @param n SHA-1 for this entry.
+ */
+ public void setId(final ObjectId n) {
+ // If we have a parent and our id is being cleared or changed then force
+ // the parent's id to become unset as it depends on our id.
+ //
+ final Tree p = getParent();
+ if (p != null && id != n) {
+ if ((id == null && n != null) || (id != null && n == null)
+ || !id.equals(n)) {
+ p.setId(null);
+ }
+ }
+
+ id = n;
+ }
+
+ /**
+ * @return repository relative name of this entry
+ */
+ public String getFullName() {
+ final StringBuffer r = new StringBuffer();
+ appendFullName(r);
+ return r.toString();
+ }
+
+ /**
+ * @return repository relative name of the entry
+ * FIXME better encoding
+ */
+ public byte[] getFullNameUTF8() {
+ return getFullName().getBytes();
+ }
+
+ public int compareTo(final Object o) {
+ if (this == o)
+ return 0;
+ if (o instanceof TreeEntry)
+ return Tree.compareNames(nameUTF8, ((TreeEntry) o).nameUTF8, lastChar(this), lastChar((TreeEntry)o));
+ return -1;
+ }
+
+ /**
+ * Helper for accessing tree/blob methods.
+ *
+ * @param treeEntry
+ * @return '/' for Tree entries and NUL for non-treeish objects.
+ */
+ final public static int lastChar(TreeEntry treeEntry) {
+ if (!(treeEntry instanceof Tree))
+ return '\0';
+ else
+ return '/';
+ }
+
+ /**
+ * Helper for accessing tree/blob/index methods.
+ *
+ * @param i
+ * @return '/' for Tree entries and NUL for non-treeish objects
+ */
+ final public static int lastChar(Entry i) {
+ // FIXME, gitlink etc. Currently Trees cannot appear in the
+ // index so '\0' is always returned, except maybe for submodules
+ // which we do not support yet.
+ return FileMode.TREE.equals(i.getModeBits()) ? '/' : '\0';
+ }
+
+ /**
+ * See @{link {@link #accept(TreeVisitor, int)}.
+ *
+ * @param tv
+ * @throws IOException
+ */
+ public void accept(final TreeVisitor tv) throws IOException {
+ accept(tv, 0);
+ }
+
+ /**
+ * Visit the members of this TreeEntry.
+ *
+ * @param tv
+ * A visitor object doing the work
+ * @param flags
+ * Specification for what members to visit. See
+ * {@link #MODIFIED_ONLY}, {@link #LOADED_ONLY},
+ * {@link #CONCURRENT_MODIFICATION}.
+ * @throws IOException
+ */
+ public abstract void accept(TreeVisitor tv, int flags) throws IOException;
+
+ /**
+ * @return mode (type of object)
+ */
+ public abstract FileMode getMode();
+
+ private void appendFullName(final StringBuffer r) {
+ final TreeEntry p = getParent();
+ final String n = getName();
+ if (p != null) {
+ p.appendFullName(r);
+ if (r.length() > 0) {
+ r.append('/');
+ }
+ }
+ if (n != null) {
+ r.append(n);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeIterator.java
new file mode 100644
index 0000000000..937baf6cc5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeIterator.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+/**
+ * A tree iterator iterates over a tree and all its members recursing into
+ * subtrees according to order.
+ *
+ * Default is to only visit leafs. An {@link Order} value can be supplied to
+ * make the iteration include Tree nodes as well either before or after the
+ * child nodes have been visited.
+ */
+public class TreeIterator implements Iterator<TreeEntry> {
+
+ private Tree tree;
+
+ private int index;
+
+ private TreeIterator sub;
+
+ private Order order;
+
+ private boolean visitTreeNodes;
+
+ private boolean hasVisitedTree;
+
+ /**
+ * Traversal order
+ */
+ public enum Order {
+ /**
+ * Visit node first, then leaves
+ */
+ PREORDER,
+
+ /**
+ * Visit leaves first, then node
+ */
+ POSTORDER
+ }
+
+ /**
+ * Construct a {@link TreeIterator} for visiting all non-tree nodes.
+ *
+ * @param start
+ */
+ public TreeIterator(Tree start) {
+ this(start, Order.PREORDER, false);
+ }
+
+ /**
+ * Construct a {@link TreeIterator} visiting all nodes in a tree in a given
+ * order.
+ *
+ * @param start Root node
+ * @param order {@link Order}
+ */
+ public TreeIterator(Tree start, Order order) {
+ this(start, order, true);
+ }
+
+ /**
+ * Construct a {@link TreeIterator}
+ *
+ * @param start First node to visit
+ * @param order Visitation {@link Order}
+ * @param visitTreeNode True to include tree node
+ */
+ private TreeIterator(Tree start, Order order, boolean visitTreeNode) {
+ this.tree = start;
+ this.visitTreeNodes = visitTreeNode;
+ this.index = -1;
+ this.order = order;
+ if (!visitTreeNodes)
+ this.hasVisitedTree = true;
+ try {
+ step();
+ } catch (IOException e) {
+ throw new Error(e);
+ }
+ }
+
+ public TreeEntry next() {
+ try {
+ TreeEntry ret = nextTreeEntry();
+ step();
+ return ret;
+ } catch (IOException e) {
+ throw new Error(e);
+ }
+ }
+
+ private TreeEntry nextTreeEntry() throws IOException {
+ TreeEntry ret;
+ if (sub != null)
+ ret = sub.nextTreeEntry();
+ else {
+ if (index < 0 && order == Order.PREORDER) {
+ return tree;
+ }
+ if (order == Order.POSTORDER && index == tree.memberCount()) {
+ return tree;
+ }
+ ret = tree.members()[index];
+ }
+ return ret;
+ }
+
+ public boolean hasNext() {
+ try {
+ return hasNextTreeEntry();
+ } catch (IOException e) {
+ throw new Error(e);
+ }
+ }
+
+ private boolean hasNextTreeEntry() throws IOException {
+ if (tree == null)
+ return false;
+ return sub != null
+ || index < tree.memberCount()
+ || order == Order.POSTORDER && index == tree.memberCount();
+ }
+
+ private boolean step() throws IOException {
+ if (tree == null)
+ return false;
+
+ if (sub != null) {
+ if (sub.step())
+ return true;
+ sub = null;
+ }
+
+ if (index < 0 && !hasVisitedTree && order == Order.PREORDER) {
+ hasVisitedTree = true;
+ return true;
+ }
+
+ while (++index < tree.memberCount()) {
+ TreeEntry e = tree.members()[index];
+ if (e instanceof Tree) {
+ sub = new TreeIterator((Tree) e, order, visitTreeNodes);
+ if (sub.hasNextTreeEntry())
+ return true;
+ sub = null;
+ continue;
+ }
+ return true;
+ }
+
+ if (index == tree.memberCount() && !hasVisitedTree
+ && order == Order.POSTORDER) {
+ hasVisitedTree = true;
+ return true;
+ }
+ return false;
+ }
+
+ public void remove() {
+ throw new IllegalStateException(
+ "TreeIterator does not support remove()");
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitor.java
new file mode 100644
index 0000000000..1745515460
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitor.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2009, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+/**
+ * A TreeVisitor is invoked depth first for every node in a tree and is expected
+ * to perform different actions.
+ */
+public interface TreeVisitor {
+ /**
+ * Visit to a tree node before child nodes are visited.
+ *
+ * @param t
+ * Tree
+ * @throws IOException
+ */
+ public void startVisitTree(final Tree t) throws IOException;
+
+ /**
+ * Visit to a tree node. after child nodes have been visited.
+ *
+ * @param t Tree
+ * @throws IOException
+ */
+ public void endVisitTree(final Tree t) throws IOException;
+
+ /**
+ * Visit to a blob.
+ *
+ * @param f Blob
+ * @throws IOException
+ */
+ public void visitFile(final FileTreeEntry f) throws IOException;
+
+ /**
+ * Visit to a symlink.
+ *
+ * @param s Symlink entry
+ * @throws IOException
+ */
+ public void visitSymlink(final SymlinkTreeEntry s) throws IOException;
+
+ /**
+ * Visit to a gitlink.
+ *
+ * @param s Gitlink entry
+ * @throws IOException
+ */
+ public void visitGitlink(final GitlinkTreeEntry s) throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitorWithCurrentDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitorWithCurrentDirectory.java
new file mode 100644
index 0000000000..680bab6bec
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitorWithCurrentDirectory.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2009, Vasyl' Vavrychuk <vvavrychuk@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Abstract TreeVisitor for visiting all files known by a Tree.
+ */
+public abstract class TreeVisitorWithCurrentDirectory implements TreeVisitor {
+ private final ArrayList<File> stack = new ArrayList<File>(16);
+
+ private File currentDirectory;
+
+ TreeVisitorWithCurrentDirectory(final File rootDirectory) {
+ currentDirectory = rootDirectory;
+ }
+
+ File getCurrentDirectory() {
+ return currentDirectory;
+ }
+
+ public void startVisitTree(final Tree t) throws IOException {
+ stack.add(currentDirectory);
+ if (!t.isRoot()) {
+ currentDirectory = new File(currentDirectory, t.getName());
+ }
+ }
+
+ public void endVisitTree(final Tree t) throws IOException {
+ currentDirectory = stack.remove(stack.size() - 1);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Treeish.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Treeish.java
new file mode 100644
index 0000000000..7da14172e4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Treeish.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+
+/**
+ * Tree-ish is an interface for tree-like Git objects.
+ */
+public interface Treeish {
+ /**
+ * @return the id of this tree
+ */
+ public ObjectId getTreeId();
+
+ /**
+ * @return the tree of this tree-ish object
+ * @throws IOException
+ */
+ public Tree getTree() throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java
new file mode 100644
index 0000000000..3cef48242d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.lang.ref.SoftReference;
+
+class UnpackedObjectCache {
+ private static final int CACHE_SZ = 1024;
+
+ private static final SoftReference<Entry> DEAD;
+
+ private static int hash(final long position) {
+ return (((int) position) << 22) >>> 22;
+ }
+
+ private static int maxByteCount;
+
+ private static final Slot[] cache;
+
+ private static Slot lruHead;
+
+ private static Slot lruTail;
+
+ private static int openByteCount;
+
+ static {
+ DEAD = new SoftReference<Entry>(null);
+ maxByteCount = new WindowCacheConfig().getDeltaBaseCacheLimit();
+
+ cache = new Slot[CACHE_SZ];
+ for (int i = 0; i < CACHE_SZ; i++)
+ cache[i] = new Slot();
+ }
+
+ static synchronized void reconfigure(final WindowCacheConfig cfg) {
+ final int dbLimit = cfg.getDeltaBaseCacheLimit();
+ if (maxByteCount != dbLimit) {
+ maxByteCount = dbLimit;
+ releaseMemory();
+ }
+ }
+
+ static synchronized Entry get(final PackFile pack, final long position) {
+ final Slot e = cache[hash(position)];
+ if (e.provider == pack && e.position == position) {
+ final Entry buf = e.data.get();
+ if (buf != null) {
+ moveToHead(e);
+ return buf;
+ }
+ }
+ return null;
+ }
+
+ static synchronized void store(final PackFile pack, final long position,
+ final byte[] data, final int objectType) {
+ if (data.length > maxByteCount)
+ return; // Too large to cache.
+
+ final Slot e = cache[hash(position)];
+ clearEntry(e);
+
+ openByteCount += data.length;
+ releaseMemory();
+
+ e.provider = pack;
+ e.position = position;
+ e.sz = data.length;
+ e.data = new SoftReference<Entry>(new Entry(data, objectType));
+ moveToHead(e);
+ }
+
+ private static void releaseMemory() {
+ while (openByteCount > maxByteCount && lruTail != null) {
+ final Slot currOldest = lruTail;
+ final Slot nextOldest = currOldest.lruPrev;
+
+ clearEntry(currOldest);
+ currOldest.lruPrev = null;
+ currOldest.lruNext = null;
+
+ if (nextOldest == null)
+ lruHead = null;
+ else
+ nextOldest.lruNext = null;
+ lruTail = nextOldest;
+ }
+ }
+
+ static synchronized void purge(final PackFile file) {
+ for (final Slot e : cache) {
+ if (e.provider == file) {
+ clearEntry(e);
+ unlink(e);
+ }
+ }
+ }
+
+ private static void moveToHead(final Slot e) {
+ unlink(e);
+ e.lruPrev = null;
+ e.lruNext = lruHead;
+ if (lruHead != null)
+ lruHead.lruPrev = e;
+ else
+ lruTail = e;
+ lruHead = e;
+ }
+
+ private static void unlink(final Slot e) {
+ final Slot prev = e.lruPrev;
+ final Slot next = e.lruNext;
+ if (prev != null)
+ prev.lruNext = next;
+ if (next != null)
+ next.lruPrev = prev;
+ }
+
+ private static void clearEntry(final Slot e) {
+ openByteCount -= e.sz;
+ e.provider = null;
+ e.data = DEAD;
+ e.sz = 0;
+ }
+
+ private UnpackedObjectCache() {
+ throw new UnsupportedOperationException();
+ }
+
+ static class Entry {
+ final byte[] data;
+
+ final int type;
+
+ Entry(final byte[] aData, final int aType) {
+ data = aData;
+ type = aType;
+ }
+ }
+
+ private static class Slot {
+ Slot lruPrev;
+
+ Slot lruNext;
+
+ PackFile provider;
+
+ long position;
+
+ int sz;
+
+ SoftReference<Entry> data = DEAD;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java
new file mode 100644
index 0000000000..c31dfdee42
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.util.MutableInteger;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Loose object loader. This class loads an object not stored in a pack.
+ */
+public class UnpackedObjectLoader extends ObjectLoader {
+ private final int objectType;
+
+ private final int objectSize;
+
+ private final byte[] bytes;
+
+ /**
+ * Construct an ObjectLoader to read from the file.
+ *
+ * @param path
+ * location of the loose object to read.
+ * @param id
+ * expected identity of the object being loaded, if known.
+ * @throws FileNotFoundException
+ * the loose object file does not exist.
+ * @throws IOException
+ * the loose object file exists, but is corrupt.
+ */
+ public UnpackedObjectLoader(final File path, final AnyObjectId id)
+ throws IOException {
+ this(NB.readFully(path), id);
+ }
+
+ /**
+ * Construct an ObjectLoader from a loose object's compressed form.
+ *
+ * @param compressed
+ * entire content of the loose object file.
+ * @throws CorruptObjectException
+ * The compressed data supplied does not match the format for a
+ * valid loose object.
+ */
+ public UnpackedObjectLoader(final byte[] compressed)
+ throws CorruptObjectException {
+ this(compressed, null);
+ }
+
+ private UnpackedObjectLoader(final byte[] compressed, final AnyObjectId id)
+ throws CorruptObjectException {
+ // Try to determine if this is a legacy format loose object or
+ // a new style loose object. The legacy format was completely
+ // compressed with zlib so the first byte must be 0x78 (15-bit
+ // window size, deflated) and the first 16 bit word must be
+ // evenly divisible by 31. Otherwise its a new style loose
+ // object.
+ //
+ final Inflater inflater = InflaterCache.get();
+ try {
+ final int fb = compressed[0] & 0xff;
+ if (fb == 0x78 && (((fb << 8) | compressed[1] & 0xff) % 31) == 0) {
+ inflater.setInput(compressed);
+ final byte[] hdr = new byte[64];
+ int avail = 0;
+ while (!inflater.finished() && avail < hdr.length)
+ try {
+ avail += inflater.inflate(hdr, avail, hdr.length
+ - avail);
+ } catch (DataFormatException dfe) {
+ final CorruptObjectException coe;
+ coe = new CorruptObjectException(id, "bad stream");
+ coe.initCause(dfe);
+ inflater.end();
+ throw coe;
+ }
+ if (avail < 5)
+ throw new CorruptObjectException(id, "no header");
+
+ final MutableInteger p = new MutableInteger();
+ objectType = Constants.decodeTypeString(id, hdr, (byte) ' ', p);
+ objectSize = RawParseUtils.parseBase10(hdr, p.value, p);
+ if (objectSize < 0)
+ throw new CorruptObjectException(id, "negative size");
+ if (hdr[p.value++] != 0)
+ throw new CorruptObjectException(id, "garbage after size");
+ bytes = new byte[objectSize];
+ if (p.value < avail)
+ System.arraycopy(hdr, p.value, bytes, 0, avail - p.value);
+ decompress(id, inflater, avail - p.value);
+ } else {
+ int p = 0;
+ int c = compressed[p++] & 0xff;
+ final int typeCode = (c >> 4) & 7;
+ int size = c & 15;
+ int shift = 4;
+ while ((c & 0x80) != 0) {
+ c = compressed[p++] & 0xff;
+ size += (c & 0x7f) << shift;
+ shift += 7;
+ }
+
+ switch (typeCode) {
+ case Constants.OBJ_COMMIT:
+ case Constants.OBJ_TREE:
+ case Constants.OBJ_BLOB:
+ case Constants.OBJ_TAG:
+ objectType = typeCode;
+ break;
+ default:
+ throw new CorruptObjectException(id, "invalid type");
+ }
+
+ objectSize = size;
+ bytes = new byte[objectSize];
+ inflater.setInput(compressed, p, compressed.length - p);
+ decompress(id, inflater, 0);
+ }
+ } finally {
+ InflaterCache.release(inflater);
+ }
+ }
+
+ private void decompress(final AnyObjectId id, final Inflater inf, int p)
+ throws CorruptObjectException {
+ try {
+ while (!inf.finished())
+ p += inf.inflate(bytes, p, objectSize - p);
+ } catch (DataFormatException dfe) {
+ final CorruptObjectException coe;
+ coe = new CorruptObjectException(id, "bad stream");
+ coe.initCause(dfe);
+ throw coe;
+ }
+ if (p != objectSize)
+ throw new CorruptObjectException(id, "incorrect length");
+ }
+
+ @Override
+ public int getType() {
+ return objectType;
+ }
+
+ @Override
+ public long getSize() {
+ return objectSize;
+ }
+
+ @Override
+ public byte[] getCachedBytes() {
+ return bytes;
+ }
+
+ @Override
+ public int getRawType() {
+ return objectType;
+ }
+
+ @Override
+ public long getRawSize() {
+ return objectSize;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java
new file mode 100644
index 0000000000..28b3cd0277
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import org.eclipse.jgit.lib.Config.SectionParser;
+import org.eclipse.jgit.util.SystemReader;
+
+/** The standard "user" configuration parameters. */
+public class UserConfig {
+ /** Key for {@link Config#get(SectionParser)}. */
+ public static final Config.SectionParser<UserConfig> KEY = new SectionParser<UserConfig>() {
+ public UserConfig parse(final Config cfg) {
+ return new UserConfig(cfg);
+ }
+ };
+
+ private final String authorName;
+
+ private final String authorEmail;
+
+ private final String committerName;
+
+ private final String committerEmail;
+
+ private UserConfig(final Config rc) {
+ authorName = getNameInternal(rc, Constants.GIT_AUTHOR_NAME_KEY);
+ authorEmail = getEmailInternal(rc, Constants.GIT_AUTHOR_EMAIL_KEY);
+
+ committerName = getNameInternal(rc, Constants.GIT_COMMITTER_NAME_KEY);
+ committerEmail = getEmailInternal(rc, Constants.GIT_COMMITTER_EMAIL_KEY);
+ }
+
+ /**
+ * @return the author name as defined in the git variables and
+ * configurations. If no name could be found, try to use the system
+ * user name instead.
+ */
+ public String getAuthorName() {
+ return authorName;
+ }
+
+ /**
+ * @return the committer name as defined in the git variables and
+ * configurations. If no name could be found, try to use the system
+ * user name instead.
+ */
+ public String getCommitterName() {
+ return committerName;
+ }
+
+ /**
+ * @return the author email as defined in git variables and
+ * configurations. If no email could be found, try to
+ * propose one default with the user name and the
+ * host name.
+ */
+ public String getAuthorEmail() {
+ return authorEmail;
+ }
+
+ /**
+ * @return the committer email as defined in git variables and
+ * configurations. If no email could be found, try to
+ * propose one default with the user name and the
+ * host name.
+ */
+ public String getCommitterEmail() {
+ return committerEmail;
+ }
+
+ private static String getNameInternal(Config rc, String envKey) {
+ // try to get the user name from the local and global configurations.
+ String username = rc.getString("user", null, "name");
+
+ if (username == null) {
+ // try to get the user name for the system property GIT_XXX_NAME
+ username = system().getenv(envKey);
+ }
+ if (username == null) {
+ // get the system user name
+ username = system().getProperty(Constants.OS_USER_NAME_KEY);
+ }
+ if (username == null) {
+ username = Constants.UNKNOWN_USER_DEFAULT;
+ }
+ return username;
+ }
+
+ private static String getEmailInternal(Config rc, String envKey) {
+ // try to get the email from the local and global configurations.
+ String email = rc.getString("user", null, "email");
+
+ if (email == null) {
+ // try to get the email for the system property GIT_XXX_EMAIL
+ email = system().getenv(envKey);
+ }
+
+ if (email == null) {
+ // try to construct an email
+ String username = system().getProperty(Constants.OS_USER_NAME_KEY);
+ if (username == null){
+ username = Constants.UNKNOWN_USER_DEFAULT;
+ }
+ email = username + "@" + system().getHostname();
+ }
+
+ return email;
+ }
+
+ private static SystemReader system() {
+ return SystemReader.getInstance();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java
new file mode 100644
index 0000000000..31439d4891
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+
+/** Reader for a non-delta (just deflated) object in a pack file. */
+class WholePackedObjectLoader extends PackedObjectLoader {
+ private static final int OBJ_COMMIT = Constants.OBJ_COMMIT;
+
+ WholePackedObjectLoader(final PackFile pr, final long dataOffset,
+ final long objectOffset, final int type, final int size) {
+ super(pr, dataOffset, objectOffset);
+ objectType = type;
+ objectSize = size;
+ }
+
+ @Override
+ public void materialize(final WindowCursor curs) throws IOException {
+ if (cachedBytes != null) {
+ return;
+ }
+
+ if (objectType != OBJ_COMMIT) {
+ final UnpackedObjectCache.Entry cache = pack.readCache(dataOffset);
+ if (cache != null) {
+ curs.release();
+ cachedBytes = cache.data;
+ return;
+ }
+ }
+
+ try {
+ cachedBytes = pack.decompress(dataOffset, objectSize, curs);
+ curs.release();
+ if (objectType != OBJ_COMMIT)
+ pack.saveCache(dataOffset, cachedBytes, objectType);
+ } catch (DataFormatException dfe) {
+ final CorruptObjectException coe;
+ coe = new CorruptObjectException("Object at " + dataOffset + " in "
+ + pack.getPackFile() + " has bad zlib stream");
+ coe.initCause(dfe);
+ throw coe;
+ }
+ }
+
+ @Override
+ public int getRawType() {
+ return objectType;
+ }
+
+ @Override
+ public long getRawSize() {
+ return objectSize;
+ }
+
+ @Override
+ public ObjectId getDeltaBase() {
+ return null;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java
new file mode 100644
index 0000000000..9c21342637
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.lang.ref.ReferenceQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Caches slices of a {@link PackFile} in memory for faster read access.
+ * <p>
+ * The WindowCache serves as a Java based "buffer cache", loading segments of a
+ * PackFile into the JVM heap prior to use. As JGit often wants to do reads of
+ * only tiny slices of a file, the WindowCache tries to smooth out these tiny
+ * reads into larger block-sized IO operations.
+ */
+public class WindowCache extends OffsetCache<ByteWindow, WindowCache.WindowRef> {
+ private static final int bits(int newSize) {
+ if (newSize < 4096)
+ throw new IllegalArgumentException("Invalid window size");
+ if (Integer.bitCount(newSize) != 1)
+ throw new IllegalArgumentException("Window size must be power of 2");
+ return Integer.numberOfTrailingZeros(newSize);
+ }
+
+ private static volatile WindowCache cache;
+
+ static {
+ reconfigure(new WindowCacheConfig());
+ }
+
+ /**
+ * Modify the configuration of the window cache.
+ * <p>
+ * The new configuration is applied immediately. If the new limits are
+ * smaller than what what is currently cached, older entries will be purged
+ * as soon as possible to allow the cache to meet the new limit.
+ *
+ * @param packedGitLimit
+ * maximum number of bytes to hold within this instance.
+ * @param packedGitWindowSize
+ * number of bytes per window within the cache.
+ * @param packedGitMMAP
+ * true to enable use of mmap when creating windows.
+ * @param deltaBaseCacheLimit
+ * number of bytes to hold in the delta base cache.
+ * @deprecated Use {@link WindowCacheConfig} instead.
+ */
+ public static void reconfigure(final int packedGitLimit,
+ final int packedGitWindowSize, final boolean packedGitMMAP,
+ final int deltaBaseCacheLimit) {
+ final WindowCacheConfig c = new WindowCacheConfig();
+ c.setPackedGitLimit(packedGitLimit);
+ c.setPackedGitWindowSize(packedGitWindowSize);
+ c.setPackedGitMMAP(packedGitMMAP);
+ c.setDeltaBaseCacheLimit(deltaBaseCacheLimit);
+ reconfigure(c);
+ }
+
+ /**
+ * Modify the configuration of the window cache.
+ * <p>
+ * The new configuration is applied immediately. If the new limits are
+ * smaller than what what is currently cached, older entries will be purged
+ * as soon as possible to allow the cache to meet the new limit.
+ *
+ * @param cfg
+ * the new window cache configuration.
+ * @throws IllegalArgumentException
+ * the cache configuration contains one or more invalid
+ * settings, usually too low of a limit.
+ */
+ public static void reconfigure(final WindowCacheConfig cfg) {
+ final WindowCache nc = new WindowCache(cfg);
+ final WindowCache oc = cache;
+ if (oc != null)
+ oc.removeAll();
+ cache = nc;
+ UnpackedObjectCache.reconfigure(cfg);
+ }
+
+ static WindowCache getInstance() {
+ return cache;
+ }
+
+ static final ByteWindow get(final PackFile pack, final long offset)
+ throws IOException {
+ final WindowCache c = cache;
+ final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
+ if (c != cache) {
+ // The cache was reconfigured while we were using the old one
+ // to load this window. The window is still valid, but our
+ // cache may think its still live. Ensure the window is removed
+ // from the old cache so resources can be released.
+ //
+ c.removeAll();
+ }
+ return r;
+ }
+
+ static final void purge(final PackFile pack) {
+ cache.removeAll(pack);
+ }
+
+ private final int maxFiles;
+
+ private final long maxBytes;
+
+ private final boolean mmap;
+
+ private final int windowSizeShift;
+
+ private final int windowSize;
+
+ private final AtomicInteger openFiles;
+
+ private final AtomicLong openBytes;
+
+ private WindowCache(final WindowCacheConfig cfg) {
+ super(tableSize(cfg), lockCount(cfg));
+ maxFiles = cfg.getPackedGitOpenFiles();
+ maxBytes = cfg.getPackedGitLimit();
+ mmap = cfg.isPackedGitMMAP();
+ windowSizeShift = bits(cfg.getPackedGitWindowSize());
+ windowSize = 1 << windowSizeShift;
+
+ openFiles = new AtomicInteger();
+ openBytes = new AtomicLong();
+
+ if (maxFiles < 1)
+ throw new IllegalArgumentException("Open files must be >= 1");
+ if (maxBytes < windowSize)
+ throw new IllegalArgumentException("Window size must be < limit");
+ }
+
+ int getOpenFiles() {
+ return openFiles.get();
+ }
+
+ long getOpenBytes() {
+ return openBytes.get();
+ }
+
+ @Override
+ protected int hash(final int packHash, final long off) {
+ return packHash + (int) (off >>> windowSizeShift);
+ }
+
+ @Override
+ protected ByteWindow load(final PackFile pack, final long offset)
+ throws IOException {
+ if (pack.beginWindowCache())
+ openFiles.incrementAndGet();
+ try {
+ if (mmap)
+ return pack.mmap(offset, windowSize);
+ return pack.read(offset, windowSize);
+ } catch (IOException e) {
+ close(pack);
+ throw e;
+ } catch (RuntimeException e) {
+ close(pack);
+ throw e;
+ } catch (Error e) {
+ close(pack);
+ throw e;
+ }
+ }
+
+ @Override
+ protected WindowRef createRef(final PackFile p, final long o,
+ final ByteWindow v) {
+ final WindowRef ref = new WindowRef(p, o, v, queue);
+ openBytes.addAndGet(ref.size);
+ return ref;
+ }
+
+ @Override
+ protected void clear(final WindowRef ref) {
+ openBytes.addAndGet(-ref.size);
+ close(ref.pack);
+ }
+
+ private void close(final PackFile pack) {
+ if (pack.endWindowCache())
+ openFiles.decrementAndGet();
+ }
+
+ @Override
+ protected boolean isFull() {
+ return maxFiles < openFiles.get() || maxBytes < openBytes.get();
+ }
+
+ private long toStart(final long offset) {
+ return (offset >>> windowSizeShift) << windowSizeShift;
+ }
+
+ private static int tableSize(final WindowCacheConfig cfg) {
+ final int wsz = cfg.getPackedGitWindowSize();
+ final long limit = cfg.getPackedGitLimit();
+ if (wsz <= 0)
+ throw new IllegalArgumentException("Invalid window size");
+ if (limit < wsz)
+ throw new IllegalArgumentException("Window size must be < limit");
+ return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
+ }
+
+ private static int lockCount(final WindowCacheConfig cfg) {
+ return Math.max(cfg.getPackedGitOpenFiles(), 32);
+ }
+
+ static class WindowRef extends OffsetCache.Ref<ByteWindow> {
+ final int size;
+
+ WindowRef(final PackFile pack, final long position, final ByteWindow v,
+ final ReferenceQueue<ByteWindow> queue) {
+ super(pack, position, v, queue);
+ size = v.size();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java
new file mode 100644
index 0000000000..2d8aef34ca
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+/** Configuration parameters for {@link WindowCache}. */
+public class WindowCacheConfig {
+ /** 1024 (number of bytes in one kibibyte/kilobyte) */
+ public static final int KB = 1024;
+
+ /** 1024 {@link #KB} (number of bytes in one mebibyte/megabyte) */
+ public static final int MB = 1024 * KB;
+
+ private int packedGitOpenFiles;
+
+ private long packedGitLimit;
+
+ private int packedGitWindowSize;
+
+ private boolean packedGitMMAP;
+
+ private int deltaBaseCacheLimit;
+
+ /** Create a default configuration. */
+ public WindowCacheConfig() {
+ packedGitOpenFiles = 128;
+ packedGitLimit = 10 * MB;
+ packedGitWindowSize = 8 * KB;
+ packedGitMMAP = false;
+ deltaBaseCacheLimit = 10 * MB;
+ }
+
+ /**
+ * @return maximum number of streams to open at a time. Open packs count
+ * against the process limits. <b>Default is 128.</b>
+ */
+ public int getPackedGitOpenFiles() {
+ return packedGitOpenFiles;
+ }
+
+ /**
+ * @param fdLimit
+ * maximum number of streams to open at a time. Open packs count
+ * against the process limits
+ */
+ public void setPackedGitOpenFiles(final int fdLimit) {
+ packedGitOpenFiles = fdLimit;
+ }
+
+ /**
+ * @return maximum number bytes of heap memory to dedicate to caching pack
+ * file data. <b>Default is 10 MB.</b>
+ */
+ public long getPackedGitLimit() {
+ return packedGitLimit;
+ }
+
+ /**
+ * @param newLimit
+ * maximum number bytes of heap memory to dedicate to caching
+ * pack file data.
+ */
+ public void setPackedGitLimit(final long newLimit) {
+ packedGitLimit = newLimit;
+ }
+
+ /**
+ * @return size in bytes of a single window mapped or read in from the pack
+ * file. <b>Default is 8 KB.</b>
+ */
+ public int getPackedGitWindowSize() {
+ return packedGitWindowSize;
+ }
+
+ /**
+ * @param newSize
+ * size in bytes of a single window read in from the pack file.
+ */
+ public void setPackedGitWindowSize(final int newSize) {
+ packedGitWindowSize = newSize;
+ }
+
+ /**
+ * @return true enables use of Java NIO virtual memory mapping for windows;
+ * false reads entire window into a byte[] with standard read calls.
+ * <b>Default false.</b>
+ */
+ public boolean isPackedGitMMAP() {
+ return packedGitMMAP;
+ }
+
+ /**
+ * @param usemmap
+ * true enables use of Java NIO virtual memory mapping for
+ * windows; false reads entire window into a byte[] with standard
+ * read calls.
+ */
+ public void setPackedGitMMAP(final boolean usemmap) {
+ packedGitMMAP = usemmap;
+ }
+
+ /**
+ * @return maximum number of bytes to cache in {@link UnpackedObjectCache}
+ * for inflated, recently accessed objects, without delta chains.
+ * <b>Default 10 MB.</b>
+ */
+ public int getDeltaBaseCacheLimit() {
+ return deltaBaseCacheLimit;
+ }
+
+ /**
+ * @param newLimit
+ * maximum number of bytes to cache in
+ * {@link UnpackedObjectCache} for inflated, recently accessed
+ * objects, without delta chains.
+ */
+ public void setDeltaBaseCacheLimit(final int newLimit) {
+ deltaBaseCacheLimit = newLimit;
+ }
+
+ /**
+ * Update properties by setting fields from the configuration.
+ * <p>
+ * If a property is not defined in the configuration, then it is left
+ * unmodified.
+ *
+ * @param rc configuration to read properties from.
+ */
+ public void fromConfig(final Config rc) {
+ setPackedGitOpenFiles(rc.getInt("core", null, "packedgitopenfiles", getPackedGitOpenFiles()));
+ setPackedGitLimit(rc.getLong("core", null, "packedgitlimit", getPackedGitLimit()));
+ setPackedGitWindowSize(rc.getInt("core", null, "packedgitwindowsize", getPackedGitWindowSize()));
+ setPackedGitMMAP(rc.getBoolean("core", null, "packedgitmmap", isPackedGitMMAP()));
+ setDeltaBaseCacheLimit(rc.getInt("core", null, "deltabasecachelimit", getDeltaBaseCacheLimit()));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java
new file mode 100644
index 0000000000..fcf43adf62
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/** Active handle to a ByteWindow. */
+public final class WindowCursor {
+ /** Temporary buffer large enough for at least one raw object id. */
+ final byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH];
+
+ private Inflater inf;
+
+ private ByteWindow window;
+
+ /**
+ * Copy bytes from the window to a caller supplied buffer.
+ *
+ * @param pack
+ * the file the desired window is stored within.
+ * @param position
+ * position within the file to read from.
+ * @param dstbuf
+ * destination buffer to copy into.
+ * @param dstoff
+ * offset within <code>dstbuf</code> to start copying into.
+ * @param cnt
+ * number of bytes to copy. This value may exceed the number of
+ * bytes remaining in the window starting at offset
+ * <code>pos</code>.
+ * @return number of bytes actually copied; this may be less than
+ * <code>cnt</code> if <code>cnt</code> exceeded the number of
+ * bytes available.
+ * @throws IOException
+ * this cursor does not match the provider or id and the proper
+ * window could not be acquired through the provider's cache.
+ */
+ int copy(final PackFile pack, long position, final byte[] dstbuf,
+ int dstoff, final int cnt) throws IOException {
+ final long length = pack.length;
+ int need = cnt;
+ while (need > 0 && position < length) {
+ pin(pack, position);
+ final int r = window.copy(position, dstbuf, dstoff, need);
+ position += r;
+ dstoff += r;
+ need -= r;
+ }
+ return cnt - need;
+ }
+
+ /**
+ * Pump bytes into the supplied inflater as input.
+ *
+ * @param pack
+ * the file the desired window is stored within.
+ * @param position
+ * position within the file to read from.
+ * @param dstbuf
+ * destination buffer the inflater should output decompressed
+ * data to.
+ * @param dstoff
+ * current offset within <code>dstbuf</code> to inflate into.
+ * @return updated <code>dstoff</code> based on the number of bytes
+ * successfully inflated into <code>dstbuf</code>.
+ * @throws IOException
+ * this cursor does not match the provider or id and the proper
+ * window could not be acquired through the provider's cache.
+ * @throws DataFormatException
+ * the inflater encountered an invalid chunk of data. Data
+ * stream corruption is likely.
+ */
+ int inflate(final PackFile pack, long position, final byte[] dstbuf,
+ int dstoff) throws IOException, DataFormatException {
+ if (inf == null)
+ inf = InflaterCache.get();
+ else
+ inf.reset();
+ for (;;) {
+ pin(pack, position);
+ dstoff = window.inflate(position, dstbuf, dstoff, inf);
+ if (inf.finished())
+ return dstoff;
+ position = window.end;
+ }
+ }
+
+ void inflateVerify(final PackFile pack, long position)
+ throws IOException, DataFormatException {
+ if (inf == null)
+ inf = InflaterCache.get();
+ else
+ inf.reset();
+ for (;;) {
+ pin(pack, position);
+ window.inflateVerify(position, inf);
+ if (inf.finished())
+ return;
+ position = window.end;
+ }
+ }
+
+ private void pin(final PackFile pack, final long position)
+ throws IOException {
+ final ByteWindow w = window;
+ if (w == null || !w.contains(pack, position)) {
+ // If memory is low, we may need what is in our window field to
+ // be cleaned up by the GC during the get for the next window.
+ // So we always clear it, even though we are just going to set
+ // it again.
+ //
+ window = null;
+ window = WindowCache.get(pack, position);
+ }
+ }
+
+ /** Release the current window cursor. */
+ public void release() {
+ window = null;
+ try {
+ InflaterCache.release(inf);
+ } finally {
+ inf = null;
+ }
+ }
+
+ /**
+ * @param curs cursor to release; may be null.
+ * @return always null.
+ */
+ public static WindowCursor release(final WindowCursor curs) {
+ if (curs != null)
+ curs.release();
+ return null;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java
new file mode 100644
index 0000000000..75cc3bdc5c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
+ * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.eclipse.jgit.errors.CheckoutConflictException;
+import org.eclipse.jgit.lib.GitIndex.Entry;
+
+/**
+ * This class handles checking out one or two trees merging
+ * with the index (actually a tree too).
+ *
+ * Three-way merges are no performed. See {@link #setFailOnConflict(boolean)}.
+ */
+public class WorkDirCheckout {
+ Repository repo;
+
+ File root;
+
+ GitIndex index;
+
+ private boolean failOnConflict = true;
+
+ Tree merge;
+
+
+ /**
+ * If <code>true</code>, will scan first to see if it's possible to check out,
+ * otherwise throw {@link CheckoutConflictException}. If <code>false</code>,
+ * it will silently deal with the problem.
+ * @param failOnConflict
+ */
+ public void setFailOnConflict(boolean failOnConflict) {
+ this.failOnConflict = failOnConflict;
+ }
+
+ WorkDirCheckout(Repository repo, File workDir,
+ GitIndex oldIndex, GitIndex newIndex) throws IOException {
+ this.repo = repo;
+ this.root = workDir;
+ this.index = oldIndex;
+ this.merge = repo.mapTree(newIndex.writeTree());
+ }
+
+ /**
+ * Create a checkout class for checking out one tree, merging with the index
+ *
+ * @param repo
+ * @param root workdir
+ * @param index current index
+ * @param merge tree to check out
+ */
+ public WorkDirCheckout(Repository repo, File root,
+ GitIndex index, Tree merge) {
+ this.repo = repo;
+ this.root = root;
+ this.index = index;
+ this.merge = merge;
+ }
+
+ /**
+ * Create a checkout class for merging and checking our two trees and the index.
+ *
+ * @param repo
+ * @param root workdir
+ * @param head
+ * @param index
+ * @param merge
+ */
+ public WorkDirCheckout(Repository repo, File root, Tree head, GitIndex index, Tree merge) {
+ this(repo, root, index, merge);
+ this.head = head;
+ }
+
+ /**
+ * Execute this checkout
+ *
+ * @throws IOException
+ */
+ public void checkout() throws IOException {
+ if (head == null)
+ prescanOneTree();
+ else prescanTwoTrees();
+ if (!conflicts.isEmpty()) {
+ if (failOnConflict) {
+ String[] entries = conflicts.toArray(new String[0]);
+ throw new CheckoutConflictException(entries);
+ }
+ }
+
+ cleanUpConflicts();
+ if (head == null)
+ checkoutOutIndexNoHead();
+ else checkoutTwoTrees();
+ }
+
+ private void checkoutTwoTrees() throws FileNotFoundException, IOException {
+ for (String path : removed) {
+ index.remove(root, new File(root, path));
+ }
+
+ for (java.util.Map.Entry<String, ObjectId> entry : updated.entrySet()) {
+ Entry newEntry = index.addEntry(merge.findBlobMember(entry.getKey()));
+ index.checkoutEntry(root, newEntry);
+ }
+ }
+
+ ArrayList<String> conflicts = new ArrayList<String>();
+ ArrayList<String> removed = new ArrayList<String>();
+
+ Tree head = null;
+
+ HashMap<String, ObjectId> updated = new HashMap<String, ObjectId>();
+
+ private void checkoutOutIndexNoHead() throws IOException {
+ new IndexTreeWalker(index, merge, root, new AbstractIndexTreeVisitor() {
+ public void visitEntry(TreeEntry m, Entry i, File f) throws IOException {
+ if (m == null) {
+ index.remove(root, f);
+ return;
+ }
+
+ boolean needsCheckout = false;
+ if (i == null)
+ needsCheckout = true;
+ else if (i.getObjectId().equals(m.getId())) {
+ if (i.isModified(root, true))
+ needsCheckout = true;
+ } else needsCheckout = true;
+
+ if (needsCheckout) {
+ Entry newEntry = index.addEntry(m);
+ index.checkoutEntry(root, newEntry);
+ }
+ }
+ }).walk();
+ }
+
+ private void cleanUpConflicts() throws CheckoutConflictException {
+ for (String c : conflicts) {
+ File conflict = new File(root, c);
+ if (!conflict.delete())
+ throw new CheckoutConflictException("Cannot delete file: " + c);
+ removeEmptyParents(conflict);
+ }
+ for (String r : removed) {
+ File file = new File(root, r);
+ file.delete();
+ removeEmptyParents(file);
+ }
+ }
+
+ private void removeEmptyParents(File f) {
+ File parentFile = f.getParentFile();
+ while (!parentFile.equals(root)) {
+ if (parentFile.list().length == 0)
+ parentFile.delete();
+ else break;
+
+ parentFile = parentFile.getParentFile();
+ }
+ }
+
+ void prescanOneTree() throws IOException {
+ new IndexTreeWalker(index, merge, root, new AbstractIndexTreeVisitor() {
+ public void visitEntry(TreeEntry m, Entry i, File file) throws IOException {
+ if (m != null) {
+ if (!file.isFile()) {
+ checkConflictsWithFile(file);
+ }
+ } else {
+ if (file.exists()) {
+ removed.add(i.getName());
+ conflicts.remove(i.getName());
+ }
+ }
+ }
+ }).walk();
+ conflicts.removeAll(removed);
+ }
+
+ private ArrayList<String> listFiles(File file) {
+ ArrayList<String> list = new ArrayList<String>();
+ listFiles(file, list);
+ return list;
+ }
+
+ private void listFiles(File dir, ArrayList<String> list) {
+ for (File f : dir.listFiles()) {
+ if (f.isDirectory())
+ listFiles(f, list);
+ else {
+ list.add(Repository.stripWorkDir(root, f));
+ }
+ }
+ }
+
+ /**
+ * @return a list of conflicts created by this checkout
+ */
+ public ArrayList<String> getConflicts() {
+ return conflicts;
+ }
+
+ /**
+ * @return a list of all files removed by this checkout
+ */
+ public ArrayList<String> getRemoved() {
+ return removed;
+ }
+
+ void prescanTwoTrees() throws IOException {
+ new IndexTreeWalker(index, head, merge, root, new AbstractIndexTreeVisitor() {
+ public void visitEntry(TreeEntry treeEntry, TreeEntry auxEntry,
+ Entry indexEntry, File file) throws IOException {
+ if (treeEntry instanceof Tree || auxEntry instanceof Tree) {
+ throw new IllegalArgumentException("Can't pass me a tree!");
+ }
+ processEntry(treeEntry, auxEntry, indexEntry);
+ }
+
+ @Override
+ public void finishVisitTree(Tree tree, Tree auxTree, String curDir) throws IOException {
+ if (curDir.length() == 0) return;
+
+ if (auxTree != null) {
+ if (index.getEntry(curDir) != null)
+ removed.add(curDir);
+ }
+ }
+
+ }).walk();
+
+ // if there's a conflict, don't list it under
+ // to-be-removed, since that messed up our next
+ // section
+ removed.removeAll(conflicts);
+
+ for (String path : updated.keySet()) {
+ if (index.getEntry(path) == null) {
+ File file = new File(root, path);
+ if (file.isFile())
+ conflicts.add(path);
+ else if (file.isDirectory()) {
+ checkConflictsWithFile(file);
+ }
+ }
+ }
+
+
+ conflicts.removeAll(removed);
+ }
+
+ void processEntry(TreeEntry h, TreeEntry m, Entry i) throws IOException {
+ ObjectId iId = (i == null ? null : i.getObjectId());
+ ObjectId mId = (m == null ? null : m.getId());
+ ObjectId hId = (h == null ? null : h.getId());
+
+ String name = (i != null ? i.getName() :
+ (h != null ? h.getFullName() :
+ m.getFullName()));
+
+ if (i == null) {
+ /*
+ I (index) H M Result
+ -------------------------------------------------------
+ 0 nothing nothing nothing (does not happen)
+ 1 nothing nothing exists use M
+ 2 nothing exists nothing remove path from index
+ 3 nothing exists exists use M */
+
+ if (h == null) {
+ updated.put(name,mId);
+ } else if (m == null) {
+ removed.add(name);
+ } else {
+ updated.put(name, mId);
+ }
+ } else if (h == null) {
+ /*
+ clean I==H I==M H M Result
+ -----------------------------------------------------
+ 4 yes N/A N/A nothing nothing keep index
+ 5 no N/A N/A nothing nothing keep index
+
+ 6 yes N/A yes nothing exists keep index
+ 7 no N/A yes nothing exists keep index
+ 8 yes N/A no nothing exists fail
+ 9 no N/A no nothing exists fail */
+
+ if (m == null || mId.equals(iId)) {
+ if (hasParentBlob(merge, name)) {
+ if (i.isModified(root, true)) {
+ conflicts.add(name);
+ } else {
+ removed.add(name);
+ }
+ }
+ } else {
+ conflicts.add(name);
+ }
+ } else if (m == null) {
+ /*
+ 10 yes yes N/A exists nothing remove path from index
+ 11 no yes N/A exists nothing fail
+ 12 yes no N/A exists nothing fail
+ 13 no no N/A exists nothing fail
+ */
+
+ if (hId.equals(iId)) {
+ if (i.isModified(root, true)) {
+ conflicts.add(name);
+ } else {
+ removed.add(name);
+ }
+ } else {
+ conflicts.add(name);
+ }
+ } else {
+ if (!hId.equals(mId) && !hId.equals(iId)
+ && !mId.equals(iId)) {
+ conflicts.add(name);
+ } else if (hId.equals(iId) && !mId.equals(iId)) {
+ if (i.isModified(root, true))
+ conflicts.add(name);
+ else updated.put(name, mId);
+ }
+ }
+ }
+
+ private boolean hasParentBlob(Tree t, String name) throws IOException {
+ if (name.indexOf("/") == -1) return false;
+
+ String parent = name.substring(0, name.lastIndexOf("/"));
+ if (t.findBlobMember(parent) != null)
+ return true;
+ return hasParentBlob(t, parent);
+ }
+
+ private void checkConflictsWithFile(File file) {
+ if (file.isDirectory()) {
+ ArrayList<String> childFiles = listFiles(file);
+ conflicts.addAll(childFiles);
+ } else {
+ File parent = file.getParentFile();
+ while (!parent.equals(root)) {
+ if (parent.isDirectory())
+ break;
+ if (parent.isFile()) {
+ conflicts.add(Repository.stripWorkDir(root, parent));
+ break;
+ }
+ parent = parent.getParentFile();
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WriteTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WriteTree.java
new file mode 100644
index 0000000000..5bb4e535e0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WriteTree.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2009, Jonas Fonseca <fonseca@diku.dk>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.GitlinksNotSupportedException;
+import org.eclipse.jgit.errors.SymlinksNotSupportedException;
+
+/**
+ * A tree visitor for writing a directory tree to the git object database. Blob
+ * data is fetched from the files, not the cached blobs.
+ */
+public class WriteTree extends TreeVisitorWithCurrentDirectory {
+ private final ObjectWriter ow;
+
+ /**
+ * Construct a WriteTree for a given directory
+ *
+ * @param sourceDirectory
+ * @param db
+ */
+ public WriteTree(final File sourceDirectory, final Repository db) {
+ super(sourceDirectory);
+ ow = new ObjectWriter(db);
+ }
+
+ public void visitFile(final FileTreeEntry f) throws IOException {
+ f.setId(ow.writeBlob(new File(getCurrentDirectory(), f.getName())));
+ }
+
+ public void visitSymlink(final SymlinkTreeEntry s) throws IOException {
+ if (s.isModified()) {
+ throw new SymlinksNotSupportedException("Symlink \""
+ + s.getFullName()
+ + "\" cannot be written as the link target"
+ + " cannot be read from within Java.");
+ }
+ }
+
+ public void endVisitTree(final Tree t) throws IOException {
+ super.endVisitTree(t);
+ t.setId(ow.writeTree(t));
+ }
+
+ public void visitGitlink(GitlinkTreeEntry s) throws IOException {
+ if (s.isModified()) {
+ throw new GitlinksNotSupportedException(s.getFullName());
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java
new file mode 100644
index 0000000000..e7d84c68ac
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2009, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.merge;
+
+import java.util.HashMap;
+
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * A method of combining two or more trees together to form an output tree.
+ * <p>
+ * Different strategies may employ different techniques for deciding which paths
+ * (and ObjectIds) to carry from the input trees into the final output tree.
+ */
+public abstract class MergeStrategy {
+ /** Simple strategy that sets the output tree to the first input tree. */
+ public static final MergeStrategy OURS = new StrategyOneSided("ours", 0);
+
+ /** Simple strategy that sets the output tree to the second input tree. */
+ public static final MergeStrategy THEIRS = new StrategyOneSided("theirs", 1);
+
+ /** Simple strategy to merge paths, without simultaneous edits. */
+ public static final ThreeWayMergeStrategy SIMPLE_TWO_WAY_IN_CORE = new StrategySimpleTwoWayInCore();
+
+ private static final HashMap<String, MergeStrategy> STRATEGIES = new HashMap<String, MergeStrategy>();
+
+ static {
+ register(OURS);
+ register(THEIRS);
+ register(SIMPLE_TWO_WAY_IN_CORE);
+ }
+
+ /**
+ * Register a merge strategy so it can later be obtained by name.
+ *
+ * @param imp
+ * the strategy to register.
+ * @throws IllegalArgumentException
+ * a strategy by the same name has already been registered.
+ */
+ public static void register(final MergeStrategy imp) {
+ register(imp.getName(), imp);
+ }
+
+ /**
+ * Register a merge strategy so it can later be obtained by name.
+ *
+ * @param name
+ * name the strategy can be looked up under.
+ * @param imp
+ * the strategy to register.
+ * @throws IllegalArgumentException
+ * a strategy by the same name has already been registered.
+ */
+ public static synchronized void register(final String name,
+ final MergeStrategy imp) {
+ if (STRATEGIES.containsKey(name))
+ throw new IllegalArgumentException("Merge strategy \"" + name
+ + "\" already exists as a default strategy");
+ STRATEGIES.put(name, imp);
+ }
+
+ /**
+ * Locate a strategy by name.
+ *
+ * @param name
+ * name of the strategy to locate.
+ * @return the strategy instance; null if no strategy matches the name.
+ */
+ public static synchronized MergeStrategy get(final String name) {
+ return STRATEGIES.get(name);
+ }
+
+ /**
+ * Get all registered strategies.
+ *
+ * @return the registered strategy instances. No inherit order is returned;
+ * the caller may modify (and/or sort) the returned array if
+ * necessary to obtain a reasonable ordering.
+ */
+ public static synchronized MergeStrategy[] get() {
+ final MergeStrategy[] r = new MergeStrategy[STRATEGIES.size()];
+ STRATEGIES.values().toArray(r);
+ return r;
+ }
+
+ /** @return default name of this strategy implementation. */
+ public abstract String getName();
+
+ /**
+ * Create a new merge instance.
+ *
+ * @param db
+ * repository database the merger will read from, and eventually
+ * write results back to.
+ * @return the new merge instance which implements this strategy.
+ */
+ public abstract Merger newMerger(Repository db);
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
new file mode 100644
index 0000000000..275a6d68ff
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.merge;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.EmptyTreeIterator;
+
+/**
+ * Instance of a specific {@link MergeStrategy} for a single {@link Repository}.
+ */
+public abstract class Merger {
+ /** The repository this merger operates on. */
+ protected final Repository db;
+
+ /** A RevWalk for computing merge bases, or listing incoming commits. */
+ protected final RevWalk walk;
+
+ private ObjectWriter writer;
+
+ /** The original objects supplied in the merge; this can be any tree-ish. */
+ protected RevObject[] sourceObjects;
+
+ /** If {@link #sourceObjects}[i] is a commit, this is the commit. */
+ protected RevCommit[] sourceCommits;
+
+ /** The trees matching every entry in {@link #sourceObjects}. */
+ protected RevTree[] sourceTrees;
+
+ /**
+ * Create a new merge instance for a repository.
+ *
+ * @param local
+ * the repository this merger will read and write data on.
+ */
+ protected Merger(final Repository local) {
+ db = local;
+ walk = new RevWalk(db);
+ }
+
+ /**
+ * @return the repository this merger operates on.
+ */
+ public Repository getRepository() {
+ return db;
+ }
+
+ /**
+ * @return an object writer to create objects in {@link #getRepository()}.
+ */
+ public ObjectWriter getObjectWriter() {
+ if (writer == null)
+ writer = new ObjectWriter(getRepository());
+ return writer;
+ }
+
+ /**
+ * Merge together two or more tree-ish objects.
+ * <p>
+ * Any tree-ish may be supplied as inputs. Commits and/or tags pointing at
+ * trees or commits may be passed as input objects.
+ *
+ * @param tips
+ * source trees to be combined together. The merge base is not
+ * included in this set.
+ * @return true if the merge was completed without conflicts; false if the
+ * merge strategy cannot handle this merge or there were conflicts
+ * preventing it from automatically resolving all paths.
+ * @throws IncorrectObjectTypeException
+ * one of the input objects is not a commit, but the strategy
+ * requires it to be a commit.
+ * @throws IOException
+ * one or more sources could not be read, or outputs could not
+ * be written to the Repository.
+ */
+ public boolean merge(final AnyObjectId[] tips) throws IOException {
+ sourceObjects = new RevObject[tips.length];
+ for (int i = 0; i < tips.length; i++)
+ sourceObjects[i] = walk.parseAny(tips[i]);
+
+ sourceCommits = new RevCommit[sourceObjects.length];
+ for (int i = 0; i < sourceObjects.length; i++) {
+ try {
+ sourceCommits[i] = walk.parseCommit(sourceObjects[i]);
+ } catch (IncorrectObjectTypeException err) {
+ sourceCommits[i] = null;
+ }
+ }
+
+ sourceTrees = new RevTree[sourceObjects.length];
+ for (int i = 0; i < sourceObjects.length; i++)
+ sourceTrees[i] = walk.parseTree(sourceObjects[i]);
+
+ return mergeImpl();
+ }
+
+ /**
+ * Create an iterator to walk the merge base of two commits.
+ *
+ * @param aIdx
+ * index of the first commit in {@link #sourceObjects}.
+ * @param bIdx
+ * index of the second commit in {@link #sourceObjects}.
+ * @return the new iterator
+ * @throws IncorrectObjectTypeException
+ * one of the input objects is not a commit.
+ * @throws IOException
+ * objects are missing or multiple merge bases were found.
+ */
+ protected AbstractTreeIterator mergeBase(final int aIdx, final int bIdx)
+ throws IOException {
+ if (sourceCommits[aIdx] == null)
+ throw new IncorrectObjectTypeException(sourceObjects[aIdx],
+ Constants.TYPE_COMMIT);
+ if (sourceCommits[bIdx] == null)
+ throw new IncorrectObjectTypeException(sourceObjects[bIdx],
+ Constants.TYPE_COMMIT);
+
+ walk.reset();
+ walk.setRevFilter(RevFilter.MERGE_BASE);
+ walk.markStart(sourceCommits[aIdx]);
+ walk.markStart(sourceCommits[bIdx]);
+ final RevCommit base = walk.next();
+ if (base == null)
+ return new EmptyTreeIterator();
+ final RevCommit base2 = walk.next();
+ if (base2 != null) {
+ throw new IOException("Multiple merge bases for:" + "\n "
+ + sourceCommits[aIdx].name() + "\n "
+ + sourceCommits[bIdx].name() + "found:" + "\n "
+ + base.name() + "\n " + base2.name());
+ }
+ return openTree(base.getTree());
+ }
+
+ /**
+ * Open an iterator over a tree.
+ *
+ * @param treeId
+ * the tree to scan; must be a tree (not a treeish).
+ * @return an iterator for the tree.
+ * @throws IncorrectObjectTypeException
+ * the input object is not a tree.
+ * @throws IOException
+ * the tree object is not found or cannot be read.
+ */
+ protected AbstractTreeIterator openTree(final AnyObjectId treeId)
+ throws IncorrectObjectTypeException, IOException {
+ final WindowCursor curs = new WindowCursor();
+ try {
+ return new CanonicalTreeParser(null, db, treeId, curs);
+ } finally {
+ curs.release();
+ }
+ }
+
+ /**
+ * Execute the merge.
+ * <p>
+ * This method is called from {@link #merge(AnyObjectId[])} after the
+ * {@link #sourceObjects}, {@link #sourceCommits} and {@link #sourceTrees}
+ * have been populated.
+ *
+ * @return true if the merge was completed without conflicts; false if the
+ * merge strategy cannot handle this merge or there were conflicts
+ * preventing it from automatically resolving all paths.
+ * @throws IncorrectObjectTypeException
+ * one of the input objects is not a commit, but the strategy
+ * requires it to be a commit.
+ * @throws IOException
+ * one or more sources could not be read, or outputs could not
+ * be written to the Repository.
+ */
+ protected abstract boolean mergeImpl() throws IOException;
+
+ /**
+ * @return resulting tree, if {@link #merge(AnyObjectId[])} returned true.
+ */
+ public abstract ObjectId getResultTreeId();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java
new file mode 100644
index 0000000000..c941af9482
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.merge;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Trivial merge strategy to make the resulting tree exactly match an input.
+ * <p>
+ * This strategy can be used to cauterize an entire side branch of history, by
+ * setting the output tree to one of the inputs, and ignoring any of the paths
+ * of the other inputs.
+ */
+public class StrategyOneSided extends MergeStrategy {
+ private final String strategyName;
+
+ private final int treeIndex;
+
+ /**
+ * Create a new merge strategy to select a specific input tree.
+ *
+ * @param name
+ * name of this strategy.
+ * @param index
+ * the position of the input tree to accept as the result.
+ */
+ protected StrategyOneSided(final String name, final int index) {
+ strategyName = name;
+ treeIndex = index;
+ }
+
+ @Override
+ public String getName() {
+ return strategyName;
+ }
+
+ @Override
+ public Merger newMerger(final Repository db) {
+ return new OneSide(db, treeIndex);
+ }
+
+ static class OneSide extends Merger {
+ private final int treeIndex;
+
+ protected OneSide(final Repository local, final int index) {
+ super(local);
+ treeIndex = index;
+ }
+
+ @Override
+ protected boolean mergeImpl() throws IOException {
+ return treeIndex < sourceTrees.length;
+ }
+
+ @Override
+ public ObjectId getResultTreeId() {
+ return sourceTrees[treeIndex];
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
new file mode 100644
index 0000000000..6cd244599e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.merge;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.UnmergedPathException;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
+
+/**
+ * Merges two commits together in-memory, ignoring any working directory.
+ * <p>
+ * The strategy chooses a path from one of the two input trees if the path is
+ * unchanged in the other relative to their common merge base tree. This is a
+ * trivial 3-way merge (at the file path level only).
+ * <p>
+ * Modifications of the same file path (content and/or file mode) by both input
+ * trees will cause a merge conflict, as this strategy does not attempt to merge
+ * file contents.
+ */
+public class StrategySimpleTwoWayInCore extends ThreeWayMergeStrategy {
+ /** Create a new instance of the strategy. */
+ protected StrategySimpleTwoWayInCore() {
+ //
+ }
+
+ @Override
+ public String getName() {
+ return "simple-two-way-in-core";
+ }
+
+ @Override
+ public ThreeWayMerger newMerger(final Repository db) {
+ return new InCoreMerger(db);
+ }
+
+ private static class InCoreMerger extends ThreeWayMerger {
+ private static final int T_BASE = 0;
+
+ private static final int T_OURS = 1;
+
+ private static final int T_THEIRS = 2;
+
+ private final NameConflictTreeWalk tw;
+
+ private final DirCache cache;
+
+ private DirCacheBuilder builder;
+
+ private ObjectId resultTree;
+
+ InCoreMerger(final Repository local) {
+ super(local);
+ tw = new NameConflictTreeWalk(db);
+ cache = DirCache.newInCore();
+ }
+
+ @Override
+ protected boolean mergeImpl() throws IOException {
+ tw.reset();
+ tw.addTree(mergeBase());
+ tw.addTree(sourceTrees[0]);
+ tw.addTree(sourceTrees[1]);
+
+ boolean hasConflict = false;
+ builder = cache.builder();
+ while (tw.next()) {
+ final int modeO = tw.getRawMode(T_OURS);
+ final int modeT = tw.getRawMode(T_THEIRS);
+ if (modeO == modeT && tw.idEqual(T_OURS, T_THEIRS)) {
+ add(T_OURS, DirCacheEntry.STAGE_0);
+ continue;
+ }
+
+ final int modeB = tw.getRawMode(T_BASE);
+ if (modeB == modeO && tw.idEqual(T_BASE, T_OURS))
+ add(T_THEIRS, DirCacheEntry.STAGE_0);
+ else if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS))
+ add(T_OURS, DirCacheEntry.STAGE_0);
+ else if (tw.isSubtree()) {
+ if (nonTree(modeB)) {
+ add(T_BASE, DirCacheEntry.STAGE_1);
+ hasConflict = true;
+ }
+ if (nonTree(modeO)) {
+ add(T_OURS, DirCacheEntry.STAGE_2);
+ hasConflict = true;
+ }
+ if (nonTree(modeT)) {
+ add(T_THEIRS, DirCacheEntry.STAGE_3);
+ hasConflict = true;
+ }
+ tw.enterSubtree();
+ } else {
+ add(T_BASE, DirCacheEntry.STAGE_1);
+ add(T_OURS, DirCacheEntry.STAGE_2);
+ add(T_THEIRS, DirCacheEntry.STAGE_3);
+ hasConflict = true;
+ }
+ }
+ builder.finish();
+ builder = null;
+
+ if (hasConflict)
+ return false;
+ try {
+ resultTree = cache.writeTree(getObjectWriter());
+ return true;
+ } catch (UnmergedPathException upe) {
+ resultTree = null;
+ return false;
+ }
+ }
+
+ private static boolean nonTree(final int mode) {
+ return mode != 0 && !FileMode.TREE.equals(mode);
+ }
+
+ private void add(final int tree, final int stage) throws IOException {
+ final AbstractTreeIterator i = getTree(tree);
+ if (i != null) {
+ if (FileMode.TREE.equals(tw.getRawMode(tree))) {
+ builder.addTree(tw.getRawPath(), stage, db, tw
+ .getObjectId(tree));
+ } else {
+ final DirCacheEntry e;
+
+ e = new DirCacheEntry(tw.getRawPath(), stage);
+ e.setObjectIdFromRaw(i.idBuffer(), i.idOffset());
+ e.setFileMode(tw.getFileMode(tree));
+ builder.add(e);
+ }
+ }
+ }
+
+ private AbstractTreeIterator getTree(final int tree) {
+ return tw.getTree(tree, AbstractTreeIterator.class);
+ }
+
+ @Override
+ public ObjectId getResultTreeId() {
+ return resultTree;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java
new file mode 100644
index 0000000000..343d8973e9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.merge;
+
+import org.eclipse.jgit.lib.Repository;
+
+/** A merge strategy to merge 2 trees, using a common base ancestor tree. */
+public abstract class ThreeWayMergeStrategy extends MergeStrategy {
+ @Override
+ public abstract ThreeWayMerger newMerger(Repository db);
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
new file mode 100644
index 0000000000..bb23d0ee87
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.merge;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+
+/** A merge of 2 trees, using a common base ancestor tree. */
+public abstract class ThreeWayMerger extends Merger {
+ private RevTree baseTree;
+
+ /**
+ * Create a new merge instance for a repository.
+ *
+ * @param local
+ * the repository this merger will read and write data on.
+ */
+ protected ThreeWayMerger(final Repository local) {
+ super(local);
+ }
+
+ /**
+ * Set the common ancestor tree.
+ *
+ * @param id
+ * common base treeish; null to automatically compute the common
+ * base from the input commits during
+ * {@link #merge(AnyObjectId, AnyObjectId)}.
+ * @throws IncorrectObjectTypeException
+ * the object is not a treeish.
+ * @throws MissingObjectException
+ * the object does not exist.
+ * @throws IOException
+ * the object could not be read.
+ */
+ public void setBase(final AnyObjectId id) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ if (id != null) {
+ baseTree = walk.parseTree(id);
+ } else {
+ baseTree = null;
+ }
+ }
+
+ /**
+ * Merge together two tree-ish objects.
+ * <p>
+ * Any tree-ish may be supplied as inputs. Commits and/or tags pointing at
+ * trees or commits may be passed as input objects.
+ *
+ * @param a
+ * source tree to be combined together.
+ * @param b
+ * source tree to be combined together.
+ * @return true if the merge was completed without conflicts; false if the
+ * merge strategy cannot handle this merge or there were conflicts
+ * preventing it from automatically resolving all paths.
+ * @throws IncorrectObjectTypeException
+ * one of the input objects is not a commit, but the strategy
+ * requires it to be a commit.
+ * @throws IOException
+ * one or more sources could not be read, or outputs could not
+ * be written to the Repository.
+ */
+ public boolean merge(final AnyObjectId a, final AnyObjectId b)
+ throws IOException {
+ return merge(new AnyObjectId[] { a, b });
+ }
+
+ @Override
+ public boolean merge(final AnyObjectId[] tips) throws IOException {
+ if (tips.length != 2)
+ return false;
+ return super.merge(tips);
+ }
+
+ /**
+ * Create an iterator to walk the merge base.
+ *
+ * @return an iterator over the caller-specified merge base, or the natural
+ * merge base of the two input commits.
+ * @throws IOException
+ */
+ protected AbstractTreeIterator mergeBase() throws IOException {
+ if (baseTree != null)
+ return openTree(baseTree);
+ return mergeBase(0, 1);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java
new file mode 100644
index 0000000000..340b67456e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.patch;
+
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+import static org.eclipse.jgit.util.RawParseUtils.match;
+import static org.eclipse.jgit.util.RawParseUtils.nextLF;
+import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
+
+/** Part of a "GIT binary patch" to describe the pre-image or post-image */
+public class BinaryHunk {
+ private static final byte[] LITERAL = encodeASCII("literal ");
+
+ private static final byte[] DELTA = encodeASCII("delta ");
+
+ /** Type of information stored in a binary hunk. */
+ public static enum Type {
+ /** The full content is stored, deflated. */
+ LITERAL_DEFLATED,
+
+ /** A Git pack-style delta is stored, deflated. */
+ DELTA_DEFLATED;
+ }
+
+ private final FileHeader file;
+
+ /** Offset within {@link #file}.buf to the "literal" or "delta " line. */
+ final int startOffset;
+
+ /** Position 1 past the end of this hunk within {@link #file}'s buf. */
+ int endOffset;
+
+ /** Type of the data meaning. */
+ private Type type;
+
+ /** Inflated length of the data. */
+ private int length;
+
+ BinaryHunk(final FileHeader fh, final int offset) {
+ file = fh;
+ startOffset = offset;
+ }
+
+ /** @return header for the file this hunk applies to */
+ public FileHeader getFileHeader() {
+ return file;
+ }
+
+ /** @return the byte array holding this hunk's patch script. */
+ public byte[] getBuffer() {
+ return file.buf;
+ }
+
+ /** @return offset the start of this hunk in {@link #getBuffer()}. */
+ public int getStartOffset() {
+ return startOffset;
+ }
+
+ /** @return offset one past the end of the hunk in {@link #getBuffer()}. */
+ public int getEndOffset() {
+ return endOffset;
+ }
+
+ /** @return type of this binary hunk */
+ public Type getType() {
+ return type;
+ }
+
+ /** @return inflated size of this hunk's data */
+ public int getSize() {
+ return length;
+ }
+
+ int parseHunk(int ptr, final int end) {
+ final byte[] buf = file.buf;
+
+ if (match(buf, ptr, LITERAL) >= 0) {
+ type = Type.LITERAL_DEFLATED;
+ length = parseBase10(buf, ptr + LITERAL.length, null);
+
+ } else if (match(buf, ptr, DELTA) >= 0) {
+ type = Type.DELTA_DEFLATED;
+ length = parseBase10(buf, ptr + DELTA.length, null);
+
+ } else {
+ // Not a valid binary hunk. Signal to the caller that
+ // we cannot parse any further and that this line should
+ // be treated otherwise.
+ //
+ return -1;
+ }
+ ptr = nextLF(buf, ptr);
+
+ // Skip until the first blank line; that is the end of the binary
+ // encoded information in this hunk. To save time we don't do a
+ // validation of the binary data at this point.
+ //
+ while (ptr < end) {
+ final boolean empty = buf[ptr] == '\n';
+ ptr = nextLF(buf, ptr);
+ if (empty)
+ break;
+ }
+
+ return ptr;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java
new file mode 100644
index 0000000000..e95c026ddc
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.patch;
+
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+import static org.eclipse.jgit.util.RawParseUtils.match;
+import static org.eclipse.jgit.util.RawParseUtils.nextLF;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.FileMode;
+
+/**
+ * A file in the Git "diff --cc" or "diff --combined" format.
+ * <p>
+ * A combined diff shows an n-way comparison between two or more ancestors and
+ * the final revision. Its primary function is to perform code reviews on a
+ * merge which introduces changes not in any ancestor.
+ */
+public class CombinedFileHeader extends FileHeader {
+ private static final byte[] MODE = encodeASCII("mode ");
+
+ private AbbreviatedObjectId[] oldIds;
+
+ private FileMode[] oldModes;
+
+ CombinedFileHeader(final byte[] b, final int offset) {
+ super(b, offset);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List<? extends CombinedHunkHeader> getHunks() {
+ return (List<CombinedHunkHeader>) super.getHunks();
+ }
+
+ /** @return number of ancestor revisions mentioned in this diff. */
+ @Override
+ public int getParentCount() {
+ return oldIds.length;
+ }
+
+ /** @return get the file mode of the first parent. */
+ @Override
+ public FileMode getOldMode() {
+ return getOldMode(0);
+ }
+
+ /**
+ * Get the file mode of the nth ancestor
+ *
+ * @param nthParent
+ * the ancestor to get the mode of
+ * @return the mode of the requested ancestor.
+ */
+ public FileMode getOldMode(final int nthParent) {
+ return oldModes[nthParent];
+ }
+
+ /** @return get the object id of the first parent. */
+ @Override
+ public AbbreviatedObjectId getOldId() {
+ return getOldId(0);
+ }
+
+ /**
+ * Get the ObjectId of the nth ancestor
+ *
+ * @param nthParent
+ * the ancestor to get the object id of
+ * @return the id of the requested ancestor.
+ */
+ public AbbreviatedObjectId getOldId(final int nthParent) {
+ return oldIds[nthParent];
+ }
+
+ @Override
+ public String getScriptText(final Charset ocs, final Charset ncs) {
+ final Charset[] cs = new Charset[getParentCount() + 1];
+ Arrays.fill(cs, ocs);
+ cs[getParentCount()] = ncs;
+ return getScriptText(cs);
+ }
+
+ /**
+ * Convert the patch script for this file into a string.
+ *
+ * @param charsetGuess
+ * optional array to suggest the character set to use when
+ * decoding each file's line. If supplied the array must have a
+ * length of <code>{@link #getParentCount()} + 1</code>
+ * representing the old revision character sets and the new
+ * revision character set.
+ * @return the patch script, as a Unicode string.
+ */
+ @Override
+ public String getScriptText(final Charset[] charsetGuess) {
+ return super.getScriptText(charsetGuess);
+ }
+
+ @Override
+ int parseGitHeaders(int ptr, final int end) {
+ while (ptr < end) {
+ final int eol = nextLF(buf, ptr);
+ if (isHunkHdr(buf, ptr, end) >= 1) {
+ // First hunk header; break out and parse them later.
+ break;
+
+ } else if (match(buf, ptr, OLD_NAME) >= 0) {
+ parseOldName(ptr, eol);
+
+ } else if (match(buf, ptr, NEW_NAME) >= 0) {
+ parseNewName(ptr, eol);
+
+ } else if (match(buf, ptr, INDEX) >= 0) {
+ parseIndexLine(ptr + INDEX.length, eol);
+
+ } else if (match(buf, ptr, MODE) >= 0) {
+ parseModeLine(ptr + MODE.length, eol);
+
+ } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) {
+ parseNewFileMode(ptr, eol);
+
+ } else if (match(buf, ptr, DELETED_FILE_MODE) >= 0) {
+ parseDeletedFileMode(ptr + DELETED_FILE_MODE.length, eol);
+
+ } else {
+ // Probably an empty patch (stat dirty).
+ break;
+ }
+
+ ptr = eol;
+ }
+ return ptr;
+ }
+
+ @Override
+ protected void parseIndexLine(int ptr, final int eol) {
+ // "index $asha1,$bsha1..$csha1"
+ //
+ final List<AbbreviatedObjectId> ids = new ArrayList<AbbreviatedObjectId>();
+ while (ptr < eol) {
+ final int comma = nextLF(buf, ptr, ',');
+ if (eol <= comma)
+ break;
+ ids.add(AbbreviatedObjectId.fromString(buf, ptr, comma - 1));
+ ptr = comma;
+ }
+
+ oldIds = new AbbreviatedObjectId[ids.size() + 1];
+ ids.toArray(oldIds);
+ final int dot2 = nextLF(buf, ptr, '.');
+ oldIds[ids.size()] = AbbreviatedObjectId.fromString(buf, ptr, dot2 - 1);
+ newId = AbbreviatedObjectId.fromString(buf, dot2 + 1, eol - 1);
+ oldModes = new FileMode[oldIds.length];
+ }
+
+ @Override
+ protected void parseNewFileMode(final int ptr, final int eol) {
+ for (int i = 0; i < oldModes.length; i++)
+ oldModes[i] = FileMode.MISSING;
+ super.parseNewFileMode(ptr, eol);
+ }
+
+ @Override
+ HunkHeader newHunkHeader(final int offset) {
+ return new CombinedHunkHeader(this, offset);
+ }
+
+ private void parseModeLine(int ptr, final int eol) {
+ // "mode $amode,$bmode..$cmode"
+ //
+ int n = 0;
+ while (ptr < eol) {
+ final int comma = nextLF(buf, ptr, ',');
+ if (eol <= comma)
+ break;
+ oldModes[n++] = parseFileMode(ptr, comma);
+ ptr = comma;
+ }
+ final int dot2 = nextLF(buf, ptr, '.');
+ oldModes[n] = parseFileMode(ptr, dot2);
+ newMode = parseFileMode(dot2 + 1, eol);
+ }
+
+ private void parseDeletedFileMode(int ptr, final int eol) {
+ // "deleted file mode $amode,$bmode"
+ //
+ changeType = ChangeType.DELETE;
+ int n = 0;
+ while (ptr < eol) {
+ final int comma = nextLF(buf, ptr, ',');
+ if (eol <= comma)
+ break;
+ oldModes[n++] = parseFileMode(ptr, comma);
+ ptr = comma;
+ }
+ oldModes[n] = parseFileMode(ptr, eol);
+ newMode = FileMode.MISSING;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
new file mode 100644
index 0000000000..781190539f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.patch;
+
+import static org.eclipse.jgit.util.RawParseUtils.nextLF;
+import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.util.MutableInteger;
+
+/** Hunk header for a hunk appearing in a "diff --cc" style patch. */
+public class CombinedHunkHeader extends HunkHeader {
+ private static abstract class CombinedOldImage extends OldImage {
+ int nContext;
+ }
+
+ private CombinedOldImage[] old;
+
+ CombinedHunkHeader(final CombinedFileHeader fh, final int offset) {
+ super(fh, offset, null);
+ old = new CombinedOldImage[fh.getParentCount()];
+ for (int i = 0; i < old.length; i++) {
+ final int imagePos = i;
+ old[i] = new CombinedOldImage() {
+ @Override
+ public AbbreviatedObjectId getId() {
+ return fh.getOldId(imagePos);
+ }
+ };
+ }
+ }
+
+ @Override
+ public CombinedFileHeader getFileHeader() {
+ return (CombinedFileHeader) super.getFileHeader();
+ }
+
+ @Override
+ public OldImage getOldImage() {
+ return getOldImage(0);
+ }
+
+ /**
+ * Get the OldImage data related to the nth ancestor
+ *
+ * @param nthParent
+ * the ancestor to get the old image data of
+ * @return image data of the requested ancestor.
+ */
+ public OldImage getOldImage(final int nthParent) {
+ return old[nthParent];
+ }
+
+ @Override
+ void parseHeader() {
+ // Parse "@@@ -55,12 -163,13 +163,15 @@@ protected boolean"
+ //
+ final byte[] buf = file.buf;
+ final MutableInteger ptr = new MutableInteger();
+ ptr.value = nextLF(buf, startOffset, ' ');
+
+ for (int n = 0; n < old.length; n++) {
+ old[n].startLine = -parseBase10(buf, ptr.value, ptr);
+ if (buf[ptr.value] == ',')
+ old[n].lineCount = parseBase10(buf, ptr.value + 1, ptr);
+ else
+ old[n].lineCount = 1;
+ }
+
+ newStartLine = parseBase10(buf, ptr.value + 1, ptr);
+ if (buf[ptr.value] == ',')
+ newLineCount = parseBase10(buf, ptr.value + 1, ptr);
+ else
+ newLineCount = 1;
+ }
+
+ @Override
+ int parseBody(final Patch script, final int end) {
+ final byte[] buf = file.buf;
+ int c = nextLF(buf, startOffset);
+
+ for (final CombinedOldImage o : old) {
+ o.nDeleted = 0;
+ o.nAdded = 0;
+ o.nContext = 0;
+ }
+ nContext = 0;
+ int nAdded = 0;
+
+ SCAN: for (int eol; c < end; c = eol) {
+ eol = nextLF(buf, c);
+
+ if (eol - c < old.length + 1) {
+ // Line isn't long enough to mention the state of each
+ // ancestor. It must be the end of the hunk.
+ break SCAN;
+ }
+
+ switch (buf[c]) {
+ case ' ':
+ case '-':
+ case '+':
+ break;
+
+ default:
+ // Line can't possibly be part of this hunk; the first
+ // ancestor information isn't recognizable.
+ //
+ break SCAN;
+ }
+
+ int localcontext = 0;
+ for (int ancestor = 0; ancestor < old.length; ancestor++) {
+ switch (buf[c + ancestor]) {
+ case ' ':
+ localcontext++;
+ old[ancestor].nContext++;
+ continue;
+
+ case '-':
+ old[ancestor].nDeleted++;
+ continue;
+
+ case '+':
+ old[ancestor].nAdded++;
+ nAdded++;
+ continue;
+
+ default:
+ break SCAN;
+ }
+ }
+ if (localcontext == old.length)
+ nContext++;
+ }
+
+ for (int ancestor = 0; ancestor < old.length; ancestor++) {
+ final CombinedOldImage o = old[ancestor];
+ final int cmp = o.nContext + o.nDeleted;
+ if (cmp < o.lineCount) {
+ final int missingCnt = o.lineCount - cmp;
+ script.error(buf, startOffset, "Truncated hunk, at least "
+ + missingCnt + " lines is missing for ancestor "
+ + (ancestor + 1));
+ }
+ }
+
+ if (nContext + nAdded < newLineCount) {
+ final int missingCount = newLineCount - (nContext + nAdded);
+ script.error(buf, startOffset, "Truncated hunk, at least "
+ + missingCount + " new lines is missing");
+ }
+
+ return c;
+ }
+
+ @Override
+ void extractFileLines(final OutputStream[] out) throws IOException {
+ final byte[] buf = file.buf;
+ int ptr = startOffset;
+ int eol = nextLF(buf, ptr);
+ if (endOffset <= eol)
+ return;
+
+ // Treat the hunk header as though it were from the ancestor,
+ // as it may have a function header appearing after it which
+ // was copied out of the ancestor file.
+ //
+ out[0].write(buf, ptr, eol - ptr);
+
+ SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
+ eol = nextLF(buf, ptr);
+
+ if (eol - ptr < old.length + 1) {
+ // Line isn't long enough to mention the state of each
+ // ancestor. It must be the end of the hunk.
+ break SCAN;
+ }
+
+ switch (buf[ptr]) {
+ case ' ':
+ case '-':
+ case '+':
+ break;
+
+ default:
+ // Line can't possibly be part of this hunk; the first
+ // ancestor information isn't recognizable.
+ //
+ break SCAN;
+ }
+
+ int delcnt = 0;
+ for (int ancestor = 0; ancestor < old.length; ancestor++) {
+ switch (buf[ptr + ancestor]) {
+ case '-':
+ delcnt++;
+ out[ancestor].write(buf, ptr, eol - ptr);
+ continue;
+
+ case ' ':
+ out[ancestor].write(buf, ptr, eol - ptr);
+ continue;
+
+ case '+':
+ continue;
+
+ default:
+ break SCAN;
+ }
+ }
+ if (delcnt < old.length) {
+ // This line appears in the new file if it wasn't deleted
+ // relative to all ancestors.
+ //
+ out[old.length].write(buf, ptr, eol - ptr);
+ }
+ }
+ }
+
+ void extractFileLines(final StringBuilder sb, final String[] text,
+ final int[] offsets) {
+ final byte[] buf = file.buf;
+ int ptr = startOffset;
+ int eol = nextLF(buf, ptr);
+ if (endOffset <= eol)
+ return;
+ copyLine(sb, text, offsets, 0);
+ SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
+ eol = nextLF(buf, ptr);
+
+ if (eol - ptr < old.length + 1) {
+ // Line isn't long enough to mention the state of each
+ // ancestor. It must be the end of the hunk.
+ break SCAN;
+ }
+
+ switch (buf[ptr]) {
+ case ' ':
+ case '-':
+ case '+':
+ break;
+
+ default:
+ // Line can't possibly be part of this hunk; the first
+ // ancestor information isn't recognizable.
+ //
+ break SCAN;
+ }
+
+ boolean copied = false;
+ for (int ancestor = 0; ancestor < old.length; ancestor++) {
+ switch (buf[ptr + ancestor]) {
+ case ' ':
+ case '-':
+ if (copied)
+ skipLine(text, offsets, ancestor);
+ else {
+ copyLine(sb, text, offsets, ancestor);
+ copied = true;
+ }
+ continue;
+
+ case '+':
+ continue;
+
+ default:
+ break SCAN;
+ }
+ }
+ if (!copied) {
+ // If none of the ancestors caused the copy then this line
+ // must be new across the board, so it only appears in the
+ // text of the new file.
+ //
+ copyLine(sb, text, offsets, old.length);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
new file mode 100644
index 0000000000..dece17e4c8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
@@ -0,0 +1,714 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.patch;
+
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+import static org.eclipse.jgit.util.RawParseUtils.decode;
+import static org.eclipse.jgit.util.RawParseUtils.decodeNoFallback;
+import static org.eclipse.jgit.util.RawParseUtils.extractBinaryString;
+import static org.eclipse.jgit.util.RawParseUtils.match;
+import static org.eclipse.jgit.util.RawParseUtils.nextLF;
+import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
+
+import java.io.IOException;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.diff.EditList;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.util.QuotedString;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
+
+/** Patch header describing an action for a single file path. */
+public class FileHeader {
+ /** Magical file name used for file adds or deletes. */
+ public static final String DEV_NULL = "/dev/null";
+
+ private static final byte[] OLD_MODE = encodeASCII("old mode ");
+
+ private static final byte[] NEW_MODE = encodeASCII("new mode ");
+
+ static final byte[] DELETED_FILE_MODE = encodeASCII("deleted file mode ");
+
+ static final byte[] NEW_FILE_MODE = encodeASCII("new file mode ");
+
+ private static final byte[] COPY_FROM = encodeASCII("copy from ");
+
+ private static final byte[] COPY_TO = encodeASCII("copy to ");
+
+ private static final byte[] RENAME_OLD = encodeASCII("rename old ");
+
+ private static final byte[] RENAME_NEW = encodeASCII("rename new ");
+
+ private static final byte[] RENAME_FROM = encodeASCII("rename from ");
+
+ private static final byte[] RENAME_TO = encodeASCII("rename to ");
+
+ private static final byte[] SIMILARITY_INDEX = encodeASCII("similarity index ");
+
+ private static final byte[] DISSIMILARITY_INDEX = encodeASCII("dissimilarity index ");
+
+ static final byte[] INDEX = encodeASCII("index ");
+
+ static final byte[] OLD_NAME = encodeASCII("--- ");
+
+ static final byte[] NEW_NAME = encodeASCII("+++ ");
+
+ /** General type of change a single file-level patch describes. */
+ public static enum ChangeType {
+ /** Add a new file to the project */
+ ADD,
+
+ /** Modify an existing file in the project (content and/or mode) */
+ MODIFY,
+
+ /** Delete an existing file from the project */
+ DELETE,
+
+ /** Rename an existing file to a new location */
+ RENAME,
+
+ /** Copy an existing file to a new location, keeping the original */
+ COPY;
+ }
+
+ /** Type of patch used by this file. */
+ public static enum PatchType {
+ /** A traditional unified diff style patch of a text file. */
+ UNIFIED,
+
+ /** An empty patch with a message "Binary files ... differ" */
+ BINARY,
+
+ /** A Git binary patch, holding pre and post image deltas */
+ GIT_BINARY;
+ }
+
+ /** Buffer holding the patch data for this file. */
+ final byte[] buf;
+
+ /** Offset within {@link #buf} to the "diff ..." line. */
+ final int startOffset;
+
+ /** Position 1 past the end of this file within {@link #buf}. */
+ int endOffset;
+
+ /** File name of the old (pre-image). */
+ private String oldName;
+
+ /** File name of the new (post-image). */
+ private String newName;
+
+ /** Old mode of the file, if described by the patch, else null. */
+ private FileMode oldMode;
+
+ /** New mode of the file, if described by the patch, else null. */
+ protected FileMode newMode;
+
+ /** General type of change indicated by the patch. */
+ protected ChangeType changeType;
+
+ /** Similarity score if {@link #changeType} is a copy or rename. */
+ private int score;
+
+ /** ObjectId listed on the index line for the old (pre-image) */
+ private AbbreviatedObjectId oldId;
+
+ /** ObjectId listed on the index line for the new (post-image) */
+ protected AbbreviatedObjectId newId;
+
+ /** Type of patch used to modify this file */
+ PatchType patchType;
+
+ /** The hunks of this file */
+ private List<HunkHeader> hunks;
+
+ /** If {@link #patchType} is {@link PatchType#GIT_BINARY}, the new image */
+ BinaryHunk forwardBinaryHunk;
+
+ /** If {@link #patchType} is {@link PatchType#GIT_BINARY}, the old image */
+ BinaryHunk reverseBinaryHunk;
+
+ FileHeader(final byte[] b, final int offset) {
+ buf = b;
+ startOffset = offset;
+ changeType = ChangeType.MODIFY; // unless otherwise designated
+ patchType = PatchType.UNIFIED;
+ }
+
+ int getParentCount() {
+ return 1;
+ }
+
+ /** @return the byte array holding this file's patch script. */
+ public byte[] getBuffer() {
+ return buf;
+ }
+
+ /** @return offset the start of this file's script in {@link #getBuffer()}. */
+ public int getStartOffset() {
+ return startOffset;
+ }
+
+ /** @return offset one past the end of the file script. */
+ public int getEndOffset() {
+ return endOffset;
+ }
+
+ /**
+ * Convert the patch script for this file into a string.
+ * <p>
+ * The default character encoding ({@link Constants#CHARSET}) is assumed for
+ * both the old and new files.
+ *
+ * @return the patch script, as a Unicode string.
+ */
+ public String getScriptText() {
+ return getScriptText(null, null);
+ }
+
+ /**
+ * Convert the patch script for this file into a string.
+ *
+ * @param oldCharset
+ * hint character set to decode the old lines with.
+ * @param newCharset
+ * hint character set to decode the new lines with.
+ * @return the patch script, as a Unicode string.
+ */
+ public String getScriptText(Charset oldCharset, Charset newCharset) {
+ return getScriptText(new Charset[] { oldCharset, newCharset });
+ }
+
+ String getScriptText(Charset[] charsetGuess) {
+ if (getHunks().isEmpty()) {
+ // If we have no hunks then we can safely assume the entire
+ // patch is a binary style patch, or a meta-data only style
+ // patch. Either way the encoding of the headers should be
+ // strictly 7-bit US-ASCII and the body is either 7-bit ASCII
+ // (due to the base 85 encoding used for a BinaryHunk) or is
+ // arbitrary noise we have chosen to ignore and not understand
+ // (e.g. the message "Binary files ... differ").
+ //
+ return extractBinaryString(buf, startOffset, endOffset);
+ }
+
+ if (charsetGuess != null && charsetGuess.length != getParentCount() + 1)
+ throw new IllegalArgumentException("Expected "
+ + (getParentCount() + 1) + " character encoding guesses");
+
+ if (trySimpleConversion(charsetGuess)) {
+ Charset cs = charsetGuess != null ? charsetGuess[0] : null;
+ if (cs == null)
+ cs = Constants.CHARSET;
+ try {
+ return decodeNoFallback(cs, buf, startOffset, endOffset);
+ } catch (CharacterCodingException cee) {
+ // Try the much slower, more-memory intensive version which
+ // can handle a character set conversion patch.
+ }
+ }
+
+ final StringBuilder r = new StringBuilder(endOffset - startOffset);
+
+ // Always treat the headers as US-ASCII; Git file names are encoded
+ // in a C style escape if any character has the high-bit set.
+ //
+ final int hdrEnd = getHunks().get(0).getStartOffset();
+ for (int ptr = startOffset; ptr < hdrEnd;) {
+ final int eol = Math.min(hdrEnd, nextLF(buf, ptr));
+ r.append(extractBinaryString(buf, ptr, eol));
+ ptr = eol;
+ }
+
+ final String[] files = extractFileLines(charsetGuess);
+ final int[] offsets = new int[files.length];
+ for (final HunkHeader h : getHunks())
+ h.extractFileLines(r, files, offsets);
+ return r.toString();
+ }
+
+ private static boolean trySimpleConversion(final Charset[] charsetGuess) {
+ if (charsetGuess == null)
+ return true;
+ for (int i = 1; i < charsetGuess.length; i++) {
+ if (charsetGuess[i] != charsetGuess[0])
+ return false;
+ }
+ return true;
+ }
+
+ private String[] extractFileLines(final Charset[] csGuess) {
+ final TemporaryBuffer[] tmp = new TemporaryBuffer[getParentCount() + 1];
+ try {
+ for (int i = 0; i < tmp.length; i++)
+ tmp[i] = new TemporaryBuffer();
+ for (final HunkHeader h : getHunks())
+ h.extractFileLines(tmp);
+
+ final String[] r = new String[tmp.length];
+ for (int i = 0; i < tmp.length; i++) {
+ Charset cs = csGuess != null ? csGuess[i] : null;
+ if (cs == null)
+ cs = Constants.CHARSET;
+ r[i] = RawParseUtils.decode(cs, tmp[i].toByteArray());
+ }
+ return r;
+ } catch (IOException ioe) {
+ throw new RuntimeException("Cannot convert script to text", ioe);
+ } finally {
+ for (final TemporaryBuffer b : tmp) {
+ if (b != null)
+ b.destroy();
+ }
+ }
+ }
+
+ /**
+ * Get the old name associated with this file.
+ * <p>
+ * The meaning of the old name can differ depending on the semantic meaning
+ * of this patch:
+ * <ul>
+ * <li><i>file add</i>: always <code>/dev/null</code></li>
+ * <li><i>file modify</i>: always {@link #getNewName()}</li>
+ * <li><i>file delete</i>: always the file being deleted</li>
+ * <li><i>file copy</i>: source file the copy originates from</li>
+ * <li><i>file rename</i>: source file the rename originates from</li>
+ * </ul>
+ *
+ * @return old name for this file.
+ */
+ public String getOldName() {
+ return oldName;
+ }
+
+ /**
+ * Get the new name associated with this file.
+ * <p>
+ * The meaning of the new name can differ depending on the semantic meaning
+ * of this patch:
+ * <ul>
+ * <li><i>file add</i>: always the file being created</li>
+ * <li><i>file modify</i>: always {@link #getOldName()}</li>
+ * <li><i>file delete</i>: always <code>/dev/null</code></li>
+ * <li><i>file copy</i>: destination file the copy ends up at</li>
+ * <li><i>file rename</i>: destination file the rename ends up at/li>
+ * </ul>
+ *
+ * @return new name for this file.
+ */
+ public String getNewName() {
+ return newName;
+ }
+
+ /** @return the old file mode, if described in the patch */
+ public FileMode getOldMode() {
+ return oldMode;
+ }
+
+ /** @return the new file mode, if described in the patch */
+ public FileMode getNewMode() {
+ return newMode;
+ }
+
+ /** @return the type of change this patch makes on {@link #getNewName()} */
+ public ChangeType getChangeType() {
+ return changeType;
+ }
+
+ /**
+ * @return similarity score between {@link #getOldName()} and
+ * {@link #getNewName()} if {@link #getChangeType()} is
+ * {@link ChangeType#COPY} or {@link ChangeType#RENAME}.
+ */
+ public int getScore() {
+ return score;
+ }
+
+ /**
+ * Get the old object id from the <code>index</code>.
+ *
+ * @return the object id; null if there is no index line
+ */
+ public AbbreviatedObjectId getOldId() {
+ return oldId;
+ }
+
+ /**
+ * Get the new object id from the <code>index</code>.
+ *
+ * @return the object id; null if there is no index line
+ */
+ public AbbreviatedObjectId getNewId() {
+ return newId;
+ }
+
+ /** @return style of patch used to modify this file */
+ public PatchType getPatchType() {
+ return patchType;
+ }
+
+ /** @return true if this patch modifies metadata about a file */
+ public boolean hasMetaDataChanges() {
+ return changeType != ChangeType.MODIFY || newMode != oldMode;
+ }
+
+ /** @return hunks altering this file; in order of appearance in patch */
+ public List<? extends HunkHeader> getHunks() {
+ if (hunks == null)
+ return Collections.emptyList();
+ return hunks;
+ }
+
+ void addHunk(final HunkHeader h) {
+ if (h.getFileHeader() != this)
+ throw new IllegalArgumentException("Hunk belongs to another file");
+ if (hunks == null)
+ hunks = new ArrayList<HunkHeader>();
+ hunks.add(h);
+ }
+
+ HunkHeader newHunkHeader(final int offset) {
+ return new HunkHeader(this, offset);
+ }
+
+ /** @return if a {@link PatchType#GIT_BINARY}, the new-image delta/literal */
+ public BinaryHunk getForwardBinaryHunk() {
+ return forwardBinaryHunk;
+ }
+
+ /** @return if a {@link PatchType#GIT_BINARY}, the old-image delta/literal */
+ public BinaryHunk getReverseBinaryHunk() {
+ return reverseBinaryHunk;
+ }
+
+ /** @return a list describing the content edits performed on this file. */
+ public EditList toEditList() {
+ final EditList r = new EditList();
+ for (final HunkHeader hunk : hunks)
+ r.addAll(hunk.toEditList());
+ return r;
+ }
+
+ /**
+ * Parse a "diff --git" or "diff --cc" line.
+ *
+ * @param ptr
+ * first character after the "diff --git " or "diff --cc " part.
+ * @param end
+ * one past the last position to parse.
+ * @return first character after the LF at the end of the line; -1 on error.
+ */
+ int parseGitFileName(int ptr, final int end) {
+ final int eol = nextLF(buf, ptr);
+ final int bol = ptr;
+ if (eol >= end) {
+ return -1;
+ }
+
+ // buffer[ptr..eol] looks like "a/foo b/foo\n". After the first
+ // A regex to match this is "^[^/]+/(.*?) [^/+]+/\1\n$". There
+ // is only one way to split the line such that text to the left
+ // of the space matches the text to the right, excluding the part
+ // before the first slash.
+ //
+
+ final int aStart = nextLF(buf, ptr, '/');
+ if (aStart >= eol)
+ return eol;
+
+ while (ptr < eol) {
+ final int sp = nextLF(buf, ptr, ' ');
+ if (sp >= eol) {
+ // We can't split the header, it isn't valid.
+ // This may be OK if this is a rename patch.
+ //
+ return eol;
+ }
+ final int bStart = nextLF(buf, sp, '/');
+ if (bStart >= eol)
+ return eol;
+
+ // If buffer[aStart..sp - 1] = buffer[bStart..eol - 1]
+ // we have a valid split.
+ //
+ if (eq(aStart, sp - 1, bStart, eol - 1)) {
+ if (buf[bol] == '"') {
+ // We're a double quoted name. The region better end
+ // in a double quote too, and we need to decode the
+ // characters before reading the name.
+ //
+ if (buf[sp - 2] != '"') {
+ return eol;
+ }
+ oldName = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1);
+ oldName = p1(oldName);
+ } else {
+ oldName = decode(Constants.CHARSET, buf, aStart, sp - 1);
+ }
+ newName = oldName;
+ return eol;
+ }
+
+ // This split wasn't correct. Move past the space and try
+ // another split as the space must be part of the file name.
+ //
+ ptr = sp;
+ }
+
+ return eol;
+ }
+
+ int parseGitHeaders(int ptr, final int end) {
+ while (ptr < end) {
+ final int eol = nextLF(buf, ptr);
+ if (isHunkHdr(buf, ptr, eol) >= 1) {
+ // First hunk header; break out and parse them later.
+ break;
+
+ } else if (match(buf, ptr, OLD_NAME) >= 0) {
+ parseOldName(ptr, eol);
+
+ } else if (match(buf, ptr, NEW_NAME) >= 0) {
+ parseNewName(ptr, eol);
+
+ } else if (match(buf, ptr, OLD_MODE) >= 0) {
+ oldMode = parseFileMode(ptr + OLD_MODE.length, eol);
+
+ } else if (match(buf, ptr, NEW_MODE) >= 0) {
+ newMode = parseFileMode(ptr + NEW_MODE.length, eol);
+
+ } else if (match(buf, ptr, DELETED_FILE_MODE) >= 0) {
+ oldMode = parseFileMode(ptr + DELETED_FILE_MODE.length, eol);
+ newMode = FileMode.MISSING;
+ changeType = ChangeType.DELETE;
+
+ } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) {
+ parseNewFileMode(ptr, eol);
+
+ } else if (match(buf, ptr, COPY_FROM) >= 0) {
+ oldName = parseName(oldName, ptr + COPY_FROM.length, eol);
+ changeType = ChangeType.COPY;
+
+ } else if (match(buf, ptr, COPY_TO) >= 0) {
+ newName = parseName(newName, ptr + COPY_TO.length, eol);
+ changeType = ChangeType.COPY;
+
+ } else if (match(buf, ptr, RENAME_OLD) >= 0) {
+ oldName = parseName(oldName, ptr + RENAME_OLD.length, eol);
+ changeType = ChangeType.RENAME;
+
+ } else if (match(buf, ptr, RENAME_NEW) >= 0) {
+ newName = parseName(newName, ptr + RENAME_NEW.length, eol);
+ changeType = ChangeType.RENAME;
+
+ } else if (match(buf, ptr, RENAME_FROM) >= 0) {
+ oldName = parseName(oldName, ptr + RENAME_FROM.length, eol);
+ changeType = ChangeType.RENAME;
+
+ } else if (match(buf, ptr, RENAME_TO) >= 0) {
+ newName = parseName(newName, ptr + RENAME_TO.length, eol);
+ changeType = ChangeType.RENAME;
+
+ } else if (match(buf, ptr, SIMILARITY_INDEX) >= 0) {
+ score = parseBase10(buf, ptr + SIMILARITY_INDEX.length, null);
+
+ } else if (match(buf, ptr, DISSIMILARITY_INDEX) >= 0) {
+ score = parseBase10(buf, ptr + DISSIMILARITY_INDEX.length, null);
+
+ } else if (match(buf, ptr, INDEX) >= 0) {
+ parseIndexLine(ptr + INDEX.length, eol);
+
+ } else {
+ // Probably an empty patch (stat dirty).
+ break;
+ }
+
+ ptr = eol;
+ }
+ return ptr;
+ }
+
+ void parseOldName(int ptr, final int eol) {
+ oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol));
+ if (oldName == DEV_NULL)
+ changeType = ChangeType.ADD;
+ }
+
+ void parseNewName(int ptr, final int eol) {
+ newName = p1(parseName(newName, ptr + NEW_NAME.length, eol));
+ if (newName == DEV_NULL)
+ changeType = ChangeType.DELETE;
+ }
+
+ void parseNewFileMode(int ptr, final int eol) {
+ oldMode = FileMode.MISSING;
+ newMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol);
+ changeType = ChangeType.ADD;
+ }
+
+ int parseTraditionalHeaders(int ptr, final int end) {
+ while (ptr < end) {
+ final int eol = nextLF(buf, ptr);
+ if (isHunkHdr(buf, ptr, eol) >= 1) {
+ // First hunk header; break out and parse them later.
+ break;
+
+ } else if (match(buf, ptr, OLD_NAME) >= 0) {
+ parseOldName(ptr, eol);
+
+ } else if (match(buf, ptr, NEW_NAME) >= 0) {
+ parseNewName(ptr, eol);
+
+ } else {
+ // Possibly an empty patch.
+ break;
+ }
+
+ ptr = eol;
+ }
+ return ptr;
+ }
+
+ private String parseName(final String expect, int ptr, final int end) {
+ if (ptr == end)
+ return expect;
+
+ String r;
+ if (buf[ptr] == '"') {
+ // New style GNU diff format
+ //
+ r = QuotedString.GIT_PATH.dequote(buf, ptr, end - 1);
+ } else {
+ // Older style GNU diff format, an optional tab ends the name.
+ //
+ int tab = end;
+ while (ptr < tab && buf[tab - 1] != '\t')
+ tab--;
+ if (ptr == tab)
+ tab = end;
+ r = decode(Constants.CHARSET, buf, ptr, tab - 1);
+ }
+
+ if (r.equals(DEV_NULL))
+ r = DEV_NULL;
+ return r;
+ }
+
+ private static String p1(final String r) {
+ final int s = r.indexOf('/');
+ return s > 0 ? r.substring(s + 1) : r;
+ }
+
+ FileMode parseFileMode(int ptr, final int end) {
+ int tmp = 0;
+ while (ptr < end - 1) {
+ tmp <<= 3;
+ tmp += buf[ptr++] - '0';
+ }
+ return FileMode.fromBits(tmp);
+ }
+
+ void parseIndexLine(int ptr, final int end) {
+ // "index $asha1..$bsha1[ $mode]" where $asha1 and $bsha1
+ // can be unique abbreviations
+ //
+ final int dot2 = nextLF(buf, ptr, '.');
+ final int mode = nextLF(buf, dot2, ' ');
+
+ oldId = AbbreviatedObjectId.fromString(buf, ptr, dot2 - 1);
+ newId = AbbreviatedObjectId.fromString(buf, dot2 + 1, mode - 1);
+
+ if (mode < end)
+ newMode = oldMode = parseFileMode(mode, end);
+ }
+
+ private boolean eq(int aPtr, int aEnd, int bPtr, int bEnd) {
+ if (aEnd - aPtr != bEnd - bPtr) {
+ return false;
+ }
+ while (aPtr < aEnd) {
+ if (buf[aPtr++] != buf[bPtr++])
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Determine if this is a patch hunk header.
+ *
+ * @param buf
+ * the buffer to scan
+ * @param start
+ * first position in the buffer to evaluate
+ * @param end
+ * last position to consider; usually the end of the buffer (
+ * <code>buf.length</code>) or the first position on the next
+ * line. This is only used to avoid very long runs of '@' from
+ * killing the scan loop.
+ * @return the number of "ancestor revisions" in the hunk header. A
+ * traditional two-way diff ("@@ -...") returns 1; a combined diff
+ * for a 3 way-merge returns 3. If this is not a hunk header, 0 is
+ * returned instead.
+ */
+ static int isHunkHdr(final byte[] buf, final int start, final int end) {
+ int ptr = start;
+ while (ptr < end && buf[ptr] == '@')
+ ptr++;
+ if (ptr - start < 2)
+ return 0;
+ if (ptr == end || buf[ptr++] != ' ')
+ return 0;
+ if (ptr == end || buf[ptr++] != '-')
+ return 0;
+ return (ptr - 3) - start;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
new file mode 100644
index 0000000000..13046137dc
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.patch;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/** An error in a patch script */
+public class FormatError {
+ /** Classification of an error. */
+ public static enum Severity {
+ /** The error is unexpected, but can be worked around. */
+ WARNING,
+
+ /** The error indicates the script is severely flawed. */
+ ERROR;
+ }
+
+ private final byte[] buf;
+
+ private final int offset;
+
+ private final Severity severity;
+
+ private final String message;
+
+ FormatError(final byte[] buffer, final int ptr, final Severity sev,
+ final String msg) {
+ buf = buffer;
+ offset = ptr;
+ severity = sev;
+ message = msg;
+ }
+
+ /** @return the severity of the error. */
+ public Severity getSeverity() {
+ return severity;
+ }
+
+ /** @return a message describing the error. */
+ public String getMessage() {
+ return message;
+ }
+
+ /** @return the byte buffer holding the patch script. */
+ public byte[] getBuffer() {
+ return buf;
+ }
+
+ /** @return byte offset within {@link #getBuffer()} where the error is */
+ public int getOffset() {
+ return offset;
+ }
+
+ /** @return line of the patch script the error appears on. */
+ public String getLineText() {
+ final int eol = RawParseUtils.nextLF(buf, offset);
+ return RawParseUtils.decode(Constants.CHARSET, buf, offset, eol);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder r = new StringBuilder();
+ r.append(getSeverity().name().toLowerCase());
+ r.append(": at offset ");
+ r.append(getOffset());
+ r.append(": ");
+ r.append(getMessage());
+ r.append("\n");
+ r.append(" in ");
+ r.append(getLineText());
+ return r.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java
new file mode 100644
index 0000000000..9d78d0b99f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.patch;
+
+import static org.eclipse.jgit.util.RawParseUtils.match;
+import static org.eclipse.jgit.util.RawParseUtils.nextLF;
+import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.diff.EditList;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.util.MutableInteger;
+
+/** Hunk header describing the layout of a single block of lines */
+public class HunkHeader {
+ /** Details about an old image of the file. */
+ public abstract static class OldImage {
+ /** First line number the hunk starts on in this file. */
+ int startLine;
+
+ /** Total number of lines this hunk covers in this file. */
+ int lineCount;
+
+ /** Number of lines deleted by the post-image from this file. */
+ int nDeleted;
+
+ /** Number of lines added by the post-image not in this file. */
+ int nAdded;
+
+ /** @return first line number the hunk starts on in this file. */
+ public int getStartLine() {
+ return startLine;
+ }
+
+ /** @return total number of lines this hunk covers in this file. */
+ public int getLineCount() {
+ return lineCount;
+ }
+
+ /** @return number of lines deleted by the post-image from this file. */
+ public int getLinesDeleted() {
+ return nDeleted;
+ }
+
+ /** @return number of lines added by the post-image not in this file. */
+ public int getLinesAdded() {
+ return nAdded;
+ }
+
+ /** @return object id of the pre-image file. */
+ public abstract AbbreviatedObjectId getId();
+ }
+
+ final FileHeader file;
+
+ /** Offset within {@link #file}.buf to the "@@ -" line. */
+ final int startOffset;
+
+ /** Position 1 past the end of this hunk within {@link #file}'s buf. */
+ int endOffset;
+
+ private final OldImage old;
+
+ /** First line number in the post-image file where the hunk starts */
+ int newStartLine;
+
+ /** Total number of post-image lines this hunk covers (context + inserted) */
+ int newLineCount;
+
+ /** Total number of lines of context appearing in this hunk */
+ int nContext;
+
+ HunkHeader(final FileHeader fh, final int offset) {
+ this(fh, offset, new OldImage() {
+ @Override
+ public AbbreviatedObjectId getId() {
+ return fh.getOldId();
+ }
+ });
+ }
+
+ HunkHeader(final FileHeader fh, final int offset, final OldImage oi) {
+ file = fh;
+ startOffset = offset;
+ old = oi;
+ }
+
+ /** @return header for the file this hunk applies to */
+ public FileHeader getFileHeader() {
+ return file;
+ }
+
+ /** @return the byte array holding this hunk's patch script. */
+ public byte[] getBuffer() {
+ return file.buf;
+ }
+
+ /** @return offset the start of this hunk in {@link #getBuffer()}. */
+ public int getStartOffset() {
+ return startOffset;
+ }
+
+ /** @return offset one past the end of the hunk in {@link #getBuffer()}. */
+ public int getEndOffset() {
+ return endOffset;
+ }
+
+ /** @return information about the old image mentioned in this hunk. */
+ public OldImage getOldImage() {
+ return old;
+ }
+
+ /** @return first line number in the post-image file where the hunk starts */
+ public int getNewStartLine() {
+ return newStartLine;
+ }
+
+ /** @return Total number of post-image lines this hunk covers */
+ public int getNewLineCount() {
+ return newLineCount;
+ }
+
+ /** @return total number of lines of context appearing in this hunk */
+ public int getLinesContext() {
+ return nContext;
+ }
+
+ /** @return a list describing the content edits performed within the hunk. */
+ public EditList toEditList() {
+ final EditList r = new EditList();
+ final byte[] buf = file.buf;
+ int c = nextLF(buf, startOffset);
+ int oLine = old.startLine;
+ int nLine = newStartLine;
+ Edit in = null;
+
+ SCAN: for (; c < endOffset; c = nextLF(buf, c)) {
+ switch (buf[c]) {
+ case ' ':
+ case '\n':
+ in = null;
+ oLine++;
+ nLine++;
+ continue;
+
+ case '-':
+ if (in == null) {
+ in = new Edit(oLine - 1, nLine - 1);
+ r.add(in);
+ }
+ oLine++;
+ in.extendA();
+ continue;
+
+ case '+':
+ if (in == null) {
+ in = new Edit(oLine - 1, nLine - 1);
+ r.add(in);
+ }
+ nLine++;
+ in.extendB();
+ continue;
+
+ case '\\': // Matches "\ No newline at end of file"
+ continue;
+
+ default:
+ break SCAN;
+ }
+ }
+ return r;
+ }
+
+ void parseHeader() {
+ // Parse "@@ -236,9 +236,9 @@ protected boolean"
+ //
+ final byte[] buf = file.buf;
+ final MutableInteger ptr = new MutableInteger();
+ ptr.value = nextLF(buf, startOffset, ' ');
+ old.startLine = -parseBase10(buf, ptr.value, ptr);
+ if (buf[ptr.value] == ',')
+ old.lineCount = parseBase10(buf, ptr.value + 1, ptr);
+ else
+ old.lineCount = 1;
+
+ newStartLine = parseBase10(buf, ptr.value + 1, ptr);
+ if (buf[ptr.value] == ',')
+ newLineCount = parseBase10(buf, ptr.value + 1, ptr);
+ else
+ newLineCount = 1;
+ }
+
+ int parseBody(final Patch script, final int end) {
+ final byte[] buf = file.buf;
+ int c = nextLF(buf, startOffset), last = c;
+
+ old.nDeleted = 0;
+ old.nAdded = 0;
+
+ SCAN: for (; c < end; last = c, c = nextLF(buf, c)) {
+ switch (buf[c]) {
+ case ' ':
+ case '\n':
+ nContext++;
+ continue;
+
+ case '-':
+ old.nDeleted++;
+ continue;
+
+ case '+':
+ old.nAdded++;
+ continue;
+
+ case '\\': // Matches "\ No newline at end of file"
+ continue;
+
+ default:
+ break SCAN;
+ }
+ }
+
+ if (last < end && nContext + old.nDeleted - 1 == old.lineCount
+ && nContext + old.nAdded == newLineCount
+ && match(buf, last, Patch.SIG_FOOTER) >= 0) {
+ // This is an extremely common occurrence of "corruption".
+ // Users add footers with their signatures after this mark,
+ // and git diff adds the git executable version number.
+ // Let it slide; the hunk otherwise looked sound.
+ //
+ old.nDeleted--;
+ return last;
+ }
+
+ if (nContext + old.nDeleted < old.lineCount) {
+ final int missingCount = old.lineCount - (nContext + old.nDeleted);
+ script.error(buf, startOffset, "Truncated hunk, at least "
+ + missingCount + " old lines is missing");
+
+ } else if (nContext + old.nAdded < newLineCount) {
+ final int missingCount = newLineCount - (nContext + old.nAdded);
+ script.error(buf, startOffset, "Truncated hunk, at least "
+ + missingCount + " new lines is missing");
+
+ } else if (nContext + old.nDeleted > old.lineCount
+ || nContext + old.nAdded > newLineCount) {
+ final String oldcnt = old.lineCount + ":" + newLineCount;
+ final String newcnt = (nContext + old.nDeleted) + ":"
+ + (nContext + old.nAdded);
+ script.warn(buf, startOffset, "Hunk header " + oldcnt
+ + " does not match body line count of " + newcnt);
+ }
+
+ return c;
+ }
+
+ void extractFileLines(final OutputStream[] out) throws IOException {
+ final byte[] buf = file.buf;
+ int ptr = startOffset;
+ int eol = nextLF(buf, ptr);
+ if (endOffset <= eol)
+ return;
+
+ // Treat the hunk header as though it were from the ancestor,
+ // as it may have a function header appearing after it which
+ // was copied out of the ancestor file.
+ //
+ out[0].write(buf, ptr, eol - ptr);
+
+ SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
+ eol = nextLF(buf, ptr);
+ switch (buf[ptr]) {
+ case ' ':
+ case '\n':
+ case '\\':
+ out[0].write(buf, ptr, eol - ptr);
+ out[1].write(buf, ptr, eol - ptr);
+ break;
+ case '-':
+ out[0].write(buf, ptr, eol - ptr);
+ break;
+ case '+':
+ out[1].write(buf, ptr, eol - ptr);
+ break;
+ default:
+ break SCAN;
+ }
+ }
+ }
+
+ void extractFileLines(final StringBuilder sb, final String[] text,
+ final int[] offsets) {
+ final byte[] buf = file.buf;
+ int ptr = startOffset;
+ int eol = nextLF(buf, ptr);
+ if (endOffset <= eol)
+ return;
+ copyLine(sb, text, offsets, 0);
+ SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
+ eol = nextLF(buf, ptr);
+ switch (buf[ptr]) {
+ case ' ':
+ case '\n':
+ case '\\':
+ copyLine(sb, text, offsets, 0);
+ skipLine(text, offsets, 1);
+ break;
+ case '-':
+ copyLine(sb, text, offsets, 0);
+ break;
+ case '+':
+ copyLine(sb, text, offsets, 1);
+ break;
+ default:
+ break SCAN;
+ }
+ }
+ }
+
+ void copyLine(final StringBuilder sb, final String[] text,
+ final int[] offsets, final int fileIdx) {
+ final String s = text[fileIdx];
+ final int start = offsets[fileIdx];
+ int end = s.indexOf('\n', start);
+ if (end < 0)
+ end = s.length();
+ else
+ end++;
+ sb.append(s, start, end);
+ offsets[fileIdx] = end;
+ }
+
+ void skipLine(final String[] text, final int[] offsets,
+ final int fileIdx) {
+ final String s = text[fileIdx];
+ final int end = s.indexOf('\n', offsets[fileIdx]);
+ offsets[fileIdx] = end < 0 ? s.length() : end + 1;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java
new file mode 100644
index 0000000000..1eff3edd8c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.patch;
+
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+import static org.eclipse.jgit.patch.FileHeader.isHunkHdr;
+import static org.eclipse.jgit.patch.FileHeader.NEW_NAME;
+import static org.eclipse.jgit.patch.FileHeader.OLD_NAME;
+import static org.eclipse.jgit.util.RawParseUtils.match;
+import static org.eclipse.jgit.util.RawParseUtils.nextLF;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.util.TemporaryBuffer;
+
+/** A parsed collection of {@link FileHeader}s from a unified diff patch file */
+public class Patch {
+ private static final byte[] DIFF_GIT = encodeASCII("diff --git ");
+
+ private static final byte[] DIFF_CC = encodeASCII("diff --cc ");
+
+ private static final byte[] DIFF_COMBINED = encodeASCII("diff --combined ");
+
+ private static final byte[][] BIN_HEADERS = new byte[][] {
+ encodeASCII("Binary files "), encodeASCII("Files "), };
+
+ private static final byte[] BIN_TRAILER = encodeASCII(" differ\n");
+
+ private static final byte[] GIT_BINARY = encodeASCII("GIT binary patch\n");
+
+ static final byte[] SIG_FOOTER = encodeASCII("-- \n");
+
+ /** The files, in the order they were parsed out of the input. */
+ private final List<FileHeader> files;
+
+ /** Formatting errors, if any were identified. */
+ private final List<FormatError> errors;
+
+ /** Create an empty patch. */
+ public Patch() {
+ files = new ArrayList<FileHeader>();
+ errors = new ArrayList<FormatError>(0);
+ }
+
+ /**
+ * Add a single file to this patch.
+ * <p>
+ * Typically files should be added by parsing the text through one of this
+ * class's parse methods.
+ *
+ * @param fh
+ * the header of the file.
+ */
+ public void addFile(final FileHeader fh) {
+ files.add(fh);
+ }
+
+ /** @return list of files described in the patch, in occurrence order. */
+ public List<? extends FileHeader> getFiles() {
+ return files;
+ }
+
+ /**
+ * Add a formatting error to this patch script.
+ *
+ * @param err
+ * the error description.
+ */
+ public void addError(final FormatError err) {
+ errors.add(err);
+ }
+
+ /** @return collection of formatting errors, if any. */
+ public List<FormatError> getErrors() {
+ return errors;
+ }
+
+ /**
+ * Parse a patch received from an InputStream.
+ * <p>
+ * Multiple parse calls on the same instance will concatenate the patch
+ * data, but each parse input must start with a valid file header (don't
+ * split a single file across parse calls).
+ *
+ * @param is
+ * the stream to read the patch data from. The stream is read
+ * until EOF is reached.
+ * @throws IOException
+ * there was an error reading from the input stream.
+ */
+ public void parse(final InputStream is) throws IOException {
+ final byte[] buf = readFully(is);
+ parse(buf, 0, buf.length);
+ }
+
+ private static byte[] readFully(final InputStream is) throws IOException {
+ final TemporaryBuffer b = new TemporaryBuffer();
+ try {
+ b.copy(is);
+ b.close();
+ return b.toByteArray();
+ } finally {
+ b.destroy();
+ }
+ }
+
+ /**
+ * Parse a patch stored in a byte[].
+ * <p>
+ * Multiple parse calls on the same instance will concatenate the patch
+ * data, but each parse input must start with a valid file header (don't
+ * split a single file across parse calls).
+ *
+ * @param buf
+ * the buffer to parse.
+ * @param ptr
+ * starting position to parse from.
+ * @param end
+ * 1 past the last position to end parsing. The total length to
+ * be parsed is <code>end - ptr</code>.
+ */
+ public void parse(final byte[] buf, int ptr, final int end) {
+ while (ptr < end)
+ ptr = parseFile(buf, ptr, end);
+ }
+
+ private int parseFile(final byte[] buf, int c, final int end) {
+ while (c < end) {
+ if (isHunkHdr(buf, c, end) >= 1) {
+ // If we find a disconnected hunk header we might
+ // have missed a file header previously. The hunk
+ // isn't valid without knowing where it comes from.
+ //
+ error(buf, c, "Hunk disconnected from file");
+ c = nextLF(buf, c);
+ continue;
+ }
+
+ // Valid git style patch?
+ //
+ if (match(buf, c, DIFF_GIT) >= 0)
+ return parseDiffGit(buf, c, end);
+ if (match(buf, c, DIFF_CC) >= 0)
+ return parseDiffCombined(DIFF_CC, buf, c, end);
+ if (match(buf, c, DIFF_COMBINED) >= 0)
+ return parseDiffCombined(DIFF_COMBINED, buf, c, end);
+
+ // Junk between files? Leading junk? Traditional
+ // (non-git generated) patch?
+ //
+ final int n = nextLF(buf, c);
+ if (n >= end) {
+ // Patches cannot be only one line long. This must be
+ // trailing junk that we should ignore.
+ //
+ return end;
+ }
+
+ if (n - c < 6) {
+ // A valid header must be at least 6 bytes on the
+ // first line, e.g. "--- a/b\n".
+ //
+ c = n;
+ continue;
+ }
+
+ if (match(buf, c, OLD_NAME) >= 0 && match(buf, n, NEW_NAME) >= 0) {
+ // Probably a traditional patch. Ensure we have at least
+ // a "@@ -0,0" smelling line next. We only check the "@@ -".
+ //
+ final int f = nextLF(buf, n);
+ if (f >= end)
+ return end;
+ if (isHunkHdr(buf, f, end) == 1)
+ return parseTraditionalPatch(buf, c, end);
+ }
+
+ c = n;
+ }
+ return c;
+ }
+
+ private int parseDiffGit(final byte[] buf, final int start, final int end) {
+ final FileHeader fh = new FileHeader(buf, start);
+ int ptr = fh.parseGitFileName(start + DIFF_GIT.length, end);
+ if (ptr < 0)
+ return skipFile(buf, start);
+
+ ptr = fh.parseGitHeaders(ptr, end);
+ ptr = parseHunks(fh, ptr, end);
+ fh.endOffset = ptr;
+ addFile(fh);
+ return ptr;
+ }
+
+ private int parseDiffCombined(final byte[] hdr, final byte[] buf,
+ final int start, final int end) {
+ final CombinedFileHeader fh = new CombinedFileHeader(buf, start);
+ int ptr = fh.parseGitFileName(start + hdr.length, end);
+ if (ptr < 0)
+ return skipFile(buf, start);
+
+ ptr = fh.parseGitHeaders(ptr, end);
+ ptr = parseHunks(fh, ptr, end);
+ fh.endOffset = ptr;
+ addFile(fh);
+ return ptr;
+ }
+
+ private int parseTraditionalPatch(final byte[] buf, final int start,
+ final int end) {
+ final FileHeader fh = new FileHeader(buf, start);
+ int ptr = fh.parseTraditionalHeaders(start, end);
+ ptr = parseHunks(fh, ptr, end);
+ fh.endOffset = ptr;
+ addFile(fh);
+ return ptr;
+ }
+
+ private static int skipFile(final byte[] buf, int ptr) {
+ ptr = nextLF(buf, ptr);
+ if (match(buf, ptr, OLD_NAME) >= 0)
+ ptr = nextLF(buf, ptr);
+ return ptr;
+ }
+
+ private int parseHunks(final FileHeader fh, int c, final int end) {
+ final byte[] buf = fh.buf;
+ while (c < end) {
+ // If we see a file header at this point, we have all of the
+ // hunks for our current file. We should stop and report back
+ // with this position so it can be parsed again later.
+ //
+ if (match(buf, c, DIFF_GIT) >= 0)
+ break;
+ if (match(buf, c, DIFF_CC) >= 0)
+ break;
+ if (match(buf, c, DIFF_COMBINED) >= 0)
+ break;
+ if (match(buf, c, OLD_NAME) >= 0)
+ break;
+ if (match(buf, c, NEW_NAME) >= 0)
+ break;
+
+ if (isHunkHdr(buf, c, end) == fh.getParentCount()) {
+ final HunkHeader h = fh.newHunkHeader(c);
+ h.parseHeader();
+ c = h.parseBody(this, end);
+ h.endOffset = c;
+ fh.addHunk(h);
+ if (c < end) {
+ switch (buf[c]) {
+ case '@':
+ case 'd':
+ case '\n':
+ break;
+ default:
+ if (match(buf, c, SIG_FOOTER) < 0)
+ warn(buf, c, "Unexpected hunk trailer");
+ }
+ }
+ continue;
+ }
+
+ final int eol = nextLF(buf, c);
+ if (fh.getHunks().isEmpty() && match(buf, c, GIT_BINARY) >= 0) {
+ fh.patchType = FileHeader.PatchType.GIT_BINARY;
+ return parseGitBinary(fh, eol, end);
+ }
+
+ if (fh.getHunks().isEmpty() && BIN_TRAILER.length < eol - c
+ && match(buf, eol - BIN_TRAILER.length, BIN_TRAILER) >= 0
+ && matchAny(buf, c, BIN_HEADERS)) {
+ // The patch is a binary file diff, with no deltas.
+ //
+ fh.patchType = FileHeader.PatchType.BINARY;
+ return eol;
+ }
+
+ // Skip this line and move to the next. Its probably garbage
+ // after the last hunk of a file.
+ //
+ c = eol;
+ }
+
+ if (fh.getHunks().isEmpty()
+ && fh.getPatchType() == FileHeader.PatchType.UNIFIED
+ && !fh.hasMetaDataChanges()) {
+ // Hmm, an empty patch? If there is no metadata here we
+ // really have a binary patch that we didn't notice above.
+ //
+ fh.patchType = FileHeader.PatchType.BINARY;
+ }
+
+ return c;
+ }
+
+ private int parseGitBinary(final FileHeader fh, int c, final int end) {
+ final BinaryHunk postImage = new BinaryHunk(fh, c);
+ final int nEnd = postImage.parseHunk(c, end);
+ if (nEnd < 0) {
+ // Not a binary hunk.
+ //
+ error(fh.buf, c, "Missing forward-image in GIT binary patch");
+ return c;
+ }
+ c = nEnd;
+ postImage.endOffset = c;
+ fh.forwardBinaryHunk = postImage;
+
+ final BinaryHunk preImage = new BinaryHunk(fh, c);
+ final int oEnd = preImage.parseHunk(c, end);
+ if (oEnd >= 0) {
+ c = oEnd;
+ preImage.endOffset = c;
+ fh.reverseBinaryHunk = preImage;
+ }
+
+ return c;
+ }
+
+ void warn(final byte[] buf, final int ptr, final String msg) {
+ addError(new FormatError(buf, ptr, FormatError.Severity.WARNING, msg));
+ }
+
+ void error(final byte[] buf, final int ptr, final String msg) {
+ addError(new FormatError(buf, ptr, FormatError.Severity.ERROR, msg));
+ }
+
+ private static boolean matchAny(final byte[] buf, final int c,
+ final byte[][] srcs) {
+ for (final byte[] s : srcs) {
+ if (match(buf, c, s) >= 0)
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java
new file mode 100644
index 0000000000..f872ae0a40
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revplot;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevFlag;
+
+/**
+ * Basic commit graph renderer for graphical user interfaces.
+ * <p>
+ * Lanes are drawn as columns left-to-right in the graph, and the commit short
+ * message is drawn to the right of the lane lines for this cell. It is assumed
+ * that the commits are being drawn as rows of some sort of table.
+ * <p>
+ * Client applications can subclass this implementation to provide the necessary
+ * drawing primitives required to display a commit graph. Most of the graph
+ * layout is handled by this class, allowing applications to implement only a
+ * handful of primitive stubs.
+ * <p>
+ * This class is suitable for us within an AWT TableCellRenderer or within a SWT
+ * PaintListener registered on a Table instance. It is meant to rubber stamp the
+ * graphics necessary for one row of a plotted commit list.
+ * <p>
+ * Subclasses should call {@link #paintCommit(PlotCommit, int)} after they have
+ * otherwise configured their instance to draw one commit into the current
+ * location.
+ * <p>
+ * All drawing methods assume the coordinate space for the current commit's cell
+ * starts at (upper left corner is) 0,0. If this is not true (like say in SWT)
+ * the implementation must perform the cell offset computations within the
+ * various draw methods.
+ *
+ * @param <TLane>
+ * type of lane being used by the application.
+ * @param <TColor>
+ * type of color object used by the graphics library.
+ */
+public abstract class AbstractPlotRenderer<TLane extends PlotLane, TColor> {
+ private static final int LANE_WIDTH = 14;
+
+ private static final int LINE_WIDTH = 2;
+
+ private static final int LEFT_PAD = 2;
+
+ /**
+ * Paint one commit using the underlying graphics library.
+ *
+ * @param commit
+ * the commit to render in this cell. Must not be null.
+ * @param h
+ * total height (in pixels) of this cell.
+ */
+ protected void paintCommit(final PlotCommit<TLane> commit, final int h) {
+ final int dotSize = computeDotSize(h);
+ final TLane myLane = commit.getLane();
+ final int myLaneX = laneC(myLane);
+ final TColor myColor = laneColor(myLane);
+
+ int maxCenter = 0;
+ for (final TLane passingLane : (TLane[]) commit.passingLanes) {
+ final int cx = laneC(passingLane);
+ final TColor c = laneColor(passingLane);
+ drawLine(c, cx, 0, cx, h, LINE_WIDTH);
+ maxCenter = Math.max(maxCenter, cx);
+ }
+
+ final int nParent = commit.getParentCount();
+ for (int i = 0; i < nParent; i++) {
+ final PlotCommit<TLane> p;
+ final TLane pLane;
+ final TColor pColor;
+ final int cx;
+
+ p = (PlotCommit<TLane>) commit.getParent(i);
+ pLane = p.getLane();
+ if (pLane == null)
+ continue;
+
+ pColor = laneColor(pLane);
+ cx = laneC(pLane);
+
+ if (Math.abs(myLaneX - cx) > LANE_WIDTH) {
+ if (myLaneX < cx) {
+ final int ix = cx - LANE_WIDTH / 2;
+ drawLine(pColor, myLaneX, h / 2, ix, h / 2, LINE_WIDTH);
+ drawLine(pColor, ix, h / 2, cx, h, LINE_WIDTH);
+ } else {
+ final int ix = cx + LANE_WIDTH / 2;
+ drawLine(pColor, myLaneX, h / 2, ix, h / 2, LINE_WIDTH);
+ drawLine(pColor, ix, h / 2, cx, h, LINE_WIDTH);
+ }
+ } else {
+ drawLine(pColor, myLaneX, h / 2, cx, h, LINE_WIDTH);
+ }
+ maxCenter = Math.max(maxCenter, cx);
+ }
+
+ final int dotX = myLaneX - dotSize / 2 - 1;
+ final int dotY = (h - dotSize) / 2;
+
+ if (commit.getChildCount() > 0)
+ drawLine(myColor, myLaneX, 0, myLaneX, dotY, LINE_WIDTH);
+
+ if (commit.has(RevFlag.UNINTERESTING))
+ drawBoundaryDot(dotX, dotY, dotSize, dotSize);
+ else
+ drawCommitDot(dotX, dotY, dotSize, dotSize);
+
+ int textx = Math.max(maxCenter + LANE_WIDTH / 2, dotX + dotSize) + 8;
+ int n = commit.refs == null ? 0 : commit.refs.length;
+ for (int i = 0; i < n; ++i) {
+ textx += drawLabel(textx + dotSize, h/2, commit.refs[i]);
+ }
+
+ final String msg = commit.getShortMessage();
+ drawText(msg, textx + dotSize + n*2, h / 2);
+ }
+
+ /**
+ * Draw a decoration for the Ref ref at x,y
+ *
+ * @param x
+ * left
+ * @param y
+ * top
+ * @param ref
+ * A peeled ref
+ * @return width of label in pixels
+ */
+ protected abstract int drawLabel(int x, int y, Ref ref);
+
+ private int computeDotSize(final int h) {
+ int d = (int) (Math.min(h, LANE_WIDTH) * 0.50f);
+ d += (d & 1);
+ return d;
+ }
+
+ /**
+ * Obtain the color reference used to paint this lane.
+ * <p>
+ * Colors returned by this method will be passed to the other drawing
+ * primitives, so the color returned should be application specific.
+ * <p>
+ * If a null lane is supplied the return value must still be acceptable to a
+ * drawing method. Usually this means the implementation should return a
+ * default color.
+ *
+ * @param myLane
+ * the current lane. May be null.
+ * @return graphics specific color reference. Must be a valid color.
+ */
+ protected abstract TColor laneColor(TLane myLane);
+
+ /**
+ * Draw a single line within this cell.
+ *
+ * @param color
+ * the color to use while drawing the line.
+ * @param x1
+ * starting X coordinate, 0 based.
+ * @param y1
+ * starting Y coordinate, 0 based.
+ * @param x2
+ * ending X coordinate, 0 based.
+ * @param y2
+ * ending Y coordinate, 0 based.
+ * @param width
+ * number of pixels wide for the line. Always at least 1.
+ */
+ protected abstract void drawLine(TColor color, int x1, int y1, int x2,
+ int y2, int width);
+
+ /**
+ * Draw a single commit dot.
+ * <p>
+ * Usually the commit dot is a filled oval in blue, then a drawn oval in
+ * black, using the same coordinates for both operations.
+ *
+ * @param x
+ * upper left of the oval's bounding box.
+ * @param y
+ * upper left of the oval's bounding box.
+ * @param w
+ * width of the oval's bounding box.
+ * @param h
+ * height of the oval's bounding box.
+ */
+ protected abstract void drawCommitDot(int x, int y, int w, int h);
+
+ /**
+ * Draw a single boundary commit (aka uninteresting commit) dot.
+ * <p>
+ * Usually a boundary commit dot is a light gray oval with a white center.
+ *
+ * @param x
+ * upper left of the oval's bounding box.
+ * @param y
+ * upper left of the oval's bounding box.
+ * @param w
+ * width of the oval's bounding box.
+ * @param h
+ * height of the oval's bounding box.
+ */
+ protected abstract void drawBoundaryDot(int x, int y, int w, int h);
+
+ /**
+ * Draw a single line of text.
+ * <p>
+ * The font and colors used to render the text are left up to the
+ * implementation.
+ *
+ * @param msg
+ * the text to draw. Does not contain LFs.
+ * @param x
+ * first pixel from the left that the text can be drawn at.
+ * Character data must not appear before this position.
+ * @param y
+ * pixel coordinate of the centerline of the text.
+ * Implementations must adjust this coordinate to account for the
+ * way their implementation handles font rendering.
+ */
+ protected abstract void drawText(String msg, int x, int y);
+
+ private int laneX(final PlotLane myLane) {
+ final int p = myLane != null ? myLane.getPosition() : 0;
+ return LEFT_PAD + LANE_WIDTH * p;
+ }
+
+ private int laneC(final PlotLane myLane) {
+ return laneX(myLane) + LANE_WIDTH / 2;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java
new file mode 100644
index 0000000000..54d7c013d6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revplot;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.lib.Ref;
+
+/**
+ * A commit reference to a commit in the DAG.
+ *
+ * @param <L>
+ * type of lane being used by the plotter.
+ * @see PlotCommitList
+ */
+public class PlotCommit<L extends PlotLane> extends RevCommit {
+ static final PlotCommit[] NO_CHILDREN = {};
+
+ static final PlotLane[] NO_LANES = {};
+
+ PlotLane[] passingLanes;
+
+ PlotLane lane;
+
+ PlotCommit[] children;
+
+ final Ref[] refs;
+
+ /**
+ * Create a new commit.
+ *
+ * @param id
+ * the identity of this commit.
+ * @param tags
+ * the tags associated with this commit, null for no tags
+ */
+ protected PlotCommit(final AnyObjectId id, final Ref[] tags) {
+ super(id);
+ this.refs = tags;
+ passingLanes = NO_LANES;
+ children = NO_CHILDREN;
+ }
+
+ void addPassingLane(final PlotLane c) {
+ final int cnt = passingLanes.length;
+ if (cnt == 0)
+ passingLanes = new PlotLane[] { c };
+ else if (cnt == 1)
+ passingLanes = new PlotLane[] { passingLanes[0], c };
+ else {
+ final PlotLane[] n = new PlotLane[cnt + 1];
+ System.arraycopy(passingLanes, 0, n, 0, cnt);
+ n[cnt] = c;
+ passingLanes = n;
+ }
+ }
+
+ void addChild(final PlotCommit c) {
+ final int cnt = children.length;
+ if (cnt == 0)
+ children = new PlotCommit[] { c };
+ else if (cnt == 1)
+ children = new PlotCommit[] { children[0], c };
+ else {
+ final PlotCommit[] n = new PlotCommit[cnt + 1];
+ System.arraycopy(children, 0, n, 0, cnt);
+ n[cnt] = c;
+ children = n;
+ }
+ }
+
+ /**
+ * Get the number of child commits listed in this commit.
+ *
+ * @return number of children; always a positive value but can be 0.
+ */
+ public final int getChildCount() {
+ return children.length;
+ }
+
+ /**
+ * Get the nth child from this commit's child list.
+ *
+ * @param nth
+ * child index to obtain. Must be in the range 0 through
+ * {@link #getChildCount()}-1.
+ * @return the specified child.
+ * @throws ArrayIndexOutOfBoundsException
+ * an invalid child index was specified.
+ */
+ public final PlotCommit getChild(final int nth) {
+ return children[nth];
+ }
+
+ /**
+ * Determine if the given commit is a child (descendant) of this commit.
+ *
+ * @param c
+ * the commit to test.
+ * @return true if the given commit built on top of this commit.
+ */
+ public final boolean isChild(final PlotCommit c) {
+ for (final PlotCommit a : children)
+ if (a == c)
+ return true;
+ return false;
+ }
+
+ /**
+ * Obtain the lane this commit has been plotted into.
+ *
+ * @return the assigned lane for this commit.
+ */
+ public final L getLane() {
+ return (L) lane;
+ }
+
+ @Override
+ public void reset() {
+ passingLanes = NO_LANES;
+ children = NO_CHILDREN;
+ lane = null;
+ super.reset();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java
new file mode 100644
index 0000000000..7c27a86f49
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revplot;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.TreeSet;
+
+import org.eclipse.jgit.revwalk.RevCommitList;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * An ordered list of {@link PlotCommit} subclasses.
+ * <p>
+ * Commits are allocated into lanes as they enter the list, based upon their
+ * connections between descendant (child) commits and ancestor (parent) commits.
+ * <p>
+ * The source of the list must be a {@link PlotWalk} and {@link #fillTo(int)}
+ * must be used to populate the list.
+ *
+ * @param <L>
+ * type of lane used by the application.
+ */
+public class PlotCommitList<L extends PlotLane> extends
+ RevCommitList<PlotCommit<L>> {
+ static final int MAX_LENGTH = 25;
+
+ private int lanesAllocated;
+
+ private final TreeSet<Integer> freeLanes = new TreeSet<Integer>();
+
+ private HashSet<PlotLane> activeLanes = new HashSet<PlotLane>(32);
+
+ @Override
+ public void source(final RevWalk w) {
+ if (!(w instanceof PlotWalk))
+ throw new ClassCastException("Not a " + PlotWalk.class.getName());
+ super.source(w);
+ }
+
+ /**
+ * Find the set of lanes passing through a commit's row.
+ * <p>
+ * Lanes passing through a commit are lanes that the commit is not directly
+ * on, but that need to travel through this commit to connect a descendant
+ * (child) commit to an ancestor (parent) commit. Typically these lanes will
+ * be drawn as lines in the passed commit's box, and the passed commit won't
+ * appear to be connected to those lines.
+ * <p>
+ * This method modifies the passed collection by adding the lanes in any
+ * order.
+ *
+ * @param currCommit
+ * the commit the caller needs to get the lanes from.
+ * @param result
+ * collection to add the passing lanes into.
+ */
+ public void findPassingThrough(final PlotCommit<L> currCommit,
+ final Collection<L> result) {
+ for (final PlotLane p : currCommit.passingLanes)
+ result.add((L) p);
+ }
+
+ @Override
+ protected void enter(final int index, final PlotCommit<L> currCommit) {
+ setupChildren(currCommit);
+
+ final int nChildren = currCommit.getChildCount();
+ if (nChildren == 0)
+ return;
+
+ if (nChildren == 1 && currCommit.children[0].getParentCount() < 2) {
+ // Only one child, child has only us as their parent.
+ // Stay in the same lane as the child.
+ //
+ final PlotCommit c = currCommit.children[0];
+ if (c.lane == null) {
+ // Hmmph. This child must be the first along this lane.
+ //
+ c.lane = nextFreeLane();
+ activeLanes.add(c.lane);
+ }
+
+ for (int r = index - 1; r >= 0; r--) {
+ final PlotCommit rObj = get(r);
+ if (rObj == c)
+ break;
+ rObj.addPassingLane(c.lane);
+ }
+ currCommit.lane = c.lane;
+ currCommit.lane.parent = currCommit;
+ } else {
+ // More than one child, or our child is a merge.
+ // Use a different lane.
+ //
+
+ for (int i = 0; i < nChildren; i++) {
+ final PlotCommit c = currCommit.children[i];
+ if (activeLanes.remove(c.lane)) {
+ recycleLane((L) c.lane);
+ freeLanes.add(Integer.valueOf(c.lane.position));
+ }
+ }
+
+ currCommit.lane = nextFreeLane();
+ currCommit.lane.parent = currCommit;
+ activeLanes.add(currCommit.lane);
+
+ int remaining = nChildren;
+ for (int r = index - 1; r >= 0; r--) {
+ final PlotCommit rObj = get(r);
+ if (currCommit.isChild(rObj)) {
+ if (--remaining == 0)
+ break;
+ }
+ rObj.addPassingLane(currCommit.lane);
+ }
+ }
+ }
+
+ private void setupChildren(final PlotCommit<L> currCommit) {
+ final int nParents = currCommit.getParentCount();
+ for (int i = 0; i < nParents; i++)
+ ((PlotCommit) currCommit.getParent(i)).addChild(currCommit);
+ }
+
+ private PlotLane nextFreeLane() {
+ final PlotLane p = createLane();
+ if (freeLanes.isEmpty()) {
+ p.position = lanesAllocated++;
+ } else {
+ final Integer min = freeLanes.first();
+ p.position = min.intValue();
+ freeLanes.remove(min);
+ }
+ return p;
+ }
+
+ /**
+ * @return a new Lane appropriate for this particular PlotList.
+ */
+ protected L createLane() {
+ return (L) new PlotLane();
+ }
+
+ /**
+ * Return colors and other reusable information to the plotter when a lane
+ * is no longer needed.
+ *
+ * @param lane
+ */
+ protected void recycleLane(final L lane) {
+ // Nothing.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotLane.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotLane.java
new file mode 100644
index 0000000000..45dd9960df
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotLane.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revplot;
+
+/**
+ * A line space within the graph.
+ * <p>
+ * Commits are strung onto a lane. For many UIs a lane represents a column.
+ */
+public class PlotLane {
+ PlotCommit parent;
+
+ int position;
+
+ /**
+ * Logical location of this lane within the graphing plane.
+ *
+ * @return location of this lane, 0 through the maximum number of lanes.
+ */
+ public int getPosition() {
+ return position;
+ }
+
+ public int hashCode() {
+ return position;
+ }
+
+ public boolean equals(final Object o) {
+ return o == this;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
new file mode 100644
index 0000000000..bebe148ebb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revplot;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Commit;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Tag;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Specialized RevWalk for visualization of a commit graph. */
+public class PlotWalk extends RevWalk {
+
+ private Map<AnyObjectId, Set<Ref>> reverseRefMap;
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ reverseRefMap.clear();
+ }
+
+ /**
+ * Create a new revision walker for a given repository.
+ *
+ * @param repo
+ * the repository the walker will obtain data from.
+ */
+ public PlotWalk(final Repository repo) {
+ super(repo);
+ super.sort(RevSort.TOPO, true);
+ reverseRefMap = repo.getAllRefsByPeeledObjectId();
+ }
+
+ @Override
+ public void sort(final RevSort s, final boolean use) {
+ if (s == RevSort.TOPO && !use)
+ throw new IllegalArgumentException("Topological sort required.");
+ super.sort(s, use);
+ }
+
+ @Override
+ protected RevCommit createCommit(final AnyObjectId id) {
+ return new PlotCommit(id, getTags(id));
+ }
+
+ /**
+ * @param commitId
+ * @return return the list of knows tags referring to this commit
+ */
+ protected Ref[] getTags(final AnyObjectId commitId) {
+ Collection<Ref> list = reverseRefMap.get(commitId);
+ Ref[] tags;
+ if (list == null)
+ tags = null;
+ else {
+ tags = list.toArray(new Ref[list.size()]);
+ Arrays.sort(tags, new PlotRefComparator());
+ }
+ return tags;
+ }
+
+ class PlotRefComparator implements Comparator<Ref> {
+ public int compare(Ref o1, Ref o2) {
+ try {
+ Object obj1 = getRepository().mapObject(o1.getObjectId(), o1.getName());
+ Object obj2 = getRepository().mapObject(o2.getObjectId(), o2.getName());
+ long t1 = timeof(obj1);
+ long t2 = timeof(obj2);
+ if (t1 > t2)
+ return -1;
+ if (t1 < t2)
+ return 1;
+ return 0;
+ } catch (IOException e) {
+ // ignore
+ return 0;
+ }
+ }
+ long timeof(Object o) {
+ if (o instanceof Commit)
+ return ((Commit)o).getCommitter().getWhen().getTime();
+ if (o instanceof Tag)
+ return ((Tag)o).getTagger().getWhen().getTime();
+ return 0;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java
new file mode 100644
index 0000000000..30d29a80b4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+abstract class AbstractRevQueue extends Generator {
+ static final AbstractRevQueue EMPTY_QUEUE = new AlwaysEmptyQueue();
+
+ /** Current output flags set for this generator instance. */
+ int outputType;
+
+ /**
+ * Add a commit to the queue.
+ * <p>
+ * This method always adds the commit, even if it is already in the queue or
+ * previously was in the queue but has already been removed. To control
+ * queue admission use {@link #add(RevCommit, RevFlag)}.
+ *
+ * @param c
+ * commit to add.
+ */
+ public abstract void add(RevCommit c);
+
+ /**
+ * Add a commit if it does not have a flag set yet, then set the flag.
+ * <p>
+ * This method permits the application to test if the commit has the given
+ * flag; if it does not already have the flag than the commit is added to
+ * the queue and the flag is set. This later will prevent the commit from
+ * being added twice.
+ *
+ * @param c
+ * commit to add.
+ * @param queueControl
+ * flag that controls admission to the queue.
+ */
+ public final void add(final RevCommit c, final RevFlag queueControl) {
+ if (!c.has(queueControl)) {
+ c.add(queueControl);
+ add(c);
+ }
+ }
+
+ /**
+ * Add a commit's parents if one does not have a flag set yet.
+ * <p>
+ * This method permits the application to test if the commit has the given
+ * flag; if it does not already have the flag than the commit is added to
+ * the queue and the flag is set. This later will prevent the commit from
+ * being added twice.
+ *
+ * @param c
+ * commit whose parents should be added.
+ * @param queueControl
+ * flag that controls admission to the queue.
+ */
+ public final void addParents(final RevCommit c, final RevFlag queueControl) {
+ final RevCommit[] pList = c.parents;
+ if (pList == null)
+ return;
+ for (RevCommit p : pList)
+ add(p, queueControl);
+ }
+
+ /**
+ * Remove the first commit from the queue.
+ *
+ * @return the first commit of this queue.
+ */
+ public abstract RevCommit next();
+
+ /** Remove all entries from this queue. */
+ public abstract void clear();
+
+ abstract boolean everbodyHasFlag(int f);
+
+ abstract boolean anybodyHasFlag(int f);
+
+ @Override
+ int outputType() {
+ return outputType;
+ }
+
+ protected static void describe(final StringBuilder s, final RevCommit c) {
+ s.append(c.toString());
+ s.append('\n');
+ }
+
+ private static class AlwaysEmptyQueue extends AbstractRevQueue {
+ @Override
+ public void add(RevCommit c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public RevCommit next() {
+ return null;
+ }
+
+ @Override
+ boolean anybodyHasFlag(int f) {
+ return false;
+ }
+
+ @Override
+ boolean everbodyHasFlag(int f) {
+ return true;
+ }
+
+ @Override
+ public void clear() {
+ // Nothing to clear, we have no state.
+ }
+
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockObjQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockObjQueue.java
new file mode 100644
index 0000000000..371cd06dda
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockObjQueue.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+class BlockObjQueue {
+ private BlockFreeList free;
+
+ private Block head;
+
+ private Block tail;
+
+ /** Create an empty queue. */
+ BlockObjQueue() {
+ free = new BlockFreeList();
+ }
+
+ void add(final RevObject c) {
+ Block b = tail;
+ if (b == null) {
+ b = free.newBlock();
+ b.add(c);
+ head = b;
+ tail = b;
+ return;
+ } else if (b.isFull()) {
+ b = free.newBlock();
+ tail.next = b;
+ tail = b;
+ }
+ b.add(c);
+ }
+
+ RevObject next() {
+ final Block b = head;
+ if (b == null)
+ return null;
+
+ final RevObject c = b.pop();
+ if (b.isEmpty()) {
+ head = b.next;
+ if (head == null)
+ tail = null;
+ free.freeBlock(b);
+ }
+ return c;
+ }
+
+ static final class BlockFreeList {
+ private Block next;
+
+ Block newBlock() {
+ Block b = next;
+ if (b == null)
+ return new Block();
+ next = b.next;
+ b.clear();
+ return b;
+ }
+
+ void freeBlock(final Block b) {
+ b.next = next;
+ next = b;
+ }
+ }
+
+ static final class Block {
+ private static final int BLOCK_SIZE = 256;
+
+ /** Next block in our chain of blocks; null if we are the last. */
+ Block next;
+
+ /** Our table of queued objects. */
+ final RevObject[] objects = new RevObject[BLOCK_SIZE];
+
+ /** Next valid entry in {@link #objects}. */
+ int headIndex;
+
+ /** Next free entry in {@link #objects} for addition at. */
+ int tailIndex;
+
+ boolean isFull() {
+ return tailIndex == BLOCK_SIZE;
+ }
+
+ boolean isEmpty() {
+ return headIndex == tailIndex;
+ }
+
+ void add(final RevObject c) {
+ objects[tailIndex++] = c;
+ }
+
+ RevObject pop() {
+ return objects[headIndex++];
+ }
+
+ void clear() {
+ next = null;
+ headIndex = 0;
+ tailIndex = 0;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java
new file mode 100644
index 0000000000..5e7a7998e0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+abstract class BlockRevQueue extends AbstractRevQueue {
+ protected BlockFreeList free;
+
+ /** Create an empty revision queue. */
+ protected BlockRevQueue() {
+ free = new BlockFreeList();
+ }
+
+ BlockRevQueue(final Generator s) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ free = new BlockFreeList();
+ outputType = s.outputType();
+ s.shareFreeList(this);
+ for (;;) {
+ final RevCommit c = s.next();
+ if (c == null)
+ break;
+ add(c);
+ }
+ }
+
+ /**
+ * Reconfigure this queue to share the same free list as another.
+ * <p>
+ * Multiple revision queues can be connected to the same free list, making
+ * it less expensive for applications to shuttle commits between them. This
+ * method arranges for the receiver to take from / return to the same free
+ * list as the supplied queue.
+ * <p>
+ * Free lists are not thread-safe. Applications must ensure that all queues
+ * sharing the same free list are doing so from only a single thread.
+ *
+ * @param q
+ * the other queue we will steal entries from.
+ */
+ public void shareFreeList(final BlockRevQueue q) {
+ free = q.free;
+ }
+
+ static final class BlockFreeList {
+ private Block next;
+
+ Block newBlock() {
+ Block b = next;
+ if (b == null)
+ return new Block();
+ next = b.next;
+ b.clear();
+ return b;
+ }
+
+ void freeBlock(final Block b) {
+ b.next = next;
+ next = b;
+ }
+
+ void clear() {
+ next = null;
+ }
+ }
+
+ static final class Block {
+ static final int BLOCK_SIZE = 256;
+
+ /** Next block in our chain of blocks; null if we are the last. */
+ Block next;
+
+ /** Our table of queued commits. */
+ final RevCommit[] commits = new RevCommit[BLOCK_SIZE];
+
+ /** Next valid entry in {@link #commits}. */
+ int headIndex;
+
+ /** Next free entry in {@link #commits} for addition at. */
+ int tailIndex;
+
+ boolean isFull() {
+ return tailIndex == BLOCK_SIZE;
+ }
+
+ boolean isEmpty() {
+ return headIndex == tailIndex;
+ }
+
+ boolean canUnpop() {
+ return headIndex > 0;
+ }
+
+ void add(final RevCommit c) {
+ commits[tailIndex++] = c;
+ }
+
+ void unpop(final RevCommit c) {
+ commits[--headIndex] = c;
+ }
+
+ RevCommit pop() {
+ return commits[headIndex++];
+ }
+
+ RevCommit peek() {
+ return commits[headIndex];
+ }
+
+ void clear() {
+ next = null;
+ headIndex = 0;
+ tailIndex = 0;
+ }
+
+ void resetToMiddle() {
+ headIndex = tailIndex = BLOCK_SIZE / 2;
+ }
+
+ void resetToEnd() {
+ headIndex = tailIndex = BLOCK_SIZE;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BoundaryGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BoundaryGenerator.java
new file mode 100644
index 0000000000..6be0c8584e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BoundaryGenerator.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+class BoundaryGenerator extends Generator {
+ static final int UNINTERESTING = RevWalk.UNINTERESTING;
+
+ Generator g;
+
+ BoundaryGenerator(final RevWalk w, final Generator s) {
+ g = new InitialGenerator(w, s);
+ }
+
+ @Override
+ int outputType() {
+ return g.outputType() | HAS_UNINTERESTING;
+ }
+
+ @Override
+ void shareFreeList(final BlockRevQueue q) {
+ g.shareFreeList(q);
+ }
+
+ @Override
+ RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ return g.next();
+ }
+
+ private class InitialGenerator extends Generator {
+ private static final int PARSED = RevWalk.PARSED;
+
+ private static final int DUPLICATE = RevWalk.TEMP_MARK;
+
+ private final RevWalk walk;
+
+ private final FIFORevQueue held;
+
+ private final Generator source;
+
+ InitialGenerator(final RevWalk w, final Generator s) {
+ walk = w;
+ held = new FIFORevQueue();
+ source = s;
+ source.shareFreeList(held);
+ }
+
+ @Override
+ int outputType() {
+ return source.outputType();
+ }
+
+ @Override
+ void shareFreeList(final BlockRevQueue q) {
+ q.shareFreeList(held);
+ }
+
+ @Override
+ RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ RevCommit c = source.next();
+ if (c != null) {
+ for (final RevCommit p : c.parents)
+ if ((p.flags & UNINTERESTING) != 0)
+ held.add(p);
+ return c;
+ }
+
+ final FIFORevQueue boundary = new FIFORevQueue();
+ boundary.shareFreeList(held);
+ for (;;) {
+ c = held.next();
+ if (c == null)
+ break;
+ if ((c.flags & DUPLICATE) != 0)
+ continue;
+ if ((c.flags & PARSED) == 0)
+ c.parseHeaders(walk);
+ c.flags |= DUPLICATE;
+ boundary.add(c);
+ }
+ boundary.removeFlag(DUPLICATE);
+ g = boundary;
+ return boundary.next();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java
new file mode 100644
index 0000000000..6ce63fe168
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/** A queue of commits sorted by commit time order. */
+public class DateRevQueue extends AbstractRevQueue {
+ private Entry head;
+
+ private Entry free;
+
+ /** Create an empty date queue. */
+ public DateRevQueue() {
+ super();
+ }
+
+ DateRevQueue(final Generator s) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ for (;;) {
+ final RevCommit c = s.next();
+ if (c == null)
+ break;
+ add(c);
+ }
+ }
+
+ public void add(final RevCommit c) {
+ Entry q = head;
+ final long when = c.commitTime;
+ final Entry n = newEntry(c);
+ if (q == null || when > q.commit.commitTime) {
+ n.next = q;
+ head = n;
+ } else {
+ Entry p = q.next;
+ while (p != null && p.commit.commitTime > when) {
+ q = p;
+ p = q.next;
+ }
+ n.next = q.next;
+ q.next = n;
+ }
+ }
+
+ public RevCommit next() {
+ final Entry q = head;
+ if (q == null)
+ return null;
+ head = q.next;
+ freeEntry(q);
+ return q.commit;
+ }
+
+ /**
+ * Peek at the next commit, without removing it.
+ *
+ * @return the next available commit; null if there are no commits left.
+ */
+ public RevCommit peek() {
+ return head != null ? head.commit : null;
+ }
+
+ public void clear() {
+ head = null;
+ free = null;
+ }
+
+ boolean everbodyHasFlag(final int f) {
+ for (Entry q = head; q != null; q = q.next) {
+ if ((q.commit.flags & f) == 0)
+ return false;
+ }
+ return true;
+ }
+
+ boolean anybodyHasFlag(final int f) {
+ for (Entry q = head; q != null; q = q.next) {
+ if ((q.commit.flags & f) != 0)
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ int outputType() {
+ return outputType | SORT_COMMIT_TIME_DESC;
+ }
+
+ public String toString() {
+ final StringBuilder s = new StringBuilder();
+ for (Entry q = head; q != null; q = q.next)
+ describe(s, q.commit);
+ return s.toString();
+ }
+
+ private Entry newEntry(final RevCommit c) {
+ Entry r = free;
+ if (r == null)
+ r = new Entry();
+ else
+ free = r.next;
+ r.commit = c;
+ return r;
+ }
+
+ private void freeEntry(final Entry e) {
+ e.next = free;
+ free = e;
+ }
+
+ static class Entry {
+ Entry next;
+
+ RevCommit commit;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DelayRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DelayRevQueue.java
new file mode 100644
index 0000000000..4a0d19d60d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DelayRevQueue.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/**
+ * Delays commits to be at least {@link PendingGenerator#OVER_SCAN} late.
+ * <p>
+ * This helps to "fix up" weird corner cases resulting from clock skew, by
+ * slowing down what we produce to the caller we get a better chance to ensure
+ * PendingGenerator reached back far enough in the graph to correctly mark
+ * commits {@link RevWalk#UNINTERESTING} if necessary.
+ * <p>
+ * This generator should appear before {@link FixUninterestingGenerator} if the
+ * lower level {@link #pending} isn't already fully buffered.
+ */
+final class DelayRevQueue extends Generator {
+ private static final int OVER_SCAN = PendingGenerator.OVER_SCAN;
+
+ private final Generator pending;
+
+ private final FIFORevQueue delay;
+
+ private int size;
+
+ DelayRevQueue(final Generator g) {
+ pending = g;
+ delay = new FIFORevQueue();
+ }
+
+ @Override
+ int outputType() {
+ return pending.outputType();
+ }
+
+ @Override
+ RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ while (size < OVER_SCAN) {
+ final RevCommit c = pending.next();
+ if (c == null)
+ break;
+ delay.add(c);
+ size++;
+ }
+
+ final RevCommit c = delay.next();
+ if (c == null)
+ return null;
+ size--;
+ return c;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/EndGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/EndGenerator.java
new file mode 100644
index 0000000000..627e1c7a51
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/EndGenerator.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+class EndGenerator extends Generator {
+ static final EndGenerator INSTANCE = new EndGenerator();
+
+ private EndGenerator() {
+ // We have nothing to initialize.
+ }
+
+ @Override
+ RevCommit next() {
+ return null;
+ }
+
+ @Override
+ int outputType() {
+ return 0;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java
new file mode 100644
index 0000000000..5690a5d869
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/** A queue of commits in FIFO order. */
+public class FIFORevQueue extends BlockRevQueue {
+ private Block head;
+
+ private Block tail;
+
+ /** Create an empty FIFO queue. */
+ public FIFORevQueue() {
+ super();
+ }
+
+ FIFORevQueue(final Generator s) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ super(s);
+ }
+
+ public void add(final RevCommit c) {
+ Block b = tail;
+ if (b == null) {
+ b = free.newBlock();
+ b.add(c);
+ head = b;
+ tail = b;
+ return;
+ } else if (b.isFull()) {
+ b = free.newBlock();
+ tail.next = b;
+ tail = b;
+ }
+ b.add(c);
+ }
+
+ /**
+ * Insert the commit pointer at the front of the queue.
+ *
+ * @param c
+ * the commit to insert into the queue.
+ */
+ public void unpop(final RevCommit c) {
+ Block b = head;
+ if (b == null) {
+ b = free.newBlock();
+ b.resetToMiddle();
+ b.add(c);
+ head = b;
+ tail = b;
+ return;
+ } else if (b.canUnpop()) {
+ b.unpop(c);
+ return;
+ }
+
+ b = free.newBlock();
+ b.resetToEnd();
+ b.unpop(c);
+ b.next = head;
+ head = b;
+ }
+
+ public RevCommit next() {
+ final Block b = head;
+ if (b == null)
+ return null;
+
+ final RevCommit c = b.pop();
+ if (b.isEmpty()) {
+ head = b.next;
+ if (head == null)
+ tail = null;
+ free.freeBlock(b);
+ }
+ return c;
+ }
+
+ public void clear() {
+ head = null;
+ tail = null;
+ free.clear();
+ }
+
+ boolean everbodyHasFlag(final int f) {
+ for (Block b = head; b != null; b = b.next) {
+ for (int i = b.headIndex; i < b.tailIndex; i++)
+ if ((b.commits[i].flags & f) == 0)
+ return false;
+ }
+ return true;
+ }
+
+ boolean anybodyHasFlag(final int f) {
+ for (Block b = head; b != null; b = b.next) {
+ for (int i = b.headIndex; i < b.tailIndex; i++)
+ if ((b.commits[i].flags & f) != 0)
+ return true;
+ }
+ return false;
+ }
+
+ void removeFlag(final int f) {
+ final int not_f = ~f;
+ for (Block b = head; b != null; b = b.next) {
+ for (int i = b.headIndex; i < b.tailIndex; i++)
+ b.commits[i].flags &= not_f;
+ }
+ }
+
+ public String toString() {
+ final StringBuilder s = new StringBuilder();
+ for (Block q = head; q != null; q = q.next) {
+ for (int i = q.headIndex; i < q.tailIndex; i++)
+ describe(s, q.commits[i]);
+ }
+ return s.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FixUninterestingGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FixUninterestingGenerator.java
new file mode 100644
index 0000000000..9d734a7296
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FixUninterestingGenerator.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/**
+ * Filters out commits marked {@link RevWalk#UNINTERESTING}.
+ * <p>
+ * This generator is only in front of another generator that has fully buffered
+ * commits, such that we are called only after the {@link PendingGenerator} has
+ * exhausted its input queue and given up. It skips over any uninteresting
+ * commits that may have leaked out of the PendingGenerator due to clock skew
+ * being detected in the commit objects.
+ */
+final class FixUninterestingGenerator extends Generator {
+ private final Generator pending;
+
+ FixUninterestingGenerator(final Generator g) {
+ pending = g;
+ }
+
+ @Override
+ int outputType() {
+ return pending.outputType();
+ }
+
+ @Override
+ RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ for (;;) {
+ final RevCommit c = pending.next();
+ if (c == null)
+ return null;
+ if ((c.flags & RevWalk.UNINTERESTING) == 0)
+ return c;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java
new file mode 100644
index 0000000000..97a8ab2ad2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import org.eclipse.jgit.lib.Constants;
+
+/** Case insensitive key for a {@link FooterLine}. */
+public final class FooterKey {
+ /** Standard {@code Signed-off-by} */
+ public static final FooterKey SIGNED_OFF_BY = new FooterKey("Signed-off-by");
+
+ /** Standard {@code Acked-by} */
+ public static final FooterKey ACKED_BY = new FooterKey("Acked-by");
+
+ /** Standard {@code CC} */
+ public static final FooterKey CC = new FooterKey("CC");
+
+ private final String name;
+
+ final byte[] raw;
+
+ /**
+ * Create a key for a specific footer line.
+ *
+ * @param keyName
+ * name of the footer line.
+ */
+ public FooterKey(final String keyName) {
+ name = keyName;
+ raw = Constants.encode(keyName.toLowerCase());
+ }
+
+ /** @return name of this footer line. */
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return "FooterKey[" + name + "]";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
new file mode 100644
index 0000000000..541f2748e7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.nio.charset.Charset;
+
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Single line at the end of a message, such as a "Signed-off-by: someone".
+ * <p>
+ * These footer lines tend to be used to represent additional information about
+ * a commit, like the path it followed through reviewers before finally being
+ * accepted into the project's main repository as an immutable commit.
+ *
+ * @see RevCommit#getFooterLines()
+ */
+public final class FooterLine {
+ private final byte[] buffer;
+
+ private final Charset enc;
+
+ private final int keyStart;
+
+ private final int keyEnd;
+
+ private final int valStart;
+
+ private final int valEnd;
+
+ FooterLine(final byte[] b, final Charset e, final int ks, final int ke,
+ final int vs, final int ve) {
+ buffer = b;
+ enc = e;
+ keyStart = ks;
+ keyEnd = ke;
+ valStart = vs;
+ valEnd = ve;
+ }
+
+ /**
+ * @param key
+ * key to test this line's key name against.
+ * @return true if {@code key.getName().equalsIgnorecase(getKey())}.
+ */
+ public boolean matches(final FooterKey key) {
+ final byte[] kRaw = key.raw;
+ final int len = kRaw.length;
+ int bPtr = keyStart;
+ if (keyEnd - bPtr != len)
+ return false;
+ for (int kPtr = 0; bPtr < len;) {
+ byte b = buffer[bPtr++];
+ if ('A' <= b && b <= 'Z')
+ b += 'a' - 'A';
+ if (b != kRaw[kPtr++])
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return key name of this footer; that is the text before the ":" on the
+ * line footer's line. The text is decoded according to the commit's
+ * specified (or assumed) character encoding.
+ */
+ public String getKey() {
+ return RawParseUtils.decode(enc, buffer, keyStart, keyEnd);
+ }
+
+ /**
+ * @return value of this footer; that is the text after the ":" and any
+ * leading whitespace has been skipped. May be the empty string if
+ * the footer has no value (line ended with ":"). The text is
+ * decoded according to the commit's specified (or assumed)
+ * character encoding.
+ */
+ public String getValue() {
+ return RawParseUtils.decode(enc, buffer, valStart, valEnd);
+ }
+
+ /**
+ * Extract the email address (if present) from the footer.
+ * <p>
+ * If there is an email address looking string inside of angle brackets
+ * (e.g. "<a@b>"), the return value is the part extracted from inside the
+ * brackets. If no brackets are found, then {@link #getValue()} is returned
+ * if the value contains an '@' sign. Otherwise, null.
+ *
+ * @return email address appearing in the value of this footer, or null.
+ */
+ public String getEmailAddress() {
+ final int lt = RawParseUtils.nextLF(buffer, valStart, '<');
+ if (valEnd <= lt) {
+ final int at = RawParseUtils.nextLF(buffer, valStart, '@');
+ if (valStart < at && at < valEnd)
+ return getValue();
+ return null;
+ }
+ final int gt = RawParseUtils.nextLF(buffer, lt, '>');
+ if (valEnd < gt)
+ return null;
+ return RawParseUtils.decode(enc, buffer, lt, gt - 1);
+ }
+
+ @Override
+ public String toString() {
+ return getKey() + ": " + getValue();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java
new file mode 100644
index 0000000000..de9fabc196
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/**
+ * Produces commits for RevWalk to return to applications.
+ * <p>
+ * Implementations of this basic class provide the real work behind RevWalk.
+ * Conceptually a Generator is an iterator or a queue, it returns commits until
+ * there are no more relevant. Generators may be piped/stacked together to
+ * create a more complex set of operations.
+ *
+ * @see PendingGenerator
+ * @see StartGenerator
+ */
+abstract class Generator {
+ /** Commits are sorted by commit date and time, descending. */
+ static final int SORT_COMMIT_TIME_DESC = 1 << 0;
+
+ /** Output may have {@link RevWalk#REWRITE} marked on it. */
+ static final int HAS_REWRITE = 1 << 1;
+
+ /** Output needs {@link RewriteGenerator}. */
+ static final int NEEDS_REWRITE = 1 << 2;
+
+ /** Topological ordering is enforced (all children before parents). */
+ static final int SORT_TOPO = 1 << 3;
+
+ /** Output may have {@link RevWalk#UNINTERESTING} marked on it. */
+ static final int HAS_UNINTERESTING = 1 << 4;
+
+ /**
+ * Connect the supplied queue to this generator's own free list (if any).
+ *
+ * @param q
+ * another FIFO queue that wants to share our queue's free list.
+ */
+ void shareFreeList(final BlockRevQueue q) {
+ // Do nothing by default.
+ }
+
+ /**
+ * Obtain flags describing the output behavior of this generator.
+ *
+ * @return one or more of the constants declared in this class, describing
+ * how this generator produces its results.
+ */
+ abstract int outputType();
+
+ /**
+ * Return the next commit to the application, or the next generator.
+ *
+ * @return next available commit; null if no more are to be returned.
+ * @throws MissingObjectException
+ * @throws IncorrectObjectTypeException
+ * @throws IOException
+ */
+ abstract RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java
new file mode 100644
index 0000000000..9abaf8dccf
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/** A queue of commits in LIFO order. */
+public class LIFORevQueue extends BlockRevQueue {
+ private Block head;
+
+ /** Create an empty LIFO queue. */
+ public LIFORevQueue() {
+ super();
+ }
+
+ LIFORevQueue(final Generator s) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ super(s);
+ }
+
+ public void add(final RevCommit c) {
+ Block b = head;
+ if (b == null || !b.canUnpop()) {
+ b = free.newBlock();
+ b.resetToEnd();
+ b.next = head;
+ head = b;
+ }
+ b.unpop(c);
+ }
+
+ public RevCommit next() {
+ final Block b = head;
+ if (b == null)
+ return null;
+
+ final RevCommit c = b.pop();
+ if (b.isEmpty()) {
+ head = b.next;
+ free.freeBlock(b);
+ }
+ return c;
+ }
+
+ public void clear() {
+ head = null;
+ free.clear();
+ }
+
+ boolean everbodyHasFlag(final int f) {
+ for (Block b = head; b != null; b = b.next) {
+ for (int i = b.headIndex; i < b.tailIndex; i++)
+ if ((b.commits[i].flags & f) == 0)
+ return false;
+ }
+ return true;
+ }
+
+ boolean anybodyHasFlag(final int f) {
+ for (Block b = head; b != null; b = b.next) {
+ for (int i = b.headIndex; i < b.tailIndex; i++)
+ if ((b.commits[i].flags & f) != 0)
+ return true;
+ }
+ return false;
+ }
+
+ public String toString() {
+ final StringBuilder s = new StringBuilder();
+ for (Block q = head; q != null; q = q.next) {
+ for (int i = q.headIndex; i < q.tailIndex; i++)
+ describe(s, q.commits[i]);
+ }
+ return s.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
new file mode 100644
index 0000000000..2f01f541de
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/**
+ * Computes the merge base(s) of the starting commits.
+ * <p>
+ * This generator is selected if the RevFilter is only
+ * {@link org.eclipse.jgit.revwalk.filter.RevFilter#MERGE_BASE}.
+ * <p>
+ * To compute the merge base we assign a temporary flag to each of the starting
+ * commits. The maximum number of starting commits is bounded by the number of
+ * free flags available in the RevWalk when the generator is initialized. These
+ * flags will be automatically released on the next reset of the RevWalk, but
+ * not until then, as they are assigned to commits throughout the history.
+ * <p>
+ * Several internal flags are reused here for a different purpose, but this
+ * should not have any impact as this generator should be run alone, and without
+ * any other generators wrapped around it.
+ */
+class MergeBaseGenerator extends Generator {
+ private static final int PARSED = RevWalk.PARSED;
+
+ private static final int IN_PENDING = RevWalk.SEEN;
+
+ private static final int POPPED = RevWalk.TEMP_MARK;
+
+ private static final int MERGE_BASE = RevWalk.REWRITE;
+
+ private final RevWalk walker;
+
+ private final DateRevQueue pending;
+
+ private int branchMask;
+
+ private int recarryTest;
+
+ private int recarryMask;
+
+ MergeBaseGenerator(final RevWalk w) {
+ walker = w;
+ pending = new DateRevQueue();
+ }
+
+ void init(final AbstractRevQueue p) {
+ try {
+ for (;;) {
+ final RevCommit c = p.next();
+ if (c == null)
+ break;
+ add(c);
+ }
+ } finally {
+ // Always free the flags immediately. This ensures the flags
+ // will be available for reuse when the walk resets.
+ //
+ walker.freeFlag(branchMask);
+
+ // Setup the condition used by carryOntoOne to detect a late
+ // merge base and produce it on the next round.
+ //
+ recarryTest = branchMask | POPPED;
+ recarryMask = branchMask | POPPED | MERGE_BASE;
+ }
+ }
+
+ private void add(final RevCommit c) {
+ final int flag = walker.allocFlag();
+ branchMask |= flag;
+ if ((c.flags & branchMask) != 0) {
+ // This should never happen. RevWalk ensures we get a
+ // commit admitted to the initial queue only once. If
+ // we see this marks aren't correctly erased.
+ //
+ throw new IllegalStateException("Stale RevFlags on " + c.name());
+ }
+ c.flags |= flag;
+ pending.add(c);
+ }
+
+ @Override
+ int outputType() {
+ return 0;
+ }
+
+ @Override
+ RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ for (;;) {
+ final RevCommit c = pending.next();
+ if (c == null) {
+ walker.curs.release();
+ return null;
+ }
+
+ for (final RevCommit p : c.parents) {
+ if ((p.flags & IN_PENDING) != 0)
+ continue;
+ if ((p.flags & PARSED) == 0)
+ p.parseHeaders(walker);
+ p.flags |= IN_PENDING;
+ pending.add(p);
+ }
+
+ int carry = c.flags & branchMask;
+ boolean mb = carry == branchMask;
+ if (mb) {
+ // If we are a merge base make sure our ancestors are
+ // also flagged as being popped, so that they do not
+ // generate to the caller.
+ //
+ carry |= MERGE_BASE;
+ }
+ carryOntoHistory(c, carry);
+
+ if ((c.flags & MERGE_BASE) != 0) {
+ // This commit is an ancestor of a merge base we already
+ // popped back to the caller. If everyone in pending is
+ // that way we are done traversing; if not we just need
+ // to move to the next available commit and try again.
+ //
+ if (pending.everbodyHasFlag(MERGE_BASE))
+ return null;
+ continue;
+ }
+ c.flags |= POPPED;
+
+ if (mb) {
+ c.flags |= MERGE_BASE;
+ return c;
+ }
+ }
+ }
+
+ private void carryOntoHistory(RevCommit c, final int carry) {
+ for (;;) {
+ final RevCommit[] pList = c.parents;
+ if (pList == null)
+ return;
+ final int n = pList.length;
+ if (n == 0)
+ return;
+
+ for (int i = 1; i < n; i++) {
+ final RevCommit p = pList[i];
+ if (!carryOntoOne(p, carry))
+ carryOntoHistory(p, carry);
+ }
+
+ c = pList[0];
+ if (carryOntoOne(c, carry))
+ break;
+ }
+ }
+
+ private boolean carryOntoOne(final RevCommit p, final int carry) {
+ final boolean haveAll = (p.flags & carry) == carry;
+ p.flags |= carry;
+
+ if ((p.flags & recarryMask) == recarryTest) {
+ // We were popped without being a merge base, but we just got
+ // voted to be one. Inject ourselves back at the front of the
+ // pending queue and tell all of our ancestors they are within
+ // the merge base now.
+ //
+ p.flags &= ~POPPED;
+ pending.add(p);
+ carryOntoHistory(p, branchMask | MERGE_BASE);
+ return true;
+ }
+
+ // If we already had all carried flags, our parents do too.
+ // Return true to stop the caller from running down this leg
+ // of the revision graph any further.
+ //
+ return haveAll;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
new file mode 100644
index 0000000000..d8f88ea305
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+
+/**
+ * Specialized subclass of RevWalk to include trees, blobs and tags.
+ * <p>
+ * Unlike RevWalk this subclass is able to remember starting roots that include
+ * annotated tags, or arbitrary trees or blobs. Once commit generation is
+ * complete and all commits have been popped by the application, individual
+ * annotated tag, tree and blob objects can be popped through the additional
+ * method {@link #nextObject()}.
+ * <p>
+ * Tree and blob objects reachable from interesting commits are automatically
+ * scheduled for inclusion in the results of {@link #nextObject()}, returning
+ * each object exactly once. Objects are sorted and returned according to the
+ * the commits that reference them and the order they appear within a tree.
+ * Ordering can be affected by changing the {@link RevSort} used to order the
+ * commits that are returned first.
+ */
+public class ObjectWalk extends RevWalk {
+ /**
+ * Indicates a non-RevCommit is in {@link #pendingObjects}.
+ * <p>
+ * We can safely reuse {@link RevWalk#REWRITE} here for the same value as it
+ * is only set on RevCommit and {@link #pendingObjects} never has RevCommit
+ * instances inserted into it.
+ */
+ private static final int IN_PENDING = RevWalk.REWRITE;
+
+ private CanonicalTreeParser treeWalk;
+
+ private BlockObjQueue pendingObjects;
+
+ private RevTree currentTree;
+
+ private boolean fromTreeWalk;
+
+ private RevTree nextSubtree;
+
+ /**
+ * Create a new revision and object walker for a given repository.
+ *
+ * @param repo
+ * the repository the walker will obtain data from.
+ */
+ public ObjectWalk(final Repository repo) {
+ super(repo);
+ pendingObjects = new BlockObjQueue();
+ treeWalk = new CanonicalTreeParser();
+ }
+
+ /**
+ * Mark an object or commit to start graph traversal from.
+ * <p>
+ * Callers are encouraged to use {@link RevWalk#parseAny(AnyObjectId)}
+ * instead of {@link RevWalk#lookupAny(AnyObjectId, int)}, as this method
+ * requires the object to be parsed before it can be added as a root for the
+ * traversal.
+ * <p>
+ * The method will automatically parse an unparsed object, but error
+ * handling may be more difficult for the application to explain why a
+ * RevObject is not actually valid. The object pool of this walker would
+ * also be 'poisoned' by the invalid RevObject.
+ * <p>
+ * This method will automatically call {@link RevWalk#markStart(RevCommit)}
+ * if passed RevCommit instance, or a RevTag that directly (or indirectly)
+ * references a RevCommit.
+ *
+ * @param o
+ * the object to start traversing from. The object passed must be
+ * from this same revision walker.
+ * @throws MissingObjectException
+ * the object supplied is not available from the object
+ * database. This usually indicates the supplied object is
+ * invalid, but the reference was constructed during an earlier
+ * invocation to {@link RevWalk#lookupAny(AnyObjectId, int)}.
+ * @throws IncorrectObjectTypeException
+ * the object was not parsed yet and it was discovered during
+ * parsing that it is not actually the type of the instance
+ * passed in. This usually indicates the caller used the wrong
+ * type in a {@link RevWalk#lookupAny(AnyObjectId, int)} call.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public void markStart(RevObject o) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ while (o instanceof RevTag) {
+ addObject(o);
+ o = ((RevTag) o).getObject();
+ parseHeaders(o);
+ }
+
+ if (o instanceof RevCommit)
+ super.markStart((RevCommit) o);
+ else
+ addObject(o);
+ }
+
+ /**
+ * Mark an object to not produce in the output.
+ * <p>
+ * Uninteresting objects denote not just themselves but also their entire
+ * reachable chain, back until the merge base of an uninteresting commit and
+ * an otherwise interesting commit.
+ * <p>
+ * Callers are encouraged to use {@link RevWalk#parseAny(AnyObjectId)}
+ * instead of {@link RevWalk#lookupAny(AnyObjectId, int)}, as this method
+ * requires the object to be parsed before it can be added as a root for the
+ * traversal.
+ * <p>
+ * The method will automatically parse an unparsed object, but error
+ * handling may be more difficult for the application to explain why a
+ * RevObject is not actually valid. The object pool of this walker would
+ * also be 'poisoned' by the invalid RevObject.
+ * <p>
+ * This method will automatically call {@link RevWalk#markStart(RevCommit)}
+ * if passed RevCommit instance, or a RevTag that directly (or indirectly)
+ * references a RevCommit.
+ *
+ * @param o
+ * the object to start traversing from. The object passed must be
+ * @throws MissingObjectException
+ * the object supplied is not available from the object
+ * database. This usually indicates the supplied object is
+ * invalid, but the reference was constructed during an earlier
+ * invocation to {@link RevWalk#lookupAny(AnyObjectId, int)}.
+ * @throws IncorrectObjectTypeException
+ * the object was not parsed yet and it was discovered during
+ * parsing that it is not actually the type of the instance
+ * passed in. This usually indicates the caller used the wrong
+ * type in a {@link RevWalk#lookupAny(AnyObjectId, int)} call.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public void markUninteresting(RevObject o) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ while (o instanceof RevTag) {
+ o.flags |= UNINTERESTING;
+ if (hasRevSort(RevSort.BOUNDARY))
+ addObject(o);
+ o = ((RevTag) o).getObject();
+ parseHeaders(o);
+ }
+
+ if (o instanceof RevCommit)
+ super.markUninteresting((RevCommit) o);
+ else if (o instanceof RevTree)
+ markTreeUninteresting((RevTree) o);
+ else
+ o.flags |= UNINTERESTING;
+
+ if (o.getType() != Constants.OBJ_COMMIT && hasRevSort(RevSort.BOUNDARY)) {
+ addObject(o);
+ }
+ }
+
+ @Override
+ public RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ for (;;) {
+ final RevCommit r = super.next();
+ if (r == null)
+ return null;
+ if ((r.flags & UNINTERESTING) != 0) {
+ markTreeUninteresting(r.getTree());
+ if (hasRevSort(RevSort.BOUNDARY)) {
+ pendingObjects.add(r.getTree());
+ return r;
+ }
+ continue;
+ }
+ pendingObjects.add(r.getTree());
+ return r;
+ }
+ }
+
+ /**
+ * Pop the next most recent object.
+ *
+ * @return next most recent object; null if traversal is over.
+ * @throws MissingObjectException
+ * one or or more of the next objects are not available from the
+ * object database, but were thought to be candidates for
+ * traversal. This usually indicates a broken link.
+ * @throws IncorrectObjectTypeException
+ * one or or more of the objects in a tree do not match the type
+ * indicated.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public RevObject nextObject() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ fromTreeWalk = false;
+
+ if (nextSubtree != null) {
+ treeWalk = treeWalk.createSubtreeIterator0(db, nextSubtree, curs);
+ nextSubtree = null;
+ }
+
+ while (!treeWalk.eof()) {
+ final FileMode mode = treeWalk.getEntryFileMode();
+ final int sType = mode.getObjectType();
+
+ switch (sType) {
+ case Constants.OBJ_BLOB: {
+ treeWalk.getEntryObjectId(idBuffer);
+ final RevBlob o = lookupBlob(idBuffer);
+ if ((o.flags & SEEN) != 0)
+ break;
+ o.flags |= SEEN;
+ if (shouldSkipObject(o))
+ break;
+ fromTreeWalk = true;
+ return o;
+ }
+ case Constants.OBJ_TREE: {
+ treeWalk.getEntryObjectId(idBuffer);
+ final RevTree o = lookupTree(idBuffer);
+ if ((o.flags & SEEN) != 0)
+ break;
+ o.flags |= SEEN;
+ if (shouldSkipObject(o))
+ break;
+ nextSubtree = o;
+ fromTreeWalk = true;
+ return o;
+ }
+ default:
+ if (FileMode.GITLINK.equals(mode))
+ break;
+ treeWalk.getEntryObjectId(idBuffer);
+ throw new CorruptObjectException("Invalid mode " + mode
+ + " for " + idBuffer.name() + " "
+ + treeWalk.getEntryPathString() + " in " + currentTree
+ + ".");
+ }
+
+ treeWalk = treeWalk.next();
+ }
+
+ for (;;) {
+ final RevObject o = pendingObjects.next();
+ if (o == null)
+ return null;
+ if ((o.flags & SEEN) != 0)
+ continue;
+ o.flags |= SEEN;
+ if (shouldSkipObject(o))
+ continue;
+ if (o instanceof RevTree) {
+ currentTree = (RevTree) o;
+ treeWalk = treeWalk.resetRoot(db, currentTree, curs);
+ }
+ return o;
+ }
+ }
+
+ private final boolean shouldSkipObject(final RevObject o) {
+ return (o.flags & UNINTERESTING) != 0 && !hasRevSort(RevSort.BOUNDARY);
+ }
+
+ /**
+ * Verify all interesting objects are available, and reachable.
+ * <p>
+ * Callers should populate starting points and ending points with
+ * {@link #markStart(RevObject)} and {@link #markUninteresting(RevObject)}
+ * and then use this method to verify all objects between those two points
+ * exist in the repository and are readable.
+ * <p>
+ * This method returns successfully if everything is connected; it throws an
+ * exception if there is a connectivity problem. The exception message
+ * provides some detail about the connectivity failure.
+ *
+ * @throws MissingObjectException
+ * one or or more of the next objects are not available from the
+ * object database, but were thought to be candidates for
+ * traversal. This usually indicates a broken link.
+ * @throws IncorrectObjectTypeException
+ * one or or more of the objects in a tree do not match the type
+ * indicated.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public void checkConnectivity() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ for (;;) {
+ final RevCommit c = next();
+ if (c == null)
+ break;
+ }
+ for (;;) {
+ final RevObject o = nextObject();
+ if (o == null)
+ break;
+ if (o instanceof RevBlob && !db.hasObject(o))
+ throw new MissingObjectException(o, Constants.TYPE_BLOB);
+ }
+ }
+
+ /**
+ * Get the current object's complete path.
+ * <p>
+ * This method is not very efficient and is primarily meant for debugging
+ * and final output generation. Applications should try to avoid calling it,
+ * and if invoked do so only once per interesting entry, where the name is
+ * absolutely required for correct function.
+ *
+ * @return complete path of the current entry, from the root of the
+ * repository. If the current entry is in a subtree there will be at
+ * least one '/' in the returned string. Null if the current entry
+ * has no path, such as for annotated tags or root level trees.
+ */
+ public String getPathString() {
+ return fromTreeWalk ? treeWalk.getEntryPathString() : null;
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ pendingObjects = new BlockObjQueue();
+ nextSubtree = null;
+ currentTree = null;
+ }
+
+ @Override
+ protected void reset(final int retainFlags) {
+ super.reset(retainFlags);
+ pendingObjects = new BlockObjQueue();
+ nextSubtree = null;
+ }
+
+ private void addObject(final RevObject o) {
+ if ((o.flags & IN_PENDING) == 0) {
+ o.flags |= IN_PENDING;
+ pendingObjects.add(o);
+ }
+ }
+
+ private void markTreeUninteresting(final RevTree tree)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ if ((tree.flags & UNINTERESTING) != 0)
+ return;
+ tree.flags |= UNINTERESTING;
+
+ treeWalk = treeWalk.resetRoot(db, tree, curs);
+ while (!treeWalk.eof()) {
+ final FileMode mode = treeWalk.getEntryFileMode();
+ final int sType = mode.getObjectType();
+
+ switch (sType) {
+ case Constants.OBJ_BLOB: {
+ treeWalk.getEntryObjectId(idBuffer);
+ lookupBlob(idBuffer).flags |= UNINTERESTING;
+ break;
+ }
+ case Constants.OBJ_TREE: {
+ treeWalk.getEntryObjectId(idBuffer);
+ final RevTree t = lookupTree(idBuffer);
+ if ((t.flags & UNINTERESTING) == 0) {
+ t.flags |= UNINTERESTING;
+ treeWalk = treeWalk.createSubtreeIterator0(db, t, curs);
+ continue;
+ }
+ break;
+ }
+ default:
+ if (FileMode.GITLINK.equals(mode))
+ break;
+ treeWalk.getEntryObjectId(idBuffer);
+ throw new CorruptObjectException("Invalid mode " + mode
+ + " for " + idBuffer.name() + " "
+ + treeWalk.getEntryPathString() + " in " + tree + ".");
+ }
+
+ treeWalk = treeWalk.next();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java
new file mode 100644
index 0000000000..e723bce51b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.StopWalkException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+
+/**
+ * Default (and first pass) RevCommit Generator implementation for RevWalk.
+ * <p>
+ * This generator starts from a set of one or more commits and process them in
+ * descending (newest to oldest) commit time order. Commits automatically cause
+ * their parents to be enqueued for further processing, allowing the entire
+ * commit graph to be walked. A {@link RevFilter} may be used to select a subset
+ * of the commits and return them to the caller.
+ */
+class PendingGenerator extends Generator {
+ private static final int PARSED = RevWalk.PARSED;
+
+ private static final int SEEN = RevWalk.SEEN;
+
+ private static final int UNINTERESTING = RevWalk.UNINTERESTING;
+
+ /**
+ * Number of additional commits to scan after we think we are done.
+ * <p>
+ * This small buffer of commits is scanned to ensure we didn't miss anything
+ * as a result of clock skew when the commits were made. We need to set our
+ * constant to 1 additional commit due to the use of a pre-increment
+ * operator when accessing the value.
+ */
+ static final int OVER_SCAN = 5 + 1;
+
+ /** A commit near the end of time, to initialize {@link #last} with. */
+ private static final RevCommit INIT_LAST;
+
+ static {
+ INIT_LAST = new RevCommit(ObjectId.zeroId());
+ INIT_LAST.commitTime = Integer.MAX_VALUE;
+ }
+
+ private final RevWalk walker;
+
+ private final DateRevQueue pending;
+
+ private final RevFilter filter;
+
+ private final int output;
+
+ /** Last commit produced to the caller from {@link #next()}. */
+ private RevCommit last = INIT_LAST;
+
+ /**
+ * Number of commits we have remaining in our over-scan allotment.
+ * <p>
+ * Only relevant if there are {@link #UNINTERESTING} commits in the
+ * {@link #pending} queue.
+ */
+ private int overScan = OVER_SCAN;
+
+ boolean canDispose;
+
+ PendingGenerator(final RevWalk w, final DateRevQueue p,
+ final RevFilter f, final int out) {
+ walker = w;
+ pending = p;
+ filter = f;
+ output = out;
+ canDispose = true;
+ }
+
+ @Override
+ int outputType() {
+ return output | SORT_COMMIT_TIME_DESC;
+ }
+
+ @Override
+ RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ try {
+ for (;;) {
+ final RevCommit c = pending.next();
+ if (c == null) {
+ walker.curs.release();
+ return null;
+ }
+
+ final boolean produce;
+ if ((c.flags & UNINTERESTING) != 0)
+ produce = false;
+ else
+ produce = filter.include(walker, c);
+
+ for (final RevCommit p : c.parents) {
+ if ((p.flags & SEEN) != 0)
+ continue;
+ if ((p.flags & PARSED) == 0)
+ p.parseHeaders(walker);
+ p.flags |= SEEN;
+ pending.add(p);
+ }
+ walker.carryFlagsImpl(c);
+
+ if ((c.flags & UNINTERESTING) != 0) {
+ if (pending.everbodyHasFlag(UNINTERESTING)) {
+ final RevCommit n = pending.peek();
+ if (n != null && n.commitTime >= last.commitTime) {
+ // This is too close to call. The next commit we
+ // would pop is dated after the last one produced.
+ // We have to keep going to ensure that we carry
+ // flags as much as necessary.
+ //
+ overScan = OVER_SCAN;
+ } else if (--overScan == 0)
+ throw StopWalkException.INSTANCE;
+ } else {
+ overScan = OVER_SCAN;
+ }
+ if (canDispose)
+ c.disposeBody();
+ continue;
+ }
+
+ if (produce)
+ return last = c;
+ else if (canDispose)
+ c.disposeBody();
+ }
+ } catch (StopWalkException swe) {
+ walker.curs.release();
+ pending.clear();
+ return null;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java
new file mode 100644
index 0000000000..f4d46e7e6f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+
+/** A binary file, or a symbolic link. */
+public class RevBlob extends RevObject {
+ /**
+ * Create a new blob reference.
+ *
+ * @param id
+ * object name for the blob.
+ */
+ protected RevBlob(final AnyObjectId id) {
+ super(id);
+ }
+
+ @Override
+ public final int getType() {
+ return Constants.OBJ_BLOB;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
new file mode 100644
index 0000000000..1d2a49d3af
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Commit;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/** A commit reference to a commit in the DAG. */
+public class RevCommit extends RevObject {
+ static final RevCommit[] NO_PARENTS = {};
+
+ private RevTree tree;
+
+ RevCommit[] parents;
+
+ int commitTime; // An int here for performance, overflows in 2038
+
+ int inDegree;
+
+ private byte[] buffer;
+
+ /**
+ * Create a new commit reference.
+ *
+ * @param id
+ * object name for the commit.
+ */
+ protected RevCommit(final AnyObjectId id) {
+ super(id);
+ }
+
+ @Override
+ void parseHeaders(final RevWalk walk) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ parseCanonical(walk, loadCanonical(walk));
+ }
+
+ @Override
+ void parseBody(final RevWalk walk) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ if (buffer == null) {
+ buffer = loadCanonical(walk);
+ if ((flags & PARSED) == 0)
+ parseCanonical(walk, buffer);
+ }
+ }
+
+ void parseCanonical(final RevWalk walk, final byte[] raw) {
+ final MutableObjectId idBuffer = walk.idBuffer;
+ idBuffer.fromString(raw, 5);
+ tree = walk.lookupTree(idBuffer);
+
+ int ptr = 46;
+ if (parents == null) {
+ RevCommit[] pList = new RevCommit[1];
+ int nParents = 0;
+ for (;;) {
+ if (raw[ptr] != 'p')
+ break;
+ idBuffer.fromString(raw, ptr + 7);
+ final RevCommit p = walk.lookupCommit(idBuffer);
+ if (nParents == 0)
+ pList[nParents++] = p;
+ else if (nParents == 1) {
+ pList = new RevCommit[] { pList[0], p };
+ nParents = 2;
+ } else {
+ if (pList.length <= nParents) {
+ RevCommit[] old = pList;
+ pList = new RevCommit[pList.length + 32];
+ System.arraycopy(old, 0, pList, 0, nParents);
+ }
+ pList[nParents++] = p;
+ }
+ ptr += 48;
+ }
+ if (nParents != pList.length) {
+ RevCommit[] old = pList;
+ pList = new RevCommit[nParents];
+ System.arraycopy(old, 0, pList, 0, nParents);
+ }
+ parents = pList;
+ }
+
+ // extract time from "committer "
+ ptr = RawParseUtils.committer(raw, ptr);
+ if (ptr > 0) {
+ ptr = RawParseUtils.nextLF(raw, ptr, '>');
+
+ // In 2038 commitTime will overflow unless it is changed to long.
+ commitTime = RawParseUtils.parseBase10(raw, ptr, null);
+ }
+
+ if (walk.isRetainBody())
+ buffer = raw;
+ flags |= PARSED;
+ }
+
+ @Override
+ public final int getType() {
+ return Constants.OBJ_COMMIT;
+ }
+
+ static void carryFlags(RevCommit c, final int carry) {
+ for (;;) {
+ final RevCommit[] pList = c.parents;
+ if (pList == null)
+ return;
+ final int n = pList.length;
+ if (n == 0)
+ return;
+
+ for (int i = 1; i < n; i++) {
+ final RevCommit p = pList[i];
+ if ((p.flags & carry) == carry)
+ continue;
+ p.flags |= carry;
+ carryFlags(p, carry);
+ }
+
+ c = pList[0];
+ if ((c.flags & carry) == carry)
+ return;
+ c.flags |= carry;
+ }
+ }
+
+ /**
+ * Carry a RevFlag set on this commit to its parents.
+ * <p>
+ * If this commit is parsed, has parents, and has the supplied flag set on
+ * it we automatically add it to the parents, grand-parents, and so on until
+ * an unparsed commit or a commit with no parents is discovered. This
+ * permits applications to force a flag through the history chain when
+ * necessary.
+ *
+ * @param flag
+ * the single flag value to carry back onto parents.
+ */
+ public void carry(final RevFlag flag) {
+ final int carry = flags & flag.mask;
+ if (carry != 0)
+ carryFlags(this, carry);
+ }
+
+ /**
+ * Time from the "committer " line of the buffer.
+ *
+ * @return time, expressed as seconds since the epoch.
+ */
+ public final int getCommitTime() {
+ return commitTime;
+ }
+
+ /**
+ * Parse this commit buffer for display.
+ *
+ * @param walk
+ * revision walker owning this reference.
+ * @return parsed commit.
+ */
+ public final Commit asCommit(final RevWalk walk) {
+ return new Commit(walk.db, this, buffer);
+ }
+
+ /**
+ * Get a reference to this commit's tree.
+ *
+ * @return tree of this commit.
+ */
+ public final RevTree getTree() {
+ return tree;
+ }
+
+ /**
+ * Get the number of parent commits listed in this commit.
+ *
+ * @return number of parents; always a positive value but can be 0.
+ */
+ public final int getParentCount() {
+ return parents.length;
+ }
+
+ /**
+ * Get the nth parent from this commit's parent list.
+ *
+ * @param nth
+ * parent index to obtain. Must be in the range 0 through
+ * {@link #getParentCount()}-1.
+ * @return the specified parent.
+ * @throws ArrayIndexOutOfBoundsException
+ * an invalid parent index was specified.
+ */
+ public final RevCommit getParent(final int nth) {
+ return parents[nth];
+ }
+
+ /**
+ * Obtain an array of all parents (<b>NOTE - THIS IS NOT A COPY</b>).
+ * <p>
+ * This method is exposed only to provide very fast, efficient access to
+ * this commit's parent list. Applications relying on this list should be
+ * very careful to ensure they do not modify its contents during their use
+ * of it.
+ *
+ * @return the array of parents.
+ */
+ public final RevCommit[] getParents() {
+ return parents;
+ }
+
+ /**
+ * Obtain the raw unparsed commit body (<b>NOTE - THIS IS NOT A COPY</b>).
+ * <p>
+ * This method is exposed only to provide very fast, efficient access to
+ * this commit's message buffer within a RevFilter. Applications relying on
+ * this buffer should be very careful to ensure they do not modify its
+ * contents during their use of it.
+ *
+ * @return the raw unparsed commit body. This is <b>NOT A COPY</b>.
+ * Altering the contents of this buffer may alter the walker's
+ * knowledge of this commit, and the results it produces.
+ */
+ public final byte[] getRawBuffer() {
+ return buffer;
+ }
+
+ /**
+ * Parse the author identity from the raw buffer.
+ * <p>
+ * This method parses and returns the content of the author line, after
+ * taking the commit's character set into account and decoding the author
+ * name and email address. This method is fairly expensive and produces a
+ * new PersonIdent instance on each invocation. Callers should invoke this
+ * method only if they are certain they will be outputting the result, and
+ * should cache the return value for as long as necessary to use all
+ * information from it.
+ * <p>
+ * RevFilter implementations should try to use {@link RawParseUtils} to scan
+ * the {@link #getRawBuffer()} instead, as this will allow faster evaluation
+ * of commits.
+ *
+ * @return identity of the author (name, email) and the time the commit was
+ * made by the author; null if no author line was found.
+ */
+ public final PersonIdent getAuthorIdent() {
+ final byte[] raw = buffer;
+ final int nameB = RawParseUtils.author(raw, 0);
+ if (nameB < 0)
+ return null;
+ return RawParseUtils.parsePersonIdent(raw, nameB);
+ }
+
+ /**
+ * Parse the committer identity from the raw buffer.
+ * <p>
+ * This method parses and returns the content of the committer line, after
+ * taking the commit's character set into account and decoding the committer
+ * name and email address. This method is fairly expensive and produces a
+ * new PersonIdent instance on each invocation. Callers should invoke this
+ * method only if they are certain they will be outputting the result, and
+ * should cache the return value for as long as necessary to use all
+ * information from it.
+ * <p>
+ * RevFilter implementations should try to use {@link RawParseUtils} to scan
+ * the {@link #getRawBuffer()} instead, as this will allow faster evaluation
+ * of commits.
+ *
+ * @return identity of the committer (name, email) and the time the commit
+ * was made by the committer; null if no committer line was found.
+ */
+ public final PersonIdent getCommitterIdent() {
+ final byte[] raw = buffer;
+ final int nameB = RawParseUtils.committer(raw, 0);
+ if (nameB < 0)
+ return null;
+ return RawParseUtils.parsePersonIdent(raw, nameB);
+ }
+
+ /**
+ * Parse the complete commit message and decode it to a string.
+ * <p>
+ * This method parses and returns the message portion of the commit buffer,
+ * after taking the commit's character set into account and decoding the
+ * buffer using that character set. This method is a fairly expensive
+ * operation and produces a new string on each invocation.
+ *
+ * @return decoded commit message as a string. Never null.
+ */
+ public final String getFullMessage() {
+ final byte[] raw = buffer;
+ final int msgB = RawParseUtils.commitMessage(raw, 0);
+ if (msgB < 0)
+ return "";
+ final Charset enc = RawParseUtils.parseEncoding(raw);
+ return RawParseUtils.decode(enc, raw, msgB, raw.length);
+ }
+
+ /**
+ * Parse the commit message and return the first "line" of it.
+ * <p>
+ * The first line is everything up to the first pair of LFs. This is the
+ * "oneline" format, suitable for output in a single line display.
+ * <p>
+ * This method parses and returns the message portion of the commit buffer,
+ * after taking the commit's character set into account and decoding the
+ * buffer using that character set. This method is a fairly expensive
+ * operation and produces a new string on each invocation.
+ *
+ * @return decoded commit message as a string. Never null. The returned
+ * string does not contain any LFs, even if the first paragraph
+ * spanned multiple lines. Embedded LFs are converted to spaces.
+ */
+ public final String getShortMessage() {
+ final byte[] raw = buffer;
+ final int msgB = RawParseUtils.commitMessage(raw, 0);
+ if (msgB < 0)
+ return "";
+
+ final Charset enc = RawParseUtils.parseEncoding(raw);
+ final int msgE = RawParseUtils.endOfParagraph(raw, msgB);
+ String str = RawParseUtils.decode(enc, raw, msgB, msgE);
+ if (hasLF(raw, msgB, msgE))
+ str = str.replace('\n', ' ');
+ return str;
+ }
+
+ static boolean hasLF(final byte[] r, int b, final int e) {
+ while (b < e)
+ if (r[b++] == '\n')
+ return true;
+ return false;
+ }
+
+ /**
+ * Determine the encoding of the commit message buffer.
+ * <p>
+ * Locates the "encoding" header (if present) and then returns the proper
+ * character set to apply to this buffer to evaluate its contents as
+ * character data.
+ * <p>
+ * If no encoding header is present, {@link Constants#CHARSET} is assumed.
+ *
+ * @return the preferred encoding of {@link #getRawBuffer()}.
+ */
+ public final Charset getEncoding() {
+ return RawParseUtils.parseEncoding(buffer);
+ }
+
+ /**
+ * Parse the footer lines (e.g. "Signed-off-by") for machine processing.
+ * <p>
+ * This method splits all of the footer lines out of the last paragraph of
+ * the commit message, providing each line as a key-value pair, ordered by
+ * the order of the line's appearance in the commit message itself.
+ * <p>
+ * A footer line's key must match the pattern {@code ^[A-Za-z0-9-]+:}, while
+ * the value is free-form, but must not contain an LF. Very common keys seen
+ * in the wild are:
+ * <ul>
+ * <li>{@code Signed-off-by} (agrees to Developer Certificate of Origin)
+ * <li>{@code Acked-by} (thinks change looks sane in context)
+ * <li>{@code Reported-by} (originally found the issue this change fixes)
+ * <li>{@code Tested-by} (validated change fixes the issue for them)
+ * <li>{@code CC}, {@code Cc} (copy on all email related to this change)
+ * <li>{@code Bug} (link to project's bug tracking system)
+ * </ul>
+ *
+ * @return ordered list of footer lines; empty list if no footers found.
+ */
+ public final List<FooterLine> getFooterLines() {
+ final byte[] raw = buffer;
+ int ptr = raw.length - 1;
+ while (raw[ptr] == '\n') // trim any trailing LFs, not interesting
+ ptr--;
+
+ final int msgB = RawParseUtils.commitMessage(raw, 0);
+ final ArrayList<FooterLine> r = new ArrayList<FooterLine>(4);
+ final Charset enc = getEncoding();
+ for (;;) {
+ ptr = RawParseUtils.prevLF(raw, ptr);
+ if (ptr <= msgB)
+ break; // Don't parse commit headers as footer lines.
+
+ final int keyStart = ptr + 2;
+ if (raw[keyStart] == '\n')
+ break; // Stop at first paragraph break, no footers above it.
+
+ final int keyEnd = RawParseUtils.endOfFooterLineKey(raw, keyStart);
+ if (keyEnd < 0)
+ continue; // Not a well formed footer line, skip it.
+
+ // Skip over the ': *' at the end of the key before the value.
+ //
+ int valStart = keyEnd + 1;
+ while (valStart < raw.length && raw[valStart] == ' ')
+ valStart++;
+
+ // Value ends at the LF, and does not include it.
+ //
+ int valEnd = RawParseUtils.nextLF(raw, valStart);
+ if (raw[valEnd - 1] == '\n')
+ valEnd--;
+
+ r.add(new FooterLine(raw, enc, keyStart, keyEnd, valStart, valEnd));
+ }
+ Collections.reverse(r);
+ return r;
+ }
+
+ /**
+ * Get the values of all footer lines with the given key.
+ *
+ * @param keyName
+ * footer key to find values of, case insensitive.
+ * @return values of footers with key of {@code keyName}, ordered by their
+ * order of appearance. Duplicates may be returned if the same
+ * footer appeared more than once. Empty list if no footers appear
+ * with the specified key, or there are no footers at all.
+ * @see #getFooterLines()
+ */
+ public final List<String> getFooterLines(final String keyName) {
+ return getFooterLines(new FooterKey(keyName));
+ }
+
+ /**
+ * Get the values of all footer lines with the given key.
+ *
+ * @param keyName
+ * footer key to find values of, case insensitive.
+ * @return values of footers with key of {@code keyName}, ordered by their
+ * order of appearance. Duplicates may be returned if the same
+ * footer appeared more than once. Empty list if no footers appear
+ * with the specified key, or there are no footers at all.
+ * @see #getFooterLines()
+ */
+ public final List<String> getFooterLines(final FooterKey keyName) {
+ final List<FooterLine> src = getFooterLines();
+ if (src.isEmpty())
+ return Collections.emptyList();
+ final ArrayList<String> r = new ArrayList<String>(src.size());
+ for (final FooterLine f : src) {
+ if (f.matches(keyName))
+ r.add(f.getValue());
+ }
+ return r;
+ }
+
+ /**
+ * Reset this commit to allow another RevWalk with the same instances.
+ * <p>
+ * Subclasses <b>must</b> call <code>super.reset()</code> to ensure the
+ * basic information can be correctly cleared out.
+ */
+ public void reset() {
+ inDegree = 0;
+ }
+
+ final void disposeBody() {
+ buffer = null;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder s = new StringBuilder();
+ s.append(Constants.typeString(getType()));
+ s.append(' ');
+ s.append(name());
+ s.append(' ');
+ s.append(commitTime);
+ s.append(' ');
+ appendCoreFlags(s);
+ return s.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java
new file mode 100644
index 0000000000..d6abccfba4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+
+/**
+ * An ordered list of {@link RevCommit} subclasses.
+ *
+ * @param <E>
+ * type of subclass of RevCommit the list is storing.
+ */
+public class RevCommitList<E extends RevCommit> extends RevObjectList<E> {
+ private RevWalk walker;
+
+ @Override
+ public void clear() {
+ super.clear();
+ walker = null;
+ }
+
+ /**
+ * Apply a flag to all commits matching the specified filter.
+ * <p>
+ * Same as <code>applyFlag(matching, flag, 0, size())</code>, but without
+ * the incremental behavior.
+ *
+ * @param matching
+ * the filter to test commits with. If the filter includes a
+ * commit it will have the flag set; if the filter does not
+ * include the commit the flag will be unset.
+ * @param flag
+ * the flag to apply (or remove). Applications are responsible
+ * for allocating this flag from the source RevWalk.
+ * @throws IOException
+ * revision filter needed to read additional objects, but an
+ * error occurred while reading the pack files or loose objects
+ * of the repository.
+ * @throws IncorrectObjectTypeException
+ * revision filter needed to read additional objects, but an
+ * object was not of the correct type. Repository corruption may
+ * have occurred.
+ * @throws MissingObjectException
+ * revision filter needed to read additional objects, but an
+ * object that should be present was not found. Repository
+ * corruption may have occurred.
+ */
+ public void applyFlag(final RevFilter matching, final RevFlag flag)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ applyFlag(matching, flag, 0, size());
+ }
+
+ /**
+ * Apply a flag to all commits matching the specified filter.
+ * <p>
+ * This version allows incremental testing and application, such as from a
+ * background thread that needs to periodically halt processing and send
+ * updates to the UI.
+ *
+ * @param matching
+ * the filter to test commits with. If the filter includes a
+ * commit it will have the flag set; if the filter does not
+ * include the commit the flag will be unset.
+ * @param flag
+ * the flag to apply (or remove). Applications are responsible
+ * for allocating this flag from the source RevWalk.
+ * @param rangeBegin
+ * first commit within the list to begin testing at, inclusive.
+ * Must not be negative, but may be beyond the end of the list.
+ * @param rangeEnd
+ * last commit within the list to end testing at, exclusive. If
+ * smaller than or equal to <code>rangeBegin</code> then no
+ * commits will be tested.
+ * @throws IOException
+ * revision filter needed to read additional objects, but an
+ * error occurred while reading the pack files or loose objects
+ * of the repository.
+ * @throws IncorrectObjectTypeException
+ * revision filter needed to read additional objects, but an
+ * object was not of the correct type. Repository corruption may
+ * have occurred.
+ * @throws MissingObjectException
+ * revision filter needed to read additional objects, but an
+ * object that should be present was not found. Repository
+ * corruption may have occurred.
+ */
+ public void applyFlag(final RevFilter matching, final RevFlag flag,
+ int rangeBegin, int rangeEnd) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ final RevWalk w = flag.getRevWalk();
+ rangeEnd = Math.min(rangeEnd, size());
+ while (rangeBegin < rangeEnd) {
+ int index = rangeBegin;
+ Block s = contents;
+ while (s.shift > 0) {
+ final int i = index >> s.shift;
+ index -= i << s.shift;
+ s = (Block) s.contents[i];
+ }
+
+ while (rangeBegin++ < rangeEnd && index < BLOCK_SIZE) {
+ final RevCommit c = (RevCommit) s.contents[index++];
+ if (matching.include(w, c))
+ c.add(flag);
+ else
+ c.remove(flag);
+ }
+ }
+ }
+
+ /**
+ * Remove the given flag from all commits.
+ * <p>
+ * Same as <code>clearFlag(flag, 0, size())</code>, but without the
+ * incremental behavior.
+ *
+ * @param flag
+ * the flag to remove. Applications are responsible for
+ * allocating this flag from the source RevWalk.
+ */
+ public void clearFlag(final RevFlag flag) {
+ clearFlag(flag, 0, size());
+ }
+
+ /**
+ * Remove the given flag from all commits.
+ * <p>
+ * This method is actually implemented in terms of:
+ * <code>applyFlag(RevFilter.NONE, flag, rangeBegin, rangeEnd)</code>.
+ *
+ * @param flag
+ * the flag to remove. Applications are responsible for
+ * allocating this flag from the source RevWalk.
+ * @param rangeBegin
+ * first commit within the list to begin testing at, inclusive.
+ * Must not be negative, but may be beyond the end of the list.
+ * @param rangeEnd
+ * last commit within the list to end testing at, exclusive. If
+ * smaller than or equal to <code>rangeBegin</code> then no
+ * commits will be tested.
+ */
+ public void clearFlag(final RevFlag flag, final int rangeBegin,
+ final int rangeEnd) {
+ try {
+ applyFlag(RevFilter.NONE, flag, rangeBegin, rangeEnd);
+ } catch (IOException e) {
+ // Never happen. The filter we use does not throw any
+ // exceptions, for any reason.
+ }
+ }
+
+ /**
+ * Find the next commit that has the given flag set.
+ *
+ * @param flag
+ * the flag to test commits against.
+ * @param begin
+ * first commit index to test at. Applications may wish to begin
+ * at 0, to test the first commit in the list.
+ * @return index of the first commit at or after index <code>begin</code>
+ * that has the specified flag set on it; -1 if no match is found.
+ */
+ public int indexOf(final RevFlag flag, int begin) {
+ while (begin < size()) {
+ int index = begin;
+ Block s = contents;
+ while (s.shift > 0) {
+ final int i = index >> s.shift;
+ index -= i << s.shift;
+ s = (Block) s.contents[i];
+ }
+
+ while (begin++ < size() && index < BLOCK_SIZE) {
+ final RevCommit c = (RevCommit) s.contents[index++];
+ if (c.has(flag))
+ return begin;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Find the next commit that has the given flag set.
+ *
+ * @param flag
+ * the flag to test commits against.
+ * @param begin
+ * first commit index to test at. Applications may wish to begin
+ * at <code>size()-1</code>, to test the last commit in the
+ * list.
+ * @return index of the first commit at or before index <code>begin</code>
+ * that has the specified flag set on it; -1 if no match is found.
+ */
+ public int lastIndexOf(final RevFlag flag, int begin) {
+ begin = Math.min(begin, size() - 1);
+ while (begin >= 0) {
+ int index = begin;
+ Block s = contents;
+ while (s.shift > 0) {
+ final int i = index >> s.shift;
+ index -= i << s.shift;
+ s = (Block) s.contents[i];
+ }
+
+ while (begin-- >= 0 && index >= 0) {
+ final RevCommit c = (RevCommit) s.contents[index--];
+ if (c.has(flag))
+ return begin;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Set the revision walker this list populates itself from.
+ *
+ * @param w
+ * the walker to populate from.
+ * @see #fillTo(int)
+ */
+ public void source(final RevWalk w) {
+ walker = w;
+ }
+
+ /**
+ * Is this list still pending more items?
+ *
+ * @return true if {@link #fillTo(int)} might be able to extend the list
+ * size when called.
+ */
+ public boolean isPending() {
+ return walker != null;
+ }
+
+ /**
+ * Ensure this list contains at least a specified number of commits.
+ * <p>
+ * The revision walker specified by {@link #source(RevWalk)} is pumped until
+ * the given number of commits are contained in this list. If there are
+ * fewer total commits available from the walk then the method will return
+ * early. Callers can test the final size of the list by {@link #size()} to
+ * determine if the high water mark specified was met.
+ *
+ * @param highMark
+ * number of commits the caller wants this list to contain when
+ * the fill operation is complete.
+ * @throws IOException
+ * see {@link RevWalk#next()}
+ * @throws IncorrectObjectTypeException
+ * see {@link RevWalk#next()}
+ * @throws MissingObjectException
+ * see {@link RevWalk#next()}
+ */
+ public void fillTo(final int highMark) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ if (walker == null || size > highMark)
+ return;
+
+ Generator p = walker.pending;
+ RevCommit c = p.next();
+ if (c == null) {
+ walker.pending = EndGenerator.INSTANCE;
+ walker = null;
+ return;
+ }
+ enter(size, (E) c);
+ add((E) c);
+ p = walker.pending;
+
+ while (size <= highMark) {
+ int index = size;
+ Block s = contents;
+ while (index >> s.shift >= BLOCK_SIZE) {
+ s = new Block(s.shift + BLOCK_SHIFT);
+ s.contents[0] = contents;
+ contents = s;
+ }
+ while (s.shift > 0) {
+ final int i = index >> s.shift;
+ index -= i << s.shift;
+ if (s.contents[i] == null)
+ s.contents[i] = new Block(s.shift - BLOCK_SHIFT);
+ s = (Block) s.contents[i];
+ }
+
+ final Object[] dst = s.contents;
+ while (size <= highMark && index < BLOCK_SIZE) {
+ c = p.next();
+ if (c == null) {
+ walker.pending = EndGenerator.INSTANCE;
+ walker = null;
+ return;
+ }
+ enter(size++, (E) c);
+ dst[index++] = c;
+ }
+ }
+ }
+
+ /**
+ * Optional callback invoked when commits enter the list by fillTo.
+ * <p>
+ * This method is only called during {@link #fillTo(int)}.
+ *
+ * @param index
+ * the list position this object will appear at.
+ * @param e
+ * the object being added (or set) into the list.
+ */
+ protected void enter(final int index, final E e) {
+ // Do nothing by default.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java
new file mode 100644
index 0000000000..a8d644c335
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+/**
+ * Application level mark bit for {@link RevObject}s.
+ * <p>
+ * To create a flag use {@link RevWalk#newFlag(String)}.
+ */
+public class RevFlag {
+ /**
+ * Uninteresting by {@link RevWalk#markUninteresting(RevCommit)}.
+ * <p>
+ * We flag commits as uninteresting if the caller does not want commits
+ * reachable from a commit to {@link RevWalk#markUninteresting(RevCommit)}.
+ * This flag is always carried into the commit's parents and is a key part
+ * of the "rev-list B --not A" feature; A is marked UNINTERESTING.
+ * <p>
+ * This is a static flag. Its RevWalk is not available.
+ */
+ public static final RevFlag UNINTERESTING = new StaticRevFlag(
+ "UNINTERESTING", RevWalk.UNINTERESTING);
+
+ final RevWalk walker;
+
+ final String name;
+
+ final int mask;
+
+ RevFlag(final RevWalk w, final String n, final int m) {
+ walker = w;
+ name = n;
+ mask = m;
+ }
+
+ /**
+ * Get the revision walk instance this flag was created from.
+ *
+ * @return the walker this flag was allocated out of, and belongs to.
+ */
+ public RevWalk getRevWalk() {
+ return walker;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ static class StaticRevFlag extends RevFlag {
+ StaticRevFlag(final String n, final int m) {
+ super(null, n, m);
+ }
+
+ @Override
+ public RevWalk getRevWalk() {
+ throw new UnsupportedOperationException(toString()
+ + " is a static flag and has no RevWalk instance");
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java
new file mode 100644
index 0000000000..fb9b4525a9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Multiple application level mark bits for {@link RevObject}s.
+ *
+ * @see RevFlag
+ */
+public class RevFlagSet extends AbstractSet<RevFlag> {
+ int mask;
+
+ private final List<RevFlag> active;
+
+ /** Create an empty set of flags. */
+ public RevFlagSet() {
+ active = new ArrayList<RevFlag>();
+ }
+
+ /**
+ * Create a set of flags, copied from an existing set.
+ *
+ * @param s
+ * the set to copy flags from.
+ */
+ public RevFlagSet(final RevFlagSet s) {
+ mask = s.mask;
+ active = new ArrayList<RevFlag>(s.active);
+ }
+
+ /**
+ * Create a set of flags, copied from an existing collection.
+ *
+ * @param s
+ * the collection to copy flags from.
+ */
+ public RevFlagSet(final Collection<RevFlag> s) {
+ this();
+ addAll(s);
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ if (o instanceof RevFlag)
+ return (mask & ((RevFlag) o).mask) != 0;
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(final Collection<?> c) {
+ if (c instanceof RevFlagSet) {
+ final int cMask = ((RevFlagSet) c).mask;
+ return (mask & cMask) == cMask;
+ }
+ return super.containsAll(c);
+ }
+
+ @Override
+ public boolean add(final RevFlag flag) {
+ if ((mask & flag.mask) != 0)
+ return false;
+ mask |= flag.mask;
+ int p = 0;
+ while (p < active.size() && active.get(p).mask < flag.mask)
+ p++;
+ active.add(p, flag);
+ return true;
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ final RevFlag flag = (RevFlag) o;
+ if ((mask & flag.mask) == 0)
+ return false;
+ mask &= ~flag.mask;
+ for (int i = 0; i < active.size(); i++)
+ if (active.get(i).mask == flag.mask)
+ active.remove(i);
+ return true;
+ }
+
+ @Override
+ public Iterator<RevFlag> iterator() {
+ final Iterator<RevFlag> i = active.iterator();
+ return new Iterator<RevFlag>() {
+ private RevFlag current;
+
+ public boolean hasNext() {
+ return i.hasNext();
+ }
+
+ public RevFlag next() {
+ return current = i.next();
+ }
+
+ public void remove() {
+ mask &= ~current.mask;
+ i.remove();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return active.size();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java
new file mode 100644
index 0000000000..e5e3abcade
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+
+/** Base object type accessed during revision walking. */
+public abstract class RevObject extends ObjectId {
+ static final int PARSED = 1;
+
+ int flags;
+
+ RevObject(final AnyObjectId name) {
+ super(name);
+ }
+
+ void parseHeaders(final RevWalk walk) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ loadCanonical(walk);
+ flags |= PARSED;
+ }
+
+ void parseBody(final RevWalk walk) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ if ((flags & PARSED) == 0)
+ parseHeaders(walk);
+ }
+
+ final byte[] loadCanonical(final RevWalk walk) throws IOException,
+ MissingObjectException, IncorrectObjectTypeException,
+ CorruptObjectException {
+ final ObjectLoader ldr = walk.db.openObject(walk.curs, this);
+ if (ldr == null)
+ throw new MissingObjectException(this, getType());
+ final byte[] data = ldr.getCachedBytes();
+ if (getType() != ldr.getType())
+ throw new IncorrectObjectTypeException(this, getType());
+ return data;
+ }
+
+ /**
+ * Get Git object type. See {@link Constants}.
+ *
+ * @return object type
+ */
+ public abstract int getType();
+
+ /**
+ * Get the name of this object.
+ *
+ * @return unique hash of this object.
+ */
+ public final ObjectId getId() {
+ return this;
+ }
+
+ @Override
+ public final boolean equals(final AnyObjectId o) {
+ return this == o;
+ }
+
+ @Override
+ public final boolean equals(final Object o) {
+ return this == o;
+ }
+
+ /**
+ * Test to see if the flag has been set on this object.
+ *
+ * @param flag
+ * the flag to test.
+ * @return true if the flag has been added to this object; false if not.
+ */
+ public final boolean has(final RevFlag flag) {
+ return (flags & flag.mask) != 0;
+ }
+
+ /**
+ * Test to see if any flag in the set has been set on this object.
+ *
+ * @param set
+ * the flags to test.
+ * @return true if any flag in the set has been added to this object; false
+ * if not.
+ */
+ public final boolean hasAny(final RevFlagSet set) {
+ return (flags & set.mask) != 0;
+ }
+
+ /**
+ * Test to see if all flags in the set have been set on this object.
+ *
+ * @param set
+ * the flags to test.
+ * @return true if all flags of the set have been added to this object;
+ * false if some or none have been added.
+ */
+ public final boolean hasAll(final RevFlagSet set) {
+ return (flags & set.mask) == set.mask;
+ }
+
+ /**
+ * Add a flag to this object.
+ * <p>
+ * If the flag is already set on this object then the method has no effect.
+ *
+ * @param flag
+ * the flag to mark on this object, for later testing.
+ */
+ public final void add(final RevFlag flag) {
+ flags |= flag.mask;
+ }
+
+ /**
+ * Add a set of flags to this object.
+ *
+ * @param set
+ * the set of flags to mark on this object, for later testing.
+ */
+ public final void add(final RevFlagSet set) {
+ flags |= set.mask;
+ }
+
+ /**
+ * Remove a flag from this object.
+ * <p>
+ * If the flag is not set on this object then the method has no effect.
+ *
+ * @param flag
+ * the flag to remove from this object.
+ */
+ public final void remove(final RevFlag flag) {
+ flags &= ~flag.mask;
+ }
+
+ /**
+ * Remove a set of flags from this object.
+ *
+ * @param set
+ * the flag to remove from this object.
+ */
+ public final void remove(final RevFlagSet set) {
+ flags &= ~set.mask;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder s = new StringBuilder();
+ s.append(Constants.typeString(getType()));
+ s.append(' ');
+ s.append(name());
+ s.append(' ');
+ appendCoreFlags(s);
+ return s.toString();
+ }
+
+ /**
+ * @param s
+ * buffer to append a debug description of core RevFlags onto.
+ */
+ protected void appendCoreFlags(final StringBuilder s) {
+ s.append((flags & RevWalk.TOPO_DELAY) != 0 ? 'o' : '-');
+ s.append((flags & RevWalk.TEMP_MARK) != 0 ? 't' : '-');
+ s.append((flags & RevWalk.REWRITE) != 0 ? 'r' : '-');
+ s.append((flags & RevWalk.UNINTERESTING) != 0 ? 'u' : '-');
+ s.append((flags & RevWalk.SEEN) != 0 ? 's' : '-');
+ s.append((flags & RevWalk.PARSED) != 0 ? 'p' : '-');
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java
new file mode 100644
index 0000000000..3ae1a71f1b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.util.AbstractList;
+
+/**
+ * An ordered list of {@link RevObject} subclasses.
+ *
+ * @param <E>
+ * type of subclass of RevObject the list is storing.
+ */
+public class RevObjectList<E extends RevObject> extends AbstractList<E> {
+ static final int BLOCK_SHIFT = 8;
+
+ static final int BLOCK_SIZE = 1 << BLOCK_SHIFT;
+
+ Block contents;
+
+ int size;
+
+ /** Create an empty object list. */
+ public RevObjectList() {
+ clear();
+ }
+
+ public void add(final int index, final E element) {
+ if (index != size)
+ throw new UnsupportedOperationException("Not add-at-end: " + index);
+ set(index, element);
+ size++;
+ }
+
+ public E set(int index, E element) {
+ Block s = contents;
+ while (index >> s.shift >= BLOCK_SIZE) {
+ s = new Block(s.shift + BLOCK_SHIFT);
+ s.contents[0] = contents;
+ contents = s;
+ }
+ while (s.shift > 0) {
+ final int i = index >> s.shift;
+ index -= i << s.shift;
+ if (s.contents[i] == null)
+ s.contents[i] = new Block(s.shift - BLOCK_SHIFT);
+ s = (Block) s.contents[i];
+ }
+ final Object old = s.contents[index];
+ s.contents[index] = element;
+ return (E) old;
+ }
+
+ public E get(int index) {
+ Block s = contents;
+ if (index >> s.shift >= 1024)
+ return null;
+ while (s != null && s.shift > 0) {
+ final int i = index >> s.shift;
+ index -= i << s.shift;
+ s = (Block) s.contents[i];
+ }
+ return s != null ? (E) s.contents[index] : null;
+ }
+
+ public int size() {
+ return size;
+ }
+
+ @Override
+ public void clear() {
+ contents = new Block(0);
+ size = 0;
+ }
+
+ static class Block {
+ final Object[] contents = new Object[BLOCK_SIZE];
+
+ final int shift;
+
+ Block(final int s) {
+ shift = s;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevSort.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevSort.java
new file mode 100644
index 0000000000..238af12fdb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevSort.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+/** Sorting strategies supported by {@link RevWalk} and {@link ObjectWalk}. */
+public enum RevSort {
+ /**
+ * No specific sorting is requested.
+ * <p>
+ * Applications should not rely upon the ordering produced by this strategy.
+ * Any ordering in the output is caused by low level implementation details
+ * and may change without notice.
+ */
+ NONE,
+
+ /**
+ * Sort by commit time, descending (newest first, oldest last).
+ * <p>
+ * This strategy can be combined with {@link #TOPO}.
+ */
+ COMMIT_TIME_DESC,
+
+ /**
+ * Topological sorting (all children before parents).
+ * <p>
+ * This strategy can be combined with {@link #COMMIT_TIME_DESC}.
+ */
+ TOPO,
+
+ /**
+ * Flip the output into the reverse ordering.
+ * <p>
+ * This strategy can be combined with the others described by this type as
+ * it is usually performed at the very end.
+ */
+ REVERSE,
+
+ /**
+ * Include {@link RevFlag#UNINTERESTING} boundary commits after all others.
+ * In {@link ObjectWalk}, objects associated with such commits (trees,
+ * blobs), and all other objects marked explicitly as UNINTERESTING are also
+ * included.
+ * <p>
+ * A boundary commit is a UNINTERESTING parent of an interesting commit that
+ * was previously output.
+ */
+ BOUNDARY;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
new file mode 100644
index 0000000000..a77757dc2c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Tag;
+import org.eclipse.jgit.util.MutableInteger;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/** An annotated tag. */
+public class RevTag extends RevObject {
+ private RevObject object;
+
+ private byte[] buffer;
+
+ private String tagName;
+
+ /**
+ * Create a new tag reference.
+ *
+ * @param id
+ * object name for the tag.
+ */
+ protected RevTag(final AnyObjectId id) {
+ super(id);
+ }
+
+ @Override
+ void parseHeaders(final RevWalk walk) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ parseCanonical(walk, loadCanonical(walk));
+ }
+
+ @Override
+ void parseBody(final RevWalk walk) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ if (buffer == null) {
+ buffer = loadCanonical(walk);
+ if ((flags & PARSED) == 0)
+ parseCanonical(walk, buffer);
+ }
+ }
+
+ void parseCanonical(final RevWalk walk, final byte[] rawTag)
+ throws CorruptObjectException {
+ final MutableInteger pos = new MutableInteger();
+ final int oType;
+
+ pos.value = 53; // "object $sha1\ntype "
+ oType = Constants.decodeTypeString(this, rawTag, (byte) '\n', pos);
+ walk.idBuffer.fromString(rawTag, 7);
+ object = walk.lookupAny(walk.idBuffer, oType);
+
+ int p = pos.value += 4; // "tag "
+ final int nameEnd = RawParseUtils.nextLF(rawTag, p) - 1;
+ tagName = RawParseUtils.decode(Constants.CHARSET, rawTag, p, nameEnd);
+
+ if (walk.isRetainBody())
+ buffer = rawTag;
+ flags |= PARSED;
+ }
+
+ @Override
+ public final int getType() {
+ return Constants.OBJ_TAG;
+ }
+
+ /**
+ * Parse the tagger identity from the raw buffer.
+ * <p>
+ * This method parses and returns the content of the tagger line, after
+ * taking the tag's character set into account and decoding the tagger
+ * name and email address. This method is fairly expensive and produces a
+ * new PersonIdent instance on each invocation. Callers should invoke this
+ * method only if they are certain they will be outputting the result, and
+ * should cache the return value for as long as necessary to use all
+ * information from it.
+ *
+ * @return identity of the tagger (name, email) and the time the tag
+ * was made by the tagger; null if no tagger line was found.
+ */
+ public final PersonIdent getTaggerIdent() {
+ final byte[] raw = buffer;
+ final int nameB = RawParseUtils.tagger(raw, 0);
+ if (nameB < 0)
+ return null;
+ return RawParseUtils.parsePersonIdent(raw, nameB);
+ }
+
+ /**
+ * Parse the complete tag message and decode it to a string.
+ * <p>
+ * This method parses and returns the message portion of the tag buffer,
+ * after taking the tag's character set into account and decoding the buffer
+ * using that character set. This method is a fairly expensive operation and
+ * produces a new string on each invocation.
+ *
+ * @return decoded tag message as a string. Never null.
+ */
+ public final String getFullMessage() {
+ final byte[] raw = buffer;
+ final int msgB = RawParseUtils.tagMessage(raw, 0);
+ if (msgB < 0)
+ return "";
+ final Charset enc = RawParseUtils.parseEncoding(raw);
+ return RawParseUtils.decode(enc, raw, msgB, raw.length);
+ }
+
+ /**
+ * Parse the tag message and return the first "line" of it.
+ * <p>
+ * The first line is everything up to the first pair of LFs. This is the
+ * "oneline" format, suitable for output in a single line display.
+ * <p>
+ * This method parses and returns the message portion of the tag buffer,
+ * after taking the tag's character set into account and decoding the buffer
+ * using that character set. This method is a fairly expensive operation and
+ * produces a new string on each invocation.
+ *
+ * @return decoded tag message as a string. Never null. The returned string
+ * does not contain any LFs, even if the first paragraph spanned
+ * multiple lines. Embedded LFs are converted to spaces.
+ */
+ public final String getShortMessage() {
+ final byte[] raw = buffer;
+ final int msgB = RawParseUtils.tagMessage(raw, 0);
+ if (msgB < 0)
+ return "";
+
+ final Charset enc = RawParseUtils.parseEncoding(raw);
+ final int msgE = RawParseUtils.endOfParagraph(raw, msgB);
+ String str = RawParseUtils.decode(enc, raw, msgB, msgE);
+ if (RevCommit.hasLF(raw, msgB, msgE))
+ str = str.replace('\n', ' ');
+ return str;
+ }
+
+ /**
+ * Parse this tag buffer for display.
+ *
+ * @param walk
+ * revision walker owning this reference.
+ * @return parsed tag.
+ */
+ public Tag asTag(final RevWalk walk) {
+ return new Tag(walk.db, this, tagName, buffer);
+ }
+
+ /**
+ * Get a reference to the object this tag was placed on.
+ *
+ * @return object this tag refers to.
+ */
+ public final RevObject getObject() {
+ return object;
+ }
+
+ /**
+ * Get the name of this tag, from the tag header.
+ *
+ * @return name of the tag, according to the tag header.
+ */
+ public final String getTagName() {
+ return tagName;
+ }
+
+ final void disposeBody() {
+ buffer = null;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java
new file mode 100644
index 0000000000..4fa153a657
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+
+/** A reference to a tree of subtrees/files. */
+public class RevTree extends RevObject {
+ /**
+ * Create a new tree reference.
+ *
+ * @param id
+ * object name for the tree.
+ */
+ protected RevTree(final AnyObjectId id) {
+ super(id);
+ }
+
+ @Override
+ public final int getType() {
+ return Constants.OBJ_TREE;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
new file mode 100644
index 0000000000..ac2643422f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -0,0 +1,1085 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Iterator;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RevWalkException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSubclassMap;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+
+/**
+ * Walks a commit graph and produces the matching commits in order.
+ * <p>
+ * A RevWalk instance can only be used once to generate results. Running a
+ * second time requires creating a new RevWalk instance, or invoking
+ * {@link #reset()} before starting again. Resetting an existing instance may be
+ * faster for some applications as commit body parsing can be avoided on the
+ * later invocations.
+ * <p>
+ * RevWalk instances are not thread-safe. Applications must either restrict
+ * usage of a RevWalk instance to a single thread, or implement their own
+ * synchronization at a higher level.
+ * <p>
+ * Multiple simultaneous RevWalk instances per {@link Repository} are permitted,
+ * even from concurrent threads. Equality of {@link RevCommit}s from two
+ * different RevWalk instances is never true, even if their {@link ObjectId}s
+ * are equal (and thus they describe the same commit).
+ * <p>
+ * The offered iterator is over the list of RevCommits described by the
+ * configuration of this instance. Applications should restrict themselves to
+ * using either the provided Iterator or {@link #next()}, but never use both on
+ * the same RevWalk at the same time. The Iterator may buffer RevCommits, while
+ * {@link #next()} does not.
+ */
+public class RevWalk implements Iterable<RevCommit> {
+ /**
+ * Set on objects whose important header data has been loaded.
+ * <p>
+ * For a RevCommit this indicates we have pulled apart the tree and parent
+ * references from the raw bytes available in the repository and translated
+ * those to our own local RevTree and RevCommit instances. The raw buffer is
+ * also available for message and other header filtering.
+ * <p>
+ * For a RevTag this indicates we have pulled part the tag references to
+ * find out who the tag refers to, and what that object's type is.
+ */
+ static final int PARSED = 1 << 0;
+
+ /**
+ * Set on RevCommit instances added to our {@link #pending} queue.
+ * <p>
+ * We use this flag to avoid adding the same commit instance twice to our
+ * queue, especially if we reached it by more than one path.
+ */
+ static final int SEEN = 1 << 1;
+
+ /**
+ * Set on RevCommit instances the caller does not want output.
+ * <p>
+ * We flag commits as uninteresting if the caller does not want commits
+ * reachable from a commit given to {@link #markUninteresting(RevCommit)}.
+ * This flag is always carried into the commit's parents and is a key part
+ * of the "rev-list B --not A" feature; A is marked UNINTERESTING.
+ */
+ static final int UNINTERESTING = 1 << 2;
+
+ /**
+ * Set on a RevCommit that can collapse out of the history.
+ * <p>
+ * If the {@link #treeFilter} concluded that this commit matches his
+ * parents' for all of the paths that the filter is interested in then we
+ * mark the commit REWRITE. Later we can rewrite the parents of a REWRITE
+ * child to remove chains of REWRITE commits before we produce the child to
+ * the application.
+ *
+ * @see RewriteGenerator
+ */
+ static final int REWRITE = 1 << 3;
+
+ /**
+ * Temporary mark for use within generators or filters.
+ * <p>
+ * This mark is only for local use within a single scope. If someone sets
+ * the mark they must unset it before any other code can see the mark.
+ */
+ static final int TEMP_MARK = 1 << 4;
+
+ /**
+ * Temporary mark for use within {@link TopoSortGenerator}.
+ * <p>
+ * This mark indicates the commit could not produce when it wanted to, as at
+ * least one child was behind it. Commits with this flag are delayed until
+ * all children have been output first.
+ */
+ static final int TOPO_DELAY = 1 << 5;
+
+ /** Number of flag bits we keep internal for our own use. See above flags. */
+ static final int RESERVED_FLAGS = 6;
+
+ private static final int APP_FLAGS = -1 & ~((1 << RESERVED_FLAGS) - 1);
+
+ final Repository db;
+
+ final WindowCursor curs;
+
+ final MutableObjectId idBuffer;
+
+ private final ObjectIdSubclassMap<RevObject> objects;
+
+ private int freeFlags = APP_FLAGS;
+
+ private int delayFreeFlags;
+
+ int carryFlags = UNINTERESTING;
+
+ private final ArrayList<RevCommit> roots;
+
+ AbstractRevQueue queue;
+
+ Generator pending;
+
+ private final EnumSet<RevSort> sorting;
+
+ private RevFilter filter;
+
+ private TreeFilter treeFilter;
+
+ private boolean retainBody;
+
+ /**
+ * Create a new revision walker for a given repository.
+ *
+ * @param repo
+ * the repository the walker will obtain data from.
+ */
+ public RevWalk(final Repository repo) {
+ db = repo;
+ curs = new WindowCursor();
+ idBuffer = new MutableObjectId();
+ objects = new ObjectIdSubclassMap<RevObject>();
+ roots = new ArrayList<RevCommit>();
+ queue = new DateRevQueue();
+ pending = new StartGenerator(this);
+ sorting = EnumSet.of(RevSort.NONE);
+ filter = RevFilter.ALL;
+ treeFilter = TreeFilter.ALL;
+ retainBody = true;
+ }
+
+ /**
+ * Get the repository this walker loads objects from.
+ *
+ * @return the repository this walker was created to read.
+ */
+ public Repository getRepository() {
+ return db;
+ }
+
+ /**
+ * Mark a commit to start graph traversal from.
+ * <p>
+ * Callers are encouraged to use {@link #parseCommit(AnyObjectId)} to obtain
+ * the commit reference, rather than {@link #lookupCommit(AnyObjectId)}, as
+ * this method requires the commit to be parsed before it can be added as a
+ * root for the traversal.
+ * <p>
+ * The method will automatically parse an unparsed commit, but error
+ * handling may be more difficult for the application to explain why a
+ * RevCommit is not actually a commit. The object pool of this walker would
+ * also be 'poisoned' by the non-commit RevCommit.
+ *
+ * @param c
+ * the commit to start traversing from. The commit passed must be
+ * from this same revision walker.
+ * @throws MissingObjectException
+ * the commit supplied is not available from the object
+ * database. This usually indicates the supplied commit is
+ * invalid, but the reference was constructed during an earlier
+ * invocation to {@link #lookupCommit(AnyObjectId)}.
+ * @throws IncorrectObjectTypeException
+ * the object was not parsed yet and it was discovered during
+ * parsing that it is not actually a commit. This usually
+ * indicates the caller supplied a non-commit SHA-1 to
+ * {@link #lookupCommit(AnyObjectId)}.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public void markStart(final RevCommit c) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ if ((c.flags & SEEN) != 0)
+ return;
+ if ((c.flags & PARSED) == 0)
+ c.parseHeaders(this);
+ c.flags |= SEEN;
+ roots.add(c);
+ queue.add(c);
+ }
+
+ /**
+ * Mark commits to start graph traversal from.
+ *
+ * @param list
+ * commits to start traversing from. The commits passed must be
+ * from this same revision walker.
+ * @throws MissingObjectException
+ * one of the commits supplied is not available from the object
+ * database. This usually indicates the supplied commit is
+ * invalid, but the reference was constructed during an earlier
+ * invocation to {@link #lookupCommit(AnyObjectId)}.
+ * @throws IncorrectObjectTypeException
+ * the object was not parsed yet and it was discovered during
+ * parsing that it is not actually a commit. This usually
+ * indicates the caller supplied a non-commit SHA-1 to
+ * {@link #lookupCommit(AnyObjectId)}.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public void markStart(final Collection<RevCommit> list)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ for (final RevCommit c : list)
+ markStart(c);
+ }
+
+ /**
+ * Mark a commit to not produce in the output.
+ * <p>
+ * Uninteresting commits denote not just themselves but also their entire
+ * ancestry chain, back until the merge base of an uninteresting commit and
+ * an otherwise interesting commit.
+ * <p>
+ * Callers are encouraged to use {@link #parseCommit(AnyObjectId)} to obtain
+ * the commit reference, rather than {@link #lookupCommit(AnyObjectId)}, as
+ * this method requires the commit to be parsed before it can be added as a
+ * root for the traversal.
+ * <p>
+ * The method will automatically parse an unparsed commit, but error
+ * handling may be more difficult for the application to explain why a
+ * RevCommit is not actually a commit. The object pool of this walker would
+ * also be 'poisoned' by the non-commit RevCommit.
+ *
+ * @param c
+ * the commit to start traversing from. The commit passed must be
+ * from this same revision walker.
+ * @throws MissingObjectException
+ * the commit supplied is not available from the object
+ * database. This usually indicates the supplied commit is
+ * invalid, but the reference was constructed during an earlier
+ * invocation to {@link #lookupCommit(AnyObjectId)}.
+ * @throws IncorrectObjectTypeException
+ * the object was not parsed yet and it was discovered during
+ * parsing that it is not actually a commit. This usually
+ * indicates the caller supplied a non-commit SHA-1 to
+ * {@link #lookupCommit(AnyObjectId)}.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public void markUninteresting(final RevCommit c)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ c.flags |= UNINTERESTING;
+ carryFlagsImpl(c);
+ markStart(c);
+ }
+
+ /**
+ * Determine if a commit is reachable from another commit.
+ * <p>
+ * A commit <code>base</code> is an ancestor of <code>tip</code> if we
+ * can find a path of commits that leads from <code>tip</code> and ends at
+ * <code>base</code>.
+ * <p>
+ * This utility function resets the walker, inserts the two supplied
+ * commits, and then executes a walk until an answer can be obtained.
+ * Currently allocated RevFlags that have been added to RevCommit instances
+ * will be retained through the reset.
+ *
+ * @param base
+ * commit the caller thinks is reachable from <code>tip</code>.
+ * @param tip
+ * commit to start iteration from, and which is most likely a
+ * descendant (child) of <code>base</code>.
+ * @return true if there is a path directly from <code>tip</code> to
+ * <code>base</code> (and thus <code>base</code> is fully merged
+ * into <code>tip</code>); false otherwise.
+ * @throws MissingObjectException
+ * one or or more of the next commit's parents are not available
+ * from the object database, but were thought to be candidates
+ * for traversal. This usually indicates a broken link.
+ * @throws IncorrectObjectTypeException
+ * one or or more of the next commit's parents are not actually
+ * commit objects.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public boolean isMergedInto(final RevCommit base, final RevCommit tip)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ final RevFilter oldRF = filter;
+ final TreeFilter oldTF = treeFilter;
+ try {
+ finishDelayedFreeFlags();
+ reset(~freeFlags & APP_FLAGS);
+ filter = RevFilter.MERGE_BASE;
+ treeFilter = TreeFilter.ALL;
+ markStart(tip);
+ markStart(base);
+ return next() == base;
+ } finally {
+ filter = oldRF;
+ treeFilter = oldTF;
+ }
+ }
+
+ /**
+ * Pop the next most recent commit.
+ *
+ * @return next most recent commit; null if traversal is over.
+ * @throws MissingObjectException
+ * one or or more of the next commit's parents are not available
+ * from the object database, but were thought to be candidates
+ * for traversal. This usually indicates a broken link.
+ * @throws IncorrectObjectTypeException
+ * one or or more of the next commit's parents are not actually
+ * commit objects.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ return pending.next();
+ }
+
+ /**
+ * Obtain the sort types applied to the commits returned.
+ *
+ * @return the sorting strategies employed. At least one strategy is always
+ * used, but that strategy may be {@link RevSort#NONE}.
+ */
+ public EnumSet<RevSort> getRevSort() {
+ return sorting.clone();
+ }
+
+ /**
+ * Check whether the provided sorting strategy is enabled.
+ *
+ * @param sort
+ * a sorting strategy to look for.
+ * @return true if this strategy is enabled, false otherwise
+ */
+ public boolean hasRevSort(RevSort sort) {
+ return sorting.contains(sort);
+ }
+
+ /**
+ * Select a single sorting strategy for the returned commits.
+ * <p>
+ * Disables all sorting strategies, then enables only the single strategy
+ * supplied by the caller.
+ *
+ * @param s
+ * a sorting strategy to enable.
+ */
+ public void sort(final RevSort s) {
+ assertNotStarted();
+ sorting.clear();
+ sorting.add(s);
+ }
+
+ /**
+ * Add or remove a sorting strategy for the returned commits.
+ * <p>
+ * Multiple strategies can be applied at once, in which case some strategies
+ * may take precedence over others. As an example, {@link RevSort#TOPO} must
+ * take precedence over {@link RevSort#COMMIT_TIME_DESC}, otherwise it
+ * cannot enforce its ordering.
+ *
+ * @param s
+ * a sorting strategy to enable or disable.
+ * @param use
+ * true if this strategy should be used, false if it should be
+ * removed.
+ */
+ public void sort(final RevSort s, final boolean use) {
+ assertNotStarted();
+ if (use)
+ sorting.add(s);
+ else
+ sorting.remove(s);
+
+ if (sorting.size() > 1)
+ sorting.remove(RevSort.NONE);
+ else if (sorting.size() == 0)
+ sorting.add(RevSort.NONE);
+ }
+
+ /**
+ * Get the currently configured commit filter.
+ *
+ * @return the current filter. Never null as a filter is always needed.
+ */
+ public RevFilter getRevFilter() {
+ return filter;
+ }
+
+ /**
+ * Set the commit filter for this walker.
+ * <p>
+ * Multiple filters may be combined by constructing an arbitrary tree of
+ * <code>AndRevFilter</code> or <code>OrRevFilter</code> instances to
+ * describe the boolean expression required by the application. Custom
+ * filter implementations may also be constructed by applications.
+ * <p>
+ * Note that filters are not thread-safe and may not be shared by concurrent
+ * RevWalk instances. Every RevWalk must be supplied its own unique filter,
+ * unless the filter implementation specifically states it is (and always
+ * will be) thread-safe. Callers may use {@link RevFilter#clone()} to create
+ * a unique filter tree for this RevWalk instance.
+ *
+ * @param newFilter
+ * the new filter. If null the special {@link RevFilter#ALL}
+ * filter will be used instead, as it matches every commit.
+ * @see org.eclipse.jgit.revwalk.filter.AndRevFilter
+ * @see org.eclipse.jgit.revwalk.filter.OrRevFilter
+ */
+ public void setRevFilter(final RevFilter newFilter) {
+ assertNotStarted();
+ filter = newFilter != null ? newFilter : RevFilter.ALL;
+ }
+
+ /**
+ * Get the tree filter used to simplify commits by modified paths.
+ *
+ * @return the current filter. Never null as a filter is always needed. If
+ * no filter is being applied {@link TreeFilter#ALL} is returned.
+ */
+ public TreeFilter getTreeFilter() {
+ return treeFilter;
+ }
+
+ /**
+ * Set the tree filter used to simplify commits by modified paths.
+ * <p>
+ * If null or {@link TreeFilter#ALL} the path limiter is removed. Commits
+ * will not be simplified.
+ * <p>
+ * If non-null and not {@link TreeFilter#ALL} then the tree filter will be
+ * installed and commits will have their ancestry simplified to hide commits
+ * that do not contain tree entries matched by the filter.
+ * <p>
+ * Usually callers should be inserting a filter graph including
+ * {@link TreeFilter#ANY_DIFF} along with one or more
+ * {@link org.eclipse.jgit.treewalk.filter.PathFilter} instances.
+ *
+ * @param newFilter
+ * new filter. If null the special {@link TreeFilter#ALL} filter
+ * will be used instead, as it matches everything.
+ * @see org.eclipse.jgit.treewalk.filter.PathFilter
+ */
+ public void setTreeFilter(final TreeFilter newFilter) {
+ assertNotStarted();
+ treeFilter = newFilter != null ? newFilter : TreeFilter.ALL;
+ }
+
+ /**
+ * Should the body of a commit or tag be retained after parsing its headers?
+ * <p>
+ * Usually the body is always retained, but some application code might not
+ * care and would prefer to discard the body of a commit as early as
+ * possible, to reduce memory usage.
+ *
+ * @return true if the body should be retained; false it is discarded.
+ */
+ public boolean isRetainBody() {
+ return retainBody;
+ }
+
+ /**
+ * Set whether or not the body of a commit or tag is retained.
+ * <p>
+ * If a body of a commit or tag is not retained, the application must
+ * call {@link #parseBody(RevObject)} before the body can be safely
+ * accessed through the type specific access methods.
+ *
+ * @param retain true to retain bodies; false to discard them early.
+ */
+ public void setRetainBody(final boolean retain) {
+ retainBody = retain;
+ }
+
+ /**
+ * Locate a reference to a blob without loading it.
+ * <p>
+ * The blob may or may not exist in the repository. It is impossible to tell
+ * from this method's return value.
+ *
+ * @param id
+ * name of the blob object.
+ * @return reference to the blob object. Never null.
+ */
+ public RevBlob lookupBlob(final AnyObjectId id) {
+ RevBlob c = (RevBlob) objects.get(id);
+ if (c == null) {
+ c = new RevBlob(id);
+ objects.add(c);
+ }
+ return c;
+ }
+
+ /**
+ * Locate a reference to a tree without loading it.
+ * <p>
+ * The tree may or may not exist in the repository. It is impossible to tell
+ * from this method's return value.
+ *
+ * @param id
+ * name of the tree object.
+ * @return reference to the tree object. Never null.
+ */
+ public RevTree lookupTree(final AnyObjectId id) {
+ RevTree c = (RevTree) objects.get(id);
+ if (c == null) {
+ c = new RevTree(id);
+ objects.add(c);
+ }
+ return c;
+ }
+
+ /**
+ * Locate a reference to a commit without loading it.
+ * <p>
+ * The commit may or may not exist in the repository. It is impossible to
+ * tell from this method's return value.
+ *
+ * @param id
+ * name of the commit object.
+ * @return reference to the commit object. Never null.
+ */
+ public RevCommit lookupCommit(final AnyObjectId id) {
+ RevCommit c = (RevCommit) objects.get(id);
+ if (c == null) {
+ c = createCommit(id);
+ objects.add(c);
+ }
+ return c;
+ }
+
+ /**
+ * Locate a reference to any object without loading it.
+ * <p>
+ * The object may or may not exist in the repository. It is impossible to
+ * tell from this method's return value.
+ *
+ * @param id
+ * name of the object.
+ * @param type
+ * type of the object. Must be a valid Git object type.
+ * @return reference to the object. Never null.
+ */
+ public RevObject lookupAny(final AnyObjectId id, final int type) {
+ RevObject r = objects.get(id);
+ if (r == null) {
+ switch (type) {
+ case Constants.OBJ_COMMIT:
+ r = createCommit(id);
+ break;
+ case Constants.OBJ_TREE:
+ r = new RevTree(id);
+ break;
+ case Constants.OBJ_BLOB:
+ r = new RevBlob(id);
+ break;
+ case Constants.OBJ_TAG:
+ r = new RevTag(id);
+ break;
+ default:
+ throw new IllegalArgumentException("invalid git type: " + type);
+ }
+ objects.add(r);
+ }
+ return r;
+ }
+
+ /**
+ * Locate a reference to a commit and immediately parse its content.
+ * <p>
+ * Unlike {@link #lookupCommit(AnyObjectId)} this method only returns
+ * successfully if the commit object exists, is verified to be a commit, and
+ * was parsed without error.
+ *
+ * @param id
+ * name of the commit object.
+ * @return reference to the commit object. Never null.
+ * @throws MissingObjectException
+ * the supplied commit does not exist.
+ * @throws IncorrectObjectTypeException
+ * the supplied id is not a commit or an annotated tag.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public RevCommit parseCommit(final AnyObjectId id)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ RevObject c = parseAny(id);
+ while (c instanceof RevTag) {
+ c = ((RevTag) c).getObject();
+ parseHeaders(c);
+ }
+ if (!(c instanceof RevCommit))
+ throw new IncorrectObjectTypeException(id.toObjectId(),
+ Constants.TYPE_COMMIT);
+ return (RevCommit) c;
+ }
+
+ /**
+ * Locate a reference to a tree.
+ * <p>
+ * This method only returns successfully if the tree object exists, is
+ * verified to be a tree.
+ *
+ * @param id
+ * name of the tree object, or a commit or annotated tag that may
+ * reference a tree.
+ * @return reference to the tree object. Never null.
+ * @throws MissingObjectException
+ * the supplied tree does not exist.
+ * @throws IncorrectObjectTypeException
+ * the supplied id is not a tree, a commit or an annotated tag.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public RevTree parseTree(final AnyObjectId id)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ RevObject c = parseAny(id);
+ while (c instanceof RevTag) {
+ c = ((RevTag) c).getObject();
+ parseHeaders(c);
+ }
+
+ final RevTree t;
+ if (c instanceof RevCommit)
+ t = ((RevCommit) c).getTree();
+ else if (!(c instanceof RevTree))
+ throw new IncorrectObjectTypeException(id.toObjectId(),
+ Constants.TYPE_TREE);
+ else
+ t = (RevTree) c;
+ parseHeaders(t);
+ return t;
+ }
+
+ /**
+ * Locate a reference to any object and immediately parse its headers.
+ * <p>
+ * This method only returns successfully if the object exists and was parsed
+ * without error. Parsing an object can be expensive as the type must be
+ * determined. For blobs this may mean the blob content was unpacked
+ * unnecessarily, and thrown away.
+ *
+ * @param id
+ * name of the object.
+ * @return reference to the object. Never null.
+ * @throws MissingObjectException
+ * the supplied does not exist.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public RevObject parseAny(final AnyObjectId id)
+ throws MissingObjectException, IOException {
+ RevObject r = objects.get(id);
+ if (r == null) {
+ final ObjectLoader ldr = db.openObject(curs, id);
+ if (ldr == null)
+ throw new MissingObjectException(id.toObjectId(), "unknown");
+ final byte[] data = ldr.getCachedBytes();
+ final int type = ldr.getType();
+ switch (type) {
+ case Constants.OBJ_COMMIT: {
+ final RevCommit c = createCommit(id);
+ c.parseCanonical(this, data);
+ r = c;
+ break;
+ }
+ case Constants.OBJ_TREE: {
+ r = new RevTree(id);
+ r.flags |= PARSED;
+ break;
+ }
+ case Constants.OBJ_BLOB: {
+ r = new RevBlob(id);
+ r.flags |= PARSED;
+ break;
+ }
+ case Constants.OBJ_TAG: {
+ final RevTag t = new RevTag(id);
+ t.parseCanonical(this, data);
+ r = t;
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Bad object type: " + type);
+ }
+ objects.add(r);
+ } else
+ parseHeaders(r);
+ return r;
+ }
+
+ /**
+ * Ensure the object's critical headers have been parsed.
+ * <p>
+ * This method only returns successfully if the object exists and was parsed
+ * without error.
+ *
+ * @param obj
+ * the object the caller needs to be parsed.
+ * @throws MissingObjectException
+ * the supplied does not exist.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public void parseHeaders(final RevObject obj)
+ throws MissingObjectException, IOException {
+ if ((obj.flags & PARSED) == 0)
+ obj.parseHeaders(this);
+ }
+
+ /**
+ * Ensure the object's fully body content is available.
+ * <p>
+ * This method only returns successfully if the object exists and was parsed
+ * without error.
+ *
+ * @param obj
+ * the object the caller needs to be parsed.
+ * @throws MissingObjectException
+ * the supplied does not exist.
+ * @throws IOException
+ * a pack file or loose object could not be read.
+ */
+ public void parseBody(final RevObject obj)
+ throws MissingObjectException, IOException {
+ obj.parseBody(this);
+ }
+
+ /**
+ * Create a new flag for application use during walking.
+ * <p>
+ * Applications are only assured to be able to create 24 unique flags on any
+ * given revision walker instance. Any flags beyond 24 are offered only if
+ * the implementation has extra free space within its internal storage.
+ *
+ * @param name
+ * description of the flag, primarily useful for debugging.
+ * @return newly constructed flag instance.
+ * @throws IllegalArgumentException
+ * too many flags have been reserved on this revision walker.
+ */
+ public RevFlag newFlag(final String name) {
+ final int m = allocFlag();
+ return new RevFlag(this, name, m);
+ }
+
+ int allocFlag() {
+ if (freeFlags == 0)
+ throw new IllegalArgumentException(32 - RESERVED_FLAGS
+ + " flags already created.");
+ final int m = Integer.lowestOneBit(freeFlags);
+ freeFlags &= ~m;
+ return m;
+ }
+
+ /**
+ * Automatically carry a flag from a child commit to its parents.
+ * <p>
+ * A carried flag is copied from the child commit onto its parents when the
+ * child commit is popped from the lowest level of walk's internal graph.
+ *
+ * @param flag
+ * the flag to carry onto parents, if set on a descendant.
+ */
+ public void carry(final RevFlag flag) {
+ if ((freeFlags & flag.mask) != 0)
+ throw new IllegalArgumentException(flag.name + " is disposed.");
+ if (flag.walker != this)
+ throw new IllegalArgumentException(flag.name + " not from this.");
+ carryFlags |= flag.mask;
+ }
+
+ /**
+ * Automatically carry flags from a child commit to its parents.
+ * <p>
+ * A carried flag is copied from the child commit onto its parents when the
+ * child commit is popped from the lowest level of walk's internal graph.
+ *
+ * @param set
+ * the flags to carry onto parents, if set on a descendant.
+ */
+ public void carry(final Collection<RevFlag> set) {
+ for (final RevFlag flag : set)
+ carry(flag);
+ }
+
+ /**
+ * Allow a flag to be recycled for a different use.
+ * <p>
+ * Recycled flags always come back as a different Java object instance when
+ * assigned again by {@link #newFlag(String)}.
+ * <p>
+ * If the flag was previously being carried, the carrying request is
+ * removed. Disposing of a carried flag while a traversal is in progress has
+ * an undefined behavior.
+ *
+ * @param flag
+ * the to recycle.
+ */
+ public void disposeFlag(final RevFlag flag) {
+ freeFlag(flag.mask);
+ }
+
+ void freeFlag(final int mask) {
+ if (isNotStarted()) {
+ freeFlags |= mask;
+ carryFlags &= ~mask;
+ } else {
+ delayFreeFlags |= mask;
+ }
+ }
+
+ private void finishDelayedFreeFlags() {
+ if (delayFreeFlags != 0) {
+ freeFlags |= delayFreeFlags;
+ carryFlags &= ~delayFreeFlags;
+ delayFreeFlags = 0;
+ }
+ }
+
+ /**
+ * Resets internal state and allows this instance to be used again.
+ * <p>
+ * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit)
+ * instances are not invalidated. RevFlag instances are not invalidated, but
+ * are removed from all RevObjects.
+ */
+ public final void reset() {
+ reset(0);
+ }
+
+ /**
+ * Resets internal state and allows this instance to be used again.
+ * <p>
+ * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit)
+ * instances are not invalidated. RevFlag instances are not invalidated, but
+ * are removed from all RevObjects.
+ *
+ * @param retainFlags
+ * application flags that should <b>not</b> be cleared from
+ * existing commit objects.
+ */
+ public final void resetRetain(final RevFlagSet retainFlags) {
+ reset(retainFlags.mask);
+ }
+
+ /**
+ * Resets internal state and allows this instance to be used again.
+ * <p>
+ * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit)
+ * instances are not invalidated. RevFlag instances are not invalidated, but
+ * are removed from all RevObjects.
+ *
+ * @param retainFlags
+ * application flags that should <b>not</b> be cleared from
+ * existing commit objects.
+ */
+ public final void resetRetain(final RevFlag... retainFlags) {
+ int mask = 0;
+ for (final RevFlag flag : retainFlags)
+ mask |= flag.mask;
+ reset(mask);
+ }
+
+ /**
+ * Resets internal state and allows this instance to be used again.
+ * <p>
+ * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit)
+ * instances are not invalidated. RevFlag instances are not invalidated, but
+ * are removed from all RevObjects.
+ *
+ * @param retainFlags
+ * application flags that should <b>not</b> be cleared from
+ * existing commit objects.
+ */
+ protected void reset(int retainFlags) {
+ finishDelayedFreeFlags();
+ retainFlags |= PARSED;
+ final int clearFlags = ~retainFlags;
+
+ final FIFORevQueue q = new FIFORevQueue();
+ for (final RevCommit c : roots) {
+ if ((c.flags & clearFlags) == 0)
+ continue;
+ c.flags &= retainFlags;
+ c.reset();
+ q.add(c);
+ }
+
+ for (;;) {
+ final RevCommit c = q.next();
+ if (c == null)
+ break;
+ if (c.parents == null)
+ continue;
+ for (final RevCommit p : c.parents) {
+ if ((p.flags & clearFlags) == 0)
+ continue;
+ p.flags &= retainFlags;
+ p.reset();
+ q.add(p);
+ }
+ }
+
+ curs.release();
+ roots.clear();
+ queue = new DateRevQueue();
+ pending = new StartGenerator(this);
+ }
+
+ /**
+ * Dispose all internal state and invalidate all RevObject instances.
+ * <p>
+ * All RevObject (and thus RevCommit, etc.) instances previously acquired
+ * from this RevWalk are invalidated by a dispose call. Applications must
+ * not retain or use RevObject instances obtained prior to the dispose call.
+ * All RevFlag instances are also invalidated, and must not be reused.
+ */
+ public void dispose() {
+ freeFlags = APP_FLAGS;
+ delayFreeFlags = 0;
+ carryFlags = UNINTERESTING;
+ objects.clear();
+ curs.release();
+ roots.clear();
+ queue = new DateRevQueue();
+ pending = new StartGenerator(this);
+ }
+
+ /**
+ * Returns an Iterator over the commits of this walker.
+ * <p>
+ * The returned iterator is only useful for one walk. If this RevWalk gets
+ * reset a new iterator must be obtained to walk over the new results.
+ * <p>
+ * Applications must not use both the Iterator and the {@link #next()} API
+ * at the same time. Pick one API and use that for the entire walk.
+ * <p>
+ * If a checked exception is thrown during the walk (see {@link #next()})
+ * it is rethrown from the Iterator as a {@link RevWalkException}.
+ *
+ * @return an iterator over this walker's commits.
+ * @see RevWalkException
+ */
+ public Iterator<RevCommit> iterator() {
+ final RevCommit first;
+ try {
+ first = RevWalk.this.next();
+ } catch (MissingObjectException e) {
+ throw new RevWalkException(e);
+ } catch (IncorrectObjectTypeException e) {
+ throw new RevWalkException(e);
+ } catch (IOException e) {
+ throw new RevWalkException(e);
+ }
+
+ return new Iterator<RevCommit>() {
+ RevCommit next = first;
+
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ public RevCommit next() {
+ try {
+ final RevCommit r = next;
+ next = RevWalk.this.next();
+ return r;
+ } catch (MissingObjectException e) {
+ throw new RevWalkException(e);
+ } catch (IncorrectObjectTypeException e) {
+ throw new RevWalkException(e);
+ } catch (IOException e) {
+ throw new RevWalkException(e);
+ }
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /** Throws an exception if we have started producing output. */
+ protected void assertNotStarted() {
+ if (isNotStarted())
+ return;
+ throw new IllegalStateException("Output has already been started.");
+ }
+
+ private boolean isNotStarted() {
+ return pending instanceof StartGenerator;
+ }
+
+ /**
+ * Construct a new unparsed commit for the given object.
+ *
+ * @param id
+ * the object this walker requires a commit reference for.
+ * @return a new unparsed reference for the object.
+ */
+ protected RevCommit createCommit(final AnyObjectId id) {
+ return new RevCommit(id);
+ }
+
+ void carryFlagsImpl(final RevCommit c) {
+ final int carry = c.flags & carryFlags;
+ if (carry != 0)
+ RevCommit.carryFlags(c, carry);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java
new file mode 100644
index 0000000000..04d8def436
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/**
+ * Replaces a RevCommit's parents until not colored with REWRITE.
+ * <p>
+ * Before a RevCommit is returned to the caller its parents are updated to
+ * create a dense DAG. Instead of reporting the actual parents as recorded when
+ * the commit was created the returned commit will reflect the next closest
+ * commit that matched the revision walker's filters.
+ * <p>
+ * This generator is the second phase of a path limited revision walk and
+ * assumes it is receiving RevCommits from {@link RewriteTreeFilter},
+ * after they have been fully buffered by {@link AbstractRevQueue}. The full
+ * buffering is necessary to allow the simple loop used within our own
+ * {@link #rewrite(RevCommit)} to pull completely through a strand of
+ * {@link RevWalk#REWRITE} colored commits and come up with a simplification
+ * that makes the DAG dense. Not fully buffering the commits first would cause
+ * this loop to abort early, due to commits not being parsed and colored
+ * correctly.
+ *
+ * @see RewriteTreeFilter
+ */
+class RewriteGenerator extends Generator {
+ private static final int REWRITE = RevWalk.REWRITE;
+
+ /** For {@link #cleanup(RevCommit[])} to remove duplicate parents. */
+ private static final int DUPLICATE = RevWalk.TEMP_MARK;
+
+ private final Generator source;
+
+ RewriteGenerator(final Generator s) {
+ source = s;
+ }
+
+ @Override
+ void shareFreeList(final BlockRevQueue q) {
+ source.shareFreeList(q);
+ }
+
+ @Override
+ int outputType() {
+ return source.outputType() & ~NEEDS_REWRITE;
+ }
+
+ @Override
+ RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ for (;;) {
+ final RevCommit c = source.next();
+ if (c == null)
+ return null;
+
+ boolean rewrote = false;
+ final RevCommit[] pList = c.parents;
+ final int nParents = pList.length;
+ for (int i = 0; i < nParents; i++) {
+ final RevCommit oldp = pList[i];
+ final RevCommit newp = rewrite(oldp);
+ if (oldp != newp) {
+ pList[i] = newp;
+ rewrote = true;
+ }
+ }
+ if (rewrote)
+ c.parents = cleanup(pList);
+
+ return c;
+ }
+ }
+
+ private RevCommit rewrite(RevCommit p) {
+ for (;;) {
+ final RevCommit[] pList = p.parents;
+ if (pList.length > 1) {
+ // This parent is a merge, so keep it.
+ //
+ return p;
+ }
+
+ if ((p.flags & RevWalk.UNINTERESTING) != 0) {
+ // Retain uninteresting parents. They show where the
+ // DAG was cut off because it wasn't interesting.
+ //
+ return p;
+ }
+
+ if ((p.flags & REWRITE) == 0) {
+ // This parent was not eligible for rewriting. We
+ // need to keep it in the DAG.
+ //
+ return p;
+ }
+
+ if (pList.length == 0) {
+ // We can't go back any further, other than to
+ // just delete the parent entirely.
+ //
+ return null;
+ }
+
+ p = pList[0];
+ }
+ }
+
+ private RevCommit[] cleanup(final RevCommit[] oldList) {
+ // Remove any duplicate parents caused due to rewrites (e.g. a merge
+ // with two sides that both simplified back into the merge base).
+ // We also may have deleted a parent by marking it null.
+ //
+ int newCnt = 0;
+ for (int o = 0; o < oldList.length; o++) {
+ final RevCommit p = oldList[o];
+ if (p == null)
+ continue;
+ if ((p.flags & DUPLICATE) != 0) {
+ oldList[o] = null;
+ continue;
+ }
+ p.flags |= DUPLICATE;
+ newCnt++;
+ }
+
+ if (newCnt == oldList.length) {
+ for (final RevCommit p : oldList)
+ p.flags &= ~DUPLICATE;
+ return oldList;
+ }
+
+ final RevCommit[] newList = new RevCommit[newCnt];
+ newCnt = 0;
+ for (final RevCommit p : oldList) {
+ if (p != null) {
+ newList[newCnt++] = p;
+ p.flags &= ~DUPLICATE;
+ }
+ }
+
+ return newList;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java
new file mode 100644
index 0000000000..4cec8a7f0f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.StopWalkException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+
+/**
+ * First phase of a path limited revision walk.
+ * <p>
+ * This filter is ANDed to evaluate after all other filters and ties the
+ * configured {@link TreeFilter} into the revision walking process.
+ * <p>
+ * Each commit is differenced concurrently against all of its parents to look
+ * for tree entries that are interesting to the TreeFilter. If none are found
+ * the commit is colored with {@link RevWalk#REWRITE}, allowing a later pass
+ * implemented by {@link RewriteGenerator} to remove those colored commits from
+ * the DAG.
+ *
+ * @see RewriteGenerator
+ */
+class RewriteTreeFilter extends RevFilter {
+ private static final int PARSED = RevWalk.PARSED;
+
+ private static final int UNINTERESTING = RevWalk.UNINTERESTING;
+
+ private static final int REWRITE = RevWalk.REWRITE;
+
+ private final TreeWalk pathFilter;
+
+ RewriteTreeFilter(final RevWalk walker, final TreeFilter t) {
+ pathFilter = new TreeWalk(walker.db);
+ pathFilter.setFilter(t);
+ pathFilter.setRecursive(t.shouldBeRecursive());
+ }
+
+ @Override
+ public RevFilter clone() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c)
+ throws StopWalkException, MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ // Reset the tree filter to scan this commit and parents.
+ //
+ final RevCommit[] pList = c.parents;
+ final int nParents = pList.length;
+ final TreeWalk tw = pathFilter;
+ final ObjectId[] trees = new ObjectId[nParents + 1];
+ for (int i = 0; i < nParents; i++) {
+ final RevCommit p = c.parents[i];
+ if ((p.flags & PARSED) == 0)
+ p.parseHeaders(walker);
+ trees[i] = p.getTree();
+ }
+ trees[nParents] = c.getTree();
+ tw.reset(trees);
+
+ if (nParents == 1) {
+ // We have exactly one parent. This is a very common case.
+ //
+ int chgs = 0, adds = 0;
+ while (tw.next()) {
+ chgs++;
+ if (tw.getRawMode(0) == 0 && tw.getRawMode(1) != 0)
+ adds++;
+ else
+ break; // no point in looking at this further.
+ }
+
+ if (chgs == 0) {
+ // No changes, so our tree is effectively the same as
+ // our parent tree. We pass the buck to our parent.
+ //
+ c.flags |= REWRITE;
+ return false;
+ } else {
+ // We have interesting items, but neither of the special
+ // cases denoted above.
+ //
+ return true;
+ }
+ } else if (nParents == 0) {
+ // We have no parents to compare against. Consider us to be
+ // REWRITE only if we have no paths matching our filter.
+ //
+ if (tw.next())
+ return true;
+ c.flags |= REWRITE;
+ return false;
+ }
+
+ // We are a merge commit. We can only be REWRITE if we are same
+ // to _all_ parents. We may also be able to eliminate a parent if
+ // it does not contribute changes to us. Such a parent may be an
+ // uninteresting side branch.
+ //
+ final int[] chgs = new int[nParents];
+ final int[] adds = new int[nParents];
+ while (tw.next()) {
+ final int myMode = tw.getRawMode(nParents);
+ for (int i = 0; i < nParents; i++) {
+ final int pMode = tw.getRawMode(i);
+ if (myMode == pMode && tw.idEqual(i, nParents))
+ continue;
+
+ chgs[i]++;
+ if (pMode == 0 && myMode != 0)
+ adds[i]++;
+ }
+ }
+
+ boolean same = false;
+ boolean diff = false;
+ for (int i = 0; i < nParents; i++) {
+ if (chgs[i] == 0) {
+ // No changes, so our tree is effectively the same as
+ // this parent tree. We pass the buck to only this one
+ // parent commit.
+ //
+
+ final RevCommit p = pList[i];
+ if ((p.flags & UNINTERESTING) != 0) {
+ // This parent was marked as not interesting by the
+ // application. We should look for another parent
+ // that is interesting.
+ //
+ same = true;
+ continue;
+ }
+
+ c.flags |= REWRITE;
+ c.parents = new RevCommit[] { p };
+ return false;
+ }
+
+ if (chgs[i] == adds[i]) {
+ // All of the differences from this parent were because we
+ // added files that they did not have. This parent is our
+ // "empty tree root" and thus their history is not relevant.
+ // Cut our grandparents to be an empty list.
+ //
+ pList[i].parents = RevCommit.NO_PARENTS;
+ }
+
+ // We have an interesting difference relative to this parent.
+ //
+ diff = true;
+ }
+
+ if (diff && !same) {
+ // We did not abort above, so we are different in at least one
+ // way from all of our parents. We have to take the blame for
+ // that difference.
+ //
+ return true;
+ }
+
+ // We are the same as all of our parents. We must keep them
+ // as they are and allow those parents to flow into pending
+ // for further scanning.
+ //
+ c.flags |= REWRITE;
+ return false;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java
new file mode 100644
index 0000000000..c5353fe8c5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.revwalk.filter.AndRevFilter;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+
+/**
+ * Initial RevWalk generator that bootstraps a new walk.
+ * <p>
+ * Initially RevWalk starts with this generator as its chosen implementation.
+ * The first request for a RevCommit from the RevWalk instance calls to our
+ * {@link #next()} method, and we replace ourselves with the best Generator
+ * implementation available based upon the current RevWalk configuration.
+ */
+class StartGenerator extends Generator {
+ private final RevWalk walker;
+
+ StartGenerator(final RevWalk w) {
+ walker = w;
+ }
+
+ @Override
+ int outputType() {
+ return 0;
+ }
+
+ @Override
+ RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ Generator g;
+
+ final RevWalk w = walker;
+ RevFilter rf = w.getRevFilter();
+ final TreeFilter tf = w.getTreeFilter();
+ AbstractRevQueue q = walker.queue;
+
+ if (rf == RevFilter.MERGE_BASE) {
+ // Computing for merge bases is a special case and does not
+ // use the bulk of the generator pipeline.
+ //
+ if (tf != TreeFilter.ALL)
+ throw new IllegalStateException("Cannot combine TreeFilter "
+ + tf + " with RevFilter " + rf + ".");
+
+ final MergeBaseGenerator mbg = new MergeBaseGenerator(w);
+ walker.pending = mbg;
+ walker.queue = AbstractRevQueue.EMPTY_QUEUE;
+ mbg.init(q);
+ return mbg.next();
+ }
+
+ final boolean uninteresting = q.anybodyHasFlag(RevWalk.UNINTERESTING);
+ boolean boundary = walker.hasRevSort(RevSort.BOUNDARY);
+
+ if (!boundary && walker instanceof ObjectWalk) {
+ // The object walker requires boundary support to color
+ // trees and blobs at the boundary uninteresting so it
+ // does not produce those in the result.
+ //
+ boundary = true;
+ }
+ if (boundary && !uninteresting) {
+ // If we were not fed uninteresting commits we will never
+ // construct a boundary. There is no reason to include the
+ // extra overhead associated with that in our pipeline.
+ //
+ boundary = false;
+ }
+
+ final DateRevQueue pending;
+ int pendingOutputType = 0;
+ if (q instanceof DateRevQueue)
+ pending = (DateRevQueue)q;
+ else
+ pending = new DateRevQueue(q);
+ if (tf != TreeFilter.ALL) {
+ rf = AndRevFilter.create(rf, new RewriteTreeFilter(w, tf));
+ pendingOutputType |= HAS_REWRITE | NEEDS_REWRITE;
+ }
+
+ walker.queue = q;
+ g = new PendingGenerator(w, pending, rf, pendingOutputType);
+
+ if (boundary) {
+ // Because the boundary generator may produce uninteresting
+ // commits we cannot allow the pending generator to dispose
+ // of them early.
+ //
+ ((PendingGenerator) g).canDispose = false;
+ }
+
+ if ((g.outputType() & NEEDS_REWRITE) != 0) {
+ // Correction for an upstream NEEDS_REWRITE is to buffer
+ // fully and then apply a rewrite generator that can
+ // pull through the rewrite chain and produce a dense
+ // output graph.
+ //
+ g = new FIFORevQueue(g);
+ g = new RewriteGenerator(g);
+ }
+
+ if (walker.hasRevSort(RevSort.TOPO)
+ && (g.outputType() & SORT_TOPO) == 0)
+ g = new TopoSortGenerator(g);
+ if (walker.hasRevSort(RevSort.REVERSE))
+ g = new LIFORevQueue(g);
+ if (boundary)
+ g = new BoundaryGenerator(w, g);
+ else if (uninteresting) {
+ // Try to protect ourselves from uninteresting commits producing
+ // due to clock skew in the commit time stamps. Delay such that
+ // we have a chance at coloring enough of the graph correctly,
+ // and then strip any UNINTERESTING nodes that may have leaked
+ // through early.
+ //
+ if (pending.peek() != null)
+ g = new DelayRevQueue(g);
+ g = new FixUninterestingGenerator(g);
+ }
+
+ w.pending = g;
+ return g.next();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java
new file mode 100644
index 0000000000..78c0d8f16a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/** Sorts commits in topological order. */
+class TopoSortGenerator extends Generator {
+ private static final int TOPO_DELAY = RevWalk.TOPO_DELAY;
+
+ private final FIFORevQueue pending;
+
+ private final int outputType;
+
+ /**
+ * Create a new sorter and completely spin the generator.
+ * <p>
+ * When the constructor completes the supplied generator will have no
+ * commits remaining, as all of the commits will be held inside of this
+ * generator's internal buffer.
+ *
+ * @param s
+ * generator to pull all commits out of, and into this buffer.
+ * @throws MissingObjectException
+ * @throws IncorrectObjectTypeException
+ * @throws IOException
+ */
+ TopoSortGenerator(final Generator s) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ pending = new FIFORevQueue();
+ outputType = s.outputType() | SORT_TOPO;
+ s.shareFreeList(pending);
+ for (;;) {
+ final RevCommit c = s.next();
+ if (c == null)
+ break;
+ for (final RevCommit p : c.parents)
+ p.inDegree++;
+ pending.add(c);
+ }
+ }
+
+ @Override
+ int outputType() {
+ return outputType;
+ }
+
+ @Override
+ void shareFreeList(final BlockRevQueue q) {
+ q.shareFreeList(pending);
+ }
+
+ @Override
+ RevCommit next() throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ for (;;) {
+ final RevCommit c = pending.next();
+ if (c == null)
+ return null;
+
+ if (c.inDegree > 0) {
+ // At least one of our children is missing. We delay
+ // production until all of our children are output.
+ //
+ c.flags |= TOPO_DELAY;
+ continue;
+ }
+
+ // All of our children have already produced,
+ // so it is OK for us to produce now as well.
+ //
+ for (final RevCommit p : c.parents) {
+ if (--p.inDegree == 0 && (p.flags & TOPO_DELAY) != 0) {
+ // This parent tried to come before us, but we are
+ // his last child. unpop the parent so it goes right
+ // behind this child.
+ //
+ p.flags &= ~TOPO_DELAY;
+ pending.unpop(p);
+ }
+ }
+ return c;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AndRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AndRevFilter.java
new file mode 100644
index 0000000000..406a7764d5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AndRevFilter.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * Includes a commit only if all subfilters include the same commit.
+ * <p>
+ * Classic shortcut behavior is used, so evaluation of the
+ * {@link RevFilter#include(RevWalk, RevCommit)} method stops as soon as a false
+ * result is obtained. Applications can improve filtering performance by placing
+ * faster filters that are more likely to reject a result earlier in the list.
+ */
+public abstract class AndRevFilter extends RevFilter {
+ /**
+ * Create a filter with two filters, both of which must match.
+ *
+ * @param a
+ * first filter to test.
+ * @param b
+ * second filter to test.
+ * @return a filter that must match both input filters.
+ */
+ public static RevFilter create(final RevFilter a, final RevFilter b) {
+ if (a == ALL)
+ return b;
+ if (b == ALL)
+ return a;
+ return new Binary(a, b);
+ }
+
+ /**
+ * Create a filter around many filters, all of which must match.
+ *
+ * @param list
+ * list of filters to match against. Must contain at least 2
+ * filters.
+ * @return a filter that must match all input filters.
+ */
+ public static RevFilter create(final RevFilter[] list) {
+ if (list.length == 2)
+ return create(list[0], list[1]);
+ if (list.length < 2)
+ throw new IllegalArgumentException("At least two filters needed.");
+ final RevFilter[] subfilters = new RevFilter[list.length];
+ System.arraycopy(list, 0, subfilters, 0, list.length);
+ return new List(subfilters);
+ }
+
+ /**
+ * Create a filter around many filters, all of which must match.
+ *
+ * @param list
+ * list of filters to match against. Must contain at least 2
+ * filters.
+ * @return a filter that must match all input filters.
+ */
+ public static RevFilter create(final Collection<RevFilter> list) {
+ if (list.size() < 2)
+ throw new IllegalArgumentException("At least two filters needed.");
+ final RevFilter[] subfilters = new RevFilter[list.size()];
+ list.toArray(subfilters);
+ if (subfilters.length == 2)
+ return create(subfilters[0], subfilters[1]);
+ return new List(subfilters);
+ }
+
+ private static class Binary extends AndRevFilter {
+ private final RevFilter a;
+
+ private final RevFilter b;
+
+ Binary(final RevFilter one, final RevFilter two) {
+ a = one;
+ b = two;
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return a.include(walker, c) && b.include(walker, c);
+ }
+
+ @Override
+ public RevFilter clone() {
+ return new Binary(a.clone(), b.clone());
+ }
+
+ @Override
+ public String toString() {
+ return "(" + a.toString() + " AND " + b.toString() + ")";
+ }
+ }
+
+ private static class List extends AndRevFilter {
+ private final RevFilter[] subfilters;
+
+ List(final RevFilter[] list) {
+ subfilters = list;
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ for (final RevFilter f : subfilters) {
+ if (!f.include(walker, c))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public RevFilter clone() {
+ final RevFilter[] s = new RevFilter[subfilters.length];
+ for (int i = 0; i < s.length; i++)
+ s[i] = subfilters[i].clone();
+ return new List(s);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer r = new StringBuffer();
+ r.append("(");
+ for (int i = 0; i < subfilters.length; i++) {
+ if (i > 0)
+ r.append(" AND ");
+ r.append(subfilters[i].toString());
+ }
+ r.append(")");
+ return r.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AuthorRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AuthorRevFilter.java
new file mode 100644
index 0000000000..2ede91b57f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AuthorRevFilter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.RawCharSequence;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/** Matches only commits whose author name matches the pattern. */
+public class AuthorRevFilter {
+ /**
+ * Create a new author filter.
+ * <p>
+ * An optimized substring search may be automatically selected if the
+ * pattern does not contain any regular expression meta-characters.
+ * <p>
+ * The search is performed using a case-insensitive comparison. The
+ * character encoding of the commit message itself is not respected. The
+ * filter matches on raw UTF-8 byte sequences.
+ *
+ * @param pattern
+ * regular expression pattern to match.
+ * @return a new filter that matches the given expression against the author
+ * name and address of a commit.
+ */
+ public static RevFilter create(String pattern) {
+ if (pattern.length() == 0)
+ throw new IllegalArgumentException("Cannot match on empty string.");
+ if (SubStringRevFilter.safe(pattern))
+ return new SubStringSearch(pattern);
+ return new PatternSearch(pattern);
+ }
+
+ private AuthorRevFilter() {
+ // Don't permit us to be created.
+ }
+
+ static RawCharSequence textFor(final RevCommit cmit) {
+ final byte[] raw = cmit.getRawBuffer();
+ final int b = RawParseUtils.author(raw, 0);
+ if (b < 0)
+ return RawCharSequence.EMPTY;
+ final int e = RawParseUtils.nextLF(raw, b, '>');
+ return new RawCharSequence(raw, b, e);
+ }
+
+ private static class PatternSearch extends PatternMatchRevFilter {
+ PatternSearch(final String patternText) {
+ super(patternText, true, true, Pattern.CASE_INSENSITIVE);
+ }
+
+ @Override
+ protected CharSequence text(final RevCommit cmit) {
+ return textFor(cmit);
+ }
+
+ @Override
+ public RevFilter clone() {
+ return new PatternSearch(pattern());
+ }
+ }
+
+ private static class SubStringSearch extends SubStringRevFilter {
+ SubStringSearch(final String patternText) {
+ super(patternText);
+ }
+
+ @Override
+ protected RawCharSequence text(final RevCommit cmit) {
+ return textFor(cmit);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java
new file mode 100644
index 0000000000..a70ff9fd6e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2009, Mark Struberg <struberg@yahoo.de>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.io.IOException;
+import java.util.Date;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.StopWalkException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Selects commits based upon the commit time field. */
+public abstract class CommitTimeRevFilter extends RevFilter {
+ /**
+ * Create a new filter to select commits before a given date/time.
+ *
+ * @param ts
+ * the point in time to cut on.
+ * @return a new filter to select commits on or before <code>ts</code>.
+ */
+ public static final RevFilter before(final Date ts) {
+ return new Before(ts.getTime());
+ }
+
+ /**
+ * Create a new filter to select commits after a given date/time.
+ *
+ * @param ts
+ * the point in time to cut on.
+ * @return a new filter to select commits on or after <code>ts</code>.
+ */
+ public static final RevFilter after(final Date ts) {
+ return new After(ts.getTime());
+ }
+
+ /**
+ * Create a new filter to select commits after or equal a given date/time <code>since</code>
+ * and before or equal a given date/time <code>until</code>.
+ *
+ * @param since the point in time to cut on.
+ * @param until the point in time to cut off.
+ * @return a new filter to select commits between the given date/times.
+ */
+ public static final RevFilter between(final Date since, final Date until) {
+ return new Between(since.getTime(), until.getTime());
+ }
+
+ final int when;
+
+ CommitTimeRevFilter(final long ts) {
+ when = (int) (ts / 1000);
+ }
+
+ @Override
+ public RevFilter clone() {
+ return this;
+ }
+
+ private static class Before extends CommitTimeRevFilter {
+ Before(final long ts) {
+ super(ts);
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit cmit)
+ throws StopWalkException, MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ return cmit.getCommitTime() <= when;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "(" + new Date(when * 1000L) + ")";
+ }
+ }
+
+ private static class After extends CommitTimeRevFilter {
+ After(final long ts) {
+ super(ts);
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit cmit)
+ throws StopWalkException, MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ // Since the walker sorts commits by commit time we can be
+ // reasonably certain there is nothing remaining worth our
+ // scanning if this commit is before the point in question.
+ //
+ if (cmit.getCommitTime() < when)
+ throw StopWalkException.INSTANCE;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "(" + new Date(when * 1000L) + ")";
+ }
+ }
+
+ private static class Between extends CommitTimeRevFilter {
+ private final int until;
+
+ Between(final long since, final long until) {
+ super(since);
+ this.until = (int) (until / 1000);
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit cmit)
+ throws StopWalkException, MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ return cmit.getCommitTime() <= until && cmit.getCommitTime() >= when;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "(" + new Date(when * 1000L) + " - " + new Date(until * 1000L) + ")";
+ }
+
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitterRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitterRevFilter.java
new file mode 100644
index 0000000000..59c3e080d5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitterRevFilter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.RawCharSequence;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/** Matches only commits whose committer name matches the pattern. */
+public class CommitterRevFilter {
+ /**
+ * Create a new committer filter.
+ * <p>
+ * An optimized substring search may be automatically selected if the
+ * pattern does not contain any regular expression meta-characters.
+ * <p>
+ * The search is performed using a case-insensitive comparison. The
+ * character encoding of the commit message itself is not respected. The
+ * filter matches on raw UTF-8 byte sequences.
+ *
+ * @param pattern
+ * regular expression pattern to match.
+ * @return a new filter that matches the given expression against the author
+ * name and address of a commit.
+ */
+ public static RevFilter create(String pattern) {
+ if (pattern.length() == 0)
+ throw new IllegalArgumentException("Cannot match on empty string.");
+ if (SubStringRevFilter.safe(pattern))
+ return new SubStringSearch(pattern);
+ return new PatternSearch(pattern);
+ }
+
+ private CommitterRevFilter() {
+ // Don't permit us to be created.
+ }
+
+ static RawCharSequence textFor(final RevCommit cmit) {
+ final byte[] raw = cmit.getRawBuffer();
+ final int b = RawParseUtils.committer(raw, 0);
+ if (b < 0)
+ return RawCharSequence.EMPTY;
+ final int e = RawParseUtils.nextLF(raw, b, '>');
+ return new RawCharSequence(raw, b, e);
+ }
+
+ private static class PatternSearch extends PatternMatchRevFilter {
+ PatternSearch(final String patternText) {
+ super(patternText, true, true, Pattern.CASE_INSENSITIVE);
+ }
+
+ @Override
+ protected CharSequence text(final RevCommit cmit) {
+ return textFor(cmit);
+ }
+
+ @Override
+ public RevFilter clone() {
+ return new PatternSearch(pattern());
+ }
+ }
+
+ private static class SubStringSearch extends SubStringRevFilter {
+ SubStringSearch(final String patternText) {
+ super(patternText);
+ }
+
+ @Override
+ protected RawCharSequence text(final RevCommit cmit) {
+ return textFor(cmit);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MessageRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MessageRevFilter.java
new file mode 100644
index 0000000000..6ab3b1d3b0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MessageRevFilter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.RawCharSequence;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/** Matches only commits whose message matches the pattern. */
+public class MessageRevFilter {
+ /**
+ * Create a message filter.
+ * <p>
+ * An optimized substring search may be automatically selected if the
+ * pattern does not contain any regular expression meta-characters.
+ * <p>
+ * The search is performed using a case-insensitive comparison. The
+ * character encoding of the commit message itself is not respected. The
+ * filter matches on raw UTF-8 byte sequences.
+ *
+ * @param pattern
+ * regular expression pattern to match.
+ * @return a new filter that matches the given expression against the
+ * message body of the commit.
+ */
+ public static RevFilter create(String pattern) {
+ if (pattern.length() == 0)
+ throw new IllegalArgumentException("Cannot match on empty string.");
+ if (SubStringRevFilter.safe(pattern))
+ return new SubStringSearch(pattern);
+ return new PatternSearch(pattern);
+ }
+
+ private MessageRevFilter() {
+ // Don't permit us to be created.
+ }
+
+ static RawCharSequence textFor(final RevCommit cmit) {
+ final byte[] raw = cmit.getRawBuffer();
+ final int b = RawParseUtils.commitMessage(raw, 0);
+ if (b < 0)
+ return RawCharSequence.EMPTY;
+ return new RawCharSequence(raw, b, raw.length);
+ }
+
+ private static class PatternSearch extends PatternMatchRevFilter {
+ PatternSearch(final String patternText) {
+ super(patternText, true, true, Pattern.CASE_INSENSITIVE
+ | Pattern.DOTALL);
+ }
+
+ @Override
+ protected CharSequence text(final RevCommit cmit) {
+ return textFor(cmit);
+ }
+
+ @Override
+ public RevFilter clone() {
+ return new PatternSearch(pattern());
+ }
+ }
+
+ private static class SubStringSearch extends SubStringRevFilter {
+ SubStringSearch(final String patternText) {
+ super(patternText);
+ }
+
+ @Override
+ protected RawCharSequence text(final RevCommit cmit) {
+ return textFor(cmit);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java
new file mode 100644
index 0000000000..117378c9fa
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Includes a commit only if the subfilter does not include the commit. */
+public class NotRevFilter extends RevFilter {
+ /**
+ * Create a filter that negates the result of another filter.
+ *
+ * @param a
+ * filter to negate.
+ * @return a filter that does the reverse of <code>a</code>.
+ */
+ public static RevFilter create(final RevFilter a) {
+ return new NotRevFilter(a);
+ }
+
+ private final RevFilter a;
+
+ private NotRevFilter(final RevFilter one) {
+ a = one;
+ }
+
+ @Override
+ public RevFilter negate() {
+ return a;
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return !a.include(walker, c);
+ }
+
+ @Override
+ public RevFilter clone() {
+ return new NotRevFilter(a.clone());
+ }
+
+ @Override
+ public String toString() {
+ return "NOT " + a.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/OrRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/OrRevFilter.java
new file mode 100644
index 0000000000..a2782763b6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/OrRevFilter.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * Includes a commit if any subfilters include the same commit.
+ * <p>
+ * Classic shortcut behavior is used, so evaluation of the
+ * {@link RevFilter#include(RevWalk, RevCommit)} method stops as soon as a true
+ * result is obtained. Applications can improve filtering performance by placing
+ * faster filters that are more likely to accept a result earlier in the list.
+ */
+public abstract class OrRevFilter extends RevFilter {
+ /**
+ * Create a filter with two filters, one of which must match.
+ *
+ * @param a
+ * first filter to test.
+ * @param b
+ * second filter to test.
+ * @return a filter that must match at least one input filter.
+ */
+ public static RevFilter create(final RevFilter a, final RevFilter b) {
+ if (a == ALL || b == ALL)
+ return ALL;
+ return new Binary(a, b);
+ }
+
+ /**
+ * Create a filter around many filters, one of which must match.
+ *
+ * @param list
+ * list of filters to match against. Must contain at least 2
+ * filters.
+ * @return a filter that must match at least one input filter.
+ */
+ public static RevFilter create(final RevFilter[] list) {
+ if (list.length == 2)
+ return create(list[0], list[1]);
+ if (list.length < 2)
+ throw new IllegalArgumentException("At least two filters needed.");
+ final RevFilter[] subfilters = new RevFilter[list.length];
+ System.arraycopy(list, 0, subfilters, 0, list.length);
+ return new List(subfilters);
+ }
+
+ /**
+ * Create a filter around many filters, one of which must match.
+ *
+ * @param list
+ * list of filters to match against. Must contain at least 2
+ * filters.
+ * @return a filter that must match at least one input filter.
+ */
+ public static RevFilter create(final Collection<RevFilter> list) {
+ if (list.size() < 2)
+ throw new IllegalArgumentException("At least two filters needed.");
+ final RevFilter[] subfilters = new RevFilter[list.size()];
+ list.toArray(subfilters);
+ if (subfilters.length == 2)
+ return create(subfilters[0], subfilters[1]);
+ return new List(subfilters);
+ }
+
+ private static class Binary extends OrRevFilter {
+ private final RevFilter a;
+
+ private final RevFilter b;
+
+ Binary(final RevFilter one, final RevFilter two) {
+ a = one;
+ b = two;
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return a.include(walker, c) || b.include(walker, c);
+ }
+
+ @Override
+ public RevFilter clone() {
+ return new Binary(a.clone(), b.clone());
+ }
+
+ @Override
+ public String toString() {
+ return "(" + a.toString() + " OR " + b.toString() + ")";
+ }
+ }
+
+ private static class List extends OrRevFilter {
+ private final RevFilter[] subfilters;
+
+ List(final RevFilter[] list) {
+ subfilters = list;
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ for (final RevFilter f : subfilters) {
+ if (f.include(walker, c))
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public RevFilter clone() {
+ final RevFilter[] s = new RevFilter[subfilters.length];
+ for (int i = 0; i < s.length; i++)
+ s[i] = subfilters[i].clone();
+ return new List(s);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer r = new StringBuffer();
+ r.append("(");
+ for (int i = 0; i < subfilters.length; i++) {
+ if (i > 0)
+ r.append(" OR ");
+ r.append(subfilters[i].toString());
+ }
+ r.append(")");
+ return r.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/PatternMatchRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/PatternMatchRevFilter.java
new file mode 100644
index 0000000000..5f2bcf26ab
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/PatternMatchRevFilter.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.RawCharSequence;
+
+/** Abstract filter that searches text using extended regular expressions. */
+public abstract class PatternMatchRevFilter extends RevFilter {
+ /**
+ * Encode a string pattern for faster matching on byte arrays.
+ * <p>
+ * Force the characters to our funny UTF-8 only convention that we use on
+ * raw buffers. This avoids needing to perform character set decodes on the
+ * individual commit buffers.
+ *
+ * @param patternText
+ * original pattern string supplied by the user or the
+ * application.
+ * @return same pattern, but re-encoded to match our funny raw UTF-8
+ * character sequence {@link RawCharSequence}.
+ */
+ protected static final String forceToRaw(final String patternText) {
+ final byte[] b = Constants.encode(patternText);
+ final StringBuilder needle = new StringBuilder(b.length);
+ for (int i = 0; i < b.length; i++)
+ needle.append((char) (b[i] & 0xff));
+ return needle.toString();
+ }
+
+ private final String patternText;
+
+ private final Matcher compiledPattern;
+
+ /**
+ * Construct a new pattern matching filter.
+ *
+ * @param pattern
+ * text of the pattern. Callers may want to surround their
+ * pattern with ".*" on either end to allow matching in the
+ * middle of the string.
+ * @param innerString
+ * should .* be wrapped around the pattern of ^ and $ are
+ * missing? Most users will want this set.
+ * @param rawEncoding
+ * should {@link #forceToRaw(String)} be applied to the pattern
+ * before compiling it?
+ * @param flags
+ * flags from {@link Pattern} to control how matching performs.
+ */
+ protected PatternMatchRevFilter(String pattern, final boolean innerString,
+ final boolean rawEncoding, final int flags) {
+ if (pattern.length() == 0)
+ throw new IllegalArgumentException("Cannot match on empty string.");
+ patternText = pattern;
+
+ if (innerString) {
+ if (!pattern.startsWith("^") && !pattern.startsWith(".*"))
+ pattern = ".*" + pattern;
+ if (!pattern.endsWith("$") && !pattern.endsWith(".*"))
+ pattern = pattern + ".*";
+ }
+ final String p = rawEncoding ? forceToRaw(pattern) : pattern;
+ compiledPattern = Pattern.compile(p, flags).matcher("");
+ }
+
+ /**
+ * Get the pattern this filter uses.
+ *
+ * @return the pattern this filter is applying to candidate strings.
+ */
+ public String pattern() {
+ return patternText;
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit cmit)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return compiledPattern.reset(text(cmit)).matches();
+ }
+
+ /**
+ * Obtain the raw text to match against.
+ *
+ * @param cmit
+ * current commit being evaluated.
+ * @return sequence for the commit's content that we need to match on.
+ */
+ protected abstract CharSequence text(RevCommit cmit);
+
+ @Override
+ public String toString() {
+ return super.toString() + "(\"" + patternText + "\")";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java
new file mode 100644
index 0000000000..2d67d9763a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.StopWalkException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * Selects interesting revisions during walking.
+ * <p>
+ * This is an abstract interface. Applications may implement a subclass, or use
+ * one of the predefined implementations already available within this package.
+ * Filters may be chained together using <code>AndRevFilter</code> and
+ * <code>OrRevFilter</code> to create complex boolean expressions.
+ * <p>
+ * Applications should install the filter on a RevWalk by
+ * {@link RevWalk#setRevFilter(RevFilter)} prior to starting traversal.
+ * <p>
+ * Unless specifically noted otherwise a RevFilter implementation is not thread
+ * safe and may not be shared by different RevWalk instances at the same time.
+ * This restriction allows RevFilter implementations to cache state within their
+ * instances during {@link #include(RevWalk, RevCommit)} if it is beneficial to
+ * their implementation. Deep clones created by {@link #clone()} may be used to
+ * construct a thread-safe copy of an existing filter.
+ *
+ * <p>
+ * <b>Message filters:</b>
+ * <ul>
+ * <li>Author name/email: {@link AuthorRevFilter}</li>
+ * <li>Committer name/email: {@link CommitterRevFilter}</li>
+ * <li>Message body: {@link MessageRevFilter}</li>
+ * </ul>
+ *
+ * <p>
+ * <b>Merge filters:</b>
+ * <ul>
+ * <li>Skip all merges: {@link #NO_MERGES}.</li>
+ * </ul>
+ *
+ * <p>
+ * <b>Boolean modifiers:</b>
+ * <ul>
+ * <li>AND: {@link AndRevFilter}</li>
+ * <li>OR: {@link OrRevFilter}</li>
+ * <li>NOT: {@link NotRevFilter}</li>
+ * </ul>
+ */
+public abstract class RevFilter {
+ /** Default filter that always returns true (thread safe). */
+ public static final RevFilter ALL = new RevFilter() {
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c) {
+ return true;
+ }
+
+ @Override
+ public RevFilter clone() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "ALL";
+ }
+ };
+
+ /** Default filter that always returns false (thread safe). */
+ public static final RevFilter NONE = new RevFilter() {
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c) {
+ return false;
+ }
+
+ @Override
+ public RevFilter clone() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "NONE";
+ }
+ };
+
+ /** Excludes commits with more than one parent (thread safe). */
+ public static final RevFilter NO_MERGES = new RevFilter() {
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c) {
+ return c.getParentCount() < 2;
+ }
+
+ @Override
+ public RevFilter clone() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "NO_MERGES";
+ }
+ };
+
+ /**
+ * Selects only merge bases of the starting points (thread safe).
+ * <p>
+ * This is a special case filter that cannot be combined with any other
+ * filter. Its include method always throws an exception as context
+ * information beyond the arguments is necessary to determine if the
+ * supplied commit is a merge base.
+ */
+ public static final RevFilter MERGE_BASE = new RevFilter() {
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c) {
+ throw new UnsupportedOperationException("Cannot be combined.");
+ }
+
+ @Override
+ public RevFilter clone() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "MERGE_BASE";
+ }
+ };
+
+ /**
+ * Create a new filter that does the opposite of this filter.
+ *
+ * @return a new filter that includes commits this filter rejects.
+ */
+ public RevFilter negate() {
+ return NotRevFilter.create(this);
+ }
+
+ /**
+ * Determine if the supplied commit should be included in results.
+ *
+ * @param walker
+ * the active walker this filter is being invoked from within.
+ * @param cmit
+ * the commit currently being tested. The commit has been parsed
+ * and its body is available for inspection.
+ * @return true to include this commit in the results; false to have this
+ * commit be omitted entirely from the results.
+ * @throws StopWalkException
+ * the filter knows for certain that no additional commits can
+ * ever match, and the current commit doesn't match either. The
+ * walk is halted and no more results are provided.
+ * @throws MissingObjectException
+ * an object the filter needs to consult to determine its answer
+ * does not exist in the Git repository the walker is operating
+ * on. Filtering this commit is impossible without the object.
+ * @throws IncorrectObjectTypeException
+ * an object the filter needed to consult was not of the
+ * expected object type. This usually indicates a corrupt
+ * repository, as an object link is referencing the wrong type.
+ * @throws IOException
+ * a loose object or pack file could not be read to obtain data
+ * necessary for the filter to make its decision.
+ */
+ public abstract boolean include(RevWalk walker, RevCommit cmit)
+ throws StopWalkException, MissingObjectException,
+ IncorrectObjectTypeException, IOException;
+
+ /**
+ * Clone this revision filter, including its parameters.
+ * <p>
+ * This is a deep clone. If this filter embeds objects or other filters it
+ * must also clone those, to ensure the instances do not share mutable data.
+ *
+ * @return another copy of this filter, suitable for another thread.
+ */
+ public abstract RevFilter clone();
+
+ @Override
+ public String toString() {
+ String n = getClass().getName();
+ int lastDot = n.lastIndexOf('.');
+ if (lastDot >= 0) {
+ n = n.substring(lastDot + 1);
+ }
+ return n.replace('$', '.');
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java
new file mode 100644
index 0000000000..8339fc7c01
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevFlagSet;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Matches only commits with some/all RevFlags already set. */
+public abstract class RevFlagFilter extends RevFilter {
+ /**
+ * Create a new filter that tests for a single flag.
+ *
+ * @param a
+ * the flag to test.
+ * @return filter that selects only commits with flag <code>a</code>.
+ */
+ public static RevFilter has(final RevFlag a) {
+ final RevFlagSet s = new RevFlagSet();
+ s.add(a);
+ return new HasAll(s);
+ }
+
+ /**
+ * Create a new filter that tests all flags in a set.
+ *
+ * @param a
+ * set of flags to test.
+ * @return filter that selects only commits with all flags in <code>a</code>.
+ */
+ public static RevFilter hasAll(final RevFlag... a) {
+ final RevFlagSet set = new RevFlagSet();
+ for (final RevFlag flag : a)
+ set.add(flag);
+ return new HasAll(set);
+ }
+
+ /**
+ * Create a new filter that tests all flags in a set.
+ *
+ * @param a
+ * set of flags to test.
+ * @return filter that selects only commits with all flags in <code>a</code>.
+ */
+ public static RevFilter hasAll(final RevFlagSet a) {
+ return new HasAll(new RevFlagSet(a));
+ }
+
+ /**
+ * Create a new filter that tests for any flag in a set.
+ *
+ * @param a
+ * set of flags to test.
+ * @return filter that selects only commits with any flag in <code>a</code>.
+ */
+ public static RevFilter hasAny(final RevFlag... a) {
+ final RevFlagSet set = new RevFlagSet();
+ for (final RevFlag flag : a)
+ set.add(flag);
+ return new HasAny(set);
+ }
+
+ /**
+ * Create a new filter that tests for any flag in a set.
+ *
+ * @param a
+ * set of flags to test.
+ * @return filter that selects only commits with any flag in <code>a</code>.
+ */
+ public static RevFilter hasAny(final RevFlagSet a) {
+ return new HasAny(new RevFlagSet(a));
+ }
+
+ final RevFlagSet flags;
+
+ RevFlagFilter(final RevFlagSet m) {
+ flags = m;
+ }
+
+ @Override
+ public RevFilter clone() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + flags;
+ }
+
+ private static class HasAll extends RevFlagFilter {
+ HasAll(final RevFlagSet m) {
+ super(m);
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return c.hasAll(flags);
+ }
+ }
+
+ private static class HasAny extends RevFlagFilter {
+ HasAny(final RevFlagSet m) {
+ super(m);
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return c.hasAny(flags);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java
new file mode 100644
index 0000000000..c6f421784a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.revwalk.filter;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.RawCharSequence;
+import org.eclipse.jgit.util.RawSubStringPattern;
+
+/** Abstract filter that searches text using only substring search. */
+public abstract class SubStringRevFilter extends RevFilter {
+ /**
+ * Can this string be safely handled by a substring filter?
+ *
+ * @param pattern
+ * the pattern text proposed by the user.
+ * @return true if a substring filter can perform this pattern match; false
+ * if {@link PatternMatchRevFilter} must be used instead.
+ */
+ public static boolean safe(final String pattern) {
+ for (int i = 0; i < pattern.length(); i++) {
+ final char c = pattern.charAt(i);
+ switch (c) {
+ case '.':
+ case '?':
+ case '*':
+ case '+':
+ case '{':
+ case '}':
+ case '(':
+ case ')':
+ case '[':
+ case ']':
+ case '\\':
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private final RawSubStringPattern pattern;
+
+ /**
+ * Construct a new matching filter.
+ *
+ * @param patternText
+ * text to locate. This should be a safe string as described by
+ * the {@link #safe(String)} as regular expression meta
+ * characters are treated as literals.
+ */
+ protected SubStringRevFilter(final String patternText) {
+ pattern = new RawSubStringPattern(patternText);
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit cmit)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return pattern.match(text(cmit)) >= 0;
+ }
+
+ /**
+ * Obtain the raw text to match against.
+ *
+ * @param cmit
+ * current commit being evaluated.
+ * @return sequence for the commit's content that we need to match on.
+ */
+ protected abstract RawCharSequence text(RevCommit cmit);
+
+ @Override
+ public RevFilter clone() {
+ return this; // Typically we are actually thread-safe.
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "(\"" + pattern.pattern() + "\")";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
new file mode 100644
index 0000000000..9343f4aba4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.DigestOutputStream;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TimeZone;
+import java.util.TreeMap;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.eclipse.jgit.awtui.AwtAuthenticator;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.util.Base64;
+import org.eclipse.jgit.util.HttpSupport;
+import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * A simple HTTP REST client for the Amazon S3 service.
+ * <p>
+ * This client uses the REST API to communicate with the Amazon S3 servers and
+ * read or write content through a bucket that the user has access to. It is a
+ * very lightweight implementation of the S3 API and therefore does not have all
+ * of the bells and whistles of popular client implementations.
+ * <p>
+ * Authentication is always performed using the user's AWSAccessKeyId and their
+ * private AWSSecretAccessKey.
+ * <p>
+ * Optional client-side encryption may be enabled if requested. The format is
+ * compatible with <a href="http://jets3t.s3.amazonaws.com/index.html">jets3t</a>,
+ * a popular Java based Amazon S3 client library. Enabling encryption can hide
+ * sensitive data from the operators of the S3 service.
+ */
+public class AmazonS3 {
+ private static final Set<String> SIGNED_HEADERS;
+
+ private static final String HMAC = "HmacSHA1";
+
+ private static final String DOMAIN = "s3.amazonaws.com";
+
+ private static final String X_AMZ_ACL = "x-amz-acl";
+
+ private static final String X_AMZ_META = "x-amz-meta-";
+
+ static {
+ SIGNED_HEADERS = new HashSet<String>();
+ SIGNED_HEADERS.add("content-type");
+ SIGNED_HEADERS.add("content-md5");
+ SIGNED_HEADERS.add("date");
+ }
+
+ private static boolean isSignedHeader(final String name) {
+ final String nameLC = StringUtils.toLowerCase(name);
+ return SIGNED_HEADERS.contains(nameLC) || nameLC.startsWith("x-amz-");
+ }
+
+ private static String toCleanString(final List<String> list) {
+ final StringBuilder s = new StringBuilder();
+ for (final String v : list) {
+ if (s.length() > 0)
+ s.append(',');
+ s.append(v.replaceAll("\n", "").trim());
+ }
+ return s.toString();
+ }
+
+ private static String remove(final Map<String, String> m, final String k) {
+ final String r = m.remove(k);
+ return r != null ? r : "";
+ }
+
+ private static String httpNow() {
+ final String tz = "GMT";
+ final SimpleDateFormat fmt;
+ fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.US);
+ fmt.setTimeZone(TimeZone.getTimeZone(tz));
+ return fmt.format(new Date()) + " " + tz;
+ }
+
+ private static MessageDigest newMD5() {
+ try {
+ return MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("JRE lacks MD5 implementation", e);
+ }
+ }
+
+ /** AWSAccessKeyId, public string that identifies the user's account. */
+ private final String publicKey;
+
+ /** Decoded form of the private AWSSecretAccessKey, to sign requests. */
+ private final SecretKeySpec privateKey;
+
+ /** Our HTTP proxy support, in case we are behind a firewall. */
+ private final ProxySelector proxySelector;
+
+ /** ACL to apply to created objects. */
+ private final String acl;
+
+ /** Maximum number of times to try an operation. */
+ private final int maxAttempts;
+
+ /** Encryption algorithm, may be a null instance that provides pass-through. */
+ private final WalkEncryption encryption;
+
+ /**
+ * Create a new S3 client for the supplied user information.
+ * <p>
+ * The connection properties are a subset of those supported by the popular
+ * <a href="http://jets3t.s3.amazonaws.com/index.html">jets3t</a> library.
+ * For example:
+ *
+ * <pre>
+ * # AWS Access and Secret Keys (required)
+ * accesskey: &lt;YourAWSAccessKey&gt;
+ * secretkey: &lt;YourAWSSecretKey&gt;
+ *
+ * # Access Control List setting to apply to uploads, must be one of:
+ * # PRIVATE, PUBLIC_READ (defaults to PRIVATE).
+ * acl: PRIVATE
+ *
+ * # Number of times to retry after internal error from S3.
+ * httpclient.retry-max: 3
+ *
+ * # End-to-end encryption (hides content from S3 owners)
+ * password: &lt;encryption pass-phrase&gt;
+ * crypto.algorithm: PBEWithMD5AndDES
+ * </pre>
+ *
+ * @param props
+ * connection properties.
+ *
+ */
+ public AmazonS3(final Properties props) {
+ publicKey = props.getProperty("accesskey");
+ if (publicKey == null)
+ throw new IllegalArgumentException("Missing accesskey.");
+
+ final String secret = props.getProperty("secretkey");
+ if (secret == null)
+ throw new IllegalArgumentException("Missing secretkey.");
+ privateKey = new SecretKeySpec(Constants.encodeASCII(secret), HMAC);
+
+ final String pacl = props.getProperty("acl", "PRIVATE");
+ if (StringUtils.equalsIgnoreCase("PRIVATE", pacl))
+ acl = "private";
+ else if (StringUtils.equalsIgnoreCase("PUBLIC", pacl))
+ acl = "public-read";
+ else if (StringUtils.equalsIgnoreCase("PUBLIC-READ", pacl))
+ acl = "public-read";
+ else if (StringUtils.equalsIgnoreCase("PUBLIC_READ", pacl))
+ acl = "public-read";
+ else
+ throw new IllegalArgumentException("Invalid acl: " + pacl);
+
+ try {
+ final String cPas = props.getProperty("password");
+ if (cPas != null) {
+ String cAlg = props.getProperty("crypto.algorithm");
+ if (cAlg == null)
+ cAlg = "PBEWithMD5AndDES";
+ encryption = new WalkEncryption.ObjectEncryptionV2(cAlg, cPas);
+ } else {
+ encryption = WalkEncryption.NONE;
+ }
+ } catch (InvalidKeySpecException e) {
+ throw new IllegalArgumentException("Invalid encryption", e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Invalid encryption", e);
+ }
+
+ maxAttempts = Integer.parseInt(props.getProperty(
+ "httpclient.retry-max", "3"));
+ proxySelector = ProxySelector.getDefault();
+ }
+
+ /**
+ * Get the content of a bucket object.
+ *
+ * @param bucket
+ * name of the bucket storing the object.
+ * @param key
+ * key of the object within its bucket.
+ * @return connection to stream the content of the object. The request
+ * properties of the connection may not be modified by the caller as
+ * the request parameters have already been signed.
+ * @throws IOException
+ * sending the request was not possible.
+ */
+ public URLConnection get(final String bucket, final String key)
+ throws IOException {
+ for (int curAttempt = 0; curAttempt < maxAttempts; curAttempt++) {
+ final HttpURLConnection c = open("GET", bucket, key);
+ authorize(c);
+ switch (HttpSupport.response(c)) {
+ case HttpURLConnection.HTTP_OK:
+ encryption.validate(c, X_AMZ_META);
+ return c;
+ case HttpURLConnection.HTTP_NOT_FOUND:
+ throw new FileNotFoundException(key);
+ case HttpURLConnection.HTTP_INTERNAL_ERROR:
+ continue;
+ default:
+ throw error("Reading", key, c);
+ }
+ }
+ throw maxAttempts("Reading", key);
+ }
+
+ /**
+ * Decrypt an input stream from {@link #get(String, String)}.
+ *
+ * @param u
+ * connection previously created by {@link #get(String, String)}}.
+ * @return stream to read plain text from.
+ * @throws IOException
+ * decryption could not be configured.
+ */
+ public InputStream decrypt(final URLConnection u) throws IOException {
+ return encryption.decrypt(u.getInputStream());
+ }
+
+ /**
+ * List the names of keys available within a bucket.
+ * <p>
+ * This method is primarily meant for obtaining a "recursive directory
+ * listing" rooted under the specified bucket and prefix location.
+ *
+ * @param bucket
+ * name of the bucket whose objects should be listed.
+ * @param prefix
+ * common prefix to filter the results by. Must not be null.
+ * Supplying the empty string will list all keys in the bucket.
+ * Supplying a non-empty string will act as though a trailing '/'
+ * appears in prefix, even if it does not.
+ * @return list of keys starting with <code>prefix</code>, after removing
+ * <code>prefix</code> (or <code>prefix + "/"</code>)from all
+ * of them.
+ * @throws IOException
+ * sending the request was not possible, or the response XML
+ * document could not be parsed properly.
+ */
+ public List<String> list(final String bucket, String prefix)
+ throws IOException {
+ if (prefix.length() > 0 && !prefix.endsWith("/"))
+ prefix += "/";
+ final ListParser lp = new ListParser(bucket, prefix);
+ do {
+ lp.list();
+ } while (lp.truncated);
+ return lp.entries;
+ }
+
+ /**
+ * Delete a single object.
+ * <p>
+ * Deletion always succeeds, even if the object does not exist.
+ *
+ * @param bucket
+ * name of the bucket storing the object.
+ * @param key
+ * key of the object within its bucket.
+ * @throws IOException
+ * deletion failed due to communications error.
+ */
+ public void delete(final String bucket, final String key)
+ throws IOException {
+ for (int curAttempt = 0; curAttempt < maxAttempts; curAttempt++) {
+ final HttpURLConnection c = open("DELETE", bucket, key);
+ authorize(c);
+ switch (HttpSupport.response(c)) {
+ case HttpURLConnection.HTTP_NO_CONTENT:
+ return;
+ case HttpURLConnection.HTTP_INTERNAL_ERROR:
+ continue;
+ default:
+ throw error("Deletion", key, c);
+ }
+ }
+ throw maxAttempts("Deletion", key);
+ }
+
+ /**
+ * Atomically create or replace a single small object.
+ * <p>
+ * This form is only suitable for smaller contents, where the caller can
+ * reasonable fit the entire thing into memory.
+ * <p>
+ * End-to-end data integrity is assured by internally computing the MD5
+ * checksum of the supplied data and transmitting the checksum along with
+ * the data itself.
+ *
+ * @param bucket
+ * name of the bucket storing the object.
+ * @param key
+ * key of the object within its bucket.
+ * @param data
+ * new data content for the object. Must not be null. Zero length
+ * array will create a zero length object.
+ * @throws IOException
+ * creation/updating failed due to communications error.
+ */
+ public void put(final String bucket, final String key, final byte[] data)
+ throws IOException {
+ if (encryption != WalkEncryption.NONE) {
+ // We have to copy to produce the cipher text anyway so use
+ // the large object code path as it supports that behavior.
+ //
+ final OutputStream os = beginPut(bucket, key, null, null);
+ os.write(data);
+ os.close();
+ return;
+ }
+
+ final String md5str = Base64.encodeBytes(newMD5().digest(data));
+ final String lenstr = String.valueOf(data.length);
+ for (int curAttempt = 0; curAttempt < maxAttempts; curAttempt++) {
+ final HttpURLConnection c = open("PUT", bucket, key);
+ c.setRequestProperty("Content-Length", lenstr);
+ c.setRequestProperty("Content-MD5", md5str);
+ c.setRequestProperty(X_AMZ_ACL, acl);
+ authorize(c);
+ c.setDoOutput(true);
+ c.setFixedLengthStreamingMode(data.length);
+ final OutputStream os = c.getOutputStream();
+ try {
+ os.write(data);
+ } finally {
+ os.close();
+ }
+
+ switch (HttpSupport.response(c)) {
+ case HttpURLConnection.HTTP_OK:
+ return;
+ case HttpURLConnection.HTTP_INTERNAL_ERROR:
+ continue;
+ default:
+ throw error("Writing", key, c);
+ }
+ }
+ throw maxAttempts("Writing", key);
+ }
+
+ /**
+ * Atomically create or replace a single large object.
+ * <p>
+ * Initially the returned output stream buffers data into memory, but if the
+ * total number of written bytes starts to exceed an internal limit the data
+ * is spooled to a temporary file on the local drive.
+ * <p>
+ * Network transmission is attempted only when <code>close()</code> gets
+ * called at the end of output. Closing the returned stream can therefore
+ * take significant time, especially if the written content is very large.
+ * <p>
+ * End-to-end data integrity is assured by internally computing the MD5
+ * checksum of the supplied data and transmitting the checksum along with
+ * the data itself.
+ *
+ * @param bucket
+ * name of the bucket storing the object.
+ * @param key
+ * key of the object within its bucket.
+ * @param monitor
+ * (optional) progress monitor to post upload completion to
+ * during the stream's close method.
+ * @param monitorTask
+ * (optional) task name to display during the close method.
+ * @return a stream which accepts the new data, and transmits once closed.
+ * @throws IOException
+ * if encryption was enabled it could not be configured.
+ */
+ public OutputStream beginPut(final String bucket, final String key,
+ final ProgressMonitor monitor, final String monitorTask)
+ throws IOException {
+ final MessageDigest md5 = newMD5();
+ final TemporaryBuffer buffer = new TemporaryBuffer() {
+ @Override
+ public void close() throws IOException {
+ super.close();
+ try {
+ putImpl(bucket, key, md5.digest(), this, monitor,
+ monitorTask);
+ } finally {
+ destroy();
+ }
+ }
+ };
+ return encryption.encrypt(new DigestOutputStream(buffer, md5));
+ }
+
+ private void putImpl(final String bucket, final String key,
+ final byte[] csum, final TemporaryBuffer buf,
+ ProgressMonitor monitor, String monitorTask) throws IOException {
+ if (monitor == null)
+ monitor = NullProgressMonitor.INSTANCE;
+ if (monitorTask == null)
+ monitorTask = "Uploading " + key;
+
+ final String md5str = Base64.encodeBytes(csum);
+ final long len = buf.length();
+ final String lenstr = String.valueOf(len);
+ for (int curAttempt = 0; curAttempt < maxAttempts; curAttempt++) {
+ final HttpURLConnection c = open("PUT", bucket, key);
+ c.setRequestProperty("Content-Length", lenstr);
+ c.setRequestProperty("Content-MD5", md5str);
+ c.setRequestProperty(X_AMZ_ACL, acl);
+ encryption.request(c, X_AMZ_META);
+ authorize(c);
+ c.setDoOutput(true);
+ c.setFixedLengthStreamingMode((int) len);
+ monitor.beginTask(monitorTask, (int) (len / 1024));
+ final OutputStream os = c.getOutputStream();
+ try {
+ buf.writeTo(os, monitor);
+ } finally {
+ monitor.endTask();
+ os.close();
+ }
+
+ switch (HttpSupport.response(c)) {
+ case HttpURLConnection.HTTP_OK:
+ return;
+ case HttpURLConnection.HTTP_INTERNAL_ERROR:
+ continue;
+ default:
+ throw error("Writing", key, c);
+ }
+ }
+ throw maxAttempts("Writing", key);
+ }
+
+ private IOException error(final String action, final String key,
+ final HttpURLConnection c) throws IOException {
+ final IOException err = new IOException(action + " of '" + key
+ + "' failed: " + HttpSupport.response(c) + " "
+ + c.getResponseMessage());
+ final ByteArrayOutputStream b = new ByteArrayOutputStream();
+ byte[] buf = new byte[2048];
+ for (;;) {
+ final int n = c.getErrorStream().read(buf);
+ if (n < 0)
+ break;
+ if (n > 0)
+ b.write(buf, 0, n);
+ }
+ buf = b.toByteArray();
+ if (buf.length > 0)
+ err.initCause(new IOException("\n" + new String(buf)));
+ return err;
+ }
+
+ private IOException maxAttempts(final String action, final String key) {
+ return new IOException(action + " of '" + key + "' failed:"
+ + " Giving up after " + maxAttempts + " attempts.");
+ }
+
+ private HttpURLConnection open(final String method, final String bucket,
+ final String key) throws IOException {
+ final Map<String, String> noArgs = Collections.emptyMap();
+ return open(method, bucket, key, noArgs);
+ }
+
+ private HttpURLConnection open(final String method, final String bucket,
+ final String key, final Map<String, String> args)
+ throws IOException {
+ final StringBuilder urlstr = new StringBuilder();
+ urlstr.append("http://");
+ urlstr.append(bucket);
+ urlstr.append('.');
+ urlstr.append(DOMAIN);
+ urlstr.append('/');
+ if (key.length() > 0)
+ HttpSupport.encode(urlstr, key);
+ if (!args.isEmpty()) {
+ final Iterator<Map.Entry<String, String>> i;
+
+ urlstr.append('?');
+ i = args.entrySet().iterator();
+ while (i.hasNext()) {
+ final Map.Entry<String, String> e = i.next();
+ urlstr.append(e.getKey());
+ urlstr.append('=');
+ HttpSupport.encode(urlstr, e.getValue());
+ if (i.hasNext())
+ urlstr.append('&');
+ }
+ }
+
+ final URL url = new URL(urlstr.toString());
+ final Proxy proxy = HttpSupport.proxyFor(proxySelector, url);
+ final HttpURLConnection c;
+
+ c = (HttpURLConnection) url.openConnection(proxy);
+ c.setRequestMethod(method);
+ c.setRequestProperty("User-Agent", "jgit/1.0");
+ c.setRequestProperty("Date", httpNow());
+ return c;
+ }
+
+ private void authorize(final HttpURLConnection c) throws IOException {
+ final Map<String, List<String>> reqHdr = c.getRequestProperties();
+ final SortedMap<String, String> sigHdr = new TreeMap<String, String>();
+ for (final Map.Entry<String, List<String>> entry : reqHdr.entrySet()) {
+ final String hdr = entry.getKey();
+ if (isSignedHeader(hdr))
+ sigHdr.put(StringUtils.toLowerCase(hdr), toCleanString(entry.getValue()));
+ }
+
+ final StringBuilder s = new StringBuilder();
+ s.append(c.getRequestMethod());
+ s.append('\n');
+
+ s.append(remove(sigHdr, "content-md5"));
+ s.append('\n');
+
+ s.append(remove(sigHdr, "content-type"));
+ s.append('\n');
+
+ s.append(remove(sigHdr, "date"));
+ s.append('\n');
+
+ for (final Map.Entry<String, String> e : sigHdr.entrySet()) {
+ s.append(e.getKey());
+ s.append(':');
+ s.append(e.getValue());
+ s.append('\n');
+ }
+
+ final String host = c.getURL().getHost();
+ s.append('/');
+ s.append(host.substring(0, host.length() - DOMAIN.length() - 1));
+ s.append(c.getURL().getPath());
+
+ final String sec;
+ try {
+ final Mac m = Mac.getInstance(HMAC);
+ m.init(privateKey);
+ sec = Base64.encodeBytes(m.doFinal(s.toString().getBytes("UTF-8")));
+ } catch (NoSuchAlgorithmException e) {
+ throw new IOException("No " + HMAC + " support:" + e.getMessage());
+ } catch (InvalidKeyException e) {
+ throw new IOException("Invalid key: " + e.getMessage());
+ }
+ c.setRequestProperty("Authorization", "AWS " + publicKey + ":" + sec);
+ }
+
+ /**
+ * Simple command line interface to {@link AmazonS3}.
+ *
+ * @param argv
+ * command line arguments. See usage for details.
+ * @throws IOException
+ * an error occurred.
+ */
+ public static void main(final String[] argv) throws IOException {
+ if (argv.length != 4) {
+ commandLineUsage();
+ return;
+ }
+
+ AwtAuthenticator.install();
+ HttpSupport.configureHttpProxy();
+
+ final AmazonS3 s3 = new AmazonS3(properties(new File(argv[0])));
+ final String op = argv[1];
+ final String bucket = argv[2];
+ final String key = argv[3];
+ if ("get".equals(op)) {
+ final URLConnection c = s3.get(bucket, key);
+ int len = c.getContentLength();
+ final InputStream in = c.getInputStream();
+ try {
+ final byte[] tmp = new byte[2048];
+ while (len > 0) {
+ final int n = in.read(tmp);
+ if (n < 0)
+ throw new EOFException("Expected " + len + " bytes.");
+ System.out.write(tmp, 0, n);
+ len -= n;
+ }
+ } finally {
+ in.close();
+ }
+ } else if ("ls".equals(op) || "list".equals(op)) {
+ for (final String k : s3.list(bucket, key))
+ System.out.println(k);
+ } else if ("rm".equals(op) || "delete".equals(op)) {
+ s3.delete(bucket, key);
+ } else if ("put".equals(op)) {
+ final OutputStream os = s3.beginPut(bucket, key, null, null);
+ final byte[] tmp = new byte[2048];
+ int n;
+ while ((n = System.in.read(tmp)) > 0)
+ os.write(tmp, 0, n);
+ os.close();
+ } else {
+ commandLineUsage();
+ }
+ }
+
+ private static void commandLineUsage() {
+ System.err.println("usage: conn.prop op bucket key");
+ System.err.println();
+ System.err.println(" where conn.prop is a jets3t properties file.");
+ System.err.println(" op is one of: get ls rm put");
+ System.err.println(" bucket is the name of the S3 bucket");
+ System.err.println(" key is the name of the object.");
+ System.exit(1);
+ }
+
+ static Properties properties(final File authFile)
+ throws FileNotFoundException, IOException {
+ final Properties p = new Properties();
+ final FileInputStream in = new FileInputStream(authFile);
+ try {
+ p.load(in);
+ } finally {
+ in.close();
+ }
+ return p;
+ }
+
+ private final class ListParser extends DefaultHandler {
+ final List<String> entries = new ArrayList<String>();
+
+ private final String bucket;
+
+ private final String prefix;
+
+ boolean truncated;
+
+ private StringBuilder data;
+
+ ListParser(final String bn, final String p) {
+ bucket = bn;
+ prefix = p;
+ }
+
+ void list() throws IOException {
+ final Map<String, String> args = new TreeMap<String, String>();
+ if (prefix.length() > 0)
+ args.put("prefix", prefix);
+ if (!entries.isEmpty())
+ args.put("marker", prefix + entries.get(entries.size() - 1));
+
+ for (int curAttempt = 0; curAttempt < maxAttempts; curAttempt++) {
+ final HttpURLConnection c = open("GET", bucket, "", args);
+ authorize(c);
+ switch (HttpSupport.response(c)) {
+ case HttpURLConnection.HTTP_OK:
+ truncated = false;
+ data = null;
+
+ final XMLReader xr;
+ try {
+ xr = XMLReaderFactory.createXMLReader();
+ } catch (SAXException e) {
+ throw new IOException("No XML parser available.");
+ }
+ xr.setContentHandler(this);
+ final InputStream in = c.getInputStream();
+ try {
+ xr.parse(new InputSource(in));
+ } catch (SAXException parsingError) {
+ final IOException p;
+ p = new IOException("Error listing " + prefix);
+ p.initCause(parsingError);
+ throw p;
+ } finally {
+ in.close();
+ }
+ return;
+
+ case HttpURLConnection.HTTP_INTERNAL_ERROR:
+ continue;
+
+ default:
+ throw AmazonS3.this.error("Listing", prefix, c);
+ }
+ }
+ throw maxAttempts("Listing", prefix);
+ }
+
+ @Override
+ public void startElement(final String uri, final String name,
+ final String qName, final Attributes attributes)
+ throws SAXException {
+ if ("Key".equals(name) || "IsTruncated".equals(name))
+ data = new StringBuilder();
+ }
+
+ @Override
+ public void ignorableWhitespace(final char[] ch, final int s,
+ final int n) throws SAXException {
+ if (data != null)
+ data.append(ch, s, n);
+ }
+
+ @Override
+ public void characters(final char[] ch, final int s, final int n)
+ throws SAXException {
+ if (data != null)
+ data.append(ch, s, n);
+ }
+
+ @Override
+ public void endElement(final String uri, final String name,
+ final String qName) throws SAXException {
+ if ("Key".equals(name))
+ entries.add(data.toString().substring(prefix.length()));
+ else if ("IsTruncated".equals(name))
+ truncated = StringUtils.equalsIgnoreCase("true", data.toString());
+ data = null;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
new file mode 100644
index 0000000000..14be1700c8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Ref;
+
+/**
+ * Base helper class for implementing operations connections.
+ *
+ * @see BasePackConnection
+ * @see BaseFetchConnection
+ */
+abstract class BaseConnection implements Connection {
+
+ private Map<String, Ref> advertisedRefs = Collections.emptyMap();
+
+ private boolean startedOperation;
+
+ public Map<String, Ref> getRefsMap() {
+ return advertisedRefs;
+ }
+
+ public final Collection<Ref> getRefs() {
+ return advertisedRefs.values();
+ }
+
+ public final Ref getRef(final String name) {
+ return advertisedRefs.get(name);
+ }
+
+ public abstract void close();
+
+ /**
+ * Denote the list of refs available on the remote repository.
+ * <p>
+ * Implementors should invoke this method once they have obtained the refs
+ * that are available from the remote repository.
+ *
+ * @param all
+ * the complete list of refs the remote has to offer. This map
+ * will be wrapped in an unmodifiable way to protect it, but it
+ * does not get copied.
+ */
+ protected void available(final Map<String, Ref> all) {
+ advertisedRefs = Collections.unmodifiableMap(all);
+ }
+
+ /**
+ * Helper method for ensuring one-operation per connection. Check whether
+ * operation was already marked as started, and mark it as started.
+ *
+ * @throws TransportException
+ * if operation was already marked as started.
+ */
+ protected void markStartedOperation() throws TransportException {
+ if (startedOperation)
+ throw new TransportException(
+ "Only one operation call per connection is supported.");
+ startedOperation = true;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java
new file mode 100644
index 0000000000..b77e644a25
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+
+/**
+ * Base helper class for fetch connection implementations. Provides some common
+ * typical structures and methods used during fetch connection.
+ * <p>
+ * Implementors of fetch over pack-based protocols should consider using
+ * {@link BasePackFetchConnection} instead.
+ * </p>
+ */
+abstract class BaseFetchConnection extends BaseConnection implements
+ FetchConnection {
+ public final void fetch(final ProgressMonitor monitor,
+ final Collection<Ref> want, final Set<ObjectId> have)
+ throws TransportException {
+ markStartedOperation();
+ doFetch(monitor, want, have);
+ }
+
+ /**
+ * Default implementation of {@link FetchConnection#didFetchIncludeTags()} -
+ * returning false.
+ */
+ public boolean didFetchIncludeTags() {
+ return false;
+ }
+
+ /**
+ * Implementation of {@link #fetch(ProgressMonitor, Collection, Set)}
+ * without checking for multiple fetch.
+ *
+ * @param monitor
+ * as in {@link #fetch(ProgressMonitor, Collection, Set)}
+ * @param want
+ * as in {@link #fetch(ProgressMonitor, Collection, Set)}
+ * @param have
+ * as in {@link #fetch(ProgressMonitor, Collection, Set)}
+ * @throws TransportException
+ * as in {@link #fetch(ProgressMonitor, Collection, Set)}, but
+ * implementation doesn't have to care about multiple
+ * {@link #fetch(ProgressMonitor, Collection, Set)} calls, as it
+ * is checked in this class.
+ */
+ protected abstract void doFetch(final ProgressMonitor monitor,
+ final Collection<Ref> want, final Set<ObjectId> have)
+ throws TransportException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
new file mode 100644
index 0000000000..bd86ec0472
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.NoRemoteRepositoryException;
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.io.InterruptTimer;
+import org.eclipse.jgit.util.io.TimeoutInputStream;
+import org.eclipse.jgit.util.io.TimeoutOutputStream;
+
+/**
+ * Base helper class for pack-based operations implementations. Provides partial
+ * implementation of pack-protocol - refs advertising and capabilities support,
+ * and some other helper methods.
+ *
+ * @see BasePackFetchConnection
+ * @see BasePackPushConnection
+ */
+abstract class BasePackConnection extends BaseConnection {
+
+ /** The repository this transport fetches into, or pushes out of. */
+ protected final Repository local;
+
+ /** Remote repository location. */
+ protected final URIish uri;
+
+ /** A transport connected to {@link #uri}. */
+ protected final Transport transport;
+
+ /** Low-level input stream, if a timeout was configured. */
+ protected TimeoutInputStream timeoutIn;
+
+ /** Low-level output stream, if a timeout was configured. */
+ protected TimeoutOutputStream timeoutOut;
+
+ /** Timer to manage {@link #timeoutIn} and {@link #timeoutOut}. */
+ private InterruptTimer myTimer;
+
+ /** Buffered input stream reading from the remote. */
+ protected InputStream in;
+
+ /** Buffered output stream sending to the remote. */
+ protected OutputStream out;
+
+ /** Packet line decoder around {@link #in}. */
+ protected PacketLineIn pckIn;
+
+ /** Packet line encoder around {@link #out}. */
+ protected PacketLineOut pckOut;
+
+ /** Send {@link PacketLineOut#end()} before closing {@link #out}? */
+ protected boolean outNeedsEnd;
+
+ /** Capability tokens advertised by the remote side. */
+ private final Set<String> remoteCapablities = new HashSet<String>();
+
+ /** Extra objects the remote has, but which aren't offered as refs. */
+ protected final Set<ObjectId> additionalHaves = new HashSet<ObjectId>();
+
+ BasePackConnection(final PackTransport packTransport) {
+ transport = (Transport)packTransport;
+ local = transport.local;
+ uri = transport.uri;
+ }
+
+ protected final void init(InputStream myIn, OutputStream myOut) {
+ final int timeout = transport.getTimeout();
+ if (timeout > 0) {
+ final Thread caller = Thread.currentThread();
+ myTimer = new InterruptTimer(caller.getName() + "-Timer");
+ timeoutIn = new TimeoutInputStream(myIn, myTimer);
+ timeoutOut = new TimeoutOutputStream(myOut, myTimer);
+ timeoutIn.setTimeout(timeout * 1000);
+ timeoutOut.setTimeout(timeout * 1000);
+ myIn = timeoutIn;
+ myOut = timeoutOut;
+ }
+
+ in = myIn instanceof BufferedInputStream ? myIn
+ : new BufferedInputStream(myIn, IndexPack.BUFFER_SIZE);
+ out = myOut instanceof BufferedOutputStream ? myOut
+ : new BufferedOutputStream(myOut);
+
+ pckIn = new PacketLineIn(in);
+ pckOut = new PacketLineOut(out);
+ outNeedsEnd = true;
+ }
+
+ protected void readAdvertisedRefs() throws TransportException {
+ try {
+ readAdvertisedRefsImpl();
+ } catch (TransportException err) {
+ close();
+ throw err;
+ } catch (IOException err) {
+ close();
+ throw new TransportException(err.getMessage(), err);
+ } catch (RuntimeException err) {
+ close();
+ throw new TransportException(err.getMessage(), err);
+ }
+ }
+
+ private void readAdvertisedRefsImpl() throws IOException {
+ final LinkedHashMap<String, Ref> avail = new LinkedHashMap<String, Ref>();
+ for (;;) {
+ String line;
+
+ try {
+ line = pckIn.readString();
+ } catch (EOFException eof) {
+ if (avail.isEmpty())
+ throw noRepository();
+ throw eof;
+ }
+ if (line == PacketLineIn.END)
+ break;
+
+ if (avail.isEmpty()) {
+ final int nul = line.indexOf('\0');
+ if (nul >= 0) {
+ // The first line (if any) may contain "hidden"
+ // capability values after a NUL byte.
+ for (String c : line.substring(nul + 1).split(" "))
+ remoteCapablities.add(c);
+ line = line.substring(0, nul);
+ }
+ }
+
+ String name = line.substring(41, line.length());
+ if (avail.isEmpty() && name.equals("capabilities^{}")) {
+ // special line from git-receive-pack to show
+ // capabilities when there are no refs to advertise
+ continue;
+ }
+
+ final ObjectId id = ObjectId.fromString(line.substring(0, 40));
+ if (name.equals(".have")) {
+ additionalHaves.add(id);
+ } else if (name.endsWith("^{}")) {
+ name = name.substring(0, name.length() - 3);
+ final Ref prior = avail.get(name);
+ if (prior == null)
+ throw new PackProtocolException(uri, "advertisement of "
+ + name + "^{} came before " + name);
+
+ if (prior.getPeeledObjectId() != null)
+ throw duplicateAdvertisement(name + "^{}");
+
+ avail.put(name, new Ref(Ref.Storage.NETWORK, name, prior
+ .getObjectId(), id, true));
+ } else {
+ final Ref prior;
+ prior = avail.put(name, new Ref(Ref.Storage.NETWORK, name, id));
+ if (prior != null)
+ throw duplicateAdvertisement(name);
+ }
+ }
+ available(avail);
+ }
+
+ /**
+ * Create an exception to indicate problems finding a remote repository. The
+ * caller is expected to throw the returned exception.
+ *
+ * Subclasses may override this method to provide better diagnostics.
+ *
+ * @return a TransportException saying a repository cannot be found and
+ * possibly why.
+ */
+ protected TransportException noRepository() {
+ return new NoRemoteRepositoryException(uri, "not found.");
+ }
+
+ protected boolean isCapableOf(final String option) {
+ return remoteCapablities.contains(option);
+ }
+
+ protected boolean wantCapability(final StringBuilder b, final String option) {
+ if (!isCapableOf(option))
+ return false;
+ b.append(' ');
+ b.append(option);
+ return true;
+ }
+
+ private PackProtocolException duplicateAdvertisement(final String name) {
+ return new PackProtocolException(uri, "duplicate advertisements of "
+ + name);
+ }
+
+ @Override
+ public void close() {
+ if (out != null) {
+ try {
+ if (outNeedsEnd)
+ pckOut.end();
+ out.close();
+ } catch (IOException err) {
+ // Ignore any close errors.
+ } finally {
+ out = null;
+ pckOut = null;
+ }
+ }
+
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException err) {
+ // Ignore any close errors.
+ } finally {
+ in = null;
+ pckIn = null;
+ }
+ }
+
+ if (myTimer != null) {
+ try {
+ myTimer.terminate();
+ } finally {
+ myTimer = null;
+ timeoutIn = null;
+ timeoutOut = null;
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
new file mode 100644
index 0000000000..3f478680aa
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PackLock;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Config.SectionParser;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevCommitList;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+
+/**
+ * Fetch implementation using the native Git pack transfer service.
+ * <p>
+ * This is the canonical implementation for transferring objects from the remote
+ * repository to the local repository by talking to the 'git-upload-pack'
+ * service. Objects are packed on the remote side into a pack file and then sent
+ * down the pipe to us.
+ * <p>
+ * This connection requires only a bi-directional pipe or socket, and thus is
+ * easily wrapped up into a local process pipe, anonymous TCP socket, or a
+ * command executed through an SSH tunnel.
+ * <p>
+ * Concrete implementations should just call
+ * {@link #init(java.io.InputStream, java.io.OutputStream)} and
+ * {@link #readAdvertisedRefs()} methods in constructor or before any use. They
+ * should also handle resources releasing in {@link #close()} method if needed.
+ */
+abstract class BasePackFetchConnection extends BasePackConnection implements
+ FetchConnection {
+ /**
+ * Maximum number of 'have' lines to send before giving up.
+ * <p>
+ * During {@link #negotiate(ProgressMonitor)} we send at most this many
+ * commits to the remote peer as 'have' lines without an ACK response before
+ * we give up.
+ */
+ private static final int MAX_HAVES = 256;
+
+ /**
+ * Amount of data the client sends before starting to read.
+ * <p>
+ * Any output stream given to the client must be able to buffer this many
+ * bytes before the client will stop writing and start reading from the
+ * input stream. If the output stream blocks before this many bytes are in
+ * the send queue, the system will deadlock.
+ */
+ protected static final int MIN_CLIENT_BUFFER = 2 * 32 * 46 + 8;
+
+ static final String OPTION_INCLUDE_TAG = "include-tag";
+
+ static final String OPTION_MULTI_ACK = "multi_ack";
+
+ static final String OPTION_THIN_PACK = "thin-pack";
+
+ static final String OPTION_SIDE_BAND = "side-band";
+
+ static final String OPTION_SIDE_BAND_64K = "side-band-64k";
+
+ static final String OPTION_OFS_DELTA = "ofs-delta";
+
+ static final String OPTION_SHALLOW = "shallow";
+
+ static final String OPTION_NO_PROGRESS = "no-progress";
+
+ private final RevWalk walk;
+
+ /** All commits that are immediately reachable by a local ref. */
+ private RevCommitList<RevCommit> reachableCommits;
+
+ /** Marks an object as having all its dependencies. */
+ final RevFlag REACHABLE;
+
+ /** Marks a commit known to both sides of the connection. */
+ final RevFlag COMMON;
+
+ /** Marks a commit listed in the advertised refs. */
+ final RevFlag ADVERTISED;
+
+ private boolean multiAck;
+
+ private boolean thinPack;
+
+ private boolean sideband;
+
+ private boolean includeTags;
+
+ private boolean allowOfsDelta;
+
+ private String lockMessage;
+
+ private PackLock packLock;
+
+ BasePackFetchConnection(final PackTransport packTransport) {
+ super(packTransport);
+
+ final FetchConfig cfg = local.getConfig().get(FetchConfig.KEY);
+ includeTags = transport.getTagOpt() != TagOpt.NO_TAGS;
+ thinPack = transport.isFetchThin();
+ allowOfsDelta = cfg.allowOfsDelta;
+
+ walk = new RevWalk(local);
+ reachableCommits = new RevCommitList<RevCommit>();
+ REACHABLE = walk.newFlag("REACHABLE");
+ COMMON = walk.newFlag("COMMON");
+ ADVERTISED = walk.newFlag("ADVERTISED");
+
+ walk.carry(COMMON);
+ walk.carry(REACHABLE);
+ walk.carry(ADVERTISED);
+ }
+
+ private static class FetchConfig {
+ static final SectionParser<FetchConfig> KEY = new SectionParser<FetchConfig>() {
+ public FetchConfig parse(final Config cfg) {
+ return new FetchConfig(cfg);
+ }
+ };
+
+ final boolean allowOfsDelta;
+
+ FetchConfig(final Config c) {
+ allowOfsDelta = c.getBoolean("repack", "usedeltabaseoffset", true);
+ }
+ }
+
+ public final void fetch(final ProgressMonitor monitor,
+ final Collection<Ref> want, final Set<ObjectId> have)
+ throws TransportException {
+ markStartedOperation();
+ doFetch(monitor, want, have);
+ }
+
+ public boolean didFetchIncludeTags() {
+ return false;
+ }
+
+ public boolean didFetchTestConnectivity() {
+ return false;
+ }
+
+ public void setPackLockMessage(final String message) {
+ lockMessage = message;
+ }
+
+ public Collection<PackLock> getPackLocks() {
+ if (packLock != null)
+ return Collections.singleton(packLock);
+ return Collections.<PackLock> emptyList();
+ }
+
+ protected void doFetch(final ProgressMonitor monitor,
+ final Collection<Ref> want, final Set<ObjectId> have)
+ throws TransportException {
+ try {
+ markRefsAdvertised();
+ markReachable(have, maxTimeWanted(want));
+
+ if (sendWants(want)) {
+ negotiate(monitor);
+
+ walk.dispose();
+ reachableCommits = null;
+
+ receivePack(monitor);
+ }
+ } catch (CancelledException ce) {
+ close();
+ return; // Caller should test (or just know) this themselves.
+ } catch (IOException err) {
+ close();
+ throw new TransportException(err.getMessage(), err);
+ } catch (RuntimeException err) {
+ close();
+ throw new TransportException(err.getMessage(), err);
+ }
+ }
+
+ private int maxTimeWanted(final Collection<Ref> wants) {
+ int maxTime = 0;
+ for (final Ref r : wants) {
+ try {
+ final RevObject obj = walk.parseAny(r.getObjectId());
+ if (obj instanceof RevCommit) {
+ final int cTime = ((RevCommit) obj).getCommitTime();
+ if (maxTime < cTime)
+ maxTime = cTime;
+ }
+ } catch (IOException error) {
+ // We don't have it, but we want to fetch (thus fixing error).
+ }
+ }
+ return maxTime;
+ }
+
+ private void markReachable(final Set<ObjectId> have, final int maxTime)
+ throws IOException {
+ for (final Ref r : local.getAllRefs().values()) {
+ try {
+ final RevCommit o = walk.parseCommit(r.getObjectId());
+ o.add(REACHABLE);
+ reachableCommits.add(o);
+ } catch (IOException readError) {
+ // If we cannot read the value of the ref skip it.
+ }
+ }
+
+ for (final ObjectId id : have) {
+ try {
+ final RevCommit o = walk.parseCommit(id);
+ o.add(REACHABLE);
+ reachableCommits.add(o);
+ } catch (IOException readError) {
+ // If we cannot read the value of the ref skip it.
+ }
+ }
+
+ if (maxTime > 0) {
+ // Mark reachable commits until we reach maxTime. These may
+ // wind up later matching up against things we want and we
+ // can avoid asking for something we already happen to have.
+ //
+ final Date maxWhen = new Date(maxTime * 1000L);
+ walk.sort(RevSort.COMMIT_TIME_DESC);
+ walk.markStart(reachableCommits);
+ walk.setRevFilter(CommitTimeRevFilter.after(maxWhen));
+ for (;;) {
+ final RevCommit c = walk.next();
+ if (c == null)
+ break;
+ if (c.has(ADVERTISED) && !c.has(COMMON)) {
+ // This is actually going to be a common commit, but
+ // our peer doesn't know that fact yet.
+ //
+ c.add(COMMON);
+ c.carry(COMMON);
+ reachableCommits.add(c);
+ }
+ }
+ }
+ }
+
+ private boolean sendWants(final Collection<Ref> want) throws IOException {
+ boolean first = true;
+ for (final Ref r : want) {
+ try {
+ if (walk.parseAny(r.getObjectId()).has(REACHABLE)) {
+ // We already have this object. Asking for it is
+ // not a very good idea.
+ //
+ continue;
+ }
+ } catch (IOException err) {
+ // Its OK, we don't have it, but we want to fix that
+ // by fetching the object from the other side.
+ }
+
+ final StringBuilder line = new StringBuilder(46);
+ line.append("want ");
+ line.append(r.getObjectId().name());
+ if (first) {
+ line.append(enableCapabilities());
+ first = false;
+ }
+ line.append('\n');
+ pckOut.writeString(line.toString());
+ }
+ pckOut.end();
+ outNeedsEnd = false;
+ return !first;
+ }
+
+ private String enableCapabilities() {
+ final StringBuilder line = new StringBuilder();
+ if (includeTags)
+ includeTags = wantCapability(line, OPTION_INCLUDE_TAG);
+ if (allowOfsDelta)
+ wantCapability(line, OPTION_OFS_DELTA);
+ multiAck = wantCapability(line, OPTION_MULTI_ACK);
+ if (thinPack)
+ thinPack = wantCapability(line, OPTION_THIN_PACK);
+ if (wantCapability(line, OPTION_SIDE_BAND_64K))
+ sideband = true;
+ else if (wantCapability(line, OPTION_SIDE_BAND))
+ sideband = true;
+ return line.toString();
+ }
+
+ private void negotiate(final ProgressMonitor monitor) throws IOException,
+ CancelledException {
+ final MutableObjectId ackId = new MutableObjectId();
+ int resultsPending = 0;
+ int havesSent = 0;
+ int havesSinceLastContinue = 0;
+ boolean receivedContinue = false;
+ boolean receivedAck = false;
+ boolean sendHaves = true;
+
+ negotiateBegin();
+ while (sendHaves) {
+ final RevCommit c = walk.next();
+ if (c == null)
+ break;
+
+ pckOut.writeString("have " + c.getId().name() + "\n");
+ havesSent++;
+ havesSinceLastContinue++;
+
+ if ((31 & havesSent) != 0) {
+ // We group the have lines into blocks of 32, each marked
+ // with a flush (aka end). This one is within a block so
+ // continue with another have line.
+ //
+ continue;
+ }
+
+ if (monitor.isCancelled())
+ throw new CancelledException();
+
+ pckOut.end();
+ resultsPending++; // Each end will cause a result to come back.
+
+ if (havesSent == 32) {
+ // On the first block we race ahead and try to send
+ // more of the second block while waiting for the
+ // remote to respond to our first block request.
+ // This keeps us one block ahead of the peer.
+ //
+ continue;
+ }
+
+ for (;;) {
+ final PacketLineIn.AckNackResult anr;
+
+ anr = pckIn.readACK(ackId);
+ if (anr == PacketLineIn.AckNackResult.NAK) {
+ // More have lines are necessary to compute the
+ // pack on the remote side. Keep doing that.
+ //
+ resultsPending--;
+ break;
+ }
+
+ if (anr == PacketLineIn.AckNackResult.ACK) {
+ // The remote side is happy and knows exactly what
+ // to send us. There is no further negotiation and
+ // we can break out immediately.
+ //
+ multiAck = false;
+ resultsPending = 0;
+ receivedAck = true;
+ sendHaves = false;
+ break;
+ }
+
+ if (anr == PacketLineIn.AckNackResult.ACK_CONTINUE) {
+ // The server knows this commit (ackId). We don't
+ // need to send any further along its ancestry, but
+ // we need to continue to talk about other parts of
+ // our local history.
+ //
+ markCommon(walk.parseAny(ackId));
+ receivedAck = true;
+ receivedContinue = true;
+ havesSinceLastContinue = 0;
+ }
+
+ if (monitor.isCancelled())
+ throw new CancelledException();
+ }
+
+ if (receivedContinue && havesSinceLastContinue > MAX_HAVES) {
+ // Our history must be really different from the remote's.
+ // We just sent a whole slew of have lines, and it did not
+ // recognize any of them. Avoid sending our entire history
+ // to them by giving up early.
+ //
+ break;
+ }
+ }
+
+ // Tell the remote side we have run out of things to talk about.
+ //
+ if (monitor.isCancelled())
+ throw new CancelledException();
+ pckOut.writeString("done\n");
+ pckOut.flush();
+
+ if (!receivedAck) {
+ // Apparently if we have never received an ACK earlier
+ // there is one more result expected from the done we
+ // just sent to the remote.
+ //
+ multiAck = false;
+ resultsPending++;
+ }
+
+ while (resultsPending > 0 || multiAck) {
+ final PacketLineIn.AckNackResult anr;
+
+ anr = pckIn.readACK(ackId);
+ resultsPending--;
+
+ if (anr == PacketLineIn.AckNackResult.ACK)
+ break; // commit negotiation is finished.
+
+ if (anr == PacketLineIn.AckNackResult.ACK_CONTINUE) {
+ // There must be a normal ACK following this.
+ //
+ multiAck = true;
+ }
+
+ if (monitor.isCancelled())
+ throw new CancelledException();
+ }
+ }
+
+ private void negotiateBegin() throws IOException {
+ walk.resetRetain(REACHABLE, ADVERTISED);
+ walk.markStart(reachableCommits);
+ walk.sort(RevSort.COMMIT_TIME_DESC);
+ walk.setRevFilter(new RevFilter() {
+ @Override
+ public RevFilter clone() {
+ return this;
+ }
+
+ @Override
+ public boolean include(final RevWalk walker, final RevCommit c) {
+ final boolean remoteKnowsIsCommon = c.has(COMMON);
+ if (c.has(ADVERTISED)) {
+ // Remote advertised this, and we have it, hence common.
+ // Whether or not the remote knows that fact is tested
+ // before we added the flag. If the remote doesn't know
+ // we have to still send them this object.
+ //
+ c.add(COMMON);
+ }
+ return !remoteKnowsIsCommon;
+ }
+ });
+ }
+
+ private void markRefsAdvertised() {
+ for (final Ref r : getRefs()) {
+ markAdvertised(r.getObjectId());
+ if (r.getPeeledObjectId() != null)
+ markAdvertised(r.getPeeledObjectId());
+ }
+ }
+
+ private void markAdvertised(final AnyObjectId id) {
+ try {
+ walk.parseAny(id).add(ADVERTISED);
+ } catch (IOException readError) {
+ // We probably just do not have this object locally.
+ }
+ }
+
+ private void markCommon(final RevObject obj) {
+ obj.add(COMMON);
+ if (obj instanceof RevCommit)
+ ((RevCommit) obj).carry(COMMON);
+ }
+
+ private void receivePack(final ProgressMonitor monitor) throws IOException {
+ final IndexPack ip;
+
+ ip = IndexPack.create(local, sideband ? pckIn.sideband(monitor) : in);
+ ip.setFixThin(thinPack);
+ ip.setObjectChecking(transport.isCheckFetchedObjects());
+ ip.index(monitor);
+ packLock = ip.renameAndOpenPack(lockMessage);
+ }
+
+ private static class CancelledException extends Exception {
+ private static final long serialVersionUID = 1L;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
new file mode 100644
index 0000000000..b1ce28d35f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+import org.eclipse.jgit.errors.NoRemoteRepositoryException;
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PackWriter;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+
+/**
+ * Push implementation using the native Git pack transfer service.
+ * <p>
+ * This is the canonical implementation for transferring objects to the remote
+ * repository from the local repository by talking to the 'git-receive-pack'
+ * service. Objects are packed on the local side into a pack file and then sent
+ * to the remote repository.
+ * <p>
+ * This connection requires only a bi-directional pipe or socket, and thus is
+ * easily wrapped up into a local process pipe, anonymous TCP socket, or a
+ * command executed through an SSH tunnel.
+ * <p>
+ * This implementation honors {@link Transport#isPushThin()} option.
+ * <p>
+ * Concrete implementations should just call
+ * {@link #init(java.io.InputStream, java.io.OutputStream)} and
+ * {@link #readAdvertisedRefs()} methods in constructor or before any use. They
+ * should also handle resources releasing in {@link #close()} method if needed.
+ */
+class BasePackPushConnection extends BasePackConnection implements
+ PushConnection {
+ static final String CAPABILITY_REPORT_STATUS = "report-status";
+
+ static final String CAPABILITY_DELETE_REFS = "delete-refs";
+
+ static final String CAPABILITY_OFS_DELTA = "ofs-delta";
+
+ private final boolean thinPack;
+
+ private boolean capableDeleteRefs;
+
+ private boolean capableReport;
+
+ private boolean capableOfsDelta;
+
+ private boolean sentCommand;
+
+ private boolean writePack;
+
+ /** Time in milliseconds spent transferring the pack data. */
+ private long packTransferTime;
+
+ BasePackPushConnection(final PackTransport packTransport) {
+ super(packTransport);
+ thinPack = transport.isPushThin();
+ }
+
+ public void push(final ProgressMonitor monitor,
+ final Map<String, RemoteRefUpdate> refUpdates)
+ throws TransportException {
+ markStartedOperation();
+ doPush(monitor, refUpdates);
+ }
+
+ @Override
+ protected TransportException noRepository() {
+ // Sadly we cannot tell the "invalid URI" case from "push not allowed".
+ // Opening a fetch connection can help us tell the difference, as any
+ // useful repository is going to support fetch if it also would allow
+ // push. So if fetch throws NoRemoteRepositoryException we know the
+ // URI is wrong. Otherwise we can correctly state push isn't allowed
+ // as the fetch connection opened successfully.
+ //
+ try {
+ transport.openFetch().close();
+ } catch (NotSupportedException e) {
+ // Fall through.
+ } catch (NoRemoteRepositoryException e) {
+ // Fetch concluded the repository doesn't exist.
+ //
+ return e;
+ } catch (TransportException e) {
+ // Fall through.
+ }
+ return new TransportException(uri, "push not permitted");
+ }
+
+ protected void doPush(final ProgressMonitor monitor,
+ final Map<String, RemoteRefUpdate> refUpdates)
+ throws TransportException {
+ try {
+ writeCommands(refUpdates.values(), monitor);
+ if (writePack)
+ writePack(refUpdates, monitor);
+ if (sentCommand && capableReport)
+ readStatusReport(refUpdates);
+ } catch (TransportException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new TransportException(uri, e.getMessage(), e);
+ } finally {
+ close();
+ }
+ }
+
+ private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
+ final ProgressMonitor monitor) throws IOException {
+ final String capabilities = enableCapabilities();
+ for (final RemoteRefUpdate rru : refUpdates) {
+ if (!capableDeleteRefs && rru.isDelete()) {
+ rru.setStatus(Status.REJECTED_NODELETE);
+ continue;
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ final Ref advertisedRef = getRef(rru.getRemoteName());
+ final ObjectId oldId = (advertisedRef == null ? ObjectId.zeroId()
+ : advertisedRef.getObjectId());
+ sb.append(oldId.name());
+ sb.append(' ');
+ sb.append(rru.getNewObjectId().name());
+ sb.append(' ');
+ sb.append(rru.getRemoteName());
+ if (!sentCommand) {
+ sentCommand = true;
+ sb.append(capabilities);
+ }
+
+ pckOut.writeString(sb.toString());
+ rru.setStatus(sentCommand ? Status.AWAITING_REPORT : Status.OK);
+ if (!rru.isDelete())
+ writePack = true;
+ }
+
+ if (monitor.isCancelled())
+ throw new TransportException(uri, "push cancelled");
+ pckOut.end();
+ outNeedsEnd = false;
+ }
+
+ private String enableCapabilities() {
+ final StringBuilder line = new StringBuilder();
+ capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS);
+ capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
+ capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA);
+ if (line.length() > 0)
+ line.setCharAt(0, '\0');
+ return line.toString();
+ }
+
+ private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
+ final ProgressMonitor monitor) throws IOException {
+ final PackWriter writer = new PackWriter(local, monitor);
+ final ArrayList<ObjectId> remoteObjects = new ArrayList<ObjectId>(
+ getRefs().size());
+ final ArrayList<ObjectId> newObjects = new ArrayList<ObjectId>(
+ refUpdates.size());
+
+ for (final Ref r : getRefs())
+ remoteObjects.add(r.getObjectId());
+ remoteObjects.addAll(additionalHaves);
+ for (final RemoteRefUpdate r : refUpdates.values()) {
+ if (!ObjectId.zeroId().equals(r.getNewObjectId()))
+ newObjects.add(r.getNewObjectId());
+ }
+
+ writer.setThin(thinPack);
+ writer.setDeltaBaseAsOffset(capableOfsDelta);
+ writer.preparePack(newObjects, remoteObjects);
+ final long start = System.currentTimeMillis();
+ writer.writePack(out);
+ packTransferTime = System.currentTimeMillis() - start;
+ }
+
+ private void readStatusReport(final Map<String, RemoteRefUpdate> refUpdates)
+ throws IOException {
+ final String unpackLine = readStringLongTimeout();
+ if (!unpackLine.startsWith("unpack "))
+ throw new PackProtocolException(uri, "unexpected report line: "
+ + unpackLine);
+ final String unpackStatus = unpackLine.substring("unpack ".length());
+ if (!unpackStatus.equals("ok"))
+ throw new TransportException(uri,
+ "error occurred during unpacking on the remote end: "
+ + unpackStatus);
+
+ String refLine;
+ while ((refLine = pckIn.readString()) != PacketLineIn.END) {
+ boolean ok = false;
+ int refNameEnd = -1;
+ if (refLine.startsWith("ok ")) {
+ ok = true;
+ refNameEnd = refLine.length();
+ } else if (refLine.startsWith("ng ")) {
+ ok = false;
+ refNameEnd = refLine.indexOf(" ", 3);
+ }
+ if (refNameEnd == -1)
+ throw new PackProtocolException(uri
+ + ": unexpected report line: " + refLine);
+ final String refName = refLine.substring(3, refNameEnd);
+ final String message = (ok ? null : refLine
+ .substring(refNameEnd + 1));
+
+ final RemoteRefUpdate rru = refUpdates.get(refName);
+ if (rru == null)
+ throw new PackProtocolException(uri
+ + ": unexpected ref report: " + refName);
+ if (ok) {
+ rru.setStatus(Status.OK);
+ } else {
+ rru.setStatus(Status.REJECTED_OTHER_REASON);
+ rru.setMessage(message);
+ }
+ }
+ for (final RemoteRefUpdate rru : refUpdates.values()) {
+ if (rru.getStatus() == Status.AWAITING_REPORT)
+ throw new PackProtocolException(uri
+ + ": expected report for ref " + rru.getRemoteName()
+ + " not received");
+ }
+ }
+
+ private String readStringLongTimeout() throws IOException {
+ if (timeoutIn == null)
+ return pckIn.readString();
+
+ // The remote side may need a lot of time to choke down the pack
+ // we just sent them. There may be many deltas that need to be
+ // resolved by the remote. Its hard to say how long the other
+ // end is going to be silent. Taking 10x the configured timeout
+ // or the time spent transferring the pack, whichever is larger,
+ // gives the other side some reasonable window to process the data,
+ // but this is just a wild guess.
+ //
+ final int oldTimeout = timeoutIn.getTimeout();
+ final int sendTime = (int) Math.min(packTransferTime, 28800000L);
+ try {
+ timeoutIn.setTimeout(10 * Math.max(sendTime, oldTimeout));
+ return pckIn.readString();
+ } finally {
+ timeoutIn.setTimeout(oldTimeout);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
new file mode 100644
index 0000000000..a0d172e0fa
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2009, Matthias Sohn <matthias.sohn@sap.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.MissingBundlePrerequisiteException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PackLock;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Fetch connection for bundle based classes. It used by
+ * instances of {@link TransportBundle}
+ */
+class BundleFetchConnection extends BaseFetchConnection {
+
+ private final Transport transport;
+
+ InputStream bin;
+
+ final Set<ObjectId> prereqs = new HashSet<ObjectId>();
+
+ private String lockMessage;
+
+ private PackLock packLock;
+
+ BundleFetchConnection(Transport transportBundle, final InputStream src) throws TransportException {
+ transport = transportBundle;
+ bin = new BufferedInputStream(src, IndexPack.BUFFER_SIZE);
+ try {
+ switch (readSignature()) {
+ case 2:
+ readBundleV2();
+ break;
+ default:
+ throw new TransportException(transport.uri, "not a bundle");
+ }
+ } catch (TransportException err) {
+ close();
+ throw err;
+ } catch (IOException err) {
+ close();
+ throw new TransportException(transport.uri, err.getMessage(), err);
+ } catch (RuntimeException err) {
+ close();
+ throw new TransportException(transport.uri, err.getMessage(), err);
+ }
+ }
+
+ private int readSignature() throws IOException {
+ final String rev = readLine(new byte[1024]);
+ if (TransportBundle.V2_BUNDLE_SIGNATURE.equals(rev))
+ return 2;
+ throw new TransportException(transport.uri, "not a bundle");
+ }
+
+ private void readBundleV2() throws IOException {
+ final byte[] hdrbuf = new byte[1024];
+ final LinkedHashMap<String, Ref> avail = new LinkedHashMap<String, Ref>();
+ for (;;) {
+ String line = readLine(hdrbuf);
+ if (line.length() == 0)
+ break;
+
+ if (line.charAt(0) == '-') {
+ prereqs.add(ObjectId.fromString(line.substring(1, 41)));
+ continue;
+ }
+
+ final String name = line.substring(41, line.length());
+ final ObjectId id = ObjectId.fromString(line.substring(0, 40));
+ final Ref prior = avail.put(name, new Ref(Ref.Storage.NETWORK,
+ name, id));
+ if (prior != null)
+ throw duplicateAdvertisement(name);
+ }
+ available(avail);
+ }
+
+ private PackProtocolException duplicateAdvertisement(final String name) {
+ return new PackProtocolException(transport.uri,
+ "duplicate advertisements of " + name);
+ }
+
+ private String readLine(final byte[] hdrbuf) throws IOException {
+ bin.mark(hdrbuf.length);
+ final int cnt = bin.read(hdrbuf);
+ int lf = 0;
+ while (lf < cnt && hdrbuf[lf] != '\n')
+ lf++;
+ bin.reset();
+ NB.skipFully(bin, lf);
+ if (lf < cnt && hdrbuf[lf] == '\n')
+ NB.skipFully(bin, 1);
+ return RawParseUtils.decode(Constants.CHARSET, hdrbuf, 0, lf);
+ }
+
+ public boolean didFetchTestConnectivity() {
+ return false;
+ }
+
+ @Override
+ protected void doFetch(final ProgressMonitor monitor,
+ final Collection<Ref> want, final Set<ObjectId> have)
+ throws TransportException {
+ verifyPrerequisites();
+ try {
+ final IndexPack ip = newIndexPack();
+ ip.index(monitor);
+ packLock = ip.renameAndOpenPack(lockMessage);
+ } catch (IOException err) {
+ close();
+ throw new TransportException(transport.uri, err.getMessage(), err);
+ } catch (RuntimeException err) {
+ close();
+ throw new TransportException(transport.uri, err.getMessage(), err);
+ }
+ }
+
+ public void setPackLockMessage(final String message) {
+ lockMessage = message;
+ }
+
+ public Collection<PackLock> getPackLocks() {
+ if (packLock != null)
+ return Collections.singleton(packLock);
+ return Collections.<PackLock> emptyList();
+ }
+
+ private IndexPack newIndexPack() throws IOException {
+ final IndexPack ip = IndexPack.create(transport.local, bin);
+ ip.setFixThin(true);
+ ip.setObjectChecking(transport.isCheckFetchedObjects());
+ return ip;
+ }
+
+ private void verifyPrerequisites() throws TransportException {
+ if (prereqs.isEmpty())
+ return;
+
+ final RevWalk rw = new RevWalk(transport.local);
+ final RevFlag PREREQ = rw.newFlag("PREREQ");
+ final RevFlag SEEN = rw.newFlag("SEEN");
+
+ final List<ObjectId> missing = new ArrayList<ObjectId>();
+ final List<RevObject> commits = new ArrayList<RevObject>();
+ for (final ObjectId p : prereqs) {
+ try {
+ final RevCommit c = rw.parseCommit(p);
+ if (!c.has(PREREQ)) {
+ c.add(PREREQ);
+ commits.add(c);
+ }
+ } catch (MissingObjectException notFound) {
+ missing.add(p);
+ } catch (IOException err) {
+ throw new TransportException(transport.uri, "Cannot read commit "
+ + p.name(), err);
+ }
+ }
+ if (!missing.isEmpty())
+ throw new MissingBundlePrerequisiteException(transport.uri, missing);
+
+ for (final Ref r : transport.local.getAllRefs().values()) {
+ try {
+ rw.markStart(rw.parseCommit(r.getObjectId()));
+ } catch (IOException readError) {
+ // If we cannot read the value of the ref skip it.
+ }
+ }
+
+ int remaining = commits.size();
+ try {
+ RevCommit c;
+ while ((c = rw.next()) != null) {
+ if (c.has(PREREQ)) {
+ c.add(SEEN);
+ if (--remaining == 0)
+ break;
+ }
+ }
+ } catch (IOException err) {
+ throw new TransportException(transport.uri, "Cannot read object", err);
+ }
+
+ if (remaining > 0) {
+ for (final RevObject o : commits) {
+ if (!o.has(SEEN))
+ missing.add(o);
+ }
+ throw new MissingBundlePrerequisiteException(transport.uri, missing);
+ }
+ }
+
+ @Override
+ public void close() {
+ if (bin != null) {
+ try {
+ bin.close();
+ } catch (IOException ie) {
+ // Ignore close failures.
+ } finally {
+ bin = null;
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
new file mode 100644
index 0000000000..92d07e1283
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PackWriter;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+/**
+ * Creates a Git bundle file, for sneaker-net transport to another system.
+ * <p>
+ * Bundles generated by this class can be later read in from a file URI using
+ * the bundle transport, or from an application controlled buffer by the more
+ * generic {@link TransportBundleStream}.
+ * <p>
+ * Applications creating bundles need to call one or more <code>include</code>
+ * calls to reflect which objects should be available as refs in the bundle for
+ * the other side to fetch. At least one include is required to create a valid
+ * bundle file, and duplicate names are not permitted.
+ * <p>
+ * Optional <code>assume</code> calls can be made to declare commits which the
+ * recipient must have in order to fetch from the bundle file. Objects reachable
+ * from these assumed commits can be used as delta bases in order to reduce the
+ * overall bundle size.
+ */
+public class BundleWriter {
+ private final PackWriter packWriter;
+
+ private final Map<String, ObjectId> include;
+
+ private final Set<RevCommit> assume;
+
+ /**
+ * Create a writer for a bundle.
+ *
+ * @param repo
+ * repository where objects are stored.
+ * @param monitor
+ * operations progress monitor.
+ */
+ public BundleWriter(final Repository repo, final ProgressMonitor monitor) {
+ packWriter = new PackWriter(repo, monitor);
+ include = new TreeMap<String, ObjectId>();
+ assume = new HashSet<RevCommit>();
+ }
+
+ /**
+ * Include an object (and everything reachable from it) in the bundle.
+ *
+ * @param name
+ * name the recipient can discover this object as from the
+ * bundle's list of advertised refs . The name must be a valid
+ * ref format and must not have already been included in this
+ * bundle writer.
+ * @param id
+ * object to pack. Multiple refs may point to the same object.
+ */
+ public void include(final String name, final AnyObjectId id) {
+ if (!Repository.isValidRefName(name))
+ throw new IllegalArgumentException("Invalid ref name: " + name);
+ if (include.containsKey(name))
+ throw new IllegalStateException("Duplicate ref: " + name);
+ include.put(name, id.toObjectId());
+ }
+
+ /**
+ * Include a single ref (a name/object pair) in the bundle.
+ * <p>
+ * This is a utility function for:
+ * <code>include(r.getName(), r.getObjectId())</code>.
+ *
+ * @param r
+ * the ref to include.
+ */
+ public void include(final Ref r) {
+ include(r.getName(), r.getObjectId());
+ }
+
+ /**
+ * Assume a commit is available on the recipient's side.
+ * <p>
+ * In order to fetch from a bundle the recipient must have any assumed
+ * commit. Each assumed commit is explicitly recorded in the bundle header
+ * to permit the recipient to validate it has these objects.
+ *
+ * @param c
+ * the commit to assume being available. This commit should be
+ * parsed and not disposed in order to maximize the amount of
+ * debugging information available in the bundle stream.
+ */
+ public void assume(final RevCommit c) {
+ if (c != null)
+ assume.add(c);
+ }
+
+ /**
+ * Generate and write the bundle to the output stream.
+ * <p>
+ * This method can only be called once per BundleWriter instance.
+ *
+ * @param os
+ * the stream the bundle is written to. If the stream is not
+ * buffered it will be buffered by the writer. Caller is
+ * responsible for closing the stream.
+ * @throws IOException
+ * an error occurred reading a local object's data to include in
+ * the bundle, or writing compressed object data to the output
+ * stream.
+ */
+ public void writeBundle(OutputStream os) throws IOException {
+ if (!(os instanceof BufferedOutputStream))
+ os = new BufferedOutputStream(os);
+
+ final HashSet<ObjectId> inc = new HashSet<ObjectId>();
+ final HashSet<ObjectId> exc = new HashSet<ObjectId>();
+ inc.addAll(include.values());
+ for (final RevCommit r : assume)
+ exc.add(r.getId());
+ packWriter.setThin(exc.size() > 0);
+ packWriter.preparePack(inc, exc);
+
+ final Writer w = new OutputStreamWriter(os, Constants.CHARSET);
+ w.write(TransportBundle.V2_BUNDLE_SIGNATURE);
+ w.write('\n');
+
+ final char[] tmp = new char[Constants.OBJECT_ID_LENGTH * 2];
+ for (final RevCommit a : assume) {
+ w.write('-');
+ a.copyTo(tmp, w);
+ if (a.getRawBuffer() != null) {
+ w.write(' ');
+ w.write(a.getShortMessage());
+ }
+ w.write('\n');
+ }
+ for (final Map.Entry<String, ObjectId> e : include.entrySet()) {
+ e.getValue().copyTo(tmp, w);
+ w.write(' ');
+ w.write(e.getKey());
+ w.write('\n');
+ }
+
+ w.write('\n');
+ w.flush();
+ packWriter.writePack(os);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
new file mode 100644
index 0000000000..fbed0693ca
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.eclipse.jgit.lib.Ref;
+
+/**
+ * Represent connection for operation on a remote repository.
+ * <p>
+ * Currently all operations on remote repository (fetch and push) provide
+ * information about remote refs. Every connection is able to be closed and
+ * should be closed - this is a connection client responsibility.
+ *
+ * @see Transport
+ */
+public interface Connection {
+
+ /**
+ * Get the complete map of refs advertised as available for fetching or
+ * pushing.
+ *
+ * @return available/advertised refs: map of refname to ref. Never null. Not
+ * modifiable. The collection can be empty if the remote side has no
+ * refs (it is an empty/newly created repository).
+ */
+ public Map<String, Ref> getRefsMap();
+
+ /**
+ * Get the complete list of refs advertised as available for fetching or
+ * pushing.
+ * <p>
+ * The returned refs may appear in any order. If the caller needs these to
+ * be sorted, they should be copied into a new array or List and then sorted
+ * by the caller as necessary.
+ *
+ * @return available/advertised refs. Never null. Not modifiable. The
+ * collection can be empty if the remote side has no refs (it is an
+ * empty/newly created repository).
+ */
+ public Collection<Ref> getRefs();
+
+ /**
+ * Get a single advertised ref by name.
+ * <p>
+ * The name supplied should be valid ref name. To get a peeled value for a
+ * ref (aka <code>refs/tags/v1.0^{}</code>) use the base name (without
+ * the <code>^{}</code> suffix) and look at the peeled object id.
+ *
+ * @param name
+ * name of the ref to obtain.
+ * @return the requested ref; null if the remote did not advertise this ref.
+ */
+ public Ref getRef(final String name);
+
+ /**
+ * Close any resources used by this connection.
+ * <p>
+ * If the remote repository is contacted by a network socket this method
+ * must close that network socket, disconnecting the two peers. If the
+ * remote repository is actually local (same system) this method must close
+ * any open file handles used to read the "remote" repository.
+ */
+ public void close();
+
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
new file mode 100644
index 0000000000..c6f69043be
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+
+/** Basic daemon for the anonymous <code>git://</code> transport protocol. */
+public class Daemon {
+ /** 9418: IANA assigned port number for Git. */
+ public static final int DEFAULT_PORT = 9418;
+
+ private static final int BACKLOG = 5;
+
+ private InetSocketAddress myAddress;
+
+ private final DaemonService[] services;
+
+ private final ThreadGroup processors;
+
+ private volatile boolean exportAll;
+
+ private Map<String, Repository> exports;
+
+ private Collection<File> exportBase;
+
+ private boolean run;
+
+ private Thread acceptThread;
+
+ private int timeout;
+
+ /** Configure a daemon to listen on any available network port. */
+ public Daemon() {
+ this(null);
+ }
+
+ /**
+ * Configure a new daemon for the specified network address.
+ *
+ * @param addr
+ * address to listen for connections on. If null, any available
+ * port will be chosen on all network interfaces.
+ */
+ public Daemon(final InetSocketAddress addr) {
+ myAddress = addr;
+ exports = new ConcurrentHashMap<String, Repository>();
+ exportBase = new CopyOnWriteArrayList<File>();
+ processors = new ThreadGroup("Git-Daemon");
+
+ services = new DaemonService[] {
+ new DaemonService("upload-pack", "uploadpack") {
+ {
+ setEnabled(true);
+ }
+
+ @Override
+ protected void execute(final DaemonClient dc,
+ final Repository db) throws IOException {
+ final UploadPack rp = new UploadPack(db);
+ final InputStream in = dc.getInputStream();
+ rp.setTimeout(Daemon.this.getTimeout());
+ rp.upload(in, dc.getOutputStream(), null);
+ }
+ }, new DaemonService("receive-pack", "receivepack") {
+ {
+ setEnabled(false);
+ }
+
+ @Override
+ protected void execute(final DaemonClient dc,
+ final Repository db) throws IOException {
+ final InetAddress peer = dc.getRemoteAddress();
+ String host = peer.getCanonicalHostName();
+ if (host == null)
+ host = peer.getHostAddress();
+ final ReceivePack rp = new ReceivePack(db);
+ final InputStream in = dc.getInputStream();
+ final String name = "anonymous";
+ final String email = name + "@" + host;
+ rp.setRefLogIdent(new PersonIdent(name, email));
+ rp.setTimeout(Daemon.this.getTimeout());
+ rp.receive(in, dc.getOutputStream(), null);
+ }
+ } };
+ }
+
+ /** @return the address connections are received on. */
+ public synchronized InetSocketAddress getAddress() {
+ return myAddress;
+ }
+
+ /**
+ * Lookup a supported service so it can be reconfigured.
+ *
+ * @param name
+ * name of the service; e.g. "receive-pack"/"git-receive-pack" or
+ * "upload-pack"/"git-upload-pack".
+ * @return the service; null if this daemon implementation doesn't support
+ * the requested service type.
+ */
+ public synchronized DaemonService getService(String name) {
+ if (!name.startsWith("git-"))
+ name = "git-" + name;
+ for (final DaemonService s : services) {
+ if (s.getCommandName().equals(name))
+ return s;
+ }
+ return null;
+ }
+
+ /**
+ * @return false if <code>git-daemon-export-ok</code> is required to export
+ * a repository; true if <code>git-daemon-export-ok</code> is
+ * ignored.
+ * @see #setExportAll(boolean)
+ */
+ public boolean isExportAll() {
+ return exportAll;
+ }
+
+ /**
+ * Set whether or not to export all repositories.
+ * <p>
+ * If false (the default), repositories must have a
+ * <code>git-daemon-export-ok</code> file to be accessed through this
+ * daemon.
+ * <p>
+ * If true, all repositories are available through the daemon, whether or
+ * not <code>git-daemon-export-ok</code> exists.
+ *
+ * @param export
+ */
+ public void setExportAll(final boolean export) {
+ exportAll = export;
+ }
+
+ /**
+ * Add a single repository to the set that is exported by this daemon.
+ * <p>
+ * The existence (or lack-thereof) of <code>git-daemon-export-ok</code> is
+ * ignored by this method. The repository is always published.
+ *
+ * @param name
+ * name the repository will be published under.
+ * @param db
+ * the repository instance.
+ */
+ public void exportRepository(String name, final Repository db) {
+ if (!name.endsWith(".git"))
+ name = name + ".git";
+ exports.put(name, db);
+ RepositoryCache.register(db);
+ }
+
+ /**
+ * Recursively export all Git repositories within a directory.
+ *
+ * @param dir
+ * the directory to export. This directory must not itself be a
+ * git repository, but any directory below it which has a file
+ * named <code>git-daemon-export-ok</code> will be published.
+ */
+ public void exportDirectory(final File dir) {
+ exportBase.add(dir);
+ }
+
+ /** @return timeout (in seconds) before aborting an IO operation. */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Set the timeout before willing to abort an IO call.
+ *
+ * @param seconds
+ * number of seconds to wait (with no data transfer occurring)
+ * before aborting an IO read or write operation with the
+ * connected client.
+ */
+ public void setTimeout(final int seconds) {
+ timeout = seconds;
+ }
+
+ /**
+ * Start this daemon on a background thread.
+ *
+ * @throws IOException
+ * the server socket could not be opened.
+ * @throws IllegalStateException
+ * the daemon is already running.
+ */
+ public synchronized void start() throws IOException {
+ if (acceptThread != null)
+ throw new IllegalStateException("Daemon already running");
+
+ final ServerSocket listenSock = new ServerSocket(
+ myAddress != null ? myAddress.getPort() : 0, BACKLOG,
+ myAddress != null ? myAddress.getAddress() : null);
+ myAddress = (InetSocketAddress) listenSock.getLocalSocketAddress();
+
+ run = true;
+ acceptThread = new Thread(processors, "Git-Daemon-Accept") {
+ public void run() {
+ while (isRunning()) {
+ try {
+ startClient(listenSock.accept());
+ } catch (InterruptedIOException e) {
+ // Test again to see if we should keep accepting.
+ } catch (IOException e) {
+ break;
+ }
+ }
+
+ try {
+ listenSock.close();
+ } catch (IOException err) {
+ //
+ } finally {
+ synchronized (Daemon.this) {
+ acceptThread = null;
+ }
+ }
+ }
+ };
+ acceptThread.start();
+ }
+
+ /** @return true if this daemon is receiving connections. */
+ public synchronized boolean isRunning() {
+ return run;
+ }
+
+ /** Stop this daemon. */
+ public synchronized void stop() {
+ if (acceptThread != null) {
+ run = false;
+ acceptThread.interrupt();
+ }
+ }
+
+ private void startClient(final Socket s) {
+ final DaemonClient dc = new DaemonClient(this);
+
+ final SocketAddress peer = s.getRemoteSocketAddress();
+ if (peer instanceof InetSocketAddress)
+ dc.setRemoteAddress(((InetSocketAddress) peer).getAddress());
+
+ new Thread(processors, "Git-Daemon-Client " + peer.toString()) {
+ public void run() {
+ try {
+ dc.execute(s);
+ } catch (IOException e) {
+ // Ignore unexpected IO exceptions from clients
+ e.printStackTrace();
+ } finally {
+ try {
+ s.getInputStream().close();
+ } catch (IOException e) {
+ // Ignore close exceptions
+ }
+ try {
+ s.getOutputStream().close();
+ } catch (IOException e) {
+ // Ignore close exceptions
+ }
+ }
+ }
+ }.start();
+ }
+
+ synchronized DaemonService matchService(final String cmd) {
+ for (final DaemonService d : services) {
+ if (d.handles(cmd))
+ return d;
+ }
+ return null;
+ }
+
+ Repository openRepository(String name) {
+ // Assume any attempt to use \ was by a Windows client
+ // and correct to the more typical / used in Git URIs.
+ //
+ name = name.replace('\\', '/');
+
+ // git://thishost/path should always be name="/path" here
+ //
+ if (!name.startsWith("/"))
+ return null;
+
+ // Forbid Windows UNC paths as they might escape the base
+ //
+ if (name.startsWith("//"))
+ return null;
+
+ // Forbid funny paths which contain an up-reference, they
+ // might be trying to escape and read /../etc/password.
+ //
+ if (name.contains("/../"))
+ return null;
+ name = name.substring(1);
+
+ Repository db;
+ db = exports.get(name.endsWith(".git") ? name : name + ".git");
+ if (db != null) {
+ db.incrementOpen();
+ return db;
+ }
+
+ for (final File baseDir : exportBase) {
+ final File gitdir = FileKey.resolve(new File(baseDir, name));
+ if (gitdir != null && canExport(gitdir))
+ return openRepository(gitdir);
+ }
+ return null;
+ }
+
+ private static Repository openRepository(final File gitdir) {
+ try {
+ return RepositoryCache.open(FileKey.exact(gitdir));
+ } catch (IOException err) {
+ // null signals it "wasn't found", which is all that is suitable
+ // for the remote client to know.
+ return null;
+ }
+ }
+
+ private boolean canExport(final File d) {
+ if (isExportAll()) {
+ return true;
+ }
+ return new File(d, "git-daemon-export-ok").exists();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonClient.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonClient.java
new file mode 100644
index 0000000000..0b8de03439
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonClient.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/** Active network client of {@link Daemon}. */
+public class DaemonClient {
+ private final Daemon daemon;
+
+ private InetAddress peer;
+
+ private InputStream rawIn;
+
+ private OutputStream rawOut;
+
+ DaemonClient(final Daemon d) {
+ daemon = d;
+ }
+
+ void setRemoteAddress(final InetAddress ia) {
+ peer = ia;
+ }
+
+ /** @return the daemon which spawned this client. */
+ public Daemon getDaemon() {
+ return daemon;
+ }
+
+ /** @return Internet address of the remote client. */
+ public InetAddress getRemoteAddress() {
+ return peer;
+ }
+
+ /** @return input stream to read from the connected client. */
+ public InputStream getInputStream() {
+ return rawIn;
+ }
+
+ /** @return output stream to send data to the connected client. */
+ public OutputStream getOutputStream() {
+ return rawOut;
+ }
+
+ void execute(final Socket sock)
+ throws IOException {
+ rawIn = new BufferedInputStream(sock.getInputStream());
+ rawOut = new BufferedOutputStream(sock.getOutputStream());
+
+ if (0 < daemon.getTimeout())
+ sock.setSoTimeout(daemon.getTimeout() * 1000);
+ String cmd = new PacketLineIn(rawIn).readStringRaw();
+ final int nul = cmd.indexOf('\0');
+ if (nul >= 0) {
+ // Newer clients hide a "host" header behind this byte.
+ // Currently we don't use it for anything, so we ignore
+ // this portion of the command.
+ //
+ cmd = cmd.substring(0, nul);
+ }
+
+ final DaemonService srv = getDaemon().matchService(cmd);
+ if (srv == null)
+ return;
+ sock.setSoTimeout(0);
+ srv.execute(this, cmd);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java
new file mode 100644
index 0000000000..2b94957983
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Config.SectionParser;
+
+/** A service exposed by {@link Daemon} over anonymous <code>git://</code>. */
+public abstract class DaemonService {
+ private final String command;
+
+ private final SectionParser<ServiceConfig> configKey;
+
+ private boolean enabled;
+
+ private boolean overridable;
+
+ DaemonService(final String cmdName, final String cfgName) {
+ command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName;
+ configKey = new SectionParser<ServiceConfig>() {
+ public ServiceConfig parse(final Config cfg) {
+ return new ServiceConfig(DaemonService.this, cfg, cfgName);
+ }
+ };
+ overridable = true;
+ }
+
+ private static class ServiceConfig {
+ final boolean enabled;
+
+ ServiceConfig(final DaemonService service, final Config cfg,
+ final String name) {
+ enabled = cfg.getBoolean("daemon", name, service.isEnabled());
+ }
+ }
+
+ /** @return is this service enabled for invocation? */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * @param on
+ * true to allow this service to be used; false to deny it.
+ */
+ public void setEnabled(final boolean on) {
+ enabled = on;
+ }
+
+ /** @return can this service be configured in the repository config file? */
+ public boolean isOverridable() {
+ return overridable;
+ }
+
+ /**
+ * @param on
+ * true to permit repositories to override this service's enabled
+ * state with the <code>daemon.servicename</code> config setting.
+ */
+ public void setOverridable(final boolean on) {
+ overridable = on;
+ }
+
+ /** @return name of the command requested by clients. */
+ public String getCommandName() {
+ return command;
+ }
+
+ /**
+ * Determine if this service can handle the requested command.
+ *
+ * @param commandLine
+ * input line from the client.
+ * @return true if this command can accept the given command line.
+ */
+ public boolean handles(final String commandLine) {
+ return command.length() + 1 < commandLine.length()
+ && commandLine.charAt(command.length()) == ' '
+ && commandLine.startsWith(command);
+ }
+
+ void execute(final DaemonClient client, final String commandLine)
+ throws IOException {
+ final String name = commandLine.substring(command.length() + 1);
+ final Repository db = client.getDaemon().openRepository(name);
+ if (db == null)
+ return;
+ try {
+ if (isEnabledFor(db))
+ execute(client, db);
+ } finally {
+ db.close();
+ }
+ }
+
+ private boolean isEnabledFor(final Repository db) {
+ if (isOverridable())
+ return db.getConfig().get(configKey).enabled;
+ return isEnabled();
+ }
+
+ abstract void execute(DaemonClient client, Repository db)
+ throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java
new file mode 100644
index 0000000000..6ff3d4b2f3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.awt.Container;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.UIKeyboardInteractive;
+import com.jcraft.jsch.UserInfo;
+
+/**
+ * Loads known hosts and private keys from <code>$HOME/.ssh</code>.
+ * <p>
+ * This is the default implementation used by JGit and provides most of the
+ * compatibility necessary to match OpenSSH, a popular implementation of SSH
+ * used by C Git.
+ * <p>
+ * If user interactivity is required by SSH (e.g. to obtain a password) AWT is
+ * used to display a password input field to the end-user.
+ */
+class DefaultSshSessionFactory extends SshConfigSessionFactory {
+ protected void configure(final OpenSshConfig.Host hc, final Session session) {
+ if (!hc.isBatchMode())
+ session.setUserInfo(new AWT_UserInfo());
+ }
+
+ private static class AWT_UserInfo implements UserInfo,
+ UIKeyboardInteractive {
+ private String passwd;
+
+ private String passphrase;
+
+ public void showMessage(final String msg) {
+ JOptionPane.showMessageDialog(null, msg);
+ }
+
+ public boolean promptYesNo(final String msg) {
+ return JOptionPane.showConfirmDialog(null, msg, "Warning",
+ JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
+ }
+
+ public boolean promptPassword(final String msg) {
+ passwd = null;
+ final JPasswordField passwordField = new JPasswordField(20);
+ final int result = JOptionPane.showConfirmDialog(null,
+ new Object[] { passwordField }, msg,
+ JOptionPane.OK_CANCEL_OPTION);
+ if (result == JOptionPane.OK_OPTION) {
+ passwd = new String(passwordField.getPassword());
+ return true;
+ }
+ return false;
+ }
+
+ public boolean promptPassphrase(final String msg) {
+ passphrase = null;
+ final JPasswordField passwordField = new JPasswordField(20);
+ final int result = JOptionPane.showConfirmDialog(null,
+ new Object[] { passwordField }, msg,
+ JOptionPane.OK_CANCEL_OPTION);
+ if (result == JOptionPane.OK_OPTION) {
+ passphrase = new String(passwordField.getPassword());
+ return true;
+ }
+ return false;
+ }
+
+ public String getPassword() {
+ return passwd;
+ }
+
+ public String getPassphrase() {
+ return passphrase;
+ }
+
+ public String[] promptKeyboardInteractive(final String destination,
+ final String name, final String instruction,
+ final String[] prompt, final boolean[] echo) {
+ final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1,
+ 1, 1, GridBagConstraints.NORTHWEST,
+ GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0);
+ final Container panel = new JPanel();
+ panel.setLayout(new GridBagLayout());
+
+ gbc.weightx = 1.0;
+ gbc.gridwidth = GridBagConstraints.REMAINDER;
+ gbc.gridx = 0;
+ panel.add(new JLabel(instruction), gbc);
+ gbc.gridy++;
+
+ gbc.gridwidth = GridBagConstraints.RELATIVE;
+
+ final JTextField[] texts = new JTextField[prompt.length];
+ for (int i = 0; i < prompt.length; i++) {
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.gridx = 0;
+ gbc.weightx = 1;
+ panel.add(new JLabel(prompt[i]), gbc);
+
+ gbc.gridx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.weighty = 1;
+ if (echo[i]) {
+ texts[i] = new JTextField(20);
+ } else {
+ texts[i] = new JPasswordField(20);
+ }
+ panel.add(texts[i], gbc);
+ gbc.gridy++;
+ }
+
+ if (JOptionPane.showConfirmDialog(null, panel, destination + ": "
+ + name, JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) {
+ String[] response = new String[prompt.length];
+ for (int i = 0; i < prompt.length; i++) {
+ response[i] = texts[i].getText();
+ }
+ return response;
+ }
+ return null; // cancel
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
new file mode 100644
index 0000000000..dea0f2dc17
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Mike Ralphson <mike@abacus.co.uk>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PackLock;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+
+/**
+ * Lists known refs from the remote and copies objects of selected refs.
+ * <p>
+ * A fetch connection typically connects to the <code>git-upload-pack</code>
+ * service running where the remote repository is stored. This provides a
+ * one-way object transfer service to copy objects from the remote repository
+ * into this local repository.
+ * <p>
+ * Instances of a FetchConnection must be created by a {@link Transport} that
+ * implements a specific object transfer protocol that both sides of the
+ * connection understand.
+ * <p>
+ * FetchConnection instances are not thread safe and may be accessed by only one
+ * thread at a time.
+ *
+ * @see Transport
+ */
+public interface FetchConnection extends Connection {
+ /**
+ * Fetch objects we don't have but that are reachable from advertised refs.
+ * <p>
+ * Only one call per connection is allowed. Subsequent calls will result in
+ * {@link TransportException}.
+ * </p>
+ * <p>
+ * Implementations are free to use network connections as necessary to
+ * efficiently (for both client and server) transfer objects from the remote
+ * repository into this repository. When possible implementations should
+ * avoid replacing/overwriting/duplicating an object already available in
+ * the local destination repository. Locally available objects and packs
+ * should always be preferred over remotely available objects and packs.
+ * {@link Transport#isFetchThin()} should be honored if applicable.
+ * </p>
+ *
+ * @param monitor
+ * progress monitor to inform the end-user about the amount of
+ * work completed, or to indicate cancellation. Implementations
+ * should poll the monitor at regular intervals to look for
+ * cancellation requests from the user.
+ * @param want
+ * one or more refs advertised by this connection that the caller
+ * wants to store locally.
+ * @param have
+ * additional objects known to exist in the destination
+ * repository, especially if they aren't yet reachable by the ref
+ * database. Connections should take this set as an addition to
+ * what is reachable through all Refs, not in replace of it.
+ * @throws TransportException
+ * objects could not be copied due to a network failure,
+ * protocol error, or error on remote side, or connection was
+ * already used for fetch.
+ */
+ public void fetch(final ProgressMonitor monitor,
+ final Collection<Ref> want, final Set<ObjectId> have)
+ throws TransportException;
+
+ /**
+ * Did the last {@link #fetch(ProgressMonitor, Collection, Set)} get tags?
+ * <p>
+ * Some Git aware transports are able to implicitly grab an annotated tag if
+ * {@link TagOpt#AUTO_FOLLOW} or {@link TagOpt#FETCH_TAGS} was selected and
+ * the object the tag peels to (references) was transferred as part of the
+ * last {@link #fetch(ProgressMonitor, Collection, Set)} call. If it is
+ * possible for such tags to have been included in the transfer this method
+ * returns true, allowing the caller to attempt tag discovery.
+ * <p>
+ * By returning only true/false (and not the actual list of tags obtained)
+ * the transport itself does not need to be aware of whether or not tags
+ * were included in the transfer.
+ *
+ * @return true if the last fetch call implicitly included tag objects;
+ * false if tags were not implicitly obtained.
+ */
+ public boolean didFetchIncludeTags();
+
+ /**
+ * Did the last {@link #fetch(ProgressMonitor, Collection, Set)} validate
+ * graph?
+ * <p>
+ * Some transports walk the object graph on the client side, with the client
+ * looking for what objects it is missing and requesting them individually
+ * from the remote peer. By virtue of completing the fetch call the client
+ * implicitly tested the object connectivity, as every object in the graph
+ * was either already local or was requested successfully from the peer. In
+ * such transports this method returns true.
+ * <p>
+ * Some transports assume the remote peer knows the Git object graph and is
+ * able to supply a fully connected graph to the client (although it may
+ * only be transferring the parts the client does not yet have). Its faster
+ * to assume such remote peers are well behaved and send the correct
+ * response to the client. In such transports this method returns false.
+ *
+ * @return true if the last fetch had to perform a connectivity check on the
+ * client side in order to succeed; false if the last fetch assumed
+ * the remote peer supplied a complete graph.
+ */
+ public boolean didFetchTestConnectivity();
+
+ /**
+ * Set the lock message used when holding a pack out of garbage collection.
+ * <p>
+ * Callers that set a lock message <b>must</b> ensure they call
+ * {@link #getPackLocks()} after
+ * {@link #fetch(ProgressMonitor, Collection, Set)}, even if an exception
+ * was thrown, and release the locks that are held.
+ *
+ * @param message message to use when holding a pack in place.
+ */
+ public void setPackLockMessage(String message);
+
+ /**
+ * All locks created by the last
+ * {@link #fetch(ProgressMonitor, Collection, Set)} call.
+ *
+ * @return collection (possibly empty) of locks created by the last call to
+ * fetch. The caller must release these after refs are updated in
+ * order to safely permit garbage collection.
+ */
+ public Collection<PackLock> getPackLocks();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchHeadRecord.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchHeadRecord.java
new file mode 100644
index 0000000000..e2ec71030c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchHeadRecord.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_REMOTES;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+class FetchHeadRecord {
+ ObjectId newValue;
+
+ boolean notForMerge;
+
+ String sourceName;
+
+ URIish sourceURI;
+
+ void write(final Writer pw) throws IOException {
+ final String type;
+ final String name;
+ if (sourceName.startsWith(R_HEADS)) {
+ type = "branch";
+ name = sourceName.substring(R_HEADS.length());
+ } else if (sourceName.startsWith(R_TAGS)) {
+ type = "tag";
+ name = sourceName.substring(R_TAGS.length());
+ } else if (sourceName.startsWith(R_REMOTES)) {
+ type = "remote branch";
+ name = sourceName.substring(R_REMOTES.length());
+ } else {
+ type = "";
+ name = sourceName;
+ }
+
+ pw.write(newValue.name());
+ pw.write('\t');
+ if (notForMerge)
+ pw.write("not-for-merge");
+ pw.write('\t');
+ pw.write(type);
+ pw.write(" '");
+ pw.write(name);
+ pw.write("' of ");
+ pw.write(sourceURI.toString());
+ pw.write("\n");
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
new file mode 100644
index 0000000000..65a5b1769e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.LockFile;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PackLock;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+class FetchProcess {
+ /** Transport we will fetch over. */
+ private final Transport transport;
+
+ /** List of things we want to fetch from the remote repository. */
+ private final Collection<RefSpec> toFetch;
+
+ /** Set of refs we will actually wind up asking to obtain. */
+ private final HashMap<ObjectId, Ref> askFor = new HashMap<ObjectId, Ref>();
+
+ /** Objects we know we have locally. */
+ private final HashSet<ObjectId> have = new HashSet<ObjectId>();
+
+ /** Updates to local tracking branches (if any). */
+ private final ArrayList<TrackingRefUpdate> localUpdates = new ArrayList<TrackingRefUpdate>();
+
+ /** Records to be recorded into FETCH_HEAD. */
+ private final ArrayList<FetchHeadRecord> fetchHeadUpdates = new ArrayList<FetchHeadRecord>();
+
+ private final ArrayList<PackLock> packLocks = new ArrayList<PackLock>();
+
+ private FetchConnection conn;
+
+ FetchProcess(final Transport t, final Collection<RefSpec> f) {
+ transport = t;
+ toFetch = f;
+ }
+
+ void execute(final ProgressMonitor monitor, final FetchResult result)
+ throws NotSupportedException, TransportException {
+ askFor.clear();
+ localUpdates.clear();
+ fetchHeadUpdates.clear();
+ packLocks.clear();
+
+ try {
+ executeImp(monitor, result);
+ } finally {
+ for (final PackLock lock : packLocks)
+ lock.unlock();
+ }
+ }
+
+ private void executeImp(final ProgressMonitor monitor,
+ final FetchResult result) throws NotSupportedException,
+ TransportException {
+ conn = transport.openFetch();
+ try {
+ result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap());
+ final Set<Ref> matched = new HashSet<Ref>();
+ for (final RefSpec spec : toFetch) {
+ if (spec.getSource() == null)
+ throw new TransportException(
+ "Source ref not specified for refspec: " + spec);
+
+ if (spec.isWildcard())
+ expandWildcard(spec, matched);
+ else
+ expandSingle(spec, matched);
+ }
+
+ Collection<Ref> additionalTags = Collections.<Ref> emptyList();
+ final TagOpt tagopt = transport.getTagOpt();
+ if (tagopt == TagOpt.AUTO_FOLLOW)
+ additionalTags = expandAutoFollowTags();
+ else if (tagopt == TagOpt.FETCH_TAGS)
+ expandFetchTags();
+
+ final boolean includedTags;
+ if (!askFor.isEmpty() && !askForIsComplete()) {
+ fetchObjects(monitor);
+ includedTags = conn.didFetchIncludeTags();
+
+ // Connection was used for object transfer. If we
+ // do another fetch we must open a new connection.
+ //
+ closeConnection();
+ } else {
+ includedTags = false;
+ }
+
+ if (tagopt == TagOpt.AUTO_FOLLOW && !additionalTags.isEmpty()) {
+ // There are more tags that we want to follow, but
+ // not all were asked for on the initial request.
+ //
+ have.addAll(askFor.keySet());
+ askFor.clear();
+ for (final Ref r : additionalTags) {
+ final ObjectId id = r.getPeeledObjectId();
+ if (id == null || transport.local.hasObject(id))
+ wantTag(r);
+ }
+
+ if (!askFor.isEmpty() && (!includedTags || !askForIsComplete())) {
+ reopenConnection();
+ if (!askFor.isEmpty())
+ fetchObjects(monitor);
+ }
+ }
+ } finally {
+ closeConnection();
+ }
+
+ final RevWalk walk = new RevWalk(transport.local);
+ if (transport.isRemoveDeletedRefs())
+ deleteStaleTrackingRefs(result, walk);
+ for (TrackingRefUpdate u : localUpdates) {
+ try {
+ u.update(walk);
+ result.add(u);
+ } catch (IOException err) {
+ throw new TransportException("Failure updating tracking ref "
+ + u.getLocalName() + ": " + err.getMessage(), err);
+ }
+ }
+
+ if (!fetchHeadUpdates.isEmpty()) {
+ try {
+ updateFETCH_HEAD(result);
+ } catch (IOException err) {
+ throw new TransportException("Failure updating FETCH_HEAD: "
+ + err.getMessage(), err);
+ }
+ }
+ }
+
+ private void fetchObjects(final ProgressMonitor monitor)
+ throws TransportException {
+ try {
+ conn.setPackLockMessage("jgit fetch " + transport.uri);
+ conn.fetch(monitor, askFor.values(), have);
+ } finally {
+ packLocks.addAll(conn.getPackLocks());
+ }
+ if (transport.isCheckFetchedObjects()
+ && !conn.didFetchTestConnectivity() && !askForIsComplete())
+ throw new TransportException(transport.getURI(),
+ "peer did not supply a complete object graph");
+ }
+
+ private void closeConnection() {
+ if (conn != null) {
+ conn.close();
+ conn = null;
+ }
+ }
+
+ private void reopenConnection() throws NotSupportedException,
+ TransportException {
+ if (conn != null)
+ return;
+
+ conn = transport.openFetch();
+
+ // Since we opened a new connection we cannot be certain
+ // that the system we connected to has the same exact set
+ // of objects available (think round-robin DNS and mirrors
+ // that aren't updated at the same time).
+ //
+ // We rebuild our askFor list using only the refs that the
+ // new connection has offered to us.
+ //
+ final HashMap<ObjectId, Ref> avail = new HashMap<ObjectId, Ref>();
+ for (final Ref r : conn.getRefs())
+ avail.put(r.getObjectId(), r);
+
+ final Collection<Ref> wants = new ArrayList<Ref>(askFor.values());
+ askFor.clear();
+ for (final Ref want : wants) {
+ final Ref newRef = avail.get(want.getObjectId());
+ if (newRef != null) {
+ askFor.put(newRef.getObjectId(), newRef);
+ } else {
+ removeFetchHeadRecord(want.getObjectId());
+ removeTrackingRefUpdate(want.getObjectId());
+ }
+ }
+ }
+
+ private void removeTrackingRefUpdate(final ObjectId want) {
+ final Iterator<TrackingRefUpdate> i = localUpdates.iterator();
+ while (i.hasNext()) {
+ final TrackingRefUpdate u = i.next();
+ if (u.getNewObjectId().equals(want))
+ i.remove();
+ }
+ }
+
+ private void removeFetchHeadRecord(final ObjectId want) {
+ final Iterator<FetchHeadRecord> i = fetchHeadUpdates.iterator();
+ while (i.hasNext()) {
+ final FetchHeadRecord fh = i.next();
+ if (fh.newValue.equals(want))
+ i.remove();
+ }
+ }
+
+ private void updateFETCH_HEAD(final FetchResult result) throws IOException {
+ final LockFile lock = new LockFile(new File(transport.local
+ .getDirectory(), "FETCH_HEAD"));
+ try {
+ if (lock.lock()) {
+ final Writer w = new OutputStreamWriter(lock.getOutputStream());
+ try {
+ for (final FetchHeadRecord h : fetchHeadUpdates) {
+ h.write(w);
+ result.add(h);
+ }
+ } finally {
+ w.close();
+ }
+ lock.commit();
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private boolean askForIsComplete() throws TransportException {
+ try {
+ final ObjectWalk ow = new ObjectWalk(transport.local);
+ for (final ObjectId want : askFor.keySet())
+ ow.markStart(ow.parseAny(want));
+ for (final Ref ref : transport.local.getAllRefs().values())
+ ow.markUninteresting(ow.parseAny(ref.getObjectId()));
+ ow.checkConnectivity();
+ return true;
+ } catch (MissingObjectException e) {
+ return false;
+ } catch (IOException e) {
+ throw new TransportException("Unable to check connectivity.", e);
+ }
+ }
+
+ private void expandWildcard(final RefSpec spec, final Set<Ref> matched)
+ throws TransportException {
+ for (final Ref src : conn.getRefs()) {
+ if (spec.matchSource(src) && matched.add(src))
+ want(src, spec.expandFromSource(src));
+ }
+ }
+
+ private void expandSingle(final RefSpec spec, final Set<Ref> matched)
+ throws TransportException {
+ final Ref src = conn.getRef(spec.getSource());
+ if (src == null) {
+ throw new TransportException("Remote does not have "
+ + spec.getSource() + " available for fetch.");
+ }
+ if (matched.add(src))
+ want(src, spec);
+ }
+
+ private Collection<Ref> expandAutoFollowTags() throws TransportException {
+ final Collection<Ref> additionalTags = new ArrayList<Ref>();
+ final Map<String, Ref> haveRefs = transport.local.getAllRefs();
+ for (final Ref r : conn.getRefs()) {
+ if (!isTag(r))
+ continue;
+ if (r.getPeeledObjectId() == null) {
+ additionalTags.add(r);
+ continue;
+ }
+
+ final Ref local = haveRefs.get(r.getName());
+ if (local != null) {
+ if (!r.getObjectId().equals(local.getObjectId()))
+ wantTag(r);
+ } else if (askFor.containsKey(r.getPeeledObjectId())
+ || transport.local.hasObject(r.getPeeledObjectId()))
+ wantTag(r);
+ else
+ additionalTags.add(r);
+ }
+ return additionalTags;
+ }
+
+ private void expandFetchTags() throws TransportException {
+ final Map<String, Ref> haveRefs = transport.local.getAllRefs();
+ for (final Ref r : conn.getRefs()) {
+ if (!isTag(r))
+ continue;
+ final Ref local = haveRefs.get(r.getName());
+ if (local == null || !r.getObjectId().equals(local.getObjectId()))
+ wantTag(r);
+ }
+ }
+
+ private void wantTag(final Ref r) throws TransportException {
+ want(r, new RefSpec().setSource(r.getName())
+ .setDestination(r.getName()));
+ }
+
+ private void want(final Ref src, final RefSpec spec)
+ throws TransportException {
+ final ObjectId newId = src.getObjectId();
+ if (spec.getDestination() != null) {
+ try {
+ final TrackingRefUpdate tru = createUpdate(spec, newId);
+ if (newId.equals(tru.getOldObjectId()))
+ return;
+ localUpdates.add(tru);
+ } catch (IOException err) {
+ // Bad symbolic ref? That is the most likely cause.
+ //
+ throw new TransportException("Cannot resolve"
+ + " local tracking ref " + spec.getDestination()
+ + " for updating.", err);
+ }
+ }
+
+ askFor.put(newId, src);
+
+ final FetchHeadRecord fhr = new FetchHeadRecord();
+ fhr.newValue = newId;
+ fhr.notForMerge = spec.getDestination() != null;
+ fhr.sourceName = src.getName();
+ fhr.sourceURI = transport.getURI();
+ fetchHeadUpdates.add(fhr);
+ }
+
+ private TrackingRefUpdate createUpdate(final RefSpec spec,
+ final ObjectId newId) throws IOException {
+ return new TrackingRefUpdate(transport.local, spec, newId, "fetch");
+ }
+
+ private void deleteStaleTrackingRefs(final FetchResult result,
+ final RevWalk walk) throws TransportException {
+ final Repository db = transport.local;
+ for (final Ref ref : db.getAllRefs().values()) {
+ final String refname = ref.getName();
+ for (final RefSpec spec : toFetch) {
+ if (spec.matchDestination(refname)) {
+ final RefSpec s = spec.expandFromDestination(refname);
+ if (result.getAdvertisedRef(s.getSource()) == null) {
+ deleteTrackingRef(result, db, walk, s, ref);
+ }
+ }
+ }
+ }
+ }
+
+ private void deleteTrackingRef(final FetchResult result,
+ final Repository db, final RevWalk walk, final RefSpec spec,
+ final Ref localRef) throws TransportException {
+ final String name = localRef.getName();
+ try {
+ final TrackingRefUpdate u = new TrackingRefUpdate(db, name, spec
+ .getSource(), true, ObjectId.zeroId(), "deleted");
+ result.add(u);
+ if (transport.isDryRun()){
+ return;
+ }
+ u.delete(walk);
+ switch (u.getResult()) {
+ case NEW:
+ case NO_CHANGE:
+ case FAST_FORWARD:
+ case FORCED:
+ break;
+ default:
+ throw new TransportException(transport.getURI(),
+ "Cannot delete stale tracking ref " + name + ": "
+ + u.getResult().name());
+ }
+ } catch (IOException e) {
+ throw new TransportException(transport.getURI(),
+ "Cannot delete stale tracking ref " + name, e);
+ }
+ }
+
+ private static boolean isTag(final Ref r) {
+ return isTag(r.getName());
+ }
+
+ private static boolean isTag(final String name) {
+ return name.startsWith(Constants.R_TAGS);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java
new file mode 100644
index 0000000000..df3a1937a3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Final status after a successful fetch from a remote repository.
+ *
+ * @see Transport#fetch(org.eclipse.jgit.lib.ProgressMonitor, Collection)
+ */
+public class FetchResult extends OperationResult {
+ private final List<FetchHeadRecord> forMerge;
+
+ FetchResult() {
+ forMerge = new ArrayList<FetchHeadRecord>();
+ }
+
+ void add(final FetchHeadRecord r) {
+ if (!r.notForMerge)
+ forMerge.add(r);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java
new file mode 100644
index 0000000000..3793a0abfb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * Copyright (C) 2009, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * The base class for transports that use HTTP as underlying protocol. This class
+ * allows customizing HTTP connection settings.
+ */
+public abstract class HttpTransport extends Transport {
+ /**
+ * Create a new transport instance.
+ *
+ * @param local
+ * the repository this instance will fetch into, or push out of.
+ * This must be the repository passed to
+ * {@link #open(Repository, URIish)}.
+ * @param uri
+ * the URI used to access the remote repository. This must be the
+ * URI passed to {@link #open(Repository, URIish)}.
+ */
+ protected HttpTransport(Repository local, URIish uri) {
+ super(local, uri);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java
new file mode 100644
index 0000000000..f368648471
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java
@@ -0,0 +1,1107 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.CRC32;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BinaryDelta;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.InflaterCache;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSubclassMap;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.PackIndexWriter;
+import org.eclipse.jgit.lib.PackLock;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.util.NB;
+
+/** Indexes Git pack files for local use. */
+public class IndexPack {
+ /** Progress message when reading raw data from the pack. */
+ public static final String PROGRESS_DOWNLOAD = "Receiving objects";
+
+ /** Progress message when computing names of delta compressed objects. */
+ public static final String PROGRESS_RESOLVE_DELTA = "Resolving deltas";
+
+ /**
+ * Size of the internal stream buffer.
+ * <p>
+ * If callers are going to be supplying IndexPack a BufferedInputStream they
+ * should use this buffer size as the size of the buffer for that
+ * BufferedInputStream, and any other its may be wrapping. This way the
+ * buffers will cascade efficiently and only the IndexPack buffer will be
+ * receiving the bulk of the data stream.
+ */
+ public static final int BUFFER_SIZE = 8192;
+
+ /**
+ * Create an index pack instance to load a new pack into a repository.
+ * <p>
+ * The received pack data and generated index will be saved to temporary
+ * files within the repository's <code>objects</code> directory. To use the
+ * data contained within them call {@link #renameAndOpenPack()} once the
+ * indexing is complete.
+ *
+ * @param db
+ * the repository that will receive the new pack.
+ * @param is
+ * stream to read the pack data from. If the stream is buffered
+ * use {@link #BUFFER_SIZE} as the buffer size for the stream.
+ * @return a new index pack instance.
+ * @throws IOException
+ * a temporary file could not be created.
+ */
+ public static IndexPack create(final Repository db, final InputStream is)
+ throws IOException {
+ final String suffix = ".pack";
+ final File objdir = db.getObjectsDirectory();
+ final File tmp = File.createTempFile("incoming_", suffix, objdir);
+ final String n = tmp.getName();
+ final File base;
+
+ base = new File(objdir, n.substring(0, n.length() - suffix.length()));
+ final IndexPack ip = new IndexPack(db, is, base);
+ ip.setIndexVersion(db.getConfig().getCore().getPackIndexVersion());
+ return ip;
+ }
+
+ private final Repository repo;
+
+ private Inflater inflater;
+
+ private final MessageDigest objectDigest;
+
+ private final MutableObjectId tempObjectId;
+
+ private InputStream in;
+
+ private byte[] buf;
+
+ private long bBase;
+
+ private int bOffset;
+
+ private int bAvail;
+
+ private ObjectChecker objCheck;
+
+ private boolean fixThin;
+
+ private boolean keepEmpty;
+
+ private int outputVersion;
+
+ private final File dstPack;
+
+ private final File dstIdx;
+
+ private long objectCount;
+
+ private PackedObjectInfo[] entries;
+
+ private int deltaCount;
+
+ private int entryCount;
+
+ private final CRC32 crc = new CRC32();
+
+ private ObjectIdSubclassMap<DeltaChain> baseById;
+
+ private LongMap<UnresolvedDelta> baseByPos;
+
+ private byte[] objectData;
+
+ private MessageDigest packDigest;
+
+ private RandomAccessFile packOut;
+
+ private byte[] packcsum;
+
+ /** If {@link #fixThin} this is the last byte of the original checksum. */
+ private long originalEOF;
+
+ private WindowCursor readCurs;
+
+ /**
+ * Create a new pack indexer utility.
+ *
+ * @param db
+ * @param src
+ * stream to read the pack data from. If the stream is buffered
+ * use {@link #BUFFER_SIZE} as the buffer size for the stream.
+ * @param dstBase
+ * @throws IOException
+ * the output packfile could not be created.
+ */
+ public IndexPack(final Repository db, final InputStream src,
+ final File dstBase) throws IOException {
+ repo = db;
+ in = src;
+ inflater = InflaterCache.get();
+ readCurs = new WindowCursor();
+ buf = new byte[BUFFER_SIZE];
+ objectData = new byte[BUFFER_SIZE];
+ objectDigest = Constants.newMessageDigest();
+ tempObjectId = new MutableObjectId();
+ packDigest = Constants.newMessageDigest();
+
+ if (dstBase != null) {
+ final File dir = dstBase.getParentFile();
+ final String nam = dstBase.getName();
+ dstPack = new File(dir, nam + ".pack");
+ dstIdx = new File(dir, nam + ".idx");
+ packOut = new RandomAccessFile(dstPack, "rw");
+ packOut.setLength(0);
+ } else {
+ dstPack = null;
+ dstIdx = null;
+ }
+ }
+
+ /**
+ * Set the pack index file format version this instance will create.
+ *
+ * @param version
+ * the version to write. The special version 0 designates the
+ * oldest (most compatible) format available for the objects.
+ * @see PackIndexWriter
+ */
+ public void setIndexVersion(final int version) {
+ outputVersion = version;
+ }
+
+ /**
+ * Configure this index pack instance to make a thin pack complete.
+ * <p>
+ * Thin packs are sometimes used during network transfers to allow a delta
+ * to be sent without a base object. Such packs are not permitted on disk.
+ * They can be fixed by copying the base object onto the end of the pack.
+ *
+ * @param fix
+ * true to enable fixing a thin pack.
+ */
+ public void setFixThin(final boolean fix) {
+ fixThin = fix;
+ }
+
+ /**
+ * Configure this index pack instance to keep an empty pack.
+ * <p>
+ * By default an empty pack (a pack with no objects) is not kept, as doing
+ * so is completely pointless. With no objects in the pack there is no data
+ * stored by it, so the pack is unnecessary.
+ *
+ * @param empty true to enable keeping an empty pack.
+ */
+ public void setKeepEmpty(final boolean empty) {
+ keepEmpty = empty;
+ }
+
+ /**
+ * Configure the checker used to validate received objects.
+ * <p>
+ * Usually object checking isn't necessary, as Git implementations only
+ * create valid objects in pack files. However, additional checking may be
+ * useful if processing data from an untrusted source.
+ *
+ * @param oc
+ * the checker instance; null to disable object checking.
+ */
+ public void setObjectChecker(final ObjectChecker oc) {
+ objCheck = oc;
+ }
+
+ /**
+ * Configure the checker used to validate received objects.
+ * <p>
+ * Usually object checking isn't necessary, as Git implementations only
+ * create valid objects in pack files. However, additional checking may be
+ * useful if processing data from an untrusted source.
+ * <p>
+ * This is shorthand for:
+ *
+ * <pre>
+ * setObjectChecker(on ? new ObjectChecker() : null);
+ * </pre>
+ *
+ * @param on
+ * true to enable the default checker; false to disable it.
+ */
+ public void setObjectChecking(final boolean on) {
+ setObjectChecker(on ? new ObjectChecker() : null);
+ }
+
+ /**
+ * Consume data from the input stream until the packfile is indexed.
+ *
+ * @param progress
+ * progress feedback
+ *
+ * @throws IOException
+ */
+ public void index(final ProgressMonitor progress) throws IOException {
+ progress.start(2 /* tasks */);
+ try {
+ try {
+ readPackHeader();
+
+ entries = new PackedObjectInfo[(int) objectCount];
+ baseById = new ObjectIdSubclassMap<DeltaChain>();
+ baseByPos = new LongMap<UnresolvedDelta>();
+
+ progress.beginTask(PROGRESS_DOWNLOAD, (int) objectCount);
+ for (int done = 0; done < objectCount; done++) {
+ indexOneObject();
+ progress.update(1);
+ if (progress.isCancelled())
+ throw new IOException("Download cancelled");
+ }
+ readPackFooter();
+ endInput();
+ progress.endTask();
+ if (deltaCount > 0) {
+ if (packOut == null)
+ throw new IOException("need packOut");
+ resolveDeltas(progress);
+ if (entryCount < objectCount) {
+ if (!fixThin) {
+ throw new IOException("pack has "
+ + (objectCount - entryCount)
+ + " unresolved deltas");
+ }
+ fixThinPack(progress);
+ }
+ }
+ if (packOut != null && (keepEmpty || entryCount > 0))
+ packOut.getChannel().force(true);
+
+ packDigest = null;
+ baseById = null;
+ baseByPos = null;
+
+ if (dstIdx != null && (keepEmpty || entryCount > 0))
+ writeIdx();
+
+ } finally {
+ try {
+ InflaterCache.release(inflater);
+ } finally {
+ inflater = null;
+ }
+ readCurs = WindowCursor.release(readCurs);
+
+ progress.endTask();
+ if (packOut != null)
+ packOut.close();
+ }
+
+ if (keepEmpty || entryCount > 0) {
+ if (dstPack != null)
+ dstPack.setReadOnly();
+ if (dstIdx != null)
+ dstIdx.setReadOnly();
+ }
+ } catch (IOException err) {
+ if (dstPack != null)
+ dstPack.delete();
+ if (dstIdx != null)
+ dstIdx.delete();
+ throw err;
+ }
+ }
+
+ private void resolveDeltas(final ProgressMonitor progress)
+ throws IOException {
+ progress.beginTask(PROGRESS_RESOLVE_DELTA, deltaCount);
+ final int last = entryCount;
+ for (int i = 0; i < last; i++) {
+ final int before = entryCount;
+ resolveDeltas(entries[i]);
+ progress.update(entryCount - before);
+ if (progress.isCancelled())
+ throw new IOException("Download cancelled during indexing");
+ }
+ progress.endTask();
+ }
+
+ private void resolveDeltas(final PackedObjectInfo oe) throws IOException {
+ final int oldCRC = oe.getCRC();
+ if (baseById.get(oe) != null || baseByPos.containsKey(oe.getOffset()))
+ resolveDeltas(oe.getOffset(), oldCRC, Constants.OBJ_BAD, null, oe);
+ }
+
+ private void resolveDeltas(final long pos, final int oldCRC, int type,
+ byte[] data, PackedObjectInfo oe) throws IOException {
+ crc.reset();
+ position(pos);
+ int c = readFromFile();
+ final int typeCode = (c >> 4) & 7;
+ long sz = c & 15;
+ int shift = 4;
+ while ((c & 0x80) != 0) {
+ c = readFromFile();
+ sz += (c & 0x7f) << shift;
+ shift += 7;
+ }
+
+ switch (typeCode) {
+ case Constants.OBJ_COMMIT:
+ case Constants.OBJ_TREE:
+ case Constants.OBJ_BLOB:
+ case Constants.OBJ_TAG:
+ type = typeCode;
+ data = inflateFromFile((int) sz);
+ break;
+ case Constants.OBJ_OFS_DELTA: {
+ c = readFromFile() & 0xff;
+ while ((c & 128) != 0)
+ c = readFromFile() & 0xff;
+ data = BinaryDelta.apply(data, inflateFromFile((int) sz));
+ break;
+ }
+ case Constants.OBJ_REF_DELTA: {
+ crc.update(buf, fillFromFile(20), 20);
+ use(20);
+ data = BinaryDelta.apply(data, inflateFromFile((int) sz));
+ break;
+ }
+ default:
+ throw new IOException("Unknown object type " + typeCode + ".");
+ }
+
+ final int crc32 = (int) crc.getValue();
+ if (oldCRC != crc32)
+ throw new IOException("Corruption detected re-reading at " + pos);
+ if (oe == null) {
+ objectDigest.update(Constants.encodedTypeString(type));
+ objectDigest.update((byte) ' ');
+ objectDigest.update(Constants.encodeASCII(data.length));
+ objectDigest.update((byte) 0);
+ objectDigest.update(data);
+ tempObjectId.fromRaw(objectDigest.digest(), 0);
+
+ verifySafeObject(tempObjectId, type, data);
+ oe = new PackedObjectInfo(pos, crc32, tempObjectId);
+ entries[entryCount++] = oe;
+ }
+
+ resolveChildDeltas(pos, type, data, oe);
+ }
+
+ private UnresolvedDelta removeBaseById(final AnyObjectId id){
+ final DeltaChain d = baseById.get(id);
+ return d != null ? d.remove() : null;
+ }
+
+ private static UnresolvedDelta reverse(UnresolvedDelta c) {
+ UnresolvedDelta tail = null;
+ while (c != null) {
+ final UnresolvedDelta n = c.next;
+ c.next = tail;
+ tail = c;
+ c = n;
+ }
+ return tail;
+ }
+
+ private void resolveChildDeltas(final long pos, int type, byte[] data,
+ PackedObjectInfo oe) throws IOException {
+ UnresolvedDelta a = reverse(removeBaseById(oe));
+ UnresolvedDelta b = reverse(baseByPos.remove(pos));
+ while (a != null && b != null) {
+ if (a.position < b.position) {
+ resolveDeltas(a.position, a.crc, type, data, null);
+ a = a.next;
+ } else {
+ resolveDeltas(b.position, b.crc, type, data, null);
+ b = b.next;
+ }
+ }
+ resolveChildDeltaChain(type, data, a);
+ resolveChildDeltaChain(type, data, b);
+ }
+
+ private void resolveChildDeltaChain(final int type, final byte[] data,
+ UnresolvedDelta a) throws IOException {
+ while (a != null) {
+ resolveDeltas(a.position, a.crc, type, data, null);
+ a = a.next;
+ }
+ }
+
+ private void fixThinPack(final ProgressMonitor progress) throws IOException {
+ growEntries();
+
+ packDigest.reset();
+ originalEOF = packOut.length() - 20;
+ final Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, false);
+ final List<DeltaChain> missing = new ArrayList<DeltaChain>(64);
+ long end = originalEOF;
+ for (final DeltaChain baseId : baseById) {
+ if (baseId.head == null)
+ continue;
+ final ObjectLoader ldr = repo.openObject(readCurs, baseId);
+ if (ldr == null) {
+ missing.add(baseId);
+ continue;
+ }
+ final byte[] data = ldr.getCachedBytes();
+ final int typeCode = ldr.getType();
+ final PackedObjectInfo oe;
+
+ crc.reset();
+ packOut.seek(end);
+ writeWhole(def, typeCode, data);
+ oe = new PackedObjectInfo(end, (int) crc.getValue(), baseId);
+ entries[entryCount++] = oe;
+ end = packOut.getFilePointer();
+
+ resolveChildDeltas(oe.getOffset(), typeCode, data, oe);
+ if (progress.isCancelled())
+ throw new IOException("Download cancelled during indexing");
+ }
+ def.end();
+
+ for (final DeltaChain base : missing) {
+ if (base.head != null)
+ throw new MissingObjectException(base, "delta base");
+ }
+
+ fixHeaderFooter(packcsum, packDigest.digest());
+ }
+
+ private void writeWhole(final Deflater def, final int typeCode,
+ final byte[] data) throws IOException {
+ int sz = data.length;
+ int hdrlen = 0;
+ buf[hdrlen++] = (byte) ((typeCode << 4) | sz & 15);
+ sz >>>= 4;
+ while (sz > 0) {
+ buf[hdrlen - 1] |= 0x80;
+ buf[hdrlen++] = (byte) (sz & 0x7f);
+ sz >>>= 7;
+ }
+ packDigest.update(buf, 0, hdrlen);
+ crc.update(buf, 0, hdrlen);
+ packOut.write(buf, 0, hdrlen);
+ def.reset();
+ def.setInput(data);
+ def.finish();
+ while (!def.finished()) {
+ final int datlen = def.deflate(buf);
+ packDigest.update(buf, 0, datlen);
+ crc.update(buf, 0, datlen);
+ packOut.write(buf, 0, datlen);
+ }
+ }
+
+ private void fixHeaderFooter(final byte[] origcsum, final byte[] tailcsum)
+ throws IOException {
+ final MessageDigest origDigest = Constants.newMessageDigest();
+ final MessageDigest tailDigest = Constants.newMessageDigest();
+ long origRemaining = originalEOF;
+
+ packOut.seek(0);
+ bAvail = 0;
+ bOffset = 0;
+ fillFromFile(12);
+
+ {
+ final int origCnt = (int) Math.min(bAvail, origRemaining);
+ origDigest.update(buf, 0, origCnt);
+ origRemaining -= origCnt;
+ if (origRemaining == 0)
+ tailDigest.update(buf, origCnt, bAvail - origCnt);
+ }
+
+ NB.encodeInt32(buf, 8, entryCount);
+ packOut.seek(0);
+ packOut.write(buf, 0, 12);
+ packOut.seek(bAvail);
+
+ packDigest.reset();
+ packDigest.update(buf, 0, bAvail);
+ for (;;) {
+ final int n = packOut.read(buf);
+ if (n < 0)
+ break;
+ if (origRemaining != 0) {
+ final int origCnt = (int) Math.min(n, origRemaining);
+ origDigest.update(buf, 0, origCnt);
+ origRemaining -= origCnt;
+ if (origRemaining == 0)
+ tailDigest.update(buf, origCnt, n - origCnt);
+ } else
+ tailDigest.update(buf, 0, n);
+
+ packDigest.update(buf, 0, n);
+ }
+
+ if (!Arrays.equals(origDigest.digest(), origcsum)
+ || !Arrays.equals(tailDigest.digest(), tailcsum))
+ throw new IOException("Pack corrupted while writing to filesystem");
+
+ packcsum = packDigest.digest();
+ packOut.write(packcsum);
+ }
+
+ private void growEntries() {
+ final PackedObjectInfo[] ne;
+
+ ne = new PackedObjectInfo[(int) objectCount + baseById.size()];
+ System.arraycopy(entries, 0, ne, 0, entryCount);
+ entries = ne;
+ }
+
+ private void writeIdx() throws IOException {
+ Arrays.sort(entries, 0, entryCount);
+ List<PackedObjectInfo> list = Arrays.asList(entries);
+ if (entryCount < entries.length)
+ list = list.subList(0, entryCount);
+
+ final FileOutputStream os = new FileOutputStream(dstIdx);
+ try {
+ final PackIndexWriter iw;
+ if (outputVersion <= 0)
+ iw = PackIndexWriter.createOldestPossible(os, list);
+ else
+ iw = PackIndexWriter.createVersion(os, outputVersion);
+ iw.write(list, packcsum);
+ os.getChannel().force(true);
+ } finally {
+ os.close();
+ }
+ }
+
+ private void readPackHeader() throws IOException {
+ final int hdrln = Constants.PACK_SIGNATURE.length + 4 + 4;
+ final int p = fillFromInput(hdrln);
+ for (int k = 0; k < Constants.PACK_SIGNATURE.length; k++)
+ if (buf[p + k] != Constants.PACK_SIGNATURE[k])
+ throw new IOException("Not a PACK file.");
+
+ final long vers = NB.decodeUInt32(buf, p + 4);
+ if (vers != 2 && vers != 3)
+ throw new IOException("Unsupported pack version " + vers + ".");
+ objectCount = NB.decodeUInt32(buf, p + 8);
+ use(hdrln);
+ }
+
+ private void readPackFooter() throws IOException {
+ sync();
+ final byte[] cmpcsum = packDigest.digest();
+ final int c = fillFromInput(20);
+ packcsum = new byte[20];
+ System.arraycopy(buf, c, packcsum, 0, 20);
+ use(20);
+ if (packOut != null)
+ packOut.write(packcsum);
+
+ if (!Arrays.equals(cmpcsum, packcsum))
+ throw new CorruptObjectException("Packfile checksum incorrect.");
+ }
+
+ // Cleanup all resources associated with our input parsing.
+ private void endInput() {
+ in = null;
+ objectData = null;
+ }
+
+ // Read one entire object or delta from the input.
+ private void indexOneObject() throws IOException {
+ final long pos = position();
+
+ crc.reset();
+ int c = readFromInput();
+ final int typeCode = (c >> 4) & 7;
+ long sz = c & 15;
+ int shift = 4;
+ while ((c & 0x80) != 0) {
+ c = readFromInput();
+ sz += (c & 0x7f) << shift;
+ shift += 7;
+ }
+
+ switch (typeCode) {
+ case Constants.OBJ_COMMIT:
+ case Constants.OBJ_TREE:
+ case Constants.OBJ_BLOB:
+ case Constants.OBJ_TAG:
+ whole(typeCode, pos, sz);
+ break;
+ case Constants.OBJ_OFS_DELTA: {
+ c = readFromInput();
+ long ofs = c & 127;
+ while ((c & 128) != 0) {
+ ofs += 1;
+ c = readFromInput();
+ ofs <<= 7;
+ ofs += (c & 127);
+ }
+ final long base = pos - ofs;
+ final UnresolvedDelta n;
+ skipInflateFromInput(sz);
+ n = new UnresolvedDelta(pos, (int) crc.getValue());
+ n.next = baseByPos.put(base, n);
+ deltaCount++;
+ break;
+ }
+ case Constants.OBJ_REF_DELTA: {
+ c = fillFromInput(20);
+ crc.update(buf, c, 20);
+ final ObjectId base = ObjectId.fromRaw(buf, c);
+ use(20);
+ DeltaChain r = baseById.get(base);
+ if (r == null) {
+ r = new DeltaChain(base);
+ baseById.add(r);
+ }
+ skipInflateFromInput(sz);
+ r.add(new UnresolvedDelta(pos, (int) crc.getValue()));
+ deltaCount++;
+ break;
+ }
+ default:
+ throw new IOException("Unknown object type " + typeCode + ".");
+ }
+ }
+
+ private void whole(final int type, final long pos, final long sz)
+ throws IOException {
+ final byte[] data = inflateFromInput(sz);
+ objectDigest.update(Constants.encodedTypeString(type));
+ objectDigest.update((byte) ' ');
+ objectDigest.update(Constants.encodeASCII(sz));
+ objectDigest.update((byte) 0);
+ objectDigest.update(data);
+ tempObjectId.fromRaw(objectDigest.digest(), 0);
+
+ verifySafeObject(tempObjectId, type, data);
+ final int crc32 = (int) crc.getValue();
+ entries[entryCount++] = new PackedObjectInfo(pos, crc32, tempObjectId);
+ }
+
+ private void verifySafeObject(final AnyObjectId id, final int type,
+ final byte[] data) throws IOException {
+ if (objCheck != null) {
+ try {
+ objCheck.check(type, data);
+ } catch (CorruptObjectException e) {
+ throw new IOException("Invalid "
+ + Constants.typeString(type) + " " + id.name()
+ + ":" + e.getMessage());
+ }
+ }
+
+ final ObjectLoader ldr = repo.openObject(readCurs, id);
+ if (ldr != null) {
+ final byte[] existingData = ldr.getCachedBytes();
+ if (ldr.getType() != type || !Arrays.equals(data, existingData)) {
+ throw new IOException("Collision on " + id.name());
+ }
+ }
+ }
+
+ // Current position of {@link #bOffset} within the entire file.
+ private long position() {
+ return bBase + bOffset;
+ }
+
+ private void position(final long pos) throws IOException {
+ packOut.seek(pos);
+ bBase = pos;
+ bOffset = 0;
+ bAvail = 0;
+ }
+
+ // Consume exactly one byte from the buffer and return it.
+ private int readFromInput() throws IOException {
+ if (bAvail == 0)
+ fillFromInput(1);
+ bAvail--;
+ final int b = buf[bOffset++] & 0xff;
+ crc.update(b);
+ return b;
+ }
+
+ // Consume exactly one byte from the buffer and return it.
+ private int readFromFile() throws IOException {
+ if (bAvail == 0)
+ fillFromFile(1);
+ bAvail--;
+ final int b = buf[bOffset++] & 0xff;
+ crc.update(b);
+ return b;
+ }
+
+ // Consume cnt bytes from the buffer.
+ private void use(final int cnt) {
+ bOffset += cnt;
+ bAvail -= cnt;
+ }
+
+ // Ensure at least need bytes are available in in {@link #buf}.
+ private int fillFromInput(final int need) throws IOException {
+ while (bAvail < need) {
+ int next = bOffset + bAvail;
+ int free = buf.length - next;
+ if (free + bAvail < need) {
+ sync();
+ next = bAvail;
+ free = buf.length - next;
+ }
+ next = in.read(buf, next, free);
+ if (next <= 0)
+ throw new EOFException("Packfile is truncated.");
+ bAvail += next;
+ }
+ return bOffset;
+ }
+
+ // Ensure at least need bytes are available in in {@link #buf}.
+ private int fillFromFile(final int need) throws IOException {
+ if (bAvail < need) {
+ int next = bOffset + bAvail;
+ int free = buf.length - next;
+ if (free + bAvail < need) {
+ if (bAvail > 0)
+ System.arraycopy(buf, bOffset, buf, 0, bAvail);
+ bOffset = 0;
+ next = bAvail;
+ free = buf.length - next;
+ }
+ next = packOut.read(buf, next, free);
+ if (next <= 0)
+ throw new EOFException("Packfile is truncated.");
+ bAvail += next;
+ }
+ return bOffset;
+ }
+
+ // Store consumed bytes in {@link #buf} up to {@link #bOffset}.
+ private void sync() throws IOException {
+ packDigest.update(buf, 0, bOffset);
+ if (packOut != null)
+ packOut.write(buf, 0, bOffset);
+ if (bAvail > 0)
+ System.arraycopy(buf, bOffset, buf, 0, bAvail);
+ bBase += bOffset;
+ bOffset = 0;
+ }
+
+ private void skipInflateFromInput(long sz) throws IOException {
+ final Inflater inf = inflater;
+ try {
+ final byte[] dst = objectData;
+ int n = 0;
+ int p = -1;
+ while (!inf.finished()) {
+ if (inf.needsInput()) {
+ if (p >= 0) {
+ crc.update(buf, p, bAvail);
+ use(bAvail);
+ }
+ p = fillFromInput(1);
+ inf.setInput(buf, p, bAvail);
+ }
+
+ int free = dst.length - n;
+ if (free < 8) {
+ sz -= n;
+ n = 0;
+ free = dst.length;
+ }
+ n += inf.inflate(dst, n, free);
+ }
+ if (n != sz)
+ throw new DataFormatException("wrong decompressed length");
+ n = bAvail - inf.getRemaining();
+ if (n > 0) {
+ crc.update(buf, p, n);
+ use(n);
+ }
+ } catch (DataFormatException dfe) {
+ throw corrupt(dfe);
+ } finally {
+ inf.reset();
+ }
+ }
+
+ private byte[] inflateFromInput(final long sz) throws IOException {
+ final byte[] dst = new byte[(int) sz];
+ final Inflater inf = inflater;
+ try {
+ int n = 0;
+ int p = -1;
+ while (!inf.finished()) {
+ if (inf.needsInput()) {
+ if (p >= 0) {
+ crc.update(buf, p, bAvail);
+ use(bAvail);
+ }
+ p = fillFromInput(1);
+ inf.setInput(buf, p, bAvail);
+ }
+
+ n += inf.inflate(dst, n, dst.length - n);
+ }
+ if (n != sz)
+ throw new DataFormatException("wrong decompressed length");
+ n = bAvail - inf.getRemaining();
+ if (n > 0) {
+ crc.update(buf, p, n);
+ use(n);
+ }
+ return dst;
+ } catch (DataFormatException dfe) {
+ throw corrupt(dfe);
+ } finally {
+ inf.reset();
+ }
+ }
+
+ private byte[] inflateFromFile(final int sz) throws IOException {
+ final Inflater inf = inflater;
+ try {
+ final byte[] dst = new byte[sz];
+ int n = 0;
+ int p = -1;
+ while (!inf.finished()) {
+ if (inf.needsInput()) {
+ if (p >= 0) {
+ crc.update(buf, p, bAvail);
+ use(bAvail);
+ }
+ p = fillFromFile(1);
+ inf.setInput(buf, p, bAvail);
+ }
+ n += inf.inflate(dst, n, sz - n);
+ }
+ n = bAvail - inf.getRemaining();
+ if (n > 0) {
+ crc.update(buf, p, n);
+ use(n);
+ }
+ return dst;
+ } catch (DataFormatException dfe) {
+ throw corrupt(dfe);
+ } finally {
+ inf.reset();
+ }
+ }
+
+ private static CorruptObjectException corrupt(final DataFormatException dfe) {
+ return new CorruptObjectException("Packfile corruption detected: "
+ + dfe.getMessage());
+ }
+
+ private static class DeltaChain extends ObjectId {
+ UnresolvedDelta head;
+
+ DeltaChain(final AnyObjectId id) {
+ super(id);
+ }
+
+ UnresolvedDelta remove() {
+ final UnresolvedDelta r = head;
+ if (r != null)
+ head = null;
+ return r;
+ }
+
+ void add(final UnresolvedDelta d) {
+ d.next = head;
+ head = d;
+ }
+ }
+
+ private static class UnresolvedDelta {
+ final long position;
+
+ final int crc;
+
+ UnresolvedDelta next;
+
+ UnresolvedDelta(final long headerOffset, final int crc32) {
+ position = headerOffset;
+ crc = crc32;
+ }
+ }
+
+ /**
+ * Rename the pack to it's final name and location and open it.
+ * <p>
+ * If the call completes successfully the repository this IndexPack instance
+ * was created with will have the objects in the pack available for reading
+ * and use, without needing to scan for packs.
+ *
+ * @throws IOException
+ * The pack could not be inserted into the repository's objects
+ * directory. The pack no longer exists on disk, as it was
+ * removed prior to throwing the exception to the caller.
+ */
+ public void renameAndOpenPack() throws IOException {
+ renameAndOpenPack(null);
+ }
+
+ /**
+ * Rename the pack to it's final name and location and open it.
+ * <p>
+ * If the call completes successfully the repository this IndexPack instance
+ * was created with will have the objects in the pack available for reading
+ * and use, without needing to scan for packs.
+ *
+ * @param lockMessage
+ * message to place in the pack-*.keep file. If null, no lock
+ * will be created, and this method returns null.
+ * @return the pack lock object, if lockMessage is not null.
+ * @throws IOException
+ * The pack could not be inserted into the repository's objects
+ * directory. The pack no longer exists on disk, as it was
+ * removed prior to throwing the exception to the caller.
+ */
+ public PackLock renameAndOpenPack(final String lockMessage)
+ throws IOException {
+ if (!keepEmpty && entryCount == 0) {
+ cleanupTemporaryFiles();
+ return null;
+ }
+
+ final MessageDigest d = Constants.newMessageDigest();
+ final byte[] oeBytes = new byte[Constants.OBJECT_ID_LENGTH];
+ for (int i = 0; i < entryCount; i++) {
+ final PackedObjectInfo oe = entries[i];
+ oe.copyRawTo(oeBytes, 0);
+ d.update(oeBytes);
+ }
+
+ final String name = ObjectId.fromRaw(d.digest()).name();
+ final File packDir = new File(repo.getObjectsDirectory(), "pack");
+ final File finalPack = new File(packDir, "pack-" + name + ".pack");
+ final File finalIdx = new File(packDir, "pack-" + name + ".idx");
+ final PackLock keep = new PackLock(finalPack);
+
+ if (!packDir.exists() && !packDir.mkdir() && !packDir.exists()) {
+ // The objects/pack directory isn't present, and we are unable
+ // to create it. There is no way to move this pack in.
+ //
+ cleanupTemporaryFiles();
+ throw new IOException("Cannot create " + packDir.getAbsolutePath());
+ }
+
+ if (finalPack.exists()) {
+ // If the pack is already present we should never replace it.
+ //
+ cleanupTemporaryFiles();
+ return null;
+ }
+
+ if (lockMessage != null) {
+ // If we have a reason to create a keep file for this pack, do
+ // so, or fail fast and don't put the pack in place.
+ //
+ try {
+ if (!keep.lock(lockMessage))
+ throw new IOException("Cannot lock pack in " + finalPack);
+ } catch (IOException e) {
+ cleanupTemporaryFiles();
+ throw e;
+ }
+ }
+
+ if (!dstPack.renameTo(finalPack)) {
+ cleanupTemporaryFiles();
+ keep.unlock();
+ throw new IOException("Cannot move pack to " + finalPack);
+ }
+
+ if (!dstIdx.renameTo(finalIdx)) {
+ cleanupTemporaryFiles();
+ keep.unlock();
+ if (!finalPack.delete())
+ finalPack.deleteOnExit();
+ throw new IOException("Cannot move index to " + finalIdx);
+ }
+
+ try {
+ repo.openPack(finalPack, finalIdx);
+ } catch (IOException err) {
+ keep.unlock();
+ finalPack.delete();
+ finalIdx.delete();
+ throw err;
+ }
+
+ return lockMessage != null ? keep : null;
+ }
+
+ private void cleanupTemporaryFiles() {
+ if (!dstIdx.delete())
+ dstIdx.deleteOnExit();
+ if (!dstPack.delete())
+ dstPack.deleteOnExit();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java
new file mode 100644
index 0000000000..6381c24dcc
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+/**
+ * Simple Map<long,Object> helper for {@link IndexPack}.
+ *
+ * @param <V>
+ * type of the value instance.
+ */
+final class LongMap<V> {
+ private static final float LOAD_FACTOR = 0.75f;
+
+ private Node<V>[] table;
+
+ /** Number of entries currently in the map. */
+ private int size;
+
+ /** Next {@link #size} to trigger a {@link #grow()}. */
+ private int growAt;
+
+ LongMap() {
+ table = createArray(64);
+ growAt = (int) (table.length * LOAD_FACTOR);
+ }
+
+ boolean containsKey(final long key) {
+ return get(key) != null;
+ }
+
+ V get(final long key) {
+ for (Node<V> n = table[index(key)]; n != null; n = n.next) {
+ if (n.key == key)
+ return n.value;
+ }
+ return null;
+ }
+
+ V remove(final long key) {
+ Node<V> n = table[index(key)];
+ Node<V> prior = null;
+ while (n != null) {
+ if (n.key == key) {
+ if (prior == null)
+ table[index(key)] = n.next;
+ else
+ prior.next = n.next;
+ size--;
+ return n.value;
+ }
+ prior = n;
+ n = n.next;
+ }
+ return null;
+ }
+
+ V put(final long key, final V value) {
+ for (Node<V> n = table[index(key)]; n != null; n = n.next) {
+ if (n.key == key) {
+ final V o = n.value;
+ n.value = value;
+ return o;
+ }
+ }
+
+ if (++size == growAt)
+ grow();
+ insert(new Node<V>(key, value));
+ return null;
+ }
+
+ private void insert(final Node<V> n) {
+ final int idx = index(n.key);
+ n.next = table[idx];
+ table[idx] = n;
+ }
+
+ private void grow() {
+ final Node<V>[] oldTable = table;
+ final int oldSize = table.length;
+
+ table = createArray(oldSize << 1);
+ growAt = (int) (table.length * LOAD_FACTOR);
+ for (int i = 0; i < oldSize; i++) {
+ Node<V> e = oldTable[i];
+ while (e != null) {
+ final Node<V> n = e.next;
+ insert(e);
+ e = n;
+ }
+ }
+ }
+
+ private final int index(final long key) {
+ int h = ((int) key) >>> 1;
+ h ^= (h >>> 20) ^ (h >>> 12);
+ return h & (table.length - 1);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static final <V> Node<V>[] createArray(final int sz) {
+ return new Node[sz];
+ }
+
+ private static class Node<V> {
+ final long key;
+
+ V value;
+
+ Node<V> next;
+
+ Node(final long k, final V v) {
+ key = k;
+ value = v;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
new file mode 100644
index 0000000000..e7a307f809
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.errors.InvalidPatternException;
+import org.eclipse.jgit.fnmatch.FileNameMatcher;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * Simple configuration parser for the OpenSSH ~/.ssh/config file.
+ * <p>
+ * Since JSch does not (currently) have the ability to parse an OpenSSH
+ * configuration file this is a simple parser to read that file and make the
+ * critical options available to {@link SshSessionFactory}.
+ */
+public class OpenSshConfig {
+ /** IANA assigned port number for SSH. */
+ static final int SSH_PORT = 22;
+
+ /**
+ * Obtain the user's configuration data.
+ * <p>
+ * The configuration file is always returned to the caller, even if no file
+ * exists in the user's home directory at the time the call was made. Lookup
+ * requests are cached and are automatically updated if the user modifies
+ * the configuration file since the last time it was cached.
+ *
+ * @return a caching reader of the user's configuration file.
+ */
+ public static OpenSshConfig get() {
+ File home = FS.userHome();
+ if (home == null)
+ home = new File(".").getAbsoluteFile();
+
+ final File config = new File(new File(home, ".ssh"), "config");
+ final OpenSshConfig osc = new OpenSshConfig(home, config);
+ osc.refresh();
+ return osc;
+ }
+
+ /** The user's home directory, as key files may be relative to here. */
+ private final File home;
+
+ /** The .ssh/config file we read and monitor for updates. */
+ private final File configFile;
+
+ /** Modification time of {@link #configFile} when {@link #hosts} loaded. */
+ private long lastModified;
+
+ /** Cached entries read out of the configuration file. */
+ private Map<String, Host> hosts;
+
+ OpenSshConfig(final File h, final File cfg) {
+ home = h;
+ configFile = cfg;
+ hosts = Collections.emptyMap();
+ }
+
+ /**
+ * Locate the configuration for a specific host request.
+ *
+ * @param hostName
+ * the name the user has supplied to the SSH tool. This may be a
+ * real host name, or it may just be a "Host" block in the
+ * configuration file.
+ * @return r configuration for the requested name. Never null.
+ */
+ public Host lookup(final String hostName) {
+ final Map<String, Host> cache = refresh();
+ Host h = cache.get(hostName);
+ if (h == null)
+ h = new Host();
+ if (h.patternsApplied)
+ return h;
+
+ for (final Map.Entry<String, Host> e : cache.entrySet()) {
+ if (!isHostPattern(e.getKey()))
+ continue;
+ if (!isHostMatch(e.getKey(), hostName))
+ continue;
+ h.copyFrom(e.getValue());
+ }
+
+ if (h.hostName == null)
+ h.hostName = hostName;
+ if (h.user == null)
+ h.user = OpenSshConfig.userName();
+ if (h.port == 0)
+ h.port = OpenSshConfig.SSH_PORT;
+ h.patternsApplied = true;
+ return h;
+ }
+
+ private synchronized Map<String, Host> refresh() {
+ final long mtime = configFile.lastModified();
+ if (mtime != lastModified) {
+ try {
+ final FileInputStream in = new FileInputStream(configFile);
+ try {
+ hosts = parse(in);
+ } finally {
+ in.close();
+ }
+ } catch (FileNotFoundException none) {
+ hosts = Collections.emptyMap();
+ } catch (IOException err) {
+ hosts = Collections.emptyMap();
+ }
+ lastModified = mtime;
+ }
+ return hosts;
+ }
+
+ private Map<String, Host> parse(final InputStream in) throws IOException {
+ final Map<String, Host> m = new LinkedHashMap<String, Host>();
+ final BufferedReader br = new BufferedReader(new InputStreamReader(in));
+ final List<Host> current = new ArrayList<Host>(4);
+ String line;
+
+ while ((line = br.readLine()) != null) {
+ line = line.trim();
+ if (line.length() == 0 || line.startsWith("#"))
+ continue;
+
+ final String[] parts = line.split("[ \t]*[= \t]", 2);
+ final String keyword = parts[0].trim();
+ final String argValue = parts[1].trim();
+
+ if (StringUtils.equalsIgnoreCase("Host", keyword)) {
+ current.clear();
+ for (final String pattern : argValue.split("[ \t]")) {
+ final String name = dequote(pattern);
+ Host c = m.get(name);
+ if (c == null) {
+ c = new Host();
+ m.put(name, c);
+ }
+ current.add(c);
+ }
+ continue;
+ }
+
+ if (current.isEmpty()) {
+ // We received an option outside of a Host block. We
+ // don't know who this should match against, so skip.
+ //
+ continue;
+ }
+
+ if (StringUtils.equalsIgnoreCase("HostName", keyword)) {
+ for (final Host c : current)
+ if (c.hostName == null)
+ c.hostName = dequote(argValue);
+ } else if (StringUtils.equalsIgnoreCase("User", keyword)) {
+ for (final Host c : current)
+ if (c.user == null)
+ c.user = dequote(argValue);
+ } else if (StringUtils.equalsIgnoreCase("Port", keyword)) {
+ try {
+ final int port = Integer.parseInt(dequote(argValue));
+ for (final Host c : current)
+ if (c.port == 0)
+ c.port = port;
+ } catch (NumberFormatException nfe) {
+ // Bad port number. Don't set it.
+ }
+ } else if (StringUtils.equalsIgnoreCase("IdentityFile", keyword)) {
+ for (final Host c : current)
+ if (c.identityFile == null)
+ c.identityFile = toFile(dequote(argValue));
+ } else if (StringUtils.equalsIgnoreCase("PreferredAuthentications", keyword)) {
+ for (final Host c : current)
+ if (c.preferredAuthentications == null)
+ c.preferredAuthentications = nows(dequote(argValue));
+ } else if (StringUtils.equalsIgnoreCase("BatchMode", keyword)) {
+ for (final Host c : current)
+ if (c.batchMode == null)
+ c.batchMode = yesno(dequote(argValue));
+ } else if (StringUtils.equalsIgnoreCase("StrictHostKeyChecking", keyword)) {
+ String value = dequote(argValue);
+ for (final Host c : current)
+ if (c.strictHostKeyChecking == null)
+ c.strictHostKeyChecking = value;
+ }
+ }
+
+ return m;
+ }
+
+ private static boolean isHostPattern(final String s) {
+ return s.indexOf('*') >= 0 || s.indexOf('?') >= 0;
+ }
+
+ private static boolean isHostMatch(final String pattern, final String name) {
+ final FileNameMatcher fn;
+ try {
+ fn = new FileNameMatcher(pattern, null);
+ } catch (InvalidPatternException e) {
+ return false;
+ }
+ fn.append(name);
+ return fn.isMatch();
+ }
+
+ private static String dequote(final String value) {
+ if (value.startsWith("\"") && value.endsWith("\""))
+ return value.substring(1, value.length() - 1);
+ return value;
+ }
+
+ private static String nows(final String value) {
+ final StringBuilder b = new StringBuilder();
+ for (int i = 0; i < value.length(); i++) {
+ if (!Character.isSpaceChar(value.charAt(i)))
+ b.append(value.charAt(i));
+ }
+ return b.toString();
+ }
+
+ private static Boolean yesno(final String value) {
+ if (StringUtils.equalsIgnoreCase("yes", value))
+ return Boolean.TRUE;
+ return Boolean.FALSE;
+ }
+
+ private File toFile(final String path) {
+ if (path.startsWith("~/"))
+ return new File(home, path.substring(2));
+ File ret = new File(path);
+ if (ret.isAbsolute())
+ return ret;
+ return new File(home, path);
+ }
+
+ static String userName() {
+ return AccessController.doPrivileged(new PrivilegedAction<String>() {
+ public String run() {
+ return System.getProperty("user.name");
+ }
+ });
+ }
+
+ /**
+ * Configuration of one "Host" block in the configuration file.
+ * <p>
+ * If returned from {@link OpenSshConfig#lookup(String)} some or all of the
+ * properties may not be populated. The properties which are not populated
+ * should be defaulted by the caller.
+ * <p>
+ * When returned from {@link OpenSshConfig#lookup(String)} any wildcard
+ * entries which appear later in the configuration file will have been
+ * already merged into this block.
+ */
+ public static class Host {
+ boolean patternsApplied;
+
+ String hostName;
+
+ int port;
+
+ File identityFile;
+
+ String user;
+
+ String preferredAuthentications;
+
+ Boolean batchMode;
+
+ String strictHostKeyChecking;
+
+ void copyFrom(final Host src) {
+ if (hostName == null)
+ hostName = src.hostName;
+ if (port == 0)
+ port = src.port;
+ if (identityFile == null)
+ identityFile = src.identityFile;
+ if (user == null)
+ user = src.user;
+ if (preferredAuthentications == null)
+ preferredAuthentications = src.preferredAuthentications;
+ if (batchMode == null)
+ batchMode = src.batchMode;
+ if (strictHostKeyChecking == null)
+ strictHostKeyChecking = src.strictHostKeyChecking;
+ }
+
+ /**
+ * @return the value StrictHostKeyChecking property, the valid values
+ * are "yes" (unknown hosts are not accepted), "no" (unknown
+ * hosts are always accepted), and "ask" (user should be asked
+ * before accepting the host)
+ */
+ public String getStrictHostKeyChecking() {
+ return strictHostKeyChecking;
+ }
+
+ /**
+ * @return the real IP address or host name to connect to; never null.
+ */
+ public String getHostName() {
+ return hostName;
+ }
+
+ /**
+ * @return the real port number to connect to; never 0.
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * @return path of the private key file to use for authentication; null
+ * if the caller should use default authentication strategies.
+ */
+ public File getIdentityFile() {
+ return identityFile;
+ }
+
+ /**
+ * @return the real user name to connect as; never null.
+ */
+ public String getUser() {
+ return user;
+ }
+
+ /**
+ * @return the preferred authentication methods, separated by commas if
+ * more than one authentication method is preferred.
+ */
+ public String getPreferredAuthentications() {
+ return preferredAuthentications;
+ }
+
+ /**
+ * @return true if batch (non-interactive) mode is preferred for this
+ * host connection.
+ */
+ public boolean isBatchMode() {
+ return batchMode != null && batchMode.booleanValue();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
new file mode 100644
index 0000000000..c7371d60f9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.lib.Ref;
+
+/**
+ * Class holding result of operation on remote repository. This includes refs
+ * advertised by remote repo and local tracking refs updates.
+ */
+public abstract class OperationResult {
+
+ Map<String, Ref> advertisedRefs = Collections.emptyMap();
+
+ URIish uri;
+
+ final SortedMap<String, TrackingRefUpdate> updates = new TreeMap<String, TrackingRefUpdate>();
+
+ /**
+ * Get the URI this result came from.
+ * <p>
+ * Each transport instance connects to at most one URI at any point in time.
+ *
+ * @return the URI describing the location of the remote repository.
+ */
+ public URIish getURI() {
+ return uri;
+ }
+
+ /**
+ * Get the complete list of refs advertised by the remote.
+ * <p>
+ * The returned refs may appear in any order. If the caller needs these to
+ * be sorted, they should be copied into a new array or List and then sorted
+ * by the caller as necessary.
+ *
+ * @return available/advertised refs. Never null. Not modifiable. The
+ * collection can be empty if the remote side has no refs (it is an
+ * empty/newly created repository).
+ */
+ public Collection<Ref> getAdvertisedRefs() {
+ return Collections.unmodifiableCollection(advertisedRefs.values());
+ }
+
+ /**
+ * Get a single advertised ref by name.
+ * <p>
+ * The name supplied should be valid ref name. To get a peeled value for a
+ * ref (aka <code>refs/tags/v1.0^{}</code>) use the base name (without
+ * the <code>^{}</code> suffix) and look at the peeled object id.
+ *
+ * @param name
+ * name of the ref to obtain.
+ * @return the requested ref; null if the remote did not advertise this ref.
+ */
+ public final Ref getAdvertisedRef(final String name) {
+ return advertisedRefs.get(name);
+ }
+
+ /**
+ * Get the status of all local tracking refs that were updated.
+ *
+ * @return unmodifiable collection of local updates. Never null. Empty if
+ * there were no local tracking refs updated.
+ */
+ public Collection<TrackingRefUpdate> getTrackingRefUpdates() {
+ return Collections.unmodifiableCollection(updates.values());
+ }
+
+ /**
+ * Get the status for a specific local tracking ref update.
+ *
+ * @param localName
+ * name of the local ref (e.g. "refs/remotes/origin/master").
+ * @return status of the local ref; null if this local ref was not touched
+ * during this operation.
+ */
+ public TrackingRefUpdate getTrackingRefUpdate(final String localName) {
+ return updates.get(localName);
+ }
+
+ void setAdvertisedRefs(final URIish u, final Map<String, Ref> ar) {
+ uri = u;
+ advertisedRefs = ar;
+ }
+
+ void add(final TrackingRefUpdate u) {
+ updates.put(u.getLocalName(), u);
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackTransport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackTransport.java
new file mode 100644
index 0000000000..736d329653
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackTransport.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+/**
+ * Marker interface an object transport using Git pack transfers.
+ * <p>
+ * Implementations of PackTransport setup connections and move objects back and
+ * forth by creating pack files on the source side and indexing them on the
+ * receiving side.
+ *
+ * @see BasePackFetchConnection
+ * @see BasePackPushConnection
+ */
+public interface PackTransport {
+ // no methods in marker interface
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
new file mode 100644
index 0000000000..5071cc7995
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Description of an object stored in a pack file, including offset.
+ * <p>
+ * When objects are stored in packs Git needs the ObjectId and the offset
+ * (starting position of the object data) to perform random-access reads of
+ * objects from the pack. This extension of ObjectId includes the offset.
+ */
+public class PackedObjectInfo extends ObjectId {
+ private long offset;
+
+ private int crc;
+
+ PackedObjectInfo(final long headerOffset, final int packedCRC,
+ final AnyObjectId id) {
+ super(id);
+ offset = headerOffset;
+ crc = packedCRC;
+ }
+
+ /**
+ * Create a new structure to remember information about an object.
+ *
+ * @param id
+ * the identity of the object the new instance tracks.
+ */
+ public PackedObjectInfo(final AnyObjectId id) {
+ super(id);
+ }
+
+ /**
+ * @return offset in pack when object has been already written, or 0 if it
+ * has not been written yet
+ */
+ public long getOffset() {
+ return offset;
+ }
+
+ /**
+ * Set the offset in pack when object has been written to.
+ *
+ * @param offset
+ * offset where written object starts
+ */
+ public void setOffset(final long offset) {
+ this.offset = offset;
+ }
+
+ /**
+ * @return the 32 bit CRC checksum for the packed data.
+ */
+ public int getCRC() {
+ return crc;
+ }
+
+ /**
+ * Record the 32 bit CRC checksum for the packed data.
+ *
+ * @param crc
+ * checksum of all packed data (including object type code,
+ * inflated length and delta base reference) as computed by
+ * {@link java.util.zip.CRC32}.
+ */
+ public void setCRC(final int crc) {
+ this.crc = crc;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
new file mode 100644
index 0000000000..29fe831ae4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+class PacketLineIn {
+ static final String END = new String("") /* must not string pool */;
+
+ static enum AckNackResult {
+ /** NAK */
+ NAK,
+ /** ACK */
+ ACK,
+ /** ACK + continue */
+ ACK_CONTINUE
+ }
+
+ private final InputStream in;
+
+ private final byte[] lenbuffer;
+
+ PacketLineIn(final InputStream i) {
+ in = i;
+ lenbuffer = new byte[4];
+ }
+
+ InputStream sideband(final ProgressMonitor pm) {
+ return new SideBandInputStream(this, in, pm);
+ }
+
+ AckNackResult readACK(final MutableObjectId returnedId) throws IOException {
+ final String line = readString();
+ if (line.length() == 0)
+ throw new PackProtocolException("Expected ACK/NAK, found EOF");
+ if ("NAK".equals(line))
+ return AckNackResult.NAK;
+ if (line.startsWith("ACK ")) {
+ returnedId.fromString(line.substring(4, 44));
+ if (line.indexOf("continue", 44) != -1)
+ return AckNackResult.ACK_CONTINUE;
+ return AckNackResult.ACK;
+ }
+ throw new PackProtocolException("Expected ACK/NAK, got: " + line);
+ }
+
+ String readString() throws IOException {
+ int len = readLength();
+ if (len == 0)
+ return END;
+
+ len -= 4; // length header (4 bytes)
+ if (len == 0)
+ return "";
+
+ final byte[] raw = new byte[len];
+ NB.readFully(in, raw, 0, len);
+ if (raw[len - 1] == '\n')
+ len--;
+ return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+ }
+
+ String readStringRaw() throws IOException {
+ int len = readLength();
+ if (len == 0)
+ return END;
+
+ len -= 4; // length header (4 bytes)
+
+ final byte[] raw = new byte[len];
+ NB.readFully(in, raw, 0, len);
+ return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+ }
+
+ int readLength() throws IOException {
+ NB.readFully(in, lenbuffer, 0, 4);
+ try {
+ final int len = RawParseUtils.parseHexInt16(lenbuffer, 0);
+ if (len != 0 && len < 4)
+ throw new ArrayIndexOutOfBoundsException();
+ return len;
+ } catch (ArrayIndexOutOfBoundsException err) {
+ throw new IOException("Invalid packet line header: "
+ + (char) lenbuffer[0] + (char) lenbuffer[1]
+ + (char) lenbuffer[2] + (char) lenbuffer[3]);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
new file mode 100644
index 0000000000..e7a7198d7c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.lib.Constants;
+
+class PacketLineOut {
+ private final OutputStream out;
+
+ private final byte[] lenbuffer;
+
+ PacketLineOut(final OutputStream i) {
+ out = i;
+ lenbuffer = new byte[5];
+ }
+
+ void writeString(final String s) throws IOException {
+ writePacket(Constants.encode(s));
+ }
+
+ void writePacket(final byte[] packet) throws IOException {
+ formatLength(packet.length + 4);
+ out.write(lenbuffer, 0, 4);
+ out.write(packet);
+ }
+
+ void writeChannelPacket(final int channel, final byte[] buf, int off,
+ int len) throws IOException {
+ formatLength(len + 5);
+ lenbuffer[4] = (byte) channel;
+ out.write(lenbuffer, 0, 5);
+ out.write(buf, off, len);
+ }
+
+ void end() throws IOException {
+ formatLength(0);
+ out.write(lenbuffer, 0, 4);
+ flush();
+ }
+
+ void flush() throws IOException {
+ out.flush();
+ }
+
+ private static final byte[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
+ '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ private void formatLength(int w) {
+ int o = 3;
+ while (o >= 0 && w != 0) {
+ lenbuffer[o--] = hexchar[w & 0xf];
+ w >>>= 4;
+ }
+ while (o >= 0)
+ lenbuffer[o--] = '0';
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
new file mode 100644
index 0000000000..1e662751bc
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+
+/**
+ * Hook invoked by {@link ReceivePack} after all updates are executed.
+ * <p>
+ * The hook is called after all commands have been processed. Only commands with
+ * a status of {@link ReceiveCommand.Result#OK} are passed into the hook. To get
+ * all commands within the hook, see {@link ReceivePack#getAllCommands()}.
+ * <p>
+ * Any post-receive hook implementation should not update the status of a
+ * command, as the command has already completed or failed, and the status has
+ * already been returned to the client.
+ * <p>
+ * Hooks should execute quickly, as they block the server and the client from
+ * completing the connection.
+ */
+public interface PostReceiveHook {
+ /** A simple no-op hook. */
+ public static final PostReceiveHook NULL = new PostReceiveHook() {
+ public void onPostReceive(final ReceivePack rp,
+ final Collection<ReceiveCommand> commands) {
+ // Do nothing.
+ }
+ };
+
+ /**
+ * Invoked after all commands are executed and status has been returned.
+ *
+ * @param rp
+ * the process handling the current receive. Hooks may obtain
+ * details about the destination repository through this handle.
+ * @param commands
+ * unmodifiable set of successfully completed commands. May be
+ * the empty set.
+ */
+ public void onPostReceive(ReceivePack rp,
+ Collection<ReceiveCommand> commands);
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
new file mode 100644
index 0000000000..9a743a515b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+
+/**
+ * Hook invoked by {@link ReceivePack} before any updates are executed.
+ * <p>
+ * The hook is called with any commands that are deemed valid after parsing them
+ * from the client and applying the standard receive configuration options to
+ * them:
+ * <ul>
+ * <li><code>receive.denyDenyDeletes</code></li>
+ * <li><code>receive.denyNonFastForwards</code></li>
+ * </ul>
+ * This means the hook will not receive a non-fast-forward update command if
+ * denyNonFastForwards is set to true in the configuration file. To get all
+ * commands within the hook, see {@link ReceivePack#getAllCommands()}.
+ * <p>
+ * As the hook is invoked prior to the commands being executed, the hook may
+ * choose to block any command by setting its result status with
+ * {@link ReceiveCommand#setResult(ReceiveCommand.Result)}.
+ * <p>
+ * The hook may also choose to perform the command itself (or merely pretend
+ * that it has performed the command), by setting the result status to
+ * {@link ReceiveCommand.Result#OK}.
+ * <p>
+ * Hooks should run quickly, as they block the caller thread and the client
+ * process from completing.
+ * <p>
+ * Hooks may send optional messages back to the client via methods on
+ * {@link ReceivePack}. Implementors should be aware that not all network
+ * transports support this output, so some (or all) messages may simply be
+ * discarded. These messages should be advisory only.
+ */
+public interface PreReceiveHook {
+ /** A simple no-op hook. */
+ public static final PreReceiveHook NULL = new PreReceiveHook() {
+ public void onPreReceive(final ReceivePack rp,
+ final Collection<ReceiveCommand> commands) {
+ // Do nothing.
+ }
+ };
+
+ /**
+ * Invoked just before commands are executed.
+ * <p>
+ * See the class description for how this method can impact execution.
+ *
+ * @param rp
+ * the process handling the current receive. Hooks may obtain
+ * details about the destination repository through this handle.
+ * @param commands
+ * unmodifiable set of valid commands still pending execution.
+ * May be the empty set.
+ */
+ public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands);
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
new file mode 100644
index 0000000000..14e6a1e800
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.util.Map;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+
+/**
+ * Lists known refs from the remote and sends objects to the remote.
+ * <p>
+ * A push connection typically connects to the <code>git-receive-pack</code>
+ * service running where the remote repository is stored. This provides a
+ * one-way object transfer service to copy objects from the local repository
+ * into the remote repository, as well as a way to modify the refs stored by the
+ * remote repository.
+ * <p>
+ * Instances of a PushConnection must be created by a {@link Transport} that
+ * implements a specific object transfer protocol that both sides of the
+ * connection understand.
+ * <p>
+ * PushConnection instances are not thread safe and may be accessed by only one
+ * thread at a time.
+ *
+ * @see Transport
+ */
+public interface PushConnection extends Connection {
+
+ /**
+ * Pushes to the remote repository basing on provided specification. This
+ * possibly result in update/creation/deletion of refs on remote repository
+ * and sending objects that remote repository need to have a consistent
+ * objects graph from new refs.
+ * <p>
+ * <p>
+ * Only one call per connection is allowed. Subsequent calls will result in
+ * {@link TransportException}.
+ * </p>
+ * <p>
+ * Implementation may use local repository to send a minimum set of objects
+ * needed by remote repository in efficient way.
+ * {@link Transport#isPushThin()} should be honored if applicable.
+ * refUpdates should be filled with information about status of each update.
+ * </p>
+ *
+ * @param monitor
+ * progress monitor to update the end-user about the amount of
+ * work completed, or to indicate cancellation. Implementors
+ * should poll the monitor at regular intervals to look for
+ * cancellation requests from the user.
+ * @param refUpdates
+ * map of remote refnames to remote refs update
+ * specifications/statuses. Can't be empty. This indicate what
+ * refs caller want to update on remote side. Only refs updates
+ * with {@link Status#NOT_ATTEMPTED} should passed.
+ * Implementation must ensure that and appropriate status with
+ * optional message should be set during call. No refUpdate with
+ * {@link Status#AWAITING_REPORT} or {@link Status#NOT_ATTEMPTED}
+ * can be leaved by implementation after return from this call.
+ * @throws TransportException
+ * objects could not be copied due to a network failure,
+ * critical protocol error, or error on remote side, or
+ * connection was already used for push - new connection must be
+ * created. Non-critical errors concerning only isolated refs
+ * should be placed in refUpdates.
+ */
+ public void push(final ProgressMonitor monitor,
+ final Map<String, RemoteRefUpdate> refUpdates)
+ throws TransportException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
new file mode 100644
index 0000000000..17e1dfc77b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+
+/**
+ * Class performing push operation on remote repository.
+ *
+ * @see Transport#push(ProgressMonitor, Collection)
+ */
+class PushProcess {
+ /** Task name for {@link ProgressMonitor} used during opening connection. */
+ static final String PROGRESS_OPENING_CONNECTION = "Opening connection";
+
+ /** Transport used to perform this operation. */
+ private final Transport transport;
+
+ /** Push operation connection created to perform this operation */
+ private PushConnection connection;
+
+ /** Refs to update on remote side. */
+ private final Map<String, RemoteRefUpdate> toPush;
+
+ /** Revision walker for checking some updates properties. */
+ private final RevWalk walker;
+
+ /**
+ * Create process for specified transport and refs updates specification.
+ *
+ * @param transport
+ * transport between remote and local repository, used to create
+ * connection.
+ * @param toPush
+ * specification of refs updates (and local tracking branches).
+ * @throws TransportException
+ */
+ PushProcess(final Transport transport,
+ final Collection<RemoteRefUpdate> toPush) throws TransportException {
+ this.walker = new RevWalk(transport.local);
+ this.transport = transport;
+ this.toPush = new HashMap<String, RemoteRefUpdate>();
+ for (final RemoteRefUpdate rru : toPush) {
+ if (this.toPush.put(rru.getRemoteName(), rru) != null)
+ throw new TransportException(
+ "Duplicate remote ref update is illegal. Affected remote name: "
+ + rru.getRemoteName());
+ }
+ }
+
+ /**
+ * Perform push operation between local and remote repository - set remote
+ * refs appropriately, send needed objects and update local tracking refs.
+ * <p>
+ * When {@link Transport#isDryRun()} is true, result of this operation is
+ * just estimation of real operation result, no real action is performed.
+ *
+ * @param monitor
+ * progress monitor used for feedback about operation.
+ * @return result of push operation with complete status description.
+ * @throws NotSupportedException
+ * when push operation is not supported by provided transport.
+ * @throws TransportException
+ * when some error occurred during operation, like I/O, protocol
+ * error, or local database consistency error.
+ */
+ PushResult execute(final ProgressMonitor monitor)
+ throws NotSupportedException, TransportException {
+ monitor.beginTask(PROGRESS_OPENING_CONNECTION, ProgressMonitor.UNKNOWN);
+ connection = transport.openPush();
+ try {
+ monitor.endTask();
+
+ final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
+ if (transport.isDryRun())
+ modifyUpdatesForDryRun();
+ else if (!preprocessed.isEmpty())
+ connection.push(monitor, preprocessed);
+ } finally {
+ connection.close();
+ }
+ if (!transport.isDryRun())
+ updateTrackingRefs();
+ return prepareOperationResult();
+ }
+
+ private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
+ throws TransportException {
+ final Map<String, RemoteRefUpdate> result = new HashMap<String, RemoteRefUpdate>();
+ for (final RemoteRefUpdate rru : toPush.values()) {
+ final Ref advertisedRef = connection.getRef(rru.getRemoteName());
+ final ObjectId advertisedOld = (advertisedRef == null ? ObjectId
+ .zeroId() : advertisedRef.getObjectId());
+
+ if (rru.getNewObjectId().equals(advertisedOld)) {
+ if (rru.isDelete()) {
+ // ref does exist neither locally nor remotely
+ rru.setStatus(Status.NON_EXISTING);
+ } else {
+ // same object - nothing to do
+ rru.setStatus(Status.UP_TO_DATE);
+ }
+ continue;
+ }
+
+ // caller has explicitly specified expected old object id, while it
+ // has been changed in the mean time - reject
+ if (rru.isExpectingOldObjectId()
+ && !rru.getExpectedOldObjectId().equals(advertisedOld)) {
+ rru.setStatus(Status.REJECTED_REMOTE_CHANGED);
+ continue;
+ }
+
+ // create ref (hasn't existed on remote side) and delete ref
+ // are always fast-forward commands, feasible at this level
+ if (advertisedOld.equals(ObjectId.zeroId()) || rru.isDelete()) {
+ rru.setFastForward(true);
+ result.put(rru.getRemoteName(), rru);
+ continue;
+ }
+
+ // check for fast-forward:
+ // - both old and new ref must point to commits, AND
+ // - both of them must be known for us, exist in repository, AND
+ // - old commit must be ancestor of new commit
+ boolean fastForward = true;
+ try {
+ RevObject oldRev = walker.parseAny(advertisedOld);
+ final RevObject newRev = walker.parseAny(rru.getNewObjectId());
+ if (!(oldRev instanceof RevCommit)
+ || !(newRev instanceof RevCommit)
+ || !walker.isMergedInto((RevCommit) oldRev,
+ (RevCommit) newRev))
+ fastForward = false;
+ } catch (MissingObjectException x) {
+ fastForward = false;
+ } catch (Exception x) {
+ throw new TransportException(transport.getURI(),
+ "reading objects from local repository failed: "
+ + x.getMessage(), x);
+ }
+ rru.setFastForward(fastForward);
+ if (!fastForward && !rru.isForceUpdate())
+ rru.setStatus(Status.REJECTED_NONFASTFORWARD);
+ else
+ result.put(rru.getRemoteName(), rru);
+ }
+ return result;
+ }
+
+ private void modifyUpdatesForDryRun() {
+ for (final RemoteRefUpdate rru : toPush.values())
+ if (rru.getStatus() == Status.NOT_ATTEMPTED)
+ rru.setStatus(Status.OK);
+ }
+
+ private void updateTrackingRefs() {
+ for (final RemoteRefUpdate rru : toPush.values()) {
+ final Status status = rru.getStatus();
+ if (rru.hasTrackingRefUpdate()
+ && (status == Status.UP_TO_DATE || status == Status.OK)) {
+ // update local tracking branch only when there is a chance that
+ // it has changed; this is possible for:
+ // -updated (OK) status,
+ // -up to date (UP_TO_DATE) status
+ try {
+ rru.updateTrackingRef(walker);
+ } catch (IOException e) {
+ // ignore as RefUpdate has stored I/O error status
+ }
+ }
+ }
+ }
+
+ private PushResult prepareOperationResult() {
+ final PushResult result = new PushResult();
+ result.setAdvertisedRefs(transport.getURI(), connection.getRefsMap());
+ result.setRemoteUpdates(toPush);
+
+ for (final RemoteRefUpdate rru : toPush.values()) {
+ final TrackingRefUpdate tru = rru.getTrackingRefUpdate();
+ if (tru != null)
+ result.add(tru);
+ }
+ return result;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushResult.java
new file mode 100644
index 0000000000..41aa73cc3b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushResult.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Result of push operation to the remote repository. Holding information of
+ * {@link OperationResult} and remote refs updates status.
+ *
+ * @see Transport#push(org.eclipse.jgit.lib.ProgressMonitor, Collection)
+ */
+public class PushResult extends OperationResult {
+ private Map<String, RemoteRefUpdate> remoteUpdates = Collections.emptyMap();
+
+ /**
+ * Get status of remote refs updates. Together with
+ * {@link #getAdvertisedRefs()} it provides full description/status of each
+ * ref update.
+ * <p>
+ * Returned collection is not sorted in any order.
+ * </p>
+ *
+ * @return collection of remote refs updates
+ */
+ public Collection<RemoteRefUpdate> getRemoteUpdates() {
+ return Collections.unmodifiableCollection(remoteUpdates.values());
+ }
+
+ /**
+ * Get status of specific remote ref update by remote ref name. Together
+ * with {@link #getAdvertisedRef(String)} it provide full description/status
+ * of this ref update.
+ *
+ * @param refName
+ * remote ref name
+ * @return status of remote ref update
+ */
+ public RemoteRefUpdate getRemoteUpdate(final String refName) {
+ return remoteUpdates.get(refName);
+ }
+
+ void setRemoteUpdates(
+ final Map<String, RemoteRefUpdate> remoteUpdates) {
+ this.remoteUpdates = remoteUpdates;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
new file mode 100644
index 0000000000..60ebeabd99
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+
+/**
+ * A command being processed by {@link ReceivePack}.
+ * <p>
+ * This command instance roughly translates to the server side representation of
+ * the {@link RemoteRefUpdate} created by the client.
+ */
+public class ReceiveCommand {
+ /** Type of operation requested. */
+ public static enum Type {
+ /** Create a new ref; the ref must not already exist. */
+ CREATE,
+
+ /**
+ * Update an existing ref with a fast-forward update.
+ * <p>
+ * During a fast-forward update no changes will be lost; only new
+ * commits are inserted into the ref.
+ */
+ UPDATE,
+
+ /**
+ * Update an existing ref by potentially discarding objects.
+ * <p>
+ * The current value of the ref is not fully reachable from the new
+ * value of the ref, so a successful command may result in one or more
+ * objects becoming unreachable.
+ */
+ UPDATE_NONFASTFORWARD,
+
+ /** Delete an existing ref; the ref should already exist. */
+ DELETE;
+ }
+
+ /** Result of the update command. */
+ public static enum Result {
+ /** The command has not yet been attempted by the server. */
+ NOT_ATTEMPTED,
+
+ /** The server is configured to deny creation of this ref. */
+ REJECTED_NOCREATE,
+
+ /** The server is configured to deny deletion of this ref. */
+ REJECTED_NODELETE,
+
+ /** The update is a non-fast-forward update and isn't permitted. */
+ REJECTED_NONFASTFORWARD,
+
+ /** The update affects <code>HEAD</code> and cannot be permitted. */
+ REJECTED_CURRENT_BRANCH,
+
+ /**
+ * One or more objects aren't in the repository.
+ * <p>
+ * This is severe indication of either repository corruption on the
+ * server side, or a bug in the client wherein the client did not supply
+ * all required objects during the pack transfer.
+ */
+ REJECTED_MISSING_OBJECT,
+
+ /** Other failure; see {@link ReceiveCommand#getMessage()}. */
+ REJECTED_OTHER_REASON,
+
+ /** The ref could not be locked and updated atomically; try again. */
+ LOCK_FAILURE,
+
+ /** The change was completed successfully. */
+ OK;
+ }
+
+ private final ObjectId oldId;
+
+ private final ObjectId newId;
+
+ private final String name;
+
+ private Type type;
+
+ private Ref ref;
+
+ private Result status;
+
+ private String message;
+
+ /**
+ * Create a new command for {@link ReceivePack}.
+ *
+ * @param oldId
+ * the old object id; must not be null. Use
+ * {@link ObjectId#zeroId()} to indicate a ref creation.
+ * @param newId
+ * the new object id; must not be null. Use
+ * {@link ObjectId#zeroId()} to indicate a ref deletion.
+ * @param name
+ * name of the ref being affected.
+ */
+ public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
+ final String name) {
+ this.oldId = oldId;
+ this.newId = newId;
+ this.name = name;
+
+ type = Type.UPDATE;
+ if (ObjectId.zeroId().equals(oldId))
+ type = Type.CREATE;
+ if (ObjectId.zeroId().equals(newId))
+ type = Type.DELETE;
+ status = Result.NOT_ATTEMPTED;
+ }
+
+ /** @return the old value the client thinks the ref has. */
+ public ObjectId getOldId() {
+ return oldId;
+ }
+
+ /** @return the requested new value for this ref. */
+ public ObjectId getNewId() {
+ return newId;
+ }
+
+ /** @return the name of the ref being updated. */
+ public String getRefName() {
+ return name;
+ }
+
+ /** @return the type of this command; see {@link Type}. */
+ public Type getType() {
+ return type;
+ }
+
+ /** @return the ref, if this was advertised by the connection. */
+ public Ref getRef() {
+ return ref;
+ }
+
+ /** @return the current status code of this command. */
+ public Result getResult() {
+ return status;
+ }
+
+ /** @return the message associated with a failure status. */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Set the status of this command.
+ *
+ * @param s
+ * the new status code for this command.
+ */
+ public void setResult(final Result s) {
+ setResult(s, null);
+ }
+
+ /**
+ * Set the status of this command.
+ *
+ * @param s
+ * new status code for this command.
+ * @param m
+ * optional message explaining the new status.
+ */
+ public void setResult(final Result s, final String m) {
+ status = s;
+ message = m;
+ }
+
+ void setRef(final Ref r) {
+ ref = r;
+ }
+
+ void setType(final Type t) {
+ type = t;
+ }
+
+ @Override
+ public String toString() {
+ return getType().name() + ": " + getOldId().name() + " "
+ + getNewId().name() + " " + getRefName();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
new file mode 100644
index 0000000000..26b66db403
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -0,0 +1,913 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedWriter;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PackLock;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Config.SectionParser;
+import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand.Result;
+import org.eclipse.jgit.util.io.InterruptTimer;
+import org.eclipse.jgit.util.io.TimeoutInputStream;
+import org.eclipse.jgit.util.io.TimeoutOutputStream;
+
+/**
+ * Implements the server side of a push connection, receiving objects.
+ */
+public class ReceivePack {
+ static final String CAPABILITY_REPORT_STATUS = BasePackPushConnection.CAPABILITY_REPORT_STATUS;
+
+ static final String CAPABILITY_DELETE_REFS = BasePackPushConnection.CAPABILITY_DELETE_REFS;
+
+ static final String CAPABILITY_OFS_DELTA = BasePackPushConnection.CAPABILITY_OFS_DELTA;
+
+ /** Database we write the stored objects into. */
+ private final Repository db;
+
+ /** Revision traversal support over {@link #db}. */
+ private final RevWalk walk;
+
+ /** Should an incoming transfer validate objects? */
+ private boolean checkReceivedObjects;
+
+ /** Should an incoming transfer permit create requests? */
+ private boolean allowCreates;
+
+ /** Should an incoming transfer permit delete requests? */
+ private boolean allowDeletes;
+
+ /** Should an incoming transfer permit non-fast-forward requests? */
+ private boolean allowNonFastForwards;
+
+ private boolean allowOfsDelta;
+
+ /** Identity to record action as within the reflog. */
+ private PersonIdent refLogIdent;
+
+ /** Hook to validate the update commands before execution. */
+ private PreReceiveHook preReceive;
+
+ /** Hook to report on the commands after execution. */
+ private PostReceiveHook postReceive;
+
+ /** Timeout in seconds to wait for client interaction. */
+ private int timeout;
+
+ /** Timer to manage {@link #timeout}. */
+ private InterruptTimer timer;
+
+ private TimeoutInputStream timeoutIn;
+
+ private InputStream rawIn;
+
+ private OutputStream rawOut;
+
+ private PacketLineIn pckIn;
+
+ private PacketLineOut pckOut;
+
+ private PrintWriter msgs;
+
+ /** The refs we advertised as existing at the start of the connection. */
+ private Map<String, Ref> refs;
+
+ /** Capabilities requested by the client. */
+ private Set<String> enabledCapablities;
+
+ /** Commands to execute, as received by the client. */
+ private List<ReceiveCommand> commands;
+
+ /** An exception caught while unpacking and fsck'ing the objects. */
+ private Throwable unpackError;
+
+ /** if {@link #enabledCapablities} has {@link #CAPABILITY_REPORT_STATUS} */
+ private boolean reportStatus;
+
+ /** Lock around the received pack file, while updating refs. */
+ private PackLock packLock;
+
+ /**
+ * Create a new pack receive for an open repository.
+ *
+ * @param into
+ * the destination repository.
+ */
+ public ReceivePack(final Repository into) {
+ db = into;
+ walk = new RevWalk(db);
+
+ final ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY);
+ checkReceivedObjects = cfg.checkReceivedObjects;
+ allowCreates = cfg.allowCreates;
+ allowDeletes = cfg.allowDeletes;
+ allowNonFastForwards = cfg.allowNonFastForwards;
+ allowOfsDelta = cfg.allowOfsDelta;
+ preReceive = PreReceiveHook.NULL;
+ postReceive = PostReceiveHook.NULL;
+ }
+
+ private static class ReceiveConfig {
+ static final SectionParser<ReceiveConfig> KEY = new SectionParser<ReceiveConfig>() {
+ public ReceiveConfig parse(final Config cfg) {
+ return new ReceiveConfig(cfg);
+ }
+ };
+
+ final boolean checkReceivedObjects;
+
+ final boolean allowCreates;
+
+ final boolean allowDeletes;
+
+ final boolean allowNonFastForwards;
+
+ final boolean allowOfsDelta;
+
+ ReceiveConfig(final Config config) {
+ checkReceivedObjects = config.getBoolean("receive", "fsckobjects",
+ false);
+ allowCreates = true;
+ allowDeletes = !config.getBoolean("receive", "denydeletes", false);
+ allowNonFastForwards = !config.getBoolean("receive",
+ "denynonfastforwards", false);
+ allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset",
+ true);
+ }
+ }
+
+ /** @return the repository this receive completes into. */
+ public final Repository getRepository() {
+ return db;
+ }
+
+ /** @return the RevWalk instance used by this connection. */
+ public final RevWalk getRevWalk() {
+ return walk;
+ }
+
+ /** @return all refs which were advertised to the client. */
+ public final Map<String, Ref> getAdvertisedRefs() {
+ return refs;
+ }
+
+ /**
+ * @return true if this instance will verify received objects are formatted
+ * correctly. Validating objects requires more CPU time on this side
+ * of the connection.
+ */
+ public boolean isCheckReceivedObjects() {
+ return checkReceivedObjects;
+ }
+
+ /**
+ * @param check
+ * true to enable checking received objects; false to assume all
+ * received objects are valid.
+ */
+ public void setCheckReceivedObjects(final boolean check) {
+ checkReceivedObjects = check;
+ }
+
+ /** @return true if the client can request refs to be created. */
+ public boolean isAllowCreates() {
+ return allowCreates;
+ }
+
+ /**
+ * @param canCreate
+ * true to permit create ref commands to be processed.
+ */
+ public void setAllowCreates(final boolean canCreate) {
+ allowCreates = canCreate;
+ }
+
+ /** @return true if the client can request refs to be deleted. */
+ public boolean isAllowDeletes() {
+ return allowDeletes;
+ }
+
+ /**
+ * @param canDelete
+ * true to permit delete ref commands to be processed.
+ */
+ public void setAllowDeletes(final boolean canDelete) {
+ allowDeletes = canDelete;
+ }
+
+ /**
+ * @return true if the client can request non-fast-forward updates of a ref,
+ * possibly making objects unreachable.
+ */
+ public boolean isAllowNonFastForwards() {
+ return allowNonFastForwards;
+ }
+
+ /**
+ * @param canRewind
+ * true to permit the client to ask for non-fast-forward updates
+ * of an existing ref.
+ */
+ public void setAllowNonFastForwards(final boolean canRewind) {
+ allowNonFastForwards = canRewind;
+ }
+
+ /** @return identity of the user making the changes in the reflog. */
+ public PersonIdent getRefLogIdent() {
+ return refLogIdent;
+ }
+
+ /**
+ * Set the identity of the user appearing in the affected reflogs.
+ * <p>
+ * The timestamp portion of the identity is ignored. A new identity with the
+ * current timestamp will be created automatically when the updates occur
+ * and the log records are written.
+ *
+ * @param pi
+ * identity of the user. If null the identity will be
+ * automatically determined based on the repository
+ * configuration.
+ */
+ public void setRefLogIdent(final PersonIdent pi) {
+ refLogIdent = pi;
+ }
+
+ /** @return get the hook invoked before updates occur. */
+ public PreReceiveHook getPreReceiveHook() {
+ return preReceive;
+ }
+
+ /**
+ * Set the hook which is invoked prior to commands being executed.
+ * <p>
+ * Only valid commands (those which have no obvious errors according to the
+ * received input and this instance's configuration) are passed into the
+ * hook. The hook may mark a command with a result of any value other than
+ * {@link Result#NOT_ATTEMPTED} to block its execution.
+ * <p>
+ * The hook may be called with an empty command collection if the current
+ * set is completely invalid.
+ *
+ * @param h
+ * the hook instance; may be null to disable the hook.
+ */
+ public void setPreReceiveHook(final PreReceiveHook h) {
+ preReceive = h != null ? h : PreReceiveHook.NULL;
+ }
+
+ /** @return get the hook invoked after updates occur. */
+ public PostReceiveHook getPostReceiveHook() {
+ return postReceive;
+ }
+
+ /**
+ * Set the hook which is invoked after commands are executed.
+ * <p>
+ * Only successful commands (type is {@link Result#OK}) are passed into the
+ * hook. The hook may be called with an empty command collection if the
+ * current set all resulted in an error.
+ *
+ * @param h
+ * the hook instance; may be null to disable the hook.
+ */
+ public void setPostReceiveHook(final PostReceiveHook h) {
+ postReceive = h != null ? h : PostReceiveHook.NULL;
+ }
+
+ /** @return timeout (in seconds) before aborting an IO operation. */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Set the timeout before willing to abort an IO call.
+ *
+ * @param seconds
+ * number of seconds to wait (with no data transfer occurring)
+ * before aborting an IO read or write operation with the
+ * connected client.
+ */
+ public void setTimeout(final int seconds) {
+ timeout = seconds;
+ }
+
+ /** @return all of the command received by the current request. */
+ public List<ReceiveCommand> getAllCommands() {
+ return Collections.unmodifiableList(commands);
+ }
+
+ /**
+ * Send an error message to the client, if it supports receiving them.
+ * <p>
+ * If the client doesn't support receiving messages, the message will be
+ * discarded, with no other indication to the caller or to the client.
+ * <p>
+ * {@link PreReceiveHook}s should always try to use
+ * {@link ReceiveCommand#setResult(Result, String)} with a result status of
+ * {@link Result#REJECTED_OTHER_REASON} to indicate any reasons for
+ * rejecting an update. Messages attached to a command are much more likely
+ * to be returned to the client.
+ *
+ * @param what
+ * string describing the problem identified by the hook. The
+ * string must not end with an LF, and must not contain an LF.
+ */
+ public void sendError(final String what) {
+ sendMessage("error", what);
+ }
+
+ /**
+ * Send a message to the client, if it supports receiving them.
+ * <p>
+ * If the client doesn't support receiving messages, the message will be
+ * discarded, with no other indication to the caller or to the client.
+ *
+ * @param what
+ * string describing the problem identified by the hook. The
+ * string must not end with an LF, and must not contain an LF.
+ */
+ public void sendMessage(final String what) {
+ sendMessage("remote", what);
+ }
+
+ private void sendMessage(final String type, final String what) {
+ if (msgs != null)
+ msgs.println(type + ": " + what);
+ }
+
+ /**
+ * Execute the receive task on the socket.
+ *
+ * @param input
+ * raw input to read client commands and pack data from. Caller
+ * must ensure the input is buffered, otherwise read performance
+ * may suffer.
+ * @param output
+ * response back to the Git network client. Caller must ensure
+ * the output is buffered, otherwise write performance may
+ * suffer.
+ * @param messages
+ * secondary "notice" channel to send additional messages out
+ * through. When run over SSH this should be tied back to the
+ * standard error channel of the command execution. For most
+ * other network connections this should be null.
+ * @throws IOException
+ */
+ public void receive(final InputStream input, final OutputStream output,
+ final OutputStream messages) throws IOException {
+ try {
+ rawIn = input;
+ rawOut = output;
+
+ if (timeout > 0) {
+ final Thread caller = Thread.currentThread();
+ timer = new InterruptTimer(caller.getName() + "-Timer");
+ timeoutIn = new TimeoutInputStream(rawIn, timer);
+ TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);
+ timeoutIn.setTimeout(timeout * 1000);
+ o.setTimeout(timeout * 1000);
+ rawIn = timeoutIn;
+ rawOut = o;
+ }
+
+ pckIn = new PacketLineIn(rawIn);
+ pckOut = new PacketLineOut(rawOut);
+ if (messages != null) {
+ msgs = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(messages, Constants.CHARSET),
+ 8192)) {
+ @Override
+ public void println() {
+ print('\n');
+ }
+ };
+ }
+
+ enabledCapablities = new HashSet<String>();
+ commands = new ArrayList<ReceiveCommand>();
+
+ service();
+ } finally {
+ try {
+ if (msgs != null) {
+ msgs.flush();
+ }
+ } finally {
+ unlockPack();
+ timeoutIn = null;
+ rawIn = null;
+ rawOut = null;
+ pckIn = null;
+ pckOut = null;
+ msgs = null;
+ refs = null;
+ enabledCapablities = null;
+ commands = null;
+ if (timer != null) {
+ try {
+ timer.terminate();
+ } finally {
+ timer = null;
+ }
+ }
+ }
+ }
+ }
+
+ private void service() throws IOException {
+ sendAdvertisedRefs();
+ recvCommands();
+ if (!commands.isEmpty()) {
+ enableCapabilities();
+
+ if (needPack()) {
+ try {
+ receivePack();
+ if (isCheckReceivedObjects())
+ checkConnectivity();
+ unpackError = null;
+ } catch (IOException err) {
+ unpackError = err;
+ } catch (RuntimeException err) {
+ unpackError = err;
+ } catch (Error err) {
+ unpackError = err;
+ }
+ }
+
+ if (unpackError == null) {
+ validateCommands();
+ executeCommands();
+ }
+ unlockPack();
+
+ if (reportStatus) {
+ sendStatusReport(true, new Reporter() {
+ void sendString(final String s) throws IOException {
+ pckOut.writeString(s + "\n");
+ }
+ });
+ pckOut.end();
+ } else if (msgs != null) {
+ sendStatusReport(false, new Reporter() {
+ void sendString(final String s) throws IOException {
+ msgs.println(s);
+ }
+ });
+ msgs.flush();
+ }
+
+ postReceive.onPostReceive(this, filterCommands(Result.OK));
+ }
+ }
+
+ private void unlockPack() {
+ if (packLock != null) {
+ packLock.unlock();
+ packLock = null;
+ }
+ }
+
+ private void sendAdvertisedRefs() throws IOException {
+ final RevFlag advertised = walk.newFlag("ADVERTISED");
+ final RefAdvertiser adv = new RefAdvertiser(pckOut, walk, advertised);
+ adv.advertiseCapability(CAPABILITY_DELETE_REFS);
+ adv.advertiseCapability(CAPABILITY_REPORT_STATUS);
+ if (allowOfsDelta)
+ adv.advertiseCapability(CAPABILITY_OFS_DELTA);
+ refs = new HashMap<String, Ref>(db.getAllRefs());
+ final Ref head = refs.remove(Constants.HEAD);
+ adv.send(refs.values());
+ if (head != null && head.getName().equals(head.getOrigName()))
+ adv.advertiseHave(head.getObjectId());
+ adv.includeAdditionalHaves();
+ if (adv.isEmpty())
+ adv.advertiseId(ObjectId.zeroId(), "capabilities^{}");
+ pckOut.end();
+ }
+
+ private void recvCommands() throws IOException {
+ for (;;) {
+ String line;
+ try {
+ line = pckIn.readStringRaw();
+ } catch (EOFException eof) {
+ if (commands.isEmpty())
+ return;
+ throw eof;
+ }
+ if (line == PacketLineIn.END)
+ break;
+
+ if (commands.isEmpty()) {
+ final int nul = line.indexOf('\0');
+ if (nul >= 0) {
+ for (String c : line.substring(nul + 1).split(" "))
+ enabledCapablities.add(c);
+ line = line.substring(0, nul);
+ }
+ }
+
+ if (line.length() < 83) {
+ final String m = "error: invalid protocol: wanted 'old new ref'";
+ sendError(m);
+ throw new PackProtocolException(m);
+ }
+
+ final ObjectId oldId = ObjectId.fromString(line.substring(0, 40));
+ final ObjectId newId = ObjectId.fromString(line.substring(41, 81));
+ final String name = line.substring(82);
+ final ReceiveCommand cmd = new ReceiveCommand(oldId, newId, name);
+ cmd.setRef(refs.get(cmd.getRefName()));
+ commands.add(cmd);
+ }
+ }
+
+ private void enableCapabilities() {
+ reportStatus = enabledCapablities.contains(CAPABILITY_REPORT_STATUS);
+ }
+
+ private boolean needPack() {
+ for (final ReceiveCommand cmd : commands) {
+ if (cmd.getType() != ReceiveCommand.Type.DELETE)
+ return true;
+ }
+ return false;
+ }
+
+ private void receivePack() throws IOException {
+ // It might take the client a while to pack the objects it needs
+ // to send to us. We should increase our timeout so we don't
+ // abort while the client is computing.
+ //
+ if (timeoutIn != null)
+ timeoutIn.setTimeout(10 * timeout * 1000);
+
+ final IndexPack ip = IndexPack.create(db, rawIn);
+ ip.setFixThin(true);
+ ip.setObjectChecking(isCheckReceivedObjects());
+ ip.index(NullProgressMonitor.INSTANCE);
+
+ String lockMsg = "jgit receive-pack";
+ if (getRefLogIdent() != null)
+ lockMsg += " from " + getRefLogIdent().toExternalString();
+ packLock = ip.renameAndOpenPack(lockMsg);
+
+ if (timeoutIn != null)
+ timeoutIn.setTimeout(timeout * 1000);
+ }
+
+ private void checkConnectivity() throws IOException {
+ final ObjectWalk ow = new ObjectWalk(db);
+ for (final ReceiveCommand cmd : commands) {
+ if (cmd.getResult() != Result.NOT_ATTEMPTED)
+ continue;
+ if (cmd.getType() == ReceiveCommand.Type.DELETE)
+ continue;
+ ow.markStart(ow.parseAny(cmd.getNewId()));
+ }
+ for (final Ref ref : refs.values())
+ ow.markUninteresting(ow.parseAny(ref.getObjectId()));
+ ow.checkConnectivity();
+ }
+
+ private void validateCommands() {
+ for (final ReceiveCommand cmd : commands) {
+ final Ref ref = cmd.getRef();
+ if (cmd.getResult() != Result.NOT_ATTEMPTED)
+ continue;
+
+ if (cmd.getType() == ReceiveCommand.Type.DELETE
+ && !isAllowDeletes()) {
+ // Deletes are not supported on this repository.
+ //
+ cmd.setResult(Result.REJECTED_NODELETE);
+ continue;
+ }
+
+ if (cmd.getType() == ReceiveCommand.Type.CREATE) {
+ if (!isAllowCreates()) {
+ cmd.setResult(Result.REJECTED_NOCREATE);
+ continue;
+ }
+
+ if (ref != null && !isAllowNonFastForwards()) {
+ // Creation over an existing ref is certainly not going
+ // to be a fast-forward update. We can reject it early.
+ //
+ cmd.setResult(Result.REJECTED_NONFASTFORWARD);
+ continue;
+ }
+
+ if (ref != null) {
+ // A well behaved client shouldn't have sent us an
+ // update command for a ref we advertised to it.
+ //
+ cmd.setResult(Result.REJECTED_OTHER_REASON, "ref exists");
+ continue;
+ }
+ }
+
+ if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null
+ && !ObjectId.zeroId().equals(cmd.getOldId())
+ && !ref.getObjectId().equals(cmd.getOldId())) {
+ // Delete commands can be sent with the old id matching our
+ // advertised value, *OR* with the old id being 0{40}. Any
+ // other requested old id is invalid.
+ //
+ cmd.setResult(Result.REJECTED_OTHER_REASON,
+ "invalid old id sent");
+ continue;
+ }
+
+ if (cmd.getType() == ReceiveCommand.Type.UPDATE) {
+ if (ref == null) {
+ // The ref must have been advertised in order to be updated.
+ //
+ cmd.setResult(Result.REJECTED_OTHER_REASON, "no such ref");
+ continue;
+ }
+
+ if (!ref.getObjectId().equals(cmd.getOldId())) {
+ // A properly functioning client will send the same
+ // object id we advertised.
+ //
+ cmd.setResult(Result.REJECTED_OTHER_REASON,
+ "invalid old id sent");
+ continue;
+ }
+
+ // Is this possibly a non-fast-forward style update?
+ //
+ RevObject oldObj, newObj;
+ try {
+ oldObj = walk.parseAny(cmd.getOldId());
+ } catch (IOException e) {
+ cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd
+ .getOldId().name());
+ continue;
+ }
+
+ try {
+ newObj = walk.parseAny(cmd.getNewId());
+ } catch (IOException e) {
+ cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd
+ .getNewId().name());
+ continue;
+ }
+
+ if (oldObj instanceof RevCommit && newObj instanceof RevCommit) {
+ try {
+ if (!walk.isMergedInto((RevCommit) oldObj,
+ (RevCommit) newObj)) {
+ cmd
+ .setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD);
+ }
+ } catch (MissingObjectException e) {
+ cmd.setResult(Result.REJECTED_MISSING_OBJECT, e
+ .getMessage());
+ } catch (IOException e) {
+ cmd.setResult(Result.REJECTED_OTHER_REASON);
+ }
+ } else {
+ cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD);
+ }
+ }
+
+ if (!cmd.getRefName().startsWith(Constants.R_REFS)
+ || !Repository.isValidRefName(cmd.getRefName())) {
+ cmd.setResult(Result.REJECTED_OTHER_REASON, "funny refname");
+ }
+ }
+ }
+
+ private void executeCommands() {
+ preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED));
+ for (final ReceiveCommand cmd : filterCommands(Result.NOT_ATTEMPTED))
+ execute(cmd);
+ }
+
+ private void execute(final ReceiveCommand cmd) {
+ try {
+ final RefUpdate ru = db.updateRef(cmd.getRefName());
+ ru.setRefLogIdent(getRefLogIdent());
+ switch (cmd.getType()) {
+ case DELETE:
+ if (!ObjectId.zeroId().equals(cmd.getOldId())) {
+ // We can only do a CAS style delete if the client
+ // didn't bork its delete request by sending the
+ // wrong zero id rather than the advertised one.
+ //
+ ru.setExpectedOldObjectId(cmd.getOldId());
+ }
+ ru.setForceUpdate(true);
+ status(cmd, ru.delete(walk));
+ break;
+
+ case CREATE:
+ case UPDATE:
+ case UPDATE_NONFASTFORWARD:
+ ru.setForceUpdate(isAllowNonFastForwards());
+ ru.setExpectedOldObjectId(cmd.getOldId());
+ ru.setNewObjectId(cmd.getNewId());
+ ru.setRefLogMessage("push", true);
+ status(cmd, ru.update(walk));
+ break;
+ }
+ } catch (IOException err) {
+ cmd.setResult(Result.REJECTED_OTHER_REASON, "lock error: "
+ + err.getMessage());
+ }
+ }
+
+ private void status(final ReceiveCommand cmd, final RefUpdate.Result result) {
+ switch (result) {
+ case NOT_ATTEMPTED:
+ cmd.setResult(Result.NOT_ATTEMPTED);
+ break;
+
+ case LOCK_FAILURE:
+ case IO_FAILURE:
+ cmd.setResult(Result.LOCK_FAILURE);
+ break;
+
+ case NO_CHANGE:
+ case NEW:
+ case FORCED:
+ case FAST_FORWARD:
+ cmd.setResult(Result.OK);
+ break;
+
+ case REJECTED:
+ cmd.setResult(Result.REJECTED_NONFASTFORWARD);
+ break;
+
+ case REJECTED_CURRENT_BRANCH:
+ cmd.setResult(Result.REJECTED_CURRENT_BRANCH);
+ break;
+
+ default:
+ cmd.setResult(Result.REJECTED_OTHER_REASON, result.name());
+ break;
+ }
+ }
+
+ private List<ReceiveCommand> filterCommands(final Result want) {
+ final List<ReceiveCommand> r = new ArrayList<ReceiveCommand>(commands
+ .size());
+ for (final ReceiveCommand cmd : commands) {
+ if (cmd.getResult() == want)
+ r.add(cmd);
+ }
+ return r;
+ }
+
+ private void sendStatusReport(final boolean forClient, final Reporter out)
+ throws IOException {
+ if (unpackError != null) {
+ out.sendString("unpack error " + unpackError.getMessage());
+ if (forClient) {
+ for (final ReceiveCommand cmd : commands) {
+ out.sendString("ng " + cmd.getRefName()
+ + " n/a (unpacker error)");
+ }
+ }
+ return;
+ }
+
+ if (forClient)
+ out.sendString("unpack ok");
+ for (final ReceiveCommand cmd : commands) {
+ if (cmd.getResult() == Result.OK) {
+ if (forClient)
+ out.sendString("ok " + cmd.getRefName());
+ continue;
+ }
+
+ final StringBuilder r = new StringBuilder();
+ r.append("ng ");
+ r.append(cmd.getRefName());
+ r.append(" ");
+
+ switch (cmd.getResult()) {
+ case NOT_ATTEMPTED:
+ r.append("server bug; ref not processed");
+ break;
+
+ case REJECTED_NOCREATE:
+ r.append("creation prohibited");
+ break;
+
+ case REJECTED_NODELETE:
+ r.append("deletion prohibited");
+ break;
+
+ case REJECTED_NONFASTFORWARD:
+ r.append("non-fast forward");
+ break;
+
+ case REJECTED_CURRENT_BRANCH:
+ r.append("branch is currently checked out");
+ break;
+
+ case REJECTED_MISSING_OBJECT:
+ if (cmd.getMessage() == null)
+ r.append("missing object(s)");
+ else if (cmd.getMessage().length() == 2 * Constants.OBJECT_ID_LENGTH)
+ r.append("object " + cmd.getMessage() + " missing");
+ else
+ r.append(cmd.getMessage());
+ break;
+
+ case REJECTED_OTHER_REASON:
+ if (cmd.getMessage() == null)
+ r.append("unspecified reason");
+ else
+ r.append(cmd.getMessage());
+ break;
+
+ case LOCK_FAILURE:
+ r.append("failed to lock");
+ break;
+
+ case OK:
+ // We shouldn't have reached this case (see 'ok' case above).
+ continue;
+ }
+ out.sendString(r.toString());
+ }
+ }
+
+ static abstract class Reporter {
+ abstract void sendString(String s) throws IOException;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
new file mode 100644
index 0000000000..dfbd891b0b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.AlternateRepositoryDatabase;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefComparator;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Support for the start of {@link UploadPack} and {@link ReceivePack}. */
+class RefAdvertiser {
+ private final PacketLineOut pckOut;
+
+ private final RevWalk walk;
+
+ private final RevFlag ADVERTISED;
+
+ private final StringBuilder tmpLine = new StringBuilder(100);
+
+ private final char[] tmpId = new char[2 * Constants.OBJECT_ID_LENGTH];
+
+ private final Set<String> capablities = new LinkedHashSet<String>();
+
+ private boolean derefTags;
+
+ private boolean first = true;
+
+ RefAdvertiser(final PacketLineOut out, final RevWalk protoWalk,
+ final RevFlag advertisedFlag) {
+ pckOut = out;
+ walk = protoWalk;
+ ADVERTISED = advertisedFlag;
+ }
+
+ void setDerefTags(final boolean deref) {
+ derefTags = deref;
+ }
+
+ void advertiseCapability(String name) {
+ capablities.add(name);
+ }
+
+ void send(final Collection<Ref> refs) throws IOException {
+ for (final Ref r : RefComparator.sort(refs)) {
+ final RevObject obj = parseAnyOrNull(r.getObjectId());
+ if (obj != null) {
+ advertiseAny(obj, r.getOrigName());
+ if (derefTags && obj instanceof RevTag)
+ advertiseTag((RevTag) obj, r.getOrigName() + "^{}");
+ }
+ }
+ }
+
+ void advertiseHave(AnyObjectId id) throws IOException {
+ RevObject obj = parseAnyOrNull(id);
+ if (obj != null) {
+ advertiseAnyOnce(obj, ".have");
+ if (obj instanceof RevTag)
+ advertiseAnyOnce(((RevTag) obj).getObject(), ".have");
+ }
+ }
+
+ void includeAdditionalHaves() throws IOException {
+ additionalHaves(walk.getRepository().getObjectDatabase());
+ }
+
+ private void additionalHaves(final ObjectDatabase db) throws IOException {
+ if (db instanceof AlternateRepositoryDatabase)
+ additionalHaves(((AlternateRepositoryDatabase) db).getRepository());
+ for (ObjectDatabase alt : db.getAlternates())
+ additionalHaves(alt);
+ }
+
+ private void additionalHaves(final Repository alt) throws IOException {
+ for (final Ref r : alt.getAllRefs().values())
+ advertiseHave(r.getObjectId());
+ }
+
+ boolean isEmpty() {
+ return first;
+ }
+
+ private RevObject parseAnyOrNull(final AnyObjectId id) {
+ if (id == null)
+ return null;
+ try {
+ return walk.parseAny(id);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ private void advertiseAnyOnce(final RevObject obj, final String refName)
+ throws IOException {
+ if (!obj.has(ADVERTISED))
+ advertiseAny(obj, refName);
+ }
+
+ private void advertiseAny(final RevObject obj, final String refName)
+ throws IOException {
+ obj.add(ADVERTISED);
+ advertiseId(obj, refName);
+ }
+
+ private void advertiseTag(final RevTag tag, final String refName)
+ throws IOException {
+ RevObject o = tag;
+ do {
+ // Fully unwrap here so later on we have these already parsed.
+ final RevObject target = ((RevTag) o).getObject();
+ try {
+ walk.parseHeaders(target);
+ } catch (IOException err) {
+ return;
+ }
+ target.add(ADVERTISED);
+ o = target;
+ } while (o instanceof RevTag);
+ advertiseAny(tag.getObject(), refName);
+ }
+
+ void advertiseId(final AnyObjectId id, final String refName)
+ throws IOException {
+ tmpLine.setLength(0);
+ id.copyTo(tmpId, tmpLine);
+ tmpLine.append(' ');
+ tmpLine.append(refName);
+ if (first) {
+ first = false;
+ if (!capablities.isEmpty()) {
+ tmpLine.append('\0');
+ for (final String capName : capablities) {
+ tmpLine.append(' ');
+ tmpLine.append(capName);
+ }
+ tmpLine.append(' ');
+ }
+ }
+ tmpLine.append('\n');
+ pckOut.writeString(tmpLine.toString());
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
new file mode 100644
index 0000000000..1949ef0878
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+
+/**
+ * Describes how refs in one repository copy into another repository.
+ * <p>
+ * A ref specification provides matching support and limited rules to rewrite a
+ * reference in one repository to another reference in another repository.
+ */
+public class RefSpec {
+ /**
+ * Suffix for wildcard ref spec component, that indicate matching all refs
+ * with specified prefix.
+ */
+ public static final String WILDCARD_SUFFIX = "/*";
+
+ /**
+ * Check whether provided string is a wildcard ref spec component.
+ *
+ * @param s
+ * ref spec component - string to test. Can be null.
+ * @return true if provided string is a wildcard ref spec component.
+ */
+ public static boolean isWildcard(final String s) {
+ return s != null && s.endsWith(WILDCARD_SUFFIX);
+ }
+
+ /** Does this specification ask for forced updated (rewind/reset)? */
+ private boolean force;
+
+ /** Is this specification actually a wildcard match? */
+ private boolean wildcard;
+
+ /** Name of the ref(s) we would copy from. */
+ private String srcName;
+
+ /** Name of the ref(s) we would copy into. */
+ private String dstName;
+
+ /**
+ * Construct an empty RefSpec.
+ * <p>
+ * A newly created empty RefSpec is not suitable for use in most
+ * applications, as at least one field must be set to match a source name.
+ */
+ public RefSpec() {
+ force = false;
+ wildcard = false;
+ srcName = Constants.HEAD;
+ dstName = null;
+ }
+
+ /**
+ * Parse a ref specification for use during transport operations.
+ * <p>
+ * Specifications are typically one of the following forms:
+ * <ul>
+ * <li><code>refs/head/master</code></li>
+ * <li><code>refs/head/master:refs/remotes/origin/master</code></li>
+ * <li><code>refs/head/*:refs/remotes/origin/*</code></li>
+ * <li><code>+refs/head/master</code></li>
+ * <li><code>+refs/head/master:refs/remotes/origin/master</code></li>
+ * <li><code>+refs/head/*:refs/remotes/origin/*</code></li>
+ * <li><code>:refs/head/master</code></li>
+ * </ul>
+ *
+ * @param spec
+ * string describing the specification.
+ * @throws IllegalArgumentException
+ * the specification is invalid.
+ */
+ public RefSpec(final String spec) {
+ String s = spec;
+ if (s.startsWith("+")) {
+ force = true;
+ s = s.substring(1);
+ }
+
+ final int c = s.lastIndexOf(':');
+ if (c == 0) {
+ s = s.substring(1);
+ if (isWildcard(s))
+ throw new IllegalArgumentException("Invalid wildcards " + spec);
+ dstName = s;
+ } else if (c > 0) {
+ srcName = s.substring(0, c);
+ dstName = s.substring(c + 1);
+ if (isWildcard(srcName) && isWildcard(dstName))
+ wildcard = true;
+ else if (isWildcard(srcName) || isWildcard(dstName))
+ throw new IllegalArgumentException("Invalid wildcards " + spec);
+ } else {
+ if (isWildcard(s))
+ throw new IllegalArgumentException("Invalid wildcards " + spec);
+ srcName = s;
+ }
+ }
+
+ private RefSpec(final RefSpec p) {
+ force = p.isForceUpdate();
+ wildcard = p.isWildcard();
+ srcName = p.getSource();
+ dstName = p.getDestination();
+ }
+
+ /**
+ * Check if this specification wants to forcefully update the destination.
+ *
+ * @return true if this specification asks for updates without merge tests.
+ */
+ public boolean isForceUpdate() {
+ return force;
+ }
+
+ /**
+ * Create a new RefSpec with a different force update setting.
+ *
+ * @param forceUpdate
+ * new value for force update in the returned instance.
+ * @return a new RefSpec with force update as specified.
+ */
+ public RefSpec setForceUpdate(final boolean forceUpdate) {
+ final RefSpec r = new RefSpec(this);
+ r.force = forceUpdate;
+ return r;
+ }
+
+ /**
+ * Check if this specification is actually a wildcard pattern.
+ * <p>
+ * If this is a wildcard pattern then the source and destination names
+ * returned by {@link #getSource()} and {@link #getDestination()} will not
+ * be actual ref names, but instead will be patterns.
+ *
+ * @return true if this specification could match more than one ref.
+ */
+ public boolean isWildcard() {
+ return wildcard;
+ }
+
+ /**
+ * Get the source ref description.
+ * <p>
+ * During a fetch this is the name of the ref on the remote repository we
+ * are fetching from. During a push this is the name of the ref on the local
+ * repository we are pushing out from.
+ *
+ * @return name (or wildcard pattern) to match the source ref.
+ */
+ public String getSource() {
+ return srcName;
+ }
+
+ /**
+ * Create a new RefSpec with a different source name setting.
+ *
+ * @param source
+ * new value for source in the returned instance.
+ * @return a new RefSpec with source as specified.
+ * @throws IllegalStateException
+ * There is already a destination configured, and the wildcard
+ * status of the existing destination disagrees with the
+ * wildcard status of the new source.
+ */
+ public RefSpec setSource(final String source) {
+ final RefSpec r = new RefSpec(this);
+ r.srcName = source;
+ if (isWildcard(r.srcName) && r.dstName == null)
+ throw new IllegalStateException("Destination is not a wildcard.");
+ if (isWildcard(r.srcName) != isWildcard(r.dstName))
+ throw new IllegalStateException("Source/Destination must match.");
+ return r;
+ }
+
+ /**
+ * Get the destination ref description.
+ * <p>
+ * During a fetch this is the local tracking branch that will be updated
+ * with the new ObjectId after fetching is complete. During a push this is
+ * the remote ref that will be updated by the remote's receive-pack process.
+ * <p>
+ * If null during a fetch no tracking branch should be updated and the
+ * ObjectId should be stored transiently in order to prepare a merge.
+ * <p>
+ * If null during a push, use {@link #getSource()} instead.
+ *
+ * @return name (or wildcard) pattern to match the destination ref.
+ */
+ public String getDestination() {
+ return dstName;
+ }
+
+ /**
+ * Create a new RefSpec with a different destination name setting.
+ *
+ * @param destination
+ * new value for destination in the returned instance.
+ * @return a new RefSpec with destination as specified.
+ * @throws IllegalStateException
+ * There is already a source configured, and the wildcard status
+ * of the existing source disagrees with the wildcard status of
+ * the new destination.
+ */
+ public RefSpec setDestination(final String destination) {
+ final RefSpec r = new RefSpec(this);
+ r.dstName = destination;
+ if (isWildcard(r.dstName) && r.srcName == null)
+ throw new IllegalStateException("Source is not a wildcard.");
+ if (isWildcard(r.srcName) != isWildcard(r.dstName))
+ throw new IllegalStateException("Source/Destination must match.");
+ return r;
+ }
+
+ /**
+ * Create a new RefSpec with a different source/destination name setting.
+ *
+ * @param source
+ * new value for source in the returned instance.
+ * @param destination
+ * new value for destination in the returned instance.
+ * @return a new RefSpec with destination as specified.
+ * @throws IllegalArgumentException
+ * The wildcard status of the new source disagrees with the
+ * wildcard status of the new destination.
+ */
+ public RefSpec setSourceDestination(final String source,
+ final String destination) {
+ if (isWildcard(source) != isWildcard(destination))
+ throw new IllegalArgumentException("Source/Destination must match.");
+ final RefSpec r = new RefSpec(this);
+ r.wildcard = isWildcard(source);
+ r.srcName = source;
+ r.dstName = destination;
+ return r;
+ }
+
+ /**
+ * Does this specification's source description match the ref name?
+ *
+ * @param r
+ * ref name that should be tested.
+ * @return true if the names match; false otherwise.
+ */
+ public boolean matchSource(final String r) {
+ return match(r, getSource());
+ }
+
+ /**
+ * Does this specification's source description match the ref?
+ *
+ * @param r
+ * ref whose name should be tested.
+ * @return true if the names match; false otherwise.
+ */
+ public boolean matchSource(final Ref r) {
+ return match(r.getName(), getSource());
+ }
+
+ /**
+ * Does this specification's destination description match the ref name?
+ *
+ * @param r
+ * ref name that should be tested.
+ * @return true if the names match; false otherwise.
+ */
+ public boolean matchDestination(final String r) {
+ return match(r, getDestination());
+ }
+
+ /**
+ * Does this specification's destination description match the ref?
+ *
+ * @param r
+ * ref whose name should be tested.
+ * @return true if the names match; false otherwise.
+ */
+ public boolean matchDestination(final Ref r) {
+ return match(r.getName(), getDestination());
+ }
+
+ /**
+ * Expand this specification to exactly match a ref name.
+ * <p>
+ * Callers must first verify the passed ref name matches this specification,
+ * otherwise expansion results may be unpredictable.
+ *
+ * @param r
+ * a ref name that matched our source specification. Could be a
+ * wildcard also.
+ * @return a new specification expanded from provided ref name. Result
+ * specification is wildcard if and only if provided ref name is
+ * wildcard.
+ */
+ public RefSpec expandFromSource(final String r) {
+ return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this;
+ }
+
+ private RefSpec expandFromSourceImp(final String name) {
+ final String psrc = srcName, pdst = dstName;
+ wildcard = false;
+ srcName = name;
+ dstName = pdst.substring(0, pdst.length() - 1)
+ + name.substring(psrc.length() - 1);
+ return this;
+ }
+
+ /**
+ * Expand this specification to exactly match a ref.
+ * <p>
+ * Callers must first verify the passed ref matches this specification,
+ * otherwise expansion results may be unpredictable.
+ *
+ * @param r
+ * a ref that matched our source specification. Could be a
+ * wildcard also.
+ * @return a new specification expanded from provided ref name. Result
+ * specification is wildcard if and only if provided ref name is
+ * wildcard.
+ */
+ public RefSpec expandFromSource(final Ref r) {
+ return expandFromSource(r.getName());
+ }
+
+ /**
+ * Expand this specification to exactly match a ref name.
+ * <p>
+ * Callers must first verify the passed ref name matches this specification,
+ * otherwise expansion results may be unpredictable.
+ *
+ * @param r
+ * a ref name that matched our destination specification. Could
+ * be a wildcard also.
+ * @return a new specification expanded from provided ref name. Result
+ * specification is wildcard if and only if provided ref name is
+ * wildcard.
+ */
+ public RefSpec expandFromDestination(final String r) {
+ return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this;
+ }
+
+ private RefSpec expandFromDstImp(final String name) {
+ final String psrc = srcName, pdst = dstName;
+ wildcard = false;
+ srcName = psrc.substring(0, psrc.length() - 1)
+ + name.substring(pdst.length() - 1);
+ dstName = name;
+ return this;
+ }
+
+ /**
+ * Expand this specification to exactly match a ref.
+ * <p>
+ * Callers must first verify the passed ref matches this specification,
+ * otherwise expansion results may be unpredictable.
+ *
+ * @param r
+ * a ref that matched our destination specification.
+ * @return a new specification expanded from provided ref name. Result
+ * specification is wildcard if and only if provided ref name is
+ * wildcard.
+ */
+ public RefSpec expandFromDestination(final Ref r) {
+ return expandFromDestination(r.getName());
+ }
+
+ private boolean match(final String refName, final String s) {
+ if (s == null)
+ return false;
+ if (isWildcard())
+ return refName.startsWith(s.substring(0, s.length() - 1));
+ return refName.equals(s);
+ }
+
+ public int hashCode() {
+ int hc = 0;
+ if (getSource() != null)
+ hc = hc * 31 + getSource().hashCode();
+ if (getDestination() != null)
+ hc = hc * 31 + getDestination().hashCode();
+ return hc;
+ }
+
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof RefSpec))
+ return false;
+ final RefSpec b = (RefSpec) obj;
+ if (isForceUpdate() != b.isForceUpdate())
+ return false;
+ if (isWildcard() != b.isWildcard())
+ return false;
+ if (!eq(getSource(), b.getSource()))
+ return false;
+ if (!eq(getDestination(), b.getDestination()))
+ return false;
+ return true;
+ }
+
+ private static boolean eq(final String a, final String b) {
+ if (a == b)
+ return true;
+ if (a == null || b == null)
+ return false;
+ return a.equals(b);
+ }
+
+ public String toString() {
+ final StringBuilder r = new StringBuilder();
+ if (isForceUpdate())
+ r.append('+');
+ if (getSource() != null)
+ r.append(getSource());
+ if (getDestination() != null) {
+ r.append(':');
+ r.append(getDestination());
+ }
+ return r.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
new file mode 100644
index 0000000000..f05b8c6a30
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * A remembered remote repository, including URLs and RefSpecs.
+ * <p>
+ * A remote configuration remembers one or more URLs for a frequently accessed
+ * remote repository as well as zero or more fetch and push specifications
+ * describing how refs should be transferred between this repository and the
+ * remote repository.
+ */
+public class RemoteConfig {
+ private static final String SECTION = "remote";
+
+ private static final String KEY_URL = "url";
+
+ private static final String KEY_PUSHURL = "pushurl";
+
+ private static final String KEY_FETCH = "fetch";
+
+ private static final String KEY_PUSH = "push";
+
+ private static final String KEY_UPLOADPACK = "uploadpack";
+
+ private static final String KEY_RECEIVEPACK = "receivepack";
+
+ private static final String KEY_TAGOPT = "tagopt";
+
+ private static final String KEY_MIRROR = "mirror";
+
+ private static final String KEY_TIMEOUT = "timeout";
+
+ private static final boolean DEFAULT_MIRROR = false;
+
+ /** Default value for {@link #getUploadPack()} if not specified. */
+ public static final String DEFAULT_UPLOAD_PACK = "git-upload-pack";
+
+ /** Default value for {@link #getReceivePack()} if not specified. */
+ public static final String DEFAULT_RECEIVE_PACK = "git-receive-pack";
+
+ /**
+ * Parse all remote blocks in an existing configuration file, looking for
+ * remotes configuration.
+ *
+ * @param rc
+ * the existing configuration to get the remote settings from.
+ * The configuration must already be loaded into memory.
+ * @return all remotes configurations existing in provided repository
+ * configuration. Returned configurations are ordered
+ * lexicographically by names.
+ * @throws URISyntaxException
+ * one of the URIs within the remote's configuration is invalid.
+ */
+ public static List<RemoteConfig> getAllRemoteConfigs(final Config rc)
+ throws URISyntaxException {
+ final List<String> names = new ArrayList<String>(rc
+ .getSubsections(SECTION));
+ Collections.sort(names);
+
+ final List<RemoteConfig> result = new ArrayList<RemoteConfig>(names
+ .size());
+ for (final String name : names)
+ result.add(new RemoteConfig(rc, name));
+ return result;
+ }
+
+ private String name;
+
+ private List<URIish> uris;
+
+ private List<URIish> pushURIs;
+
+ private List<RefSpec> fetch;
+
+ private List<RefSpec> push;
+
+ private String uploadpack;
+
+ private String receivepack;
+
+ private TagOpt tagopt;
+
+ private boolean mirror;
+
+ private int timeout;
+
+ /**
+ * Parse a remote block from an existing configuration file.
+ * <p>
+ * This constructor succeeds even if the requested remote is not defined
+ * within the supplied configuration file. If that occurs then there will be
+ * no URIs and no ref specifications known to the new instance.
+ *
+ * @param rc
+ * the existing configuration to get the remote settings from.
+ * The configuration must already be loaded into memory.
+ * @param remoteName
+ * subsection key indicating the name of this remote.
+ * @throws URISyntaxException
+ * one of the URIs within the remote's configuration is invalid.
+ */
+ public RemoteConfig(final Config rc, final String remoteName)
+ throws URISyntaxException {
+ name = remoteName;
+
+ String[] vlst;
+ String val;
+
+ vlst = rc.getStringList(SECTION, name, KEY_URL);
+ uris = new ArrayList<URIish>(vlst.length);
+ for (final String s : vlst)
+ uris.add(new URIish(s));
+
+ vlst = rc.getStringList(SECTION, name, KEY_PUSHURL);
+ pushURIs = new ArrayList<URIish>(vlst.length);
+ for (final String s : vlst)
+ pushURIs.add(new URIish(s));
+
+ vlst = rc.getStringList(SECTION, name, KEY_FETCH);
+ fetch = new ArrayList<RefSpec>(vlst.length);
+ for (final String s : vlst)
+ fetch.add(new RefSpec(s));
+
+ vlst = rc.getStringList(SECTION, name, KEY_PUSH);
+ push = new ArrayList<RefSpec>(vlst.length);
+ for (final String s : vlst)
+ push.add(new RefSpec(s));
+
+ val = rc.getString(SECTION, name, KEY_UPLOADPACK);
+ if (val == null)
+ val = DEFAULT_UPLOAD_PACK;
+ uploadpack = val;
+
+ val = rc.getString(SECTION, name, KEY_RECEIVEPACK);
+ if (val == null)
+ val = DEFAULT_RECEIVE_PACK;
+ receivepack = val;
+
+ val = rc.getString(SECTION, name, KEY_TAGOPT);
+ tagopt = TagOpt.fromOption(val);
+ mirror = rc.getBoolean(SECTION, name, KEY_MIRROR, DEFAULT_MIRROR);
+ timeout = rc.getInt(SECTION, name, KEY_TIMEOUT, 0);
+ }
+
+ /**
+ * Update this remote's definition within the configuration.
+ *
+ * @param rc
+ * the configuration file to store ourselves into.
+ */
+ public void update(final Config rc) {
+ final List<String> vlst = new ArrayList<String>();
+
+ vlst.clear();
+ for (final URIish u : getURIs())
+ vlst.add(u.toPrivateString());
+ rc.setStringList(SECTION, getName(), KEY_URL, vlst);
+
+ vlst.clear();
+ for (final URIish u : getPushURIs())
+ vlst.add(u.toPrivateString());
+ rc.setStringList(SECTION, getName(), KEY_PUSHURL, vlst);
+
+ vlst.clear();
+ for (final RefSpec u : getFetchRefSpecs())
+ vlst.add(u.toString());
+ rc.setStringList(SECTION, getName(), KEY_FETCH, vlst);
+
+ vlst.clear();
+ for (final RefSpec u : getPushRefSpecs())
+ vlst.add(u.toString());
+ rc.setStringList(SECTION, getName(), KEY_PUSH, vlst);
+
+ set(rc, KEY_UPLOADPACK, getUploadPack(), DEFAULT_UPLOAD_PACK);
+ set(rc, KEY_RECEIVEPACK, getReceivePack(), DEFAULT_RECEIVE_PACK);
+ set(rc, KEY_TAGOPT, getTagOpt().option(), TagOpt.AUTO_FOLLOW.option());
+ set(rc, KEY_MIRROR, mirror, DEFAULT_MIRROR);
+ set(rc, KEY_TIMEOUT, timeout, 0);
+ }
+
+ private void set(final Config rc, final String key,
+ final String currentValue, final String defaultValue) {
+ if (defaultValue.equals(currentValue))
+ unset(rc, key);
+ else
+ rc.setString(SECTION, getName(), key, currentValue);
+ }
+
+ private void set(final Config rc, final String key,
+ final boolean currentValue, final boolean defaultValue) {
+ if (defaultValue == currentValue)
+ unset(rc, key);
+ else
+ rc.setBoolean(SECTION, getName(), key, currentValue);
+ }
+
+ private void set(final Config rc, final String key, final int currentValue,
+ final int defaultValue) {
+ if (defaultValue == currentValue)
+ unset(rc, key);
+ else
+ rc.setInt(SECTION, getName(), key, currentValue);
+ }
+
+ private void unset(final Config rc, final String key) {
+ rc.unset(SECTION, getName(), key);
+ }
+
+ /**
+ * Get the local name this remote configuration is recognized as.
+ *
+ * @return name assigned by the user to this configuration block.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Get all configured URIs under this remote.
+ *
+ * @return the set of URIs known to this remote.
+ */
+ public List<URIish> getURIs() {
+ return Collections.unmodifiableList(uris);
+ }
+
+ /**
+ * Add a new URI to the end of the list of URIs.
+ *
+ * @param toAdd
+ * the new URI to add to this remote.
+ * @return true if the URI was added; false if it already exists.
+ */
+ public boolean addURI(final URIish toAdd) {
+ if (uris.contains(toAdd))
+ return false;
+ return uris.add(toAdd);
+ }
+
+ /**
+ * Remove a URI from the list of URIs.
+ *
+ * @param toRemove
+ * the URI to remove from this remote.
+ * @return true if the URI was added; false if it already exists.
+ */
+ public boolean removeURI(final URIish toRemove) {
+ return uris.remove(toRemove);
+ }
+
+ /**
+ * Get all configured push-only URIs under this remote.
+ *
+ * @return the set of URIs known to this remote.
+ */
+ public List<URIish> getPushURIs() {
+ return Collections.unmodifiableList(pushURIs);
+ }
+
+ /**
+ * Add a new push-only URI to the end of the list of URIs.
+ *
+ * @param toAdd
+ * the new URI to add to this remote.
+ * @return true if the URI was added; false if it already exists.
+ */
+ public boolean addPushURI(final URIish toAdd) {
+ if (pushURIs.contains(toAdd))
+ return false;
+ return pushURIs.add(toAdd);
+ }
+
+ /**
+ * Remove a push-only URI from the list of URIs.
+ *
+ * @param toRemove
+ * the URI to remove from this remote.
+ * @return true if the URI was added; false if it already exists.
+ */
+ public boolean removePushURI(final URIish toRemove) {
+ return pushURIs.remove(toRemove);
+ }
+
+ /**
+ * Remembered specifications for fetching from a repository.
+ *
+ * @return set of specs used by default when fetching.
+ */
+ public List<RefSpec> getFetchRefSpecs() {
+ return Collections.unmodifiableList(fetch);
+ }
+
+ /**
+ * Add a new fetch RefSpec to this remote.
+ *
+ * @param s
+ * the new specification to add.
+ * @return true if the specification was added; false if it already exists.
+ */
+ public boolean addFetchRefSpec(final RefSpec s) {
+ if (fetch.contains(s))
+ return false;
+ return fetch.add(s);
+ }
+
+ /**
+ * Override existing fetch specifications with new ones.
+ *
+ * @param specs
+ * list of fetch specifications to set. List is copied, it can be
+ * modified after this call.
+ */
+ public void setFetchRefSpecs(final List<RefSpec> specs) {
+ fetch.clear();
+ fetch.addAll(specs);
+ }
+
+ /**
+ * Override existing push specifications with new ones.
+ *
+ * @param specs
+ * list of push specifications to set. List is copied, it can be
+ * modified after this call.
+ */
+ public void setPushRefSpecs(final List<RefSpec> specs) {
+ push.clear();
+ push.addAll(specs);
+ }
+
+ /**
+ * Remove a fetch RefSpec from this remote.
+ *
+ * @param s
+ * the specification to remove.
+ * @return true if the specification existed and was removed.
+ */
+ public boolean removeFetchRefSpec(final RefSpec s) {
+ return fetch.remove(s);
+ }
+
+ /**
+ * Remembered specifications for pushing to a repository.
+ *
+ * @return set of specs used by default when pushing.
+ */
+ public List<RefSpec> getPushRefSpecs() {
+ return Collections.unmodifiableList(push);
+ }
+
+ /**
+ * Add a new push RefSpec to this remote.
+ *
+ * @param s
+ * the new specification to add.
+ * @return true if the specification was added; false if it already exists.
+ */
+ public boolean addPushRefSpec(final RefSpec s) {
+ if (push.contains(s))
+ return false;
+ return push.add(s);
+ }
+
+ /**
+ * Remove a push RefSpec from this remote.
+ *
+ * @param s
+ * the specification to remove.
+ * @return true if the specification existed and was removed.
+ */
+ public boolean removePushRefSpec(final RefSpec s) {
+ return push.remove(s);
+ }
+
+ /**
+ * Override for the location of 'git-upload-pack' on the remote system.
+ * <p>
+ * This value is only useful for an SSH style connection, where Git is
+ * asking the remote system to execute a program that provides the necessary
+ * network protocol.
+ *
+ * @return location of 'git-upload-pack' on the remote system. If no
+ * location has been configured the default of 'git-upload-pack' is
+ * returned instead.
+ */
+ public String getUploadPack() {
+ return uploadpack;
+ }
+
+ /**
+ * Override for the location of 'git-receive-pack' on the remote system.
+ * <p>
+ * This value is only useful for an SSH style connection, where Git is
+ * asking the remote system to execute a program that provides the necessary
+ * network protocol.
+ *
+ * @return location of 'git-receive-pack' on the remote system. If no
+ * location has been configured the default of 'git-receive-pack' is
+ * returned instead.
+ */
+ public String getReceivePack() {
+ return receivepack;
+ }
+
+ /**
+ * Get the description of how annotated tags should be treated during fetch.
+ *
+ * @return option indicating the behavior of annotated tags in fetch.
+ */
+ public TagOpt getTagOpt() {
+ return tagopt;
+ }
+
+ /**
+ * Set the description of how annotated tags should be treated on fetch.
+ *
+ * @param option
+ * method to use when handling annotated tags.
+ */
+ public void setTagOpt(final TagOpt option) {
+ tagopt = option != null ? option : TagOpt.AUTO_FOLLOW;
+ }
+
+ /**
+ * @return true if pushing to the remote automatically deletes remote refs
+ * which don't exist on the source side.
+ */
+ public boolean isMirror() {
+ return mirror;
+ }
+
+ /**
+ * Set the mirror flag to automatically delete remote refs.
+ *
+ * @param m
+ * true to automatically delete remote refs during push.
+ */
+ public void setMirror(final boolean m) {
+ mirror = m;
+ }
+
+ /** @return timeout (in seconds) before aborting an IO operation. */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Set the timeout before willing to abort an IO call.
+ *
+ * @param seconds
+ * number of seconds to wait (with no data transfer occurring)
+ * before aborting an IO read or write operation with this
+ * remote. A timeout of 0 will block indefinitely.
+ */
+ public void setTimeout(final int seconds) {
+ timeout = seconds;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
new file mode 100644
index 0000000000..b2aa6335d8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * Represent request and status of a remote ref update. Specification is
+ * provided by client, while status is handled by {@link PushProcess} class,
+ * being read-only for client.
+ * <p>
+ * Client can create instances of this class directly, basing on user
+ * specification and advertised refs ({@link Connection} or through
+ * {@link Transport} helper methods. Apply this specification on remote
+ * repository using
+ * {@link Transport#push(org.eclipse.jgit.lib.ProgressMonitor, java.util.Collection)}
+ * method.
+ * </p>
+ *
+ */
+public class RemoteRefUpdate {
+ /**
+ * Represent current status of a remote ref update.
+ */
+ public static enum Status {
+ /**
+ * Push process hasn't yet attempted to update this ref. This is the
+ * default status, prior to push process execution.
+ */
+ NOT_ATTEMPTED,
+
+ /**
+ * Remote ref was up to date, there was no need to update anything.
+ */
+ UP_TO_DATE,
+
+ /**
+ * Remote ref update was rejected, as it would cause non fast-forward
+ * update.
+ */
+ REJECTED_NONFASTFORWARD,
+
+ /**
+ * Remote ref update was rejected, because remote side doesn't
+ * support/allow deleting refs.
+ */
+ REJECTED_NODELETE,
+
+ /**
+ * Remote ref update was rejected, because old object id on remote
+ * repository wasn't the same as defined expected old object.
+ */
+ REJECTED_REMOTE_CHANGED,
+
+ /**
+ * Remote ref update was rejected for other reason, possibly described
+ * in {@link RemoteRefUpdate#getMessage()}.
+ */
+ REJECTED_OTHER_REASON,
+
+ /**
+ * Remote ref didn't exist. Can occur on delete request of a non
+ * existing ref.
+ */
+ NON_EXISTING,
+
+ /**
+ * Push process is awaiting update report from remote repository. This
+ * is a temporary state or state after critical error in push process.
+ */
+ AWAITING_REPORT,
+
+ /**
+ * Remote ref was successfully updated.
+ */
+ OK;
+ }
+
+ private final ObjectId expectedOldObjectId;
+
+ private final ObjectId newObjectId;
+
+ private final String remoteName;
+
+ private final TrackingRefUpdate trackingRefUpdate;
+
+ private final String srcRef;
+
+ private final boolean forceUpdate;
+
+ private Status status;
+
+ private boolean fastForward;
+
+ private String message;
+
+ private final Repository localDb;
+
+ /**
+ * Construct remote ref update request by providing an update specification.
+ * Object is created with default {@link Status#NOT_ATTEMPTED} status and no
+ * message.
+ *
+ * @param localDb
+ * local repository to push from.
+ * @param srcRef
+ * source revision - any string resolvable by
+ * {@link Repository#resolve(String)}. This resolves to the new
+ * object that the caller want remote ref to be after update. Use
+ * null or {@link ObjectId#zeroId()} string for delete request.
+ * @param remoteName
+ * full name of a remote ref to update, e.g. "refs/heads/master"
+ * (no wildcard, no short name).
+ * @param forceUpdate
+ * true when caller want remote ref to be updated regardless
+ * whether it is fast-forward update (old object is ancestor of
+ * new object).
+ * @param localName
+ * optional full name of a local stored tracking branch, to
+ * update after push, e.g. "refs/remotes/zawir/dirty" (no
+ * wildcard, no short name); null if no local tracking branch
+ * should be updated.
+ * @param expectedOldObjectId
+ * optional object id that caller is expecting, requiring to be
+ * advertised by remote side before update; update will take
+ * place ONLY if remote side advertise exactly this expected id;
+ * null if caller doesn't care what object id remote side
+ * advertise. Use {@link ObjectId#zeroId()} when expecting no
+ * remote ref with this name.
+ * @throws IOException
+ * when I/O error occurred during creating
+ * {@link TrackingRefUpdate} for local tracking branch or srcRef
+ * can't be resolved to any object.
+ * @throws IllegalArgumentException
+ * if some required parameter was null
+ */
+ public RemoteRefUpdate(final Repository localDb, final String srcRef,
+ final String remoteName, final boolean forceUpdate,
+ final String localName, final ObjectId expectedOldObjectId)
+ throws IOException {
+ if (remoteName == null)
+ throw new IllegalArgumentException("Remote name can't be null.");
+ this.srcRef = srcRef;
+ this.newObjectId = (srcRef == null ? ObjectId.zeroId() : localDb
+ .resolve(srcRef));
+ if (newObjectId == null)
+ throw new IOException("Source ref " + srcRef
+ + " doesn't resolve to any object.");
+ this.remoteName = remoteName;
+ this.forceUpdate = forceUpdate;
+ if (localName != null && localDb != null)
+ trackingRefUpdate = new TrackingRefUpdate(localDb, localName,
+ remoteName, true, newObjectId, "push");
+ else
+ trackingRefUpdate = null;
+ this.localDb = localDb;
+ this.expectedOldObjectId = expectedOldObjectId;
+ this.status = Status.NOT_ATTEMPTED;
+ }
+
+ /**
+ * Create a new instance of this object basing on existing instance for
+ * configuration. State (like {@link #getMessage()}, {@link #getStatus()})
+ * of base object is not shared. Expected old object id is set up from
+ * scratch, as this constructor may be used for 2-stage push: first one
+ * being dry run, second one being actual push.
+ *
+ * @param base
+ * configuration base.
+ * @param newExpectedOldObjectId
+ * new expected object id value.
+ * @throws IOException
+ * when I/O error occurred during creating
+ * {@link TrackingRefUpdate} for local tracking branch or srcRef
+ * of base object no longer can be resolved to any object.
+ */
+ public RemoteRefUpdate(final RemoteRefUpdate base,
+ final ObjectId newExpectedOldObjectId) throws IOException {
+ this(base.localDb, base.srcRef, base.remoteName, base.forceUpdate,
+ (base.trackingRefUpdate == null ? null : base.trackingRefUpdate
+ .getLocalName()), newExpectedOldObjectId);
+ }
+
+ /**
+ * @return expectedOldObjectId required to be advertised by remote side, as
+ * set in constructor; may be null.
+ */
+ public ObjectId getExpectedOldObjectId() {
+ return expectedOldObjectId;
+ }
+
+ /**
+ * @return true if some object is required to be advertised by remote side,
+ * as set in constructor; false otherwise.
+ */
+ public boolean isExpectingOldObjectId() {
+ return expectedOldObjectId != null;
+ }
+
+ /**
+ * @return newObjectId for remote ref, as set in constructor.
+ */
+ public ObjectId getNewObjectId() {
+ return newObjectId;
+ }
+
+ /**
+ * @return true if this update is deleting update; false otherwise.
+ */
+ public boolean isDelete() {
+ return ObjectId.zeroId().equals(newObjectId);
+ }
+
+ /**
+ * @return name of remote ref to update, as set in constructor.
+ */
+ public String getRemoteName() {
+ return remoteName;
+ }
+
+ /**
+ * @return local tracking branch update if localName was set in constructor.
+ */
+ public TrackingRefUpdate getTrackingRefUpdate() {
+ return trackingRefUpdate;
+ }
+
+ /**
+ * @return source revision as specified by user (in constructor), could be
+ * any string parseable by {@link Repository#resolve(String)}; can
+ * be null if specified that way in constructor - this stands for
+ * delete request.
+ */
+ public String getSrcRef() {
+ return srcRef;
+ }
+
+ /**
+ * @return true if user specified a local tracking branch for remote update;
+ * false otherwise.
+ */
+ public boolean hasTrackingRefUpdate() {
+ return trackingRefUpdate != null;
+ }
+
+ /**
+ * @return true if this update is forced regardless of old remote ref
+ * object; false otherwise.
+ */
+ public boolean isForceUpdate() {
+ return forceUpdate;
+ }
+
+ /**
+ * @return status of remote ref update operation.
+ */
+ public Status getStatus() {
+ return status;
+ }
+
+ /**
+ * Check whether update was fast-forward. Note that this result is
+ * meaningful only after successful update (when status is {@link Status#OK}).
+ *
+ * @return true if update was fast-forward; false otherwise.
+ */
+ public boolean isFastForward() {
+ return fastForward;
+ }
+
+ /**
+ * @return message describing reasons of status when needed/possible; may be
+ * null.
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ void setStatus(final Status status) {
+ this.status = status;
+ }
+
+ void setFastForward(boolean fastForward) {
+ this.fastForward = fastForward;
+ }
+
+ void setMessage(final String message) {
+ this.message = message;
+ }
+
+ /**
+ * Update locally stored tracking branch with the new object.
+ *
+ * @param walk
+ * walker used for checking update properties.
+ * @throws IOException
+ * when I/O error occurred during update
+ */
+ protected void updateTrackingRef(final RevWalk walk) throws IOException {
+ if (isDelete())
+ trackingRefUpdate.delete(walk);
+ else
+ trackingRefUpdate.update(walk);
+ }
+
+ @Override
+ public String toString() {
+ return "RemoteRefUpdate[remoteName=" + remoteName + ", " + status
+ + ", " + (expectedOldObjectId!=null?expectedOldObjectId.abbreviate(localDb).name() :"(null)")
+ + "..." + (newObjectId != null ? newObjectId.abbreviate(localDb).name() : "(null)")
+ + (fastForward ? ", fastForward" : "")
+ + ", srcRef=" + srcRef + (forceUpdate ? ", forceUpdate" : "") + ", message=" + (message != null ? "\""
+ + message + "\"" : "null") + ", " + localDb.getDirectory() + "]";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
new file mode 100644
index 0000000000..7a0765030f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Unmultiplexes the data portion of a side-band channel.
+ * <p>
+ * Reading from this input stream obtains data from channel 1, which is
+ * typically the bulk data stream.
+ * <p>
+ * Channel 2 is transparently unpacked and "scraped" to update a progress
+ * monitor. The scraping is performed behind the scenes as part of any of the
+ * read methods offered by this stream.
+ * <p>
+ * Channel 3 results in an exception being thrown, as the remote side has issued
+ * an unrecoverable error.
+ *
+ * @see PacketLineIn#sideband(ProgressMonitor)
+ */
+class SideBandInputStream extends InputStream {
+ static final int CH_DATA = 1;
+
+ static final int CH_PROGRESS = 2;
+
+ static final int CH_ERROR = 3;
+
+ private static Pattern P_UNBOUNDED = Pattern.compile(
+ "^([\\w ]+): (\\d+)( |, done)?.*", Pattern.DOTALL);
+
+ private static Pattern P_BOUNDED = Pattern.compile(
+ "^([\\w ]+):.*\\((\\d+)/(\\d+)\\).*", Pattern.DOTALL);
+
+ private final PacketLineIn pckIn;
+
+ private final InputStream in;
+
+ private final ProgressMonitor monitor;
+
+ private String progressBuffer = "";
+
+ private String currentTask;
+
+ private int lastCnt;
+
+ private boolean eof;
+
+ private int channel;
+
+ private int available;
+
+ SideBandInputStream(final PacketLineIn aPckIn, final InputStream aIn,
+ final ProgressMonitor aProgress) {
+ pckIn = aPckIn;
+ in = aIn;
+ monitor = aProgress;
+ currentTask = "";
+ }
+
+ @Override
+ public int read() throws IOException {
+ needDataPacket();
+ if (eof)
+ return -1;
+ available--;
+ return in.read();
+ }
+
+ @Override
+ public int read(final byte[] b, int off, int len) throws IOException {
+ int r = 0;
+ while (len > 0) {
+ needDataPacket();
+ if (eof)
+ break;
+ final int n = in.read(b, off, Math.min(len, available));
+ if (n < 0)
+ break;
+ r += n;
+ off += n;
+ len -= n;
+ available -= n;
+ }
+ return eof && r == 0 ? -1 : r;
+ }
+
+ private void needDataPacket() throws IOException {
+ if (eof || (channel == CH_DATA && available > 0))
+ return;
+ for (;;) {
+ available = pckIn.readLength();
+ if (available == 0) {
+ eof = true;
+ return;
+ }
+
+ channel = in.read();
+ available -= 5; // length header plus channel indicator
+ if (available == 0)
+ continue;
+
+ switch (channel) {
+ case CH_DATA:
+ return;
+ case CH_PROGRESS:
+ progress(readString(available));
+
+ continue;
+ case CH_ERROR:
+ eof = true;
+ throw new TransportException("remote: " + readString(available));
+ default:
+ throw new PackProtocolException("Invalid channel " + channel);
+ }
+ }
+ }
+
+ private void progress(String pkt) {
+ pkt = progressBuffer + pkt;
+ for (;;) {
+ final int lf = pkt.indexOf('\n');
+ final int cr = pkt.indexOf('\r');
+ final int s;
+ if (0 <= lf && 0 <= cr)
+ s = Math.min(lf, cr);
+ else if (0 <= lf)
+ s = lf;
+ else if (0 <= cr)
+ s = cr;
+ else
+ break;
+
+ final String msg = pkt.substring(0, s);
+ if (doProgressLine(msg))
+ pkt = pkt.substring(s + 1);
+ else
+ break;
+ }
+ progressBuffer = pkt;
+ }
+
+ private boolean doProgressLine(final String msg) {
+ Matcher matcher;
+
+ matcher = P_BOUNDED.matcher(msg);
+ if (matcher.matches()) {
+ final String taskname = matcher.group(1);
+ if (!currentTask.equals(taskname)) {
+ currentTask = taskname;
+ lastCnt = 0;
+ final int tot = Integer.parseInt(matcher.group(3));
+ monitor.beginTask(currentTask, tot);
+ }
+ final int cnt = Integer.parseInt(matcher.group(2));
+ monitor.update(cnt - lastCnt);
+ lastCnt = cnt;
+ return true;
+ }
+
+ matcher = P_UNBOUNDED.matcher(msg);
+ if (matcher.matches()) {
+ final String taskname = matcher.group(1);
+ if (!currentTask.equals(taskname)) {
+ currentTask = taskname;
+ lastCnt = 0;
+ monitor.beginTask(currentTask, ProgressMonitor.UNKNOWN);
+ }
+ final int cnt = Integer.parseInt(matcher.group(2));
+ monitor.update(cnt - lastCnt);
+ lastCnt = cnt;
+ return true;
+ }
+
+ return false;
+ }
+
+ private String readString(final int len) throws IOException {
+ final byte[] raw = new byte[len];
+ NB.readFully(in, raw, 0, len);
+ return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java
new file mode 100644
index 0000000000..5e50fd89b3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Multiplexes data and progress messages
+ * <p>
+ * To correctly use this class you must wrap it in a BufferedOutputStream with a
+ * buffer size no larger than either {@link #SMALL_BUF} or {@link #MAX_BUF},
+ * minus {@link #HDR_SIZE}.
+ */
+class SideBandOutputStream extends OutputStream {
+ static final int CH_DATA = SideBandInputStream.CH_DATA;
+
+ static final int CH_PROGRESS = SideBandInputStream.CH_PROGRESS;
+
+ static final int CH_ERROR = SideBandInputStream.CH_ERROR;
+
+ static final int SMALL_BUF = 1000;
+
+ static final int MAX_BUF = 65520;
+
+ static final int HDR_SIZE = 5;
+
+ private final int channel;
+
+ private final PacketLineOut pckOut;
+
+ private byte[] singleByteBuffer;
+
+ SideBandOutputStream(final int chan, final PacketLineOut out) {
+ channel = chan;
+ pckOut = out;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (channel != CH_DATA)
+ pckOut.flush();
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len)
+ throws IOException {
+ pckOut.writeChannelPacket(channel, b, off, len);
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ if (singleByteBuffer == null)
+ singleByteBuffer = new byte[1];
+ singleByteBuffer[0] = (byte) b;
+ write(singleByteBuffer);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java
new file mode 100644
index 0000000000..89d338c897
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ProgressMonitor;
+
+/** Write progress messages out to the sideband channel. */
+class SideBandProgressMonitor implements ProgressMonitor {
+ private PrintWriter out;
+
+ private boolean output;
+
+ private long taskBeganAt;
+
+ private long lastOutput;
+
+ private String msg;
+
+ private int lastWorked;
+
+ private int totalWork;
+
+ SideBandProgressMonitor(final PacketLineOut pckOut) {
+ final int bufsz = SideBandOutputStream.SMALL_BUF
+ - SideBandOutputStream.HDR_SIZE;
+ out = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(
+ new SideBandOutputStream(SideBandOutputStream.CH_PROGRESS,
+ pckOut), bufsz), Constants.CHARSET));
+ }
+
+ public void start(final int totalTasks) {
+ // Ignore the number of tasks.
+ taskBeganAt = System.currentTimeMillis();
+ lastOutput = taskBeganAt;
+ }
+
+ public void beginTask(final String title, final int total) {
+ endTask();
+ msg = title;
+ lastWorked = 0;
+ totalWork = total;
+ }
+
+ public void update(final int completed) {
+ if (msg == null)
+ return;
+
+ final int cmp = lastWorked + completed;
+ final long now = System.currentTimeMillis();
+ if (!output && now - taskBeganAt < 500)
+ return;
+ if (totalWork == UNKNOWN) {
+ if (now - lastOutput >= 500) {
+ display(cmp, null);
+ lastOutput = now;
+ }
+ } else {
+ if ((cmp * 100 / totalWork) != (lastWorked * 100) / totalWork
+ || now - lastOutput >= 500) {
+ display(cmp, null);
+ lastOutput = now;
+ }
+ }
+ lastWorked = cmp;
+ output = true;
+ }
+
+ private void display(final int cmp, final String eol) {
+ final StringBuilder m = new StringBuilder();
+ m.append(msg);
+ m.append(": ");
+
+ if (totalWork == UNKNOWN) {
+ m.append(cmp);
+ } else {
+ final int pcnt = (cmp * 100 / totalWork);
+ if (pcnt < 100)
+ m.append(' ');
+ if (pcnt < 10)
+ m.append(' ');
+ m.append(pcnt);
+ m.append("% (");
+ m.append(cmp);
+ m.append("/");
+ m.append(totalWork);
+ m.append(")");
+ }
+ if (eol != null)
+ m.append(eol);
+ else
+ m.append(" \r");
+ out.print(m);
+ out.flush();
+ }
+
+ public boolean isCancelled() {
+ return false;
+ }
+
+ public void endTask() {
+ if (output) {
+ if (totalWork == UNKNOWN)
+ display(lastWorked, ", done\n");
+ else
+ display(totalWork, "\n");
+ }
+ output = false;
+ msg = null;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigSessionFactory.java
new file mode 100644
index 0000000000..c30d32d9f9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigSessionFactory.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2009, Google, Inc.
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.util.FS;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.UserInfo;
+
+/**
+ * The base session factory that loads known hosts and private keys from
+ * <code>$HOME/.ssh</code>.
+ * <p>
+ * This is the default implementation used by JGit and provides most of the
+ * compatibility necessary to match OpenSSH, a popular implementation of SSH
+ * used by C Git.
+ * <p>
+ * The factory does not provide UI behavior. Override the method
+ * {@link #configure(org.eclipse.jgit.transport.OpenSshConfig.Host, Session)}
+ * to supply appropriate {@link UserInfo} to the session.
+ */
+public abstract class SshConfigSessionFactory extends SshSessionFactory {
+ private final Map<String, JSch> byIdentityFile = new HashMap<String, JSch>();
+
+ private JSch defaultJSch;
+
+ private OpenSshConfig config;
+
+ @Override
+ public synchronized Session getSession(String user, String pass,
+ String host, int port) throws JSchException {
+ final OpenSshConfig.Host hc = getConfig().lookup(host);
+ host = hc.getHostName();
+ if (port <= 0)
+ port = hc.getPort();
+ if (user == null)
+ user = hc.getUser();
+
+ final Session session = createSession(hc, user, host, port);
+ if (pass != null)
+ session.setPassword(pass);
+ final String strictHostKeyCheckingPolicy = hc
+ .getStrictHostKeyChecking();
+ if (strictHostKeyCheckingPolicy != null)
+ session.setConfig("StrictHostKeyChecking",
+ strictHostKeyCheckingPolicy);
+ final String pauth = hc.getPreferredAuthentications();
+ if (pauth != null)
+ session.setConfig("PreferredAuthentications", pauth);
+ configure(hc, session);
+ return session;
+ }
+
+ /**
+ * Create a new JSch session for the requested address.
+ *
+ * @param hc
+ * host configuration
+ * @param user
+ * login to authenticate as.
+ * @param host
+ * server name to connect to.
+ * @param port
+ * port number of the SSH daemon (typically 22).
+ * @return new session instance, but otherwise unconfigured.
+ * @throws JSchException
+ * the session could not be created.
+ */
+ protected Session createSession(final OpenSshConfig.Host hc,
+ final String user, final String host, final int port)
+ throws JSchException {
+ return getJSch(hc).getSession(user, host, port);
+ }
+
+ /**
+ * Provide additional configuration for the session based on the host
+ * information. This method could be used to supply {@link UserInfo}.
+ *
+ * @param hc
+ * host configuration
+ * @param session
+ * session to configure
+ */
+ protected abstract void configure(OpenSshConfig.Host hc, Session session);
+
+ /**
+ * Obtain the JSch used to create new sessions.
+ *
+ * @param hc
+ * host configuration
+ * @return the JSch instance to use.
+ * @throws JSchException
+ * the user configuration could not be created.
+ */
+ protected JSch getJSch(final OpenSshConfig.Host hc) throws JSchException {
+ final JSch def = getDefaultJSch();
+ final File identityFile = hc.getIdentityFile();
+ if (identityFile == null) {
+ return def;
+ }
+
+ final String identityKey = identityFile.getAbsolutePath();
+ JSch jsch = byIdentityFile.get(identityKey);
+ if (jsch == null) {
+ jsch = new JSch();
+ jsch.setHostKeyRepository(def.getHostKeyRepository());
+ jsch.addIdentity(identityKey);
+ byIdentityFile.put(identityKey, jsch);
+ }
+ return jsch;
+ }
+
+ private JSch getDefaultJSch() throws JSchException {
+ if (defaultJSch == null) {
+ defaultJSch = createDefaultJSch();
+ for (Object name : defaultJSch.getIdentityNames()) {
+ byIdentityFile.put((String) name, defaultJSch);
+ }
+ }
+ return defaultJSch;
+ }
+
+ /**
+ * @return the new default JSch implementation.
+ * @throws JSchException
+ * known host keys cannot be loaded.
+ */
+ protected JSch createDefaultJSch() throws JSchException {
+ final JSch jsch = new JSch();
+ knownHosts(jsch);
+ identities(jsch);
+ return jsch;
+ }
+
+ private OpenSshConfig getConfig() {
+ if (config == null)
+ config = OpenSshConfig.get();
+ return config;
+ }
+
+ private static void knownHosts(final JSch sch) throws JSchException {
+ final File home = FS.userHome();
+ if (home == null)
+ return;
+ final File known_hosts = new File(new File(home, ".ssh"), "known_hosts");
+ try {
+ final FileInputStream in = new FileInputStream(known_hosts);
+ try {
+ sch.setKnownHosts(in);
+ } finally {
+ in.close();
+ }
+ } catch (FileNotFoundException none) {
+ // Oh well. They don't have a known hosts in home.
+ } catch (IOException err) {
+ // Oh well. They don't have a known hosts in home.
+ }
+ }
+
+ private static void identities(final JSch sch) {
+ final File home = FS.userHome();
+ if (home == null)
+ return;
+ final File sshdir = new File(home, ".ssh");
+ if (sshdir.isDirectory()) {
+ loadIdentity(sch, new File(sshdir, "identity"));
+ loadIdentity(sch, new File(sshdir, "id_rsa"));
+ loadIdentity(sch, new File(sshdir, "id_dsa"));
+ }
+ }
+
+ private static void loadIdentity(final JSch sch, final File priv) {
+ if (priv.isFile()) {
+ try {
+ sch.addIdentity(priv.getAbsolutePath());
+ } catch (JSchException e) {
+ // Instead, pretend the key doesn't exist.
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
new file mode 100644
index 0000000000..76bf6c1dcf
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+
+/**
+ * Creates and destroys SSH connections to a remote system.
+ * <p>
+ * Different implementations of the session factory may be used to control
+ * communicating with the end-user as well as reading their personal SSH
+ * configuration settings, such as known hosts and private keys.
+ * <p>
+ * A {@link Session} must be returned to the factory that created it. Callers
+ * are encouraged to retain the SshSessionFactory for the duration of the period
+ * they are using the Session.
+ */
+public abstract class SshSessionFactory {
+ private static SshSessionFactory INSTANCE = new DefaultSshSessionFactory();
+
+ /**
+ * Get the currently configured JVM-wide factory.
+ * <p>
+ * A factory is always available. By default the factory will read from the
+ * user's <code>$HOME/.ssh</code> and assume OpenSSH compatibility.
+ *
+ * @return factory the current factory for this JVM.
+ */
+ public static SshSessionFactory getInstance() {
+ return INSTANCE;
+ }
+
+ /**
+ * Change the JVM-wide factory to a different implementation.
+ *
+ * @param newFactory
+ * factory for future sessions to be created through. If null the
+ * default factory will be restored.s
+ */
+ public static void setInstance(final SshSessionFactory newFactory) {
+ if (newFactory != null)
+ INSTANCE = newFactory;
+ else
+ INSTANCE = new DefaultSshSessionFactory();
+ }
+
+ /**
+ * Open (or reuse) a session to a host.
+ * <p>
+ * A reasonable UserInfo that can interact with the end-user (if necessary)
+ * is installed on the returned session by this method.
+ * <p>
+ * The caller must connect the session by invoking <code>connect()</code>
+ * if it has not already been connected.
+ *
+ * @param user
+ * username to authenticate as. If null a reasonable default must
+ * be selected by the implementation. This may be
+ * <code>System.getProperty("user.name")</code>.
+ * @param pass
+ * optional user account password or passphrase. If not null a
+ * UserInfo that supplies this value to the SSH library will be
+ * configured.
+ * @param host
+ * hostname (or IP address) to connect to. Must not be null.
+ * @param port
+ * port number the server is listening for connections on. May be <=
+ * 0 to indicate the IANA registered port of 22 should be used.
+ * @return a session that can contact the remote host.
+ * @throws JSchException
+ * the session could not be created.
+ */
+ public abstract Session getSession(String user, String pass, String host,
+ int port) throws JSchException;
+
+ /**
+ * Close (or recycle) a session to a host.
+ *
+ * @param session
+ * a session previously obtained from this factory's
+ * {@link #getSession(String,String, String, int)} method.s
+ */
+ public void releaseSession(final Session session) {
+ if (session.isConnected())
+ session.disconnect();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java
new file mode 100644
index 0000000000..5c6b498cad
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008-2009, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.net.ConnectException;
+import java.net.UnknownHostException;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Repository;
+
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+
+/**
+ * The base class for transports that use SSH protocol. This class allows
+ * customizing SSH connection settings.
+ */
+public abstract class SshTransport extends TcpTransport {
+
+ private SshSessionFactory sch;
+
+ /**
+ * The open SSH session
+ */
+ protected Session sock;
+
+ /**
+ * Create a new transport instance.
+ *
+ * @param local
+ * the repository this instance will fetch into, or push out of.
+ * This must be the repository passed to
+ * {@link #open(Repository, URIish)}.
+ * @param uri
+ * the URI used to access the remote repository. This must be the
+ * URI passed to {@link #open(Repository, URIish)}.
+ */
+ protected SshTransport(Repository local, URIish uri) {
+ super(local, uri);
+ sch = SshSessionFactory.getInstance();
+ }
+
+ /**
+ * Set SSH session factory instead of the default one for this instance of
+ * the transport.
+ *
+ * @param factory
+ * a factory to set, must not be null
+ * @throws IllegalStateException
+ * if session has been already created.
+ */
+ public void setSshSessionFactory(SshSessionFactory factory) {
+ if (factory == null)
+ throw new NullPointerException("The factory must not be null");
+ if (sock != null)
+ throw new IllegalStateException(
+ "An SSH session has been already created");
+ sch = factory;
+ }
+
+ /**
+ * @return the SSH session factory that will be used for creating SSH sessions
+ */
+ public SshSessionFactory getSshSessionFactory() {
+ return sch;
+ }
+
+
+ /**
+ * Initialize SSH session
+ *
+ * @throws TransportException
+ * in case of error with opening SSH session
+ */
+ protected void initSession() throws TransportException {
+ if (sock != null)
+ return;
+
+ final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;
+ final String user = uri.getUser();
+ final String pass = uri.getPass();
+ final String host = uri.getHost();
+ final int port = uri.getPort();
+ try {
+ sock = sch.getSession(user, pass, host, port);
+ if (!sock.isConnected())
+ sock.connect(tms);
+ } catch (JSchException je) {
+ final Throwable c = je.getCause();
+ if (c instanceof UnknownHostException)
+ throw new TransportException(uri, "unknown host");
+ if (c instanceof ConnectException)
+ throw new TransportException(uri, c.getMessage());
+ throw new TransportException(uri, je.getMessage(), je);
+ }
+ }
+
+ @Override
+ public void close() {
+ if (sock != null) {
+ try {
+ sch.releaseSession(sock);
+ } finally {
+ sock = null;
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TagOpt.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TagOpt.java
new file mode 100644
index 0000000000..09cd56a729
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TagOpt.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2008, Mike Ralphson <mike@abacus.co.uk>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+/** Specification of annotated tag behavior during fetch. */
+public enum TagOpt {
+ /**
+ * Automatically follow tags if we fetch the thing they point at.
+ * <p>
+ * This is the default behavior and tries to balance the benefit of having
+ * an annotated tag against the cost of possibly objects that are only on
+ * branches we care nothing about. Annotated tags are fetched only if we can
+ * prove that we already have (or will have when the fetch completes) the
+ * object the annotated tag peels (dereferences) to.
+ */
+ AUTO_FOLLOW(""),
+
+ /**
+ * Never fetch tags, even if we have the thing it points at.
+ * <p>
+ * This option must be requested by the user and always avoids fetching
+ * annotated tags. It is most useful if the location you are fetching from
+ * publishes annotated tags, but you are not interested in the tags and only
+ * want their branches.
+ */
+ NO_TAGS("--no-tags"),
+
+ /**
+ * Always fetch tags, even if we do not have the thing it points at.
+ * <p>
+ * Unlike {@link #AUTO_FOLLOW} the tag is always obtained. This may cause
+ * hundreds of megabytes of objects to be fetched if the receiving
+ * repository does not yet have the necessary dependencies.
+ */
+ FETCH_TAGS("--tags");
+
+ private final String option;
+
+ private TagOpt(final String o) {
+ option = o;
+ }
+
+ /**
+ * Get the command line/configuration file text for this value.
+ *
+ * @return text that appears in the configuration file to activate this.
+ */
+ public String option() {
+ return option;
+ }
+
+ /**
+ * Convert a command line/configuration file text into a value instance.
+ *
+ * @param o
+ * the configuration file text value.
+ * @return the option that matches the passed parameter.
+ */
+ public static TagOpt fromOption(final String o) {
+ if (o == null || o.length() == 0)
+ return AUTO_FOLLOW;
+ for (final TagOpt tagopt : values()) {
+ if (tagopt.option().equals(o))
+ return tagopt;
+ }
+ throw new IllegalArgumentException("Invalid tag option: " + o);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TcpTransport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TcpTransport.java
new file mode 100644
index 0000000000..a6e5390890
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TcpTransport.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * Copyright (C) 2009, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * The base class for transports based on TCP sockets. This class
+ * holds settings common for all TCP based transports.
+ */
+public abstract class TcpTransport extends Transport {
+ /**
+ * Create a new transport instance.
+ *
+ * @param local
+ * the repository this instance will fetch into, or push out of.
+ * This must be the repository passed to
+ * {@link #open(Repository, URIish)}.
+ * @param uri
+ * the URI used to access the remote repository. This must be the
+ * URI passed to {@link #open(Repository, URIish)}.
+ */
+ protected TcpTransport(Repository local, URIish uri) {
+ super(local, uri);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
new file mode 100644
index 0000000000..2655f39f01
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Update of a locally stored tracking branch. */
+public class TrackingRefUpdate {
+ private final String remoteName;
+
+ private final RefUpdate update;
+
+ TrackingRefUpdate(final Repository db, final RefSpec spec,
+ final AnyObjectId nv, final String msg) throws IOException {
+ this(db, spec.getDestination(), spec.getSource(), spec.isForceUpdate(),
+ nv, msg);
+ }
+
+ TrackingRefUpdate(final Repository db, final String localName,
+ final String remoteName, final boolean forceUpdate,
+ final AnyObjectId nv, final String msg) throws IOException {
+ this.remoteName = remoteName;
+ update = db.updateRef(localName);
+ update.setForceUpdate(forceUpdate);
+ update.setNewObjectId(nv);
+ update.setRefLogMessage(msg, true);
+ }
+
+ /**
+ * Get the name of the remote ref.
+ * <p>
+ * Usually this is of the form "refs/heads/master".
+ *
+ * @return the name used within the remote repository.
+ */
+ public String getRemoteName() {
+ return remoteName;
+ }
+
+ /**
+ * Get the name of the local tracking ref.
+ * <p>
+ * Usually this is of the form "refs/remotes/origin/master".
+ *
+ * @return the name used within this local repository.
+ */
+ public String getLocalName() {
+ return update.getName();
+ }
+
+ /**
+ * Get the new value the ref will be (or was) updated to.
+ *
+ * @return new value. Null if the caller has not configured it.
+ */
+ public ObjectId getNewObjectId() {
+ return update.getNewObjectId();
+ }
+
+ /**
+ * The old value of the ref, prior to the update being attempted.
+ * <p>
+ * This value may differ before and after the update method. Initially it is
+ * populated with the value of the ref before the lock is taken, but the old
+ * value may change if someone else modified the ref between the time we
+ * last read it and when the ref was locked for update.
+ *
+ * @return the value of the ref prior to the update being attempted; null if
+ * the updated has not been attempted yet.
+ */
+ public ObjectId getOldObjectId() {
+ return update.getOldObjectId();
+ }
+
+ /**
+ * Get the status of this update.
+ *
+ * @return the status of the update.
+ */
+ public Result getResult() {
+ return update.getResult();
+ }
+
+ void update(final RevWalk walk) throws IOException {
+ update.update(walk);
+ }
+
+ void delete(final RevWalk walk) throws IOException {
+ update.delete(walk);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
new file mode 100644
index 0000000000..e63afaf084
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -0,0 +1,932 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.TransferConfig;
+
+/**
+ * Connects two Git repositories together and copies objects between them.
+ * <p>
+ * A transport can be used for either fetching (copying objects into the
+ * caller's repository from the remote repository) or pushing (copying objects
+ * into the remote repository from the caller's repository). Each transport
+ * implementation is responsible for the details associated with establishing
+ * the network connection(s) necessary for the copy, as well as actually
+ * shuffling data back and forth.
+ * <p>
+ * Transport instances and the connections they create are not thread-safe.
+ * Callers must ensure a transport is accessed by only one thread at a time.
+ */
+public abstract class Transport {
+ /** Type of operation a Transport is being opened for. */
+ public enum Operation {
+ /** Transport is to fetch objects locally. */
+ FETCH,
+ /** Transport is to push objects remotely. */
+ PUSH;
+ }
+
+ /**
+ * Open a new transport instance to connect two repositories.
+ * <p>
+ * This method assumes {@link Operation#FETCH}.
+ *
+ * @param local
+ * existing local repository.
+ * @param remote
+ * location of the remote repository - may be URI or remote
+ * configuration name.
+ * @return the new transport instance. Never null. In case of multiple URIs
+ * in remote configuration, only the first is chosen.
+ * @throws URISyntaxException
+ * the location is not a remote defined in the configuration
+ * file and is not a well-formed URL.
+ * @throws NotSupportedException
+ * the protocol specified is not supported.
+ */
+ public static Transport open(final Repository local, final String remote)
+ throws NotSupportedException, URISyntaxException {
+ return open(local, remote, Operation.FETCH);
+ }
+
+ /**
+ * Open a new transport instance to connect two repositories.
+ *
+ * @param local
+ * existing local repository.
+ * @param remote
+ * location of the remote repository - may be URI or remote
+ * configuration name.
+ * @param op
+ * planned use of the returned Transport; the URI may differ
+ * based on the type of connection desired.
+ * @return the new transport instance. Never null. In case of multiple URIs
+ * in remote configuration, only the first is chosen.
+ * @throws URISyntaxException
+ * the location is not a remote defined in the configuration
+ * file and is not a well-formed URL.
+ * @throws NotSupportedException
+ * the protocol specified is not supported.
+ */
+ public static Transport open(final Repository local, final String remote,
+ final Operation op) throws NotSupportedException,
+ URISyntaxException {
+ final RemoteConfig cfg = new RemoteConfig(local.getConfig(), remote);
+ if (doesNotExist(cfg))
+ return open(local, new URIish(remote));
+ return open(local, cfg, op);
+ }
+
+ /**
+ * Open new transport instances to connect two repositories.
+ * <p>
+ * This method assumes {@link Operation#FETCH}.
+ *
+ * @param local
+ * existing local repository.
+ * @param remote
+ * location of the remote repository - may be URI or remote
+ * configuration name.
+ * @return the list of new transport instances for every URI in remote
+ * configuration.
+ * @throws URISyntaxException
+ * the location is not a remote defined in the configuration
+ * file and is not a well-formed URL.
+ * @throws NotSupportedException
+ * the protocol specified is not supported.
+ */
+ public static List<Transport> openAll(final Repository local,
+ final String remote) throws NotSupportedException,
+ URISyntaxException {
+ return openAll(local, remote, Operation.FETCH);
+ }
+
+ /**
+ * Open new transport instances to connect two repositories.
+ *
+ * @param local
+ * existing local repository.
+ * @param remote
+ * location of the remote repository - may be URI or remote
+ * configuration name.
+ * @param op
+ * planned use of the returned Transport; the URI may differ
+ * based on the type of connection desired.
+ * @return the list of new transport instances for every URI in remote
+ * configuration.
+ * @throws URISyntaxException
+ * the location is not a remote defined in the configuration
+ * file and is not a well-formed URL.
+ * @throws NotSupportedException
+ * the protocol specified is not supported.
+ */
+ public static List<Transport> openAll(final Repository local,
+ final String remote, final Operation op)
+ throws NotSupportedException, URISyntaxException {
+ final RemoteConfig cfg = new RemoteConfig(local.getConfig(), remote);
+ if (doesNotExist(cfg)) {
+ final ArrayList<Transport> transports = new ArrayList<Transport>(1);
+ transports.add(open(local, new URIish(remote)));
+ return transports;
+ }
+ return openAll(local, cfg, op);
+ }
+
+ /**
+ * Open a new transport instance to connect two repositories.
+ * <p>
+ * This method assumes {@link Operation#FETCH}.
+ *
+ * @param local
+ * existing local repository.
+ * @param cfg
+ * configuration describing how to connect to the remote
+ * repository.
+ * @return the new transport instance. Never null. In case of multiple URIs
+ * in remote configuration, only the first is chosen.
+ * @throws NotSupportedException
+ * the protocol specified is not supported.
+ * @throws IllegalArgumentException
+ * if provided remote configuration doesn't have any URI
+ * associated.
+ */
+ public static Transport open(final Repository local, final RemoteConfig cfg)
+ throws NotSupportedException {
+ return open(local, cfg, Operation.FETCH);
+ }
+
+ /**
+ * Open a new transport instance to connect two repositories.
+ *
+ * @param local
+ * existing local repository.
+ * @param cfg
+ * configuration describing how to connect to the remote
+ * repository.
+ * @param op
+ * planned use of the returned Transport; the URI may differ
+ * based on the type of connection desired.
+ * @return the new transport instance. Never null. In case of multiple URIs
+ * in remote configuration, only the first is chosen.
+ * @throws NotSupportedException
+ * the protocol specified is not supported.
+ * @throws IllegalArgumentException
+ * if provided remote configuration doesn't have any URI
+ * associated.
+ */
+ public static Transport open(final Repository local,
+ final RemoteConfig cfg, final Operation op)
+ throws NotSupportedException {
+ final List<URIish> uris = getURIs(cfg, op);
+ if (uris.isEmpty())
+ throw new IllegalArgumentException(
+ "Remote config \""
+ + cfg.getName() + "\" has no URIs associated");
+ final Transport tn = open(local, uris.get(0));
+ tn.applyConfig(cfg);
+ return tn;
+ }
+
+ /**
+ * Open new transport instances to connect two repositories.
+ * <p>
+ * This method assumes {@link Operation#FETCH}.
+ *
+ * @param local
+ * existing local repository.
+ * @param cfg
+ * configuration describing how to connect to the remote
+ * repository.
+ * @return the list of new transport instances for every URI in remote
+ * configuration.
+ * @throws NotSupportedException
+ * the protocol specified is not supported.
+ */
+ public static List<Transport> openAll(final Repository local,
+ final RemoteConfig cfg) throws NotSupportedException {
+ return openAll(local, cfg, Operation.FETCH);
+ }
+
+ /**
+ * Open new transport instances to connect two repositories.
+ *
+ * @param local
+ * existing local repository.
+ * @param cfg
+ * configuration describing how to connect to the remote
+ * repository.
+ * @param op
+ * planned use of the returned Transport; the URI may differ
+ * based on the type of connection desired.
+ * @return the list of new transport instances for every URI in remote
+ * configuration.
+ * @throws NotSupportedException
+ * the protocol specified is not supported.
+ */
+ public static List<Transport> openAll(final Repository local,
+ final RemoteConfig cfg, final Operation op)
+ throws NotSupportedException {
+ final List<URIish> uris = getURIs(cfg, op);
+ final List<Transport> transports = new ArrayList<Transport>(uris.size());
+ for (final URIish uri : uris) {
+ final Transport tn = open(local, uri);
+ tn.applyConfig(cfg);
+ transports.add(tn);
+ }
+ return transports;
+ }
+
+ private static List<URIish> getURIs(final RemoteConfig cfg,
+ final Operation op) {
+ switch (op) {
+ case FETCH:
+ return cfg.getURIs();
+ case PUSH: {
+ List<URIish> uris = cfg.getPushURIs();
+ if (uris.isEmpty())
+ uris = cfg.getURIs();
+ return uris;
+ }
+ default:
+ throw new IllegalArgumentException(op.toString());
+ }
+ }
+
+ private static boolean doesNotExist(final RemoteConfig cfg) {
+ return cfg.getURIs().isEmpty() && cfg.getPushURIs().isEmpty();
+ }
+
+ /**
+ * Open a new transport instance to connect two repositories.
+ *
+ * @param local
+ * existing local repository.
+ * @param remote
+ * location of the remote repository.
+ * @return the new transport instance. Never null.
+ * @throws NotSupportedException
+ * the protocol specified is not supported.
+ */
+ public static Transport open(final Repository local, final URIish remote)
+ throws NotSupportedException {
+ if (TransportGitSsh.canHandle(remote))
+ return new TransportGitSsh(local, remote);
+
+ else if (TransportHttp.canHandle(remote))
+ return new TransportHttp(local, remote);
+
+ else if (TransportSftp.canHandle(remote))
+ return new TransportSftp(local, remote);
+
+ else if (TransportGitAnon.canHandle(remote))
+ return new TransportGitAnon(local, remote);
+
+ else if (TransportAmazonS3.canHandle(remote))
+ return new TransportAmazonS3(local, remote);
+
+ else if (TransportBundleFile.canHandle(remote))
+ return new TransportBundleFile(local, remote);
+
+ else if (TransportLocal.canHandle(remote))
+ return new TransportLocal(local, remote);
+
+ throw new NotSupportedException("URI not supported: " + remote);
+ }
+
+ /**
+ * Convert push remote refs update specification from {@link RefSpec} form
+ * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching
+ * source part to local refs. expectedOldObjectId in RemoteRefUpdate is
+ * always set as null. Tracking branch is configured if RefSpec destination
+ * matches source of any fetch ref spec for this transport remote
+ * configuration.
+ *
+ * @param db
+ * local database.
+ * @param specs
+ * collection of RefSpec to convert.
+ * @param fetchSpecs
+ * fetch specifications used for finding localtracking refs. May
+ * be null or empty collection.
+ * @return collection of set up {@link RemoteRefUpdate}.
+ * @throws IOException
+ * when problem occurred during conversion or specification set
+ * up: most probably, missing objects or refs.
+ */
+ public static Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
+ final Repository db, final Collection<RefSpec> specs,
+ Collection<RefSpec> fetchSpecs) throws IOException {
+ if (fetchSpecs == null)
+ fetchSpecs = Collections.emptyList();
+ final List<RemoteRefUpdate> result = new LinkedList<RemoteRefUpdate>();
+ final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
+
+ for (final RefSpec spec : procRefs) {
+ String srcSpec = spec.getSource();
+ final Ref srcRef = db.getRef(srcSpec);
+ if (srcRef != null)
+ srcSpec = srcRef.getName();
+
+ String destSpec = spec.getDestination();
+ if (destSpec == null) {
+ // No destination (no-colon in ref-spec), DWIMery assumes src
+ //
+ destSpec = srcSpec;
+ }
+
+ if (srcRef != null && !destSpec.startsWith(Constants.R_REFS)) {
+ // Assume the same kind of ref at the destination, e.g.
+ // "refs/heads/foo:master", DWIMery assumes master is also
+ // under "refs/heads/".
+ //
+ final String n = srcRef.getName();
+ final int kindEnd = n.indexOf('/', Constants.R_REFS.length());
+ destSpec = n.substring(0, kindEnd + 1) + destSpec;
+ }
+
+ final boolean forceUpdate = spec.isForceUpdate();
+ final String localName = findTrackingRefName(destSpec, fetchSpecs);
+ final RemoteRefUpdate rru = new RemoteRefUpdate(db, srcSpec,
+ destSpec, forceUpdate, localName, null);
+ result.add(rru);
+ }
+ return result;
+ }
+
+ private static Collection<RefSpec> expandPushWildcardsFor(
+ final Repository db, final Collection<RefSpec> specs) {
+ final Map<String, Ref> localRefs = db.getAllRefs();
+ final Collection<RefSpec> procRefs = new HashSet<RefSpec>();
+
+ for (final RefSpec spec : specs) {
+ if (spec.isWildcard()) {
+ for (final Ref localRef : localRefs.values()) {
+ if (spec.matchSource(localRef))
+ procRefs.add(spec.expandFromSource(localRef));
+ }
+ } else {
+ procRefs.add(spec);
+ }
+ }
+ return procRefs;
+ }
+
+ private static String findTrackingRefName(final String remoteName,
+ final Collection<RefSpec> fetchSpecs) {
+ // try to find matching tracking refs
+ for (final RefSpec fetchSpec : fetchSpecs) {
+ if (fetchSpec.matchSource(remoteName)) {
+ if (fetchSpec.isWildcard())
+ return fetchSpec.expandFromSource(remoteName)
+ .getDestination();
+ else
+ return fetchSpec.getDestination();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Default setting for {@link #fetchThin} option.
+ */
+ public static final boolean DEFAULT_FETCH_THIN = true;
+
+ /**
+ * Default setting for {@link #pushThin} option.
+ */
+ public static final boolean DEFAULT_PUSH_THIN = false;
+
+ /**
+ * Specification for fetch or push operations, to fetch or push all tags.
+ * Acts as --tags.
+ */
+ public static final RefSpec REFSPEC_TAGS = new RefSpec(
+ "refs/tags/*:refs/tags/*");
+
+ /**
+ * Specification for push operation, to push all refs under refs/heads. Acts
+ * as --all.
+ */
+ public static final RefSpec REFSPEC_PUSH_ALL = new RefSpec(
+ "refs/heads/*:refs/heads/*");
+
+ /** The repository this transport fetches into, or pushes out of. */
+ protected final Repository local;
+
+ /** The URI used to create this transport. */
+ protected final URIish uri;
+
+ /** Name of the upload pack program, if it must be executed. */
+ private String optionUploadPack = RemoteConfig.DEFAULT_UPLOAD_PACK;
+
+ /** Specifications to apply during fetch. */
+ private List<RefSpec> fetch = Collections.emptyList();
+
+ /**
+ * How {@link #fetch(ProgressMonitor, Collection)} should handle tags.
+ * <p>
+ * We default to {@link TagOpt#NO_TAGS} so as to avoid fetching annotated
+ * tags during one-shot fetches used for later merges. This prevents
+ * dragging down tags from repositories that we do not have established
+ * tracking branches for. If we do not track the source repository, we most
+ * likely do not care about any tags it publishes.
+ */
+ private TagOpt tagopt = TagOpt.NO_TAGS;
+
+ /** Should fetch request thin-pack if remote repository can produce it. */
+ private boolean fetchThin = DEFAULT_FETCH_THIN;
+
+ /** Name of the receive pack program, if it must be executed. */
+ private String optionReceivePack = RemoteConfig.DEFAULT_RECEIVE_PACK;
+
+ /** Specifications to apply during push. */
+ private List<RefSpec> push = Collections.emptyList();
+
+ /** Should push produce thin-pack when sending objects to remote repository. */
+ private boolean pushThin = DEFAULT_PUSH_THIN;
+
+ /** Should push just check for operation result, not really push. */
+ private boolean dryRun;
+
+ /** Should an incoming (fetch) transfer validate objects? */
+ private boolean checkFetchedObjects;
+
+ /** Should refs no longer on the source be pruned from the destination? */
+ private boolean removeDeletedRefs;
+
+ /** Timeout in seconds to wait before aborting an IO read or write. */
+ private int timeout;
+
+ /**
+ * Create a new transport instance.
+ *
+ * @param local
+ * the repository this instance will fetch into, or push out of.
+ * This must be the repository passed to
+ * {@link #open(Repository, URIish)}.
+ * @param uri
+ * the URI used to access the remote repository. This must be the
+ * URI passed to {@link #open(Repository, URIish)}.
+ */
+ protected Transport(final Repository local, final URIish uri) {
+ final TransferConfig tc = local.getConfig().getTransfer();
+ this.local = local;
+ this.uri = uri;
+ this.checkFetchedObjects = tc.isFsckObjects();
+ }
+
+ /**
+ * Get the URI this transport connects to.
+ * <p>
+ * Each transport instance connects to at most one URI at any point in time.
+ *
+ * @return the URI describing the location of the remote repository.
+ */
+ public URIish getURI() {
+ return uri;
+ }
+
+ /**
+ * Get the name of the remote executable providing upload-pack service.
+ *
+ * @return typically "git-upload-pack".
+ */
+ public String getOptionUploadPack() {
+ return optionUploadPack;
+ }
+
+ /**
+ * Set the name of the remote executable providing upload-pack services.
+ *
+ * @param where
+ * name of the executable.
+ */
+ public void setOptionUploadPack(final String where) {
+ if (where != null && where.length() > 0)
+ optionUploadPack = where;
+ else
+ optionUploadPack = RemoteConfig.DEFAULT_UPLOAD_PACK;
+ }
+
+ /**
+ * Get the description of how annotated tags should be treated during fetch.
+ *
+ * @return option indicating the behavior of annotated tags in fetch.
+ */
+ public TagOpt getTagOpt() {
+ return tagopt;
+ }
+
+ /**
+ * Set the description of how annotated tags should be treated on fetch.
+ *
+ * @param option
+ * method to use when handling annotated tags.
+ */
+ public void setTagOpt(final TagOpt option) {
+ tagopt = option != null ? option : TagOpt.AUTO_FOLLOW;
+ }
+
+ /**
+ * Default setting is: {@link #DEFAULT_FETCH_THIN}
+ *
+ * @return true if fetch should request thin-pack when possible; false
+ * otherwise
+ * @see PackTransport
+ */
+ public boolean isFetchThin() {
+ return fetchThin;
+ }
+
+ /**
+ * Set the thin-pack preference for fetch operation. Default setting is:
+ * {@link #DEFAULT_FETCH_THIN}
+ *
+ * @param fetchThin
+ * true when fetch should request thin-pack when possible; false
+ * when it shouldn't
+ * @see PackTransport
+ */
+ public void setFetchThin(final boolean fetchThin) {
+ this.fetchThin = fetchThin;
+ }
+
+ /**
+ * @return true if fetch will verify received objects are formatted
+ * correctly. Validating objects requires more CPU time on the
+ * client side of the connection.
+ */
+ public boolean isCheckFetchedObjects() {
+ return checkFetchedObjects;
+ }
+
+ /**
+ * @param check
+ * true to enable checking received objects; false to assume all
+ * received objects are valid.
+ */
+ public void setCheckFetchedObjects(final boolean check) {
+ checkFetchedObjects = check;
+ }
+
+ /**
+ * Default setting is: {@link RemoteConfig#DEFAULT_RECEIVE_PACK}
+ *
+ * @return remote executable providing receive-pack service for pack
+ * transports.
+ * @see PackTransport
+ */
+ public String getOptionReceivePack() {
+ return optionReceivePack;
+ }
+
+ /**
+ * Set remote executable providing receive-pack service for pack transports.
+ * Default setting is: {@link RemoteConfig#DEFAULT_RECEIVE_PACK}
+ *
+ * @param optionReceivePack
+ * remote executable, if null or empty default one is set;
+ */
+ public void setOptionReceivePack(String optionReceivePack) {
+ if (optionReceivePack != null && optionReceivePack.length() > 0)
+ this.optionReceivePack = optionReceivePack;
+ else
+ this.optionReceivePack = RemoteConfig.DEFAULT_RECEIVE_PACK;
+ }
+
+ /**
+ * Default setting is: {@value #DEFAULT_PUSH_THIN}
+ *
+ * @return true if push should produce thin-pack in pack transports
+ * @see PackTransport
+ */
+ public boolean isPushThin() {
+ return pushThin;
+ }
+
+ /**
+ * Set thin-pack preference for push operation. Default setting is:
+ * {@value #DEFAULT_PUSH_THIN}
+ *
+ * @param pushThin
+ * true when push should produce thin-pack in pack transports;
+ * false when it shouldn't
+ * @see PackTransport
+ */
+ public void setPushThin(final boolean pushThin) {
+ this.pushThin = pushThin;
+ }
+
+ /**
+ * @return true if destination refs should be removed if they no longer
+ * exist at the source repository.
+ */
+ public boolean isRemoveDeletedRefs() {
+ return removeDeletedRefs;
+ }
+
+ /**
+ * Set whether or not to remove refs which no longer exist in the source.
+ * <p>
+ * If true, refs at the destination repository (local for fetch, remote for
+ * push) are deleted if they no longer exist on the source side (remote for
+ * fetch, local for push).
+ * <p>
+ * False by default, as this may cause data to become unreachable, and
+ * eventually be deleted on the next GC.
+ *
+ * @param remove true to remove refs that no longer exist.
+ */
+ public void setRemoveDeletedRefs(final boolean remove) {
+ removeDeletedRefs = remove;
+ }
+
+ /**
+ * Apply provided remote configuration on this transport.
+ *
+ * @param cfg
+ * configuration to apply on this transport.
+ */
+ public void applyConfig(final RemoteConfig cfg) {
+ setOptionUploadPack(cfg.getUploadPack());
+ setOptionReceivePack(cfg.getReceivePack());
+ setTagOpt(cfg.getTagOpt());
+ fetch = cfg.getFetchRefSpecs();
+ push = cfg.getPushRefSpecs();
+ timeout = cfg.getTimeout();
+ }
+
+ /**
+ * @return true if push operation should just check for possible result and
+ * not really update remote refs, false otherwise - when push should
+ * act normally.
+ */
+ public boolean isDryRun() {
+ return dryRun;
+ }
+
+ /**
+ * Set dry run option for push operation.
+ *
+ * @param dryRun
+ * true if push operation should just check for possible result
+ * and not really update remote refs, false otherwise - when push
+ * should act normally.
+ */
+ public void setDryRun(final boolean dryRun) {
+ this.dryRun = dryRun;
+ }
+
+ /** @return timeout (in seconds) before aborting an IO operation. */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Set the timeout before willing to abort an IO call.
+ *
+ * @param seconds
+ * number of seconds to wait (with no data transfer occurring)
+ * before aborting an IO read or write operation with this
+ * remote.
+ */
+ public void setTimeout(final int seconds) {
+ timeout = seconds;
+ }
+
+ /**
+ * Fetch objects and refs from the remote repository to the local one.
+ * <p>
+ * This is a utility function providing standard fetch behavior. Local
+ * tracking refs associated with the remote repository are automatically
+ * updated if this transport was created from a {@link RemoteConfig} with
+ * fetch RefSpecs defined.
+ *
+ * @param monitor
+ * progress monitor to inform the user about our processing
+ * activity. Must not be null. Use {@link NullProgressMonitor} if
+ * progress updates are not interesting or necessary.
+ * @param toFetch
+ * specification of refs to fetch locally. May be null or the
+ * empty collection to use the specifications from the
+ * RemoteConfig. Source for each RefSpec can't be null.
+ * @return information describing the tracking refs updated.
+ * @throws NotSupportedException
+ * this transport implementation does not support fetching
+ * objects.
+ * @throws TransportException
+ * the remote connection could not be established or object
+ * copying (if necessary) failed or update specification was
+ * incorrect.
+ */
+ public FetchResult fetch(final ProgressMonitor monitor,
+ Collection<RefSpec> toFetch) throws NotSupportedException,
+ TransportException {
+ if (toFetch == null || toFetch.isEmpty()) {
+ // If the caller did not ask for anything use the defaults.
+ //
+ if (fetch.isEmpty())
+ throw new TransportException("Nothing to fetch.");
+ toFetch = fetch;
+ } else if (!fetch.isEmpty()) {
+ // If the caller asked for something specific without giving
+ // us the local tracking branch see if we can update any of
+ // the local tracking branches without incurring additional
+ // object transfer overheads.
+ //
+ final Collection<RefSpec> tmp = new ArrayList<RefSpec>(toFetch);
+ for (final RefSpec requested : toFetch) {
+ final String reqSrc = requested.getSource();
+ for (final RefSpec configured : fetch) {
+ final String cfgSrc = configured.getSource();
+ final String cfgDst = configured.getDestination();
+ if (cfgSrc.equals(reqSrc) && cfgDst != null) {
+ tmp.add(configured);
+ break;
+ }
+ }
+ }
+ toFetch = tmp;
+ }
+
+ final FetchResult result = new FetchResult();
+ new FetchProcess(this, toFetch).execute(monitor, result);
+ return result;
+ }
+
+ /**
+ * Push objects and refs from the local repository to the remote one.
+ * <p>
+ * This is a utility function providing standard push behavior. It updates
+ * remote refs and send there necessary objects according to remote ref
+ * update specification. After successful remote ref update, associated
+ * locally stored tracking branch is updated if set up accordingly. Detailed
+ * operation result is provided after execution.
+ * <p>
+ * For setting up remote ref update specification from ref spec, see helper
+ * method {@link #findRemoteRefUpdatesFor(Collection)}, predefined refspecs
+ * ({@link #REFSPEC_TAGS}, {@link #REFSPEC_PUSH_ALL}) or consider using
+ * directly {@link RemoteRefUpdate} for more possibilities.
+ * <p>
+ * When {@link #isDryRun()} is true, result of this operation is just
+ * estimation of real operation result, no real action is performed.
+ *
+ * @see RemoteRefUpdate
+ *
+ * @param monitor
+ * progress monitor to inform the user about our processing
+ * activity. Must not be null. Use {@link NullProgressMonitor} if
+ * progress updates are not interesting or necessary.
+ * @param toPush
+ * specification of refs to push. May be null or the empty
+ * collection to use the specifications from the RemoteConfig
+ * converted by {@link #findRemoteRefUpdatesFor(Collection)}. No
+ * more than 1 RemoteRefUpdate with the same remoteName is
+ * allowed. These objects are modified during this call.
+ * @return information about results of remote refs updates, tracking refs
+ * updates and refs advertised by remote repository.
+ * @throws NotSupportedException
+ * this transport implementation does not support pushing
+ * objects.
+ * @throws TransportException
+ * the remote connection could not be established or object
+ * copying (if necessary) failed at I/O or protocol level or
+ * update specification was incorrect.
+ */
+ public PushResult push(final ProgressMonitor monitor,
+ Collection<RemoteRefUpdate> toPush) throws NotSupportedException,
+ TransportException {
+ if (toPush == null || toPush.isEmpty()) {
+ // If the caller did not ask for anything use the defaults.
+ try {
+ toPush = findRemoteRefUpdatesFor(push);
+ } catch (final IOException e) {
+ throw new TransportException(
+ "Problem with resolving push ref specs locally: "
+ + e.getMessage(), e);
+ }
+ if (toPush.isEmpty())
+ throw new TransportException("Nothing to push.");
+ }
+ final PushProcess pushProcess = new PushProcess(this, toPush);
+ return pushProcess.execute(monitor);
+ }
+
+ /**
+ * Convert push remote refs update specification from {@link RefSpec} form
+ * to {@link RemoteRefUpdate}. Conversion expands wildcards by matching
+ * source part to local refs. expectedOldObjectId in RemoteRefUpdate is
+ * always set as null. Tracking branch is configured if RefSpec destination
+ * matches source of any fetch ref spec for this transport remote
+ * configuration.
+ * <p>
+ * Conversion is performed for context of this transport (database, fetch
+ * specifications).
+ *
+ * @param specs
+ * collection of RefSpec to convert.
+ * @return collection of set up {@link RemoteRefUpdate}.
+ * @throws IOException
+ * when problem occurred during conversion or specification set
+ * up: most probably, missing objects or refs.
+ */
+ public Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
+ final Collection<RefSpec> specs) throws IOException {
+ return findRemoteRefUpdatesFor(local, specs, fetch);
+ }
+
+ /**
+ * Begins a new connection for fetching from the remote repository.
+ *
+ * @return a fresh connection to fetch from the remote repository.
+ * @throws NotSupportedException
+ * the implementation does not support fetching.
+ * @throws TransportException
+ * the remote connection could not be established.
+ */
+ public abstract FetchConnection openFetch() throws NotSupportedException,
+ TransportException;
+
+ /**
+ * Begins a new connection for pushing into the remote repository.
+ *
+ * @return a fresh connection to push into the remote repository.
+ * @throws NotSupportedException
+ * the implementation does not support pushing.
+ * @throws TransportException
+ * the remote connection could not be established
+ */
+ public abstract PushConnection openPush() throws NotSupportedException,
+ TransportException;
+
+ /**
+ * Close any resources used by this transport.
+ * <p>
+ * If the remote repository is contacted by a network socket this method
+ * must close that network socket, disconnecting the two peers. If the
+ * remote repository is actually local (same system) this method must close
+ * any open file handles used to read the "remote" repository.
+ */
+ public abstract void close();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
new file mode 100644
index 0000000000..6a1a17f605
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.util.FS;
+
+/**
+ * Transport over the non-Git aware Amazon S3 protocol.
+ * <p>
+ * This transport communicates with the Amazon S3 servers (a non-free commercial
+ * hosting service that users must subscribe to). Some users may find transport
+ * to and from S3 to be a useful backup service.
+ * <p>
+ * The transport does not require any specialized Git support on the remote
+ * (server side) repository, as Amazon does not provide any such support.
+ * Repository files are retrieved directly through the S3 API, which uses
+ * extended HTTP/1.1 semantics. This make it possible to read or write Git data
+ * from a remote repository that is stored on S3.
+ * <p>
+ * Unlike the HTTP variant (see {@link TransportHttp}) we rely upon being able
+ * to list objects in a bucket, as the S3 API supports this function. By listing
+ * the bucket contents we can avoid relying on <code>objects/info/packs</code>
+ * or <code>info/refs</code> in the remote repository.
+ * <p>
+ * Concurrent pushing over this transport is not supported. Multiple concurrent
+ * push operations may cause confusion in the repository state.
+ *
+ * @see WalkFetchConnection
+ * @see WalkPushConnection
+ */
+public class TransportAmazonS3 extends HttpTransport implements WalkTransport {
+ static final String S3_SCHEME = "amazon-s3";
+
+ static boolean canHandle(final URIish uri) {
+ if (!uri.isRemote())
+ return false;
+ return S3_SCHEME.equals(uri.getScheme());
+ }
+
+ /** User information necessary to connect to S3. */
+ private final AmazonS3 s3;
+
+ /** Bucket the remote repository is stored in. */
+ private final String bucket;
+
+ /**
+ * Key prefix which all objects related to the repository start with.
+ * <p>
+ * The prefix does not start with "/".
+ * <p>
+ * The prefix does not end with "/". The trailing slash is stripped during
+ * the constructor if a trailing slash was supplied in the URIish.
+ * <p>
+ * All files within the remote repository start with
+ * <code>keyPrefix + "/"</code>.
+ */
+ private final String keyPrefix;
+
+ TransportAmazonS3(final Repository local, final URIish uri)
+ throws NotSupportedException {
+ super(local, uri);
+
+ Properties props = null;
+ File propsFile = new File(local.getDirectory(), uri.getUser());
+ if (!propsFile.isFile())
+ propsFile = new File(FS.userHome(), uri.getUser());
+ if (propsFile.isFile()) {
+ try {
+ props = AmazonS3.properties(propsFile);
+ } catch (IOException e) {
+ throw new NotSupportedException("cannot read " + propsFile, e);
+ }
+ } else {
+ props = new Properties();
+ props.setProperty("accesskey", uri.getUser());
+ props.setProperty("secretkey", uri.getPass());
+ }
+
+ s3 = new AmazonS3(props);
+ bucket = uri.getHost();
+
+ String p = uri.getPath();
+ if (p.startsWith("/"))
+ p = p.substring(1);
+ if (p.endsWith("/"))
+ p = p.substring(0, p.length() - 1);
+ keyPrefix = p;
+ }
+
+ @Override
+ public FetchConnection openFetch() throws TransportException {
+ final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects");
+ final WalkFetchConnection r = new WalkFetchConnection(this, c);
+ r.available(c.readAdvertisedRefs());
+ return r;
+ }
+
+ @Override
+ public PushConnection openPush() throws TransportException {
+ final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects");
+ final WalkPushConnection r = new WalkPushConnection(this, c);
+ r.available(c.readAdvertisedRefs());
+ return r;
+ }
+
+ @Override
+ public void close() {
+ // No explicit connections are maintained.
+ }
+
+ class DatabaseS3 extends WalkRemoteObjectDatabase {
+ private final String bucketName;
+
+ private final String objectsKey;
+
+ DatabaseS3(final String b, final String o) {
+ bucketName = b;
+ objectsKey = o;
+ }
+
+ private String resolveKey(String subpath) {
+ if (subpath.endsWith("/"))
+ subpath = subpath.substring(0, subpath.length() - 1);
+ String k = objectsKey;
+ while (subpath.startsWith(ROOT_DIR)) {
+ k = k.substring(0, k.lastIndexOf('/'));
+ subpath = subpath.substring(3);
+ }
+ return k + "/" + subpath;
+ }
+
+ @Override
+ URIish getURI() {
+ URIish u = new URIish();
+ u = u.setScheme(S3_SCHEME);
+ u = u.setHost(bucketName);
+ u = u.setPath("/" + objectsKey);
+ return u;
+ }
+
+ @Override
+ Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
+ try {
+ return readAlternates(INFO_ALTERNATES);
+ } catch (FileNotFoundException err) {
+ // Fall through.
+ }
+ return null;
+ }
+
+ @Override
+ WalkRemoteObjectDatabase openAlternate(final String location)
+ throws IOException {
+ return new DatabaseS3(bucketName, resolveKey(location));
+ }
+
+ @Override
+ Collection<String> getPackNames() throws IOException {
+ final HashSet<String> have = new HashSet<String>();
+ have.addAll(s3.list(bucket, resolveKey("pack")));
+
+ final Collection<String> packs = new ArrayList<String>();
+ for (final String n : have) {
+ if (!n.startsWith("pack-") || !n.endsWith(".pack"))
+ continue;
+
+ final String in = n.substring(0, n.length() - 5) + ".idx";
+ if (have.contains(in))
+ packs.add(n);
+ }
+ return packs;
+ }
+
+ @Override
+ FileStream open(final String path) throws IOException {
+ final URLConnection c = s3.get(bucket, resolveKey(path));
+ final InputStream raw = c.getInputStream();
+ final InputStream in = s3.decrypt(c);
+ final int len = c.getContentLength();
+ return new FileStream(in, raw == in ? len : -1);
+ }
+
+ @Override
+ void deleteFile(final String path) throws IOException {
+ s3.delete(bucket, resolveKey(path));
+ }
+
+ @Override
+ OutputStream writeFile(final String path,
+ final ProgressMonitor monitor, final String monitorTask)
+ throws IOException {
+ return s3.beginPut(bucket, resolveKey(path), monitor, monitorTask);
+ }
+
+ @Override
+ void writeFile(final String path, final byte[] data) throws IOException {
+ s3.put(bucket, resolveKey(path), data);
+ }
+
+ Map<String, Ref> readAdvertisedRefs() throws TransportException {
+ final TreeMap<String, Ref> avail = new TreeMap<String, Ref>();
+ readPackedRefs(avail);
+ readLooseRefs(avail);
+ readRef(avail, Constants.HEAD);
+ return avail;
+ }
+
+ private void readLooseRefs(final TreeMap<String, Ref> avail)
+ throws TransportException {
+ try {
+ for (final String n : s3.list(bucket, resolveKey(ROOT_DIR
+ + "refs")))
+ readRef(avail, "refs/" + n);
+ } catch (IOException e) {
+ throw new TransportException(getURI(), "cannot list refs", e);
+ }
+ }
+
+ private Ref readRef(final TreeMap<String, Ref> avail, final String rn)
+ throws TransportException {
+ final String s;
+ String ref = ROOT_DIR + rn;
+ try {
+ final BufferedReader br = openReader(ref);
+ try {
+ s = br.readLine();
+ } finally {
+ br.close();
+ }
+ } catch (FileNotFoundException noRef) {
+ return null;
+ } catch (IOException err) {
+ throw new TransportException(getURI(), "read " + ref, err);
+ }
+
+ if (s == null)
+ throw new TransportException(getURI(), "Empty ref: " + rn);
+
+ if (s.startsWith("ref: ")) {
+ final String target = s.substring("ref: ".length());
+ Ref r = avail.get(target);
+ if (r == null)
+ r = readRef(avail, target);
+ if (r == null)
+ return null;
+ r = new Ref(r.getStorage(), rn, r.getObjectId(), r
+ .getPeeledObjectId(), r.isPeeled());
+ avail.put(r.getName(), r);
+ return r;
+ }
+
+ if (ObjectId.isId(s)) {
+ final Ref r = new Ref(loose(avail.get(rn)), rn, ObjectId
+ .fromString(s));
+ avail.put(r.getName(), r);
+ return r;
+ }
+
+ throw new TransportException(getURI(), "Bad ref: " + rn + ": " + s);
+ }
+
+ private Storage loose(final Ref r) {
+ if (r != null && r.getStorage() == Storage.PACKED)
+ return Storage.LOOSE_PACKED;
+ return Storage.LOOSE;
+ }
+
+ @Override
+ void close() {
+ // We do not maintain persistent connections.
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java
new file mode 100644
index 0000000000..05be0bbdf7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+/**
+ * Marker interface for transports that supports fetching from a git bundle
+ * (sneaker-net object transport).
+ * <p>
+ * Push support for a bundle is complex, as one does not have a peer to
+ * communicate with to decide what the peer already knows. So push is not
+ * supported by the bundle transport.
+ */
+public interface TransportBundle extends PackTransport {
+ /**
+ * Bundle signature
+ */
+ public static final String V2_BUNDLE_SIGNATURE = "# v2 git bundle";
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java
new file mode 100644
index 0000000000..17e3bdd229
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+
+class TransportBundleFile extends Transport implements TransportBundle {
+ static boolean canHandle(final URIish uri) {
+ if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
+ || uri.getPass() != null || uri.getPath() == null)
+ return false;
+
+ if ("file".equals(uri.getScheme()) || uri.getScheme() == null) {
+ final File f = FS.resolve(new File("."), uri.getPath());
+ return f.isFile() || f.getName().endsWith(".bundle");
+ }
+
+ return false;
+ }
+
+ private final File bundle;
+
+ TransportBundleFile(final Repository local, final URIish uri) {
+ super(local, uri);
+ bundle = FS.resolve(new File("."), uri.getPath()).getAbsoluteFile();
+ }
+
+ @Override
+ public FetchConnection openFetch() throws NotSupportedException,
+ TransportException {
+ final InputStream src;
+ try {
+ src = new FileInputStream(bundle);
+ } catch (FileNotFoundException err) {
+ throw new TransportException(uri, "not found");
+ }
+ return new BundleFetchConnection(this, src);
+ }
+
+ @Override
+ public PushConnection openPush() throws NotSupportedException {
+ throw new NotSupportedException(
+ "Push is not supported for bundle transport");
+ }
+
+ @Override
+ public void close() {
+ // Resources must be established per-connection.
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java
new file mode 100644
index 0000000000..e5188bb236
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Single shot fetch from a streamed Git bundle.
+ * <p>
+ * The bundle is read from an unbuffered input stream, which limits the
+ * transport to opening at most one FetchConnection before needing to recreate
+ * the transport instance.
+ */
+public class TransportBundleStream extends Transport implements TransportBundle {
+ private InputStream src;
+
+ /**
+ * Create a new transport to fetch objects from a streamed bundle.
+ * <p>
+ * The stream can be unbuffered (buffering is automatically provided
+ * internally to smooth out short reads) and unpositionable (the stream is
+ * read from only once, sequentially).
+ * <p>
+ * When the FetchConnection or the this instance is closed the supplied
+ * input stream is also automatically closed. This frees callers from
+ * needing to keep track of the supplied stream.
+ *
+ * @param db
+ * repository the fetched objects will be loaded into.
+ * @param uri
+ * symbolic name of the source of the stream. The URI can
+ * reference a non-existent resource. It is used only for
+ * exception reporting.
+ * @param in
+ * the stream to read the bundle from.
+ */
+ public TransportBundleStream(final Repository db, final URIish uri,
+ final InputStream in) {
+ super(db, uri);
+ src = in;
+ }
+
+ @Override
+ public FetchConnection openFetch() throws TransportException {
+ if (src == null)
+ throw new TransportException(uri, "Only one fetch supported");
+ try {
+ return new BundleFetchConnection(this, src);
+ } finally {
+ src = null;
+ }
+ }
+
+ @Override
+ public PushConnection openPush() throws NotSupportedException {
+ throw new NotSupportedException(
+ "Push is not supported for bundle transport");
+ }
+
+ @Override
+ public void close() {
+ if (src != null) {
+ try {
+ src.close();
+ } catch (IOException err) {
+ // Ignore a close error.
+ } finally {
+ src = null;
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java
new file mode 100644
index 0000000000..a127ff50ab
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Transport through a git-daemon waiting for anonymous TCP connections.
+ * <p>
+ * This transport supports the <code>git://</code> protocol, usually run on
+ * the IANA registered port 9418. It is a popular means for distributing open
+ * source projects, as there are no authentication or authorization overheads.
+ */
+class TransportGitAnon extends TcpTransport implements PackTransport {
+ static final int GIT_PORT = Daemon.DEFAULT_PORT;
+
+ static boolean canHandle(final URIish uri) {
+ return "git".equals(uri.getScheme());
+ }
+
+ TransportGitAnon(final Repository local, final URIish uri) {
+ super(local, uri);
+ }
+
+ @Override
+ public FetchConnection openFetch() throws TransportException {
+ return new TcpFetchConnection();
+ }
+
+ @Override
+ public PushConnection openPush() throws TransportException {
+ return new TcpPushConnection();
+ }
+
+ @Override
+ public void close() {
+ // Resources must be established per-connection.
+ }
+
+ Socket openConnection() throws TransportException {
+ final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;
+ final int port = uri.getPort() > 0 ? uri.getPort() : GIT_PORT;
+ final Socket s = new Socket();
+ try {
+ final InetAddress host = InetAddress.getByName(uri.getHost());
+ s.bind(null);
+ s.connect(new InetSocketAddress(host, port), tms);
+ } catch (IOException c) {
+ try {
+ s.close();
+ } catch (IOException closeErr) {
+ // ignore a failure during close, we're already failing
+ }
+ if (c instanceof UnknownHostException)
+ throw new TransportException(uri, "unknown host");
+ if (c instanceof ConnectException)
+ throw new TransportException(uri, c.getMessage());
+ throw new TransportException(uri, c.getMessage(), c);
+ }
+ return s;
+ }
+
+ void service(final String name, final PacketLineOut pckOut)
+ throws IOException {
+ final StringBuilder cmd = new StringBuilder();
+ cmd.append(name);
+ cmd.append(' ');
+ cmd.append(uri.getPath());
+ cmd.append('\0');
+ cmd.append("host=");
+ cmd.append(uri.getHost());
+ if (uri.getPort() > 0 && uri.getPort() != GIT_PORT) {
+ cmd.append(":");
+ cmd.append(uri.getPort());
+ }
+ cmd.append('\0');
+ pckOut.writeString(cmd.toString());
+ pckOut.flush();
+ }
+
+ class TcpFetchConnection extends BasePackFetchConnection {
+ private Socket sock;
+
+ TcpFetchConnection() throws TransportException {
+ super(TransportGitAnon.this);
+ sock = openConnection();
+ try {
+ init(sock.getInputStream(), sock.getOutputStream());
+ service("git-upload-pack", pckOut);
+ } catch (IOException err) {
+ close();
+ throw new TransportException(uri,
+ "remote hung up unexpectedly", err);
+ }
+ readAdvertisedRefs();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+
+ if (sock != null) {
+ try {
+ sock.close();
+ } catch (IOException err) {
+ // Ignore errors during close.
+ } finally {
+ sock = null;
+ }
+ }
+ }
+ }
+
+ class TcpPushConnection extends BasePackPushConnection {
+ private Socket sock;
+
+ TcpPushConnection() throws TransportException {
+ super(TransportGitAnon.this);
+ sock = openConnection();
+ try {
+ init(sock.getInputStream(), sock.getOutputStream());
+ service("git-receive-pack", pckOut);
+ } catch (IOException err) {
+ close();
+ throw new TransportException(uri,
+ "remote hung up unexpectedly", err);
+ }
+ readAdvertisedRefs();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+
+ if (sock != null) {
+ try {
+ sock.close();
+ } catch (IOException err) {
+ // Ignore errors during close.
+ } finally {
+ sock = null;
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
new file mode 100644
index 0000000000..55636f8dcc
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import org.eclipse.jgit.errors.NoRemoteRepositoryException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.QuotedString;
+
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.JSchException;
+
+/**
+ * Transport through an SSH tunnel.
+ * <p>
+ * The SSH transport requires the remote side to have Git installed, as the
+ * transport logs into the remote system and executes a Git helper program on
+ * the remote side to read (or write) the remote repository's files.
+ * <p>
+ * This transport does not support direct SCP style of copying files, as it
+ * assumes there are Git specific smarts on the remote side to perform object
+ * enumeration, save file modification and hook execution.
+ */
+public class TransportGitSsh extends SshTransport implements PackTransport {
+ static boolean canHandle(final URIish uri) {
+ if (!uri.isRemote())
+ return false;
+ final String scheme = uri.getScheme();
+ if ("ssh".equals(scheme))
+ return true;
+ if ("ssh+git".equals(scheme))
+ return true;
+ if ("git+ssh".equals(scheme))
+ return true;
+ if (scheme == null && uri.getHost() != null && uri.getPath() != null)
+ return true;
+ return false;
+ }
+
+ OutputStream errStream;
+
+ TransportGitSsh(final Repository local, final URIish uri) {
+ super(local, uri);
+ }
+
+ @Override
+ public FetchConnection openFetch() throws TransportException {
+ return new SshFetchConnection();
+ }
+
+ @Override
+ public PushConnection openPush() throws TransportException {
+ return new SshPushConnection();
+ }
+
+ private static void sqMinimal(final StringBuilder cmd, final String val) {
+ if (val.matches("^[a-zA-Z0-9._/-]*$")) {
+ // If the string matches only generally safe characters
+ // that the shell is not going to evaluate specially we
+ // should leave the string unquoted. Not all systems
+ // actually run a shell and over-quoting confuses them
+ // when it comes to the command name.
+ //
+ cmd.append(val);
+ } else {
+ sq(cmd, val);
+ }
+ }
+
+ private static void sqAlways(final StringBuilder cmd, final String val) {
+ sq(cmd, val);
+ }
+
+ private static void sq(final StringBuilder cmd, final String val) {
+ if (val.length() > 0)
+ cmd.append(QuotedString.BOURNE.quote(val));
+ }
+
+
+ ChannelExec exec(final String exe) throws TransportException {
+ initSession();
+
+ final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;
+ try {
+ final ChannelExec channel = (ChannelExec) sock.openChannel("exec");
+ String path = uri.getPath();
+ if (uri.getScheme() != null && uri.getPath().startsWith("/~"))
+ path = (uri.getPath().substring(1));
+
+ final StringBuilder cmd = new StringBuilder();
+ final int gitspace = exe.indexOf("git ");
+ if (gitspace >= 0) {
+ sqMinimal(cmd, exe.substring(0, gitspace + 3));
+ cmd.append(' ');
+ sqMinimal(cmd, exe.substring(gitspace + 4));
+ } else
+ sqMinimal(cmd, exe);
+ cmd.append(' ');
+ sqAlways(cmd, path);
+ channel.setCommand(cmd.toString());
+ errStream = createErrorStream();
+ channel.setErrStream(errStream, true);
+ channel.connect(tms);
+ return channel;
+ } catch (JSchException je) {
+ throw new TransportException(uri, je.getMessage(), je);
+ }
+ }
+
+ /**
+ * @return the error stream for the channel, the stream is used to detect
+ * specific error reasons for exceptions.
+ */
+ private static OutputStream createErrorStream() {
+ return new OutputStream() {
+ private StringBuilder all = new StringBuilder();
+
+ private StringBuilder sb = new StringBuilder();
+
+ public String toString() {
+ String r = all.toString();
+ while (r.endsWith("\n"))
+ r = r.substring(0, r.length() - 1);
+ return r;
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ if (b == '\r') {
+ return;
+ }
+
+ sb.append((char) b);
+
+ if (b == '\n') {
+ all.append(sb);
+ sb.setLength(0);
+ }
+ }
+ };
+ }
+
+ NoRemoteRepositoryException cleanNotFound(NoRemoteRepositoryException nf) {
+ String why = errStream.toString();
+ if (why == null || why.length() == 0)
+ return nf;
+
+ String path = uri.getPath();
+ if (uri.getScheme() != null && uri.getPath().startsWith("/~"))
+ path = uri.getPath().substring(1);
+
+ final StringBuilder pfx = new StringBuilder();
+ pfx.append("fatal: ");
+ sqAlways(pfx, path);
+ pfx.append(": ");
+ if (why.startsWith(pfx.toString()))
+ why = why.substring(pfx.length());
+
+ return new NoRemoteRepositoryException(uri, why);
+ }
+
+ // JSch won't let us interrupt writes when we use our InterruptTimer to
+ // break out of a long-running write operation. To work around that we
+ // spawn a background thread to shuttle data through a pipe, as we can
+ // issue an interrupted write out of that. Its slower, so we only use
+ // this route if there is a timeout.
+ //
+ private OutputStream outputStream(ChannelExec channel) throws IOException {
+ final OutputStream out = channel.getOutputStream();
+ if (getTimeout() <= 0)
+ return out;
+ final PipedInputStream pipeIn = new PipedInputStream();
+ final CopyThread copyThread = new CopyThread(pipeIn, out);
+ final PipedOutputStream pipeOut = new PipedOutputStream(pipeIn) {
+ @Override
+ public void flush() throws IOException {
+ super.flush();
+ copyThread.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ try {
+ copyThread.join(getTimeout() * 1000);
+ } catch (InterruptedException e) {
+ // Just wake early, the thread will terminate anyway.
+ }
+ }
+ };
+ copyThread.start();
+ return pipeOut;
+ }
+
+ private static class CopyThread extends Thread {
+ private final InputStream src;
+
+ private final OutputStream dst;
+
+ private volatile boolean doFlush;
+
+ CopyThread(final InputStream i, final OutputStream o) {
+ setName(Thread.currentThread().getName() + "-Output");
+ src = i;
+ dst = o;
+ }
+
+ void flush() {
+ if (!doFlush) {
+ doFlush = true;
+ interrupt();
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ final byte[] buf = new byte[1024];
+ for (;;) {
+ try {
+ if (doFlush) {
+ doFlush = false;
+ dst.flush();
+ }
+
+ final int n;
+ try {
+ n = src.read(buf);
+ } catch (InterruptedIOException wakey) {
+ continue;
+ }
+ if (n < 0)
+ break;
+ dst.write(buf, 0, n);
+ } catch (IOException e) {
+ break;
+ }
+ }
+ } finally {
+ try {
+ src.close();
+ } catch (IOException e) {
+ // Ignore IO errors on close
+ }
+ try {
+ dst.close();
+ } catch (IOException e) {
+ // Ignore IO errors on close
+ }
+ }
+ }
+ }
+
+ class SshFetchConnection extends BasePackFetchConnection {
+ private ChannelExec channel;
+
+ SshFetchConnection() throws TransportException {
+ super(TransportGitSsh.this);
+ try {
+ channel = exec(getOptionUploadPack());
+
+ if (channel.isConnected())
+ init(channel.getInputStream(), outputStream(channel));
+ else
+ throw new TransportException(uri, errStream.toString());
+
+ } catch (TransportException err) {
+ close();
+ throw err;
+ } catch (IOException err) {
+ close();
+ throw new TransportException(uri,
+ "remote hung up unexpectedly", err);
+ }
+
+ try {
+ readAdvertisedRefs();
+ } catch (NoRemoteRepositoryException notFound) {
+ throw cleanNotFound(notFound);
+ }
+ }
+
+ @Override
+ public void close() {
+ super.close();
+
+ if (channel != null) {
+ try {
+ if (channel.isConnected())
+ channel.disconnect();
+ } finally {
+ channel = null;
+ }
+ }
+ }
+ }
+
+ class SshPushConnection extends BasePackPushConnection {
+ private ChannelExec channel;
+
+ SshPushConnection() throws TransportException {
+ super(TransportGitSsh.this);
+ try {
+ channel = exec(getOptionReceivePack());
+
+ if (channel.isConnected())
+ init(channel.getInputStream(), outputStream(channel));
+ else
+ throw new TransportException(uri, errStream.toString());
+
+ } catch (TransportException err) {
+ close();
+ throw err;
+ } catch (IOException err) {
+ close();
+ throw new TransportException(uri,
+ "remote hung up unexpectedly", err);
+ }
+
+ try {
+ readAdvertisedRefs();
+ } catch (NoRemoteRepositoryException notFound) {
+ throw cleanNotFound(notFound);
+ }
+ }
+
+ @Override
+ public void close() {
+ super.close();
+
+ if (channel != null) {
+ try {
+ if (channel.isConnected())
+ channel.disconnect();
+ } finally {
+ channel = null;
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
new file mode 100644
index 0000000000..65686b9d42
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.HttpSupport;
+
+/**
+ * Transport over the non-Git aware HTTP and FTP protocol.
+ * <p>
+ * The HTTP transport does not require any specialized Git support on the remote
+ * (server side) repository. Object files are retrieved directly through
+ * standard HTTP GET requests, making it easy to serve a Git repository through
+ * a standard web host provider that does not offer specific support for Git.
+ *
+ * @see WalkFetchConnection
+ */
+public class TransportHttp extends HttpTransport implements WalkTransport {
+ static boolean canHandle(final URIish uri) {
+ if (!uri.isRemote())
+ return false;
+ final String s = uri.getScheme();
+ return "http".equals(s) || "https".equals(s) || "ftp".equals(s);
+ }
+
+ private final URL baseUrl;
+
+ private final URL objectsUrl;
+
+ private final ProxySelector proxySelector;
+
+ TransportHttp(final Repository local, final URIish uri)
+ throws NotSupportedException {
+ super(local, uri);
+ try {
+ String uriString = uri.toString();
+ if (!uriString.endsWith("/"))
+ uriString += "/";
+ baseUrl = new URL(uriString);
+ objectsUrl = new URL(baseUrl, "objects/");
+ } catch (MalformedURLException e) {
+ throw new NotSupportedException("Invalid URL " + uri, e);
+ }
+ proxySelector = ProxySelector.getDefault();
+ }
+
+ @Override
+ public FetchConnection openFetch() throws TransportException {
+ final HttpObjectDB c = new HttpObjectDB(objectsUrl);
+ final WalkFetchConnection r = new WalkFetchConnection(this, c);
+ r.available(c.readAdvertisedRefs());
+ return r;
+ }
+
+ @Override
+ public PushConnection openPush() throws NotSupportedException,
+ TransportException {
+ final String s = getURI().getScheme();
+ throw new NotSupportedException("Push not supported over " + s + ".");
+ }
+
+ @Override
+ public void close() {
+ // No explicit connections are maintained.
+ }
+
+ class HttpObjectDB extends WalkRemoteObjectDatabase {
+ private final URL objectsUrl;
+
+ HttpObjectDB(final URL b) {
+ objectsUrl = b;
+ }
+
+ @Override
+ URIish getURI() {
+ return new URIish(objectsUrl);
+ }
+
+ @Override
+ Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
+ try {
+ return readAlternates(INFO_HTTP_ALTERNATES);
+ } catch (FileNotFoundException err) {
+ // Fall through.
+ }
+
+ try {
+ return readAlternates(INFO_ALTERNATES);
+ } catch (FileNotFoundException err) {
+ // Fall through.
+ }
+
+ return null;
+ }
+
+ @Override
+ WalkRemoteObjectDatabase openAlternate(final String location)
+ throws IOException {
+ return new HttpObjectDB(new URL(objectsUrl, location));
+ }
+
+ @Override
+ Collection<String> getPackNames() throws IOException {
+ final Collection<String> packs = new ArrayList<String>();
+ try {
+ final BufferedReader br = openReader(INFO_PACKS);
+ try {
+ for (;;) {
+ final String s = br.readLine();
+ if (s == null || s.length() == 0)
+ break;
+ if (!s.startsWith("P pack-") || !s.endsWith(".pack"))
+ throw invalidAdvertisement(s);
+ packs.add(s.substring(2));
+ }
+ return packs;
+ } finally {
+ br.close();
+ }
+ } catch (FileNotFoundException err) {
+ return packs;
+ }
+ }
+
+ @Override
+ FileStream open(final String path) throws IOException {
+ final URL base = objectsUrl;
+ final URL u = new URL(base, path);
+ final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
+ final HttpURLConnection c;
+
+ c = (HttpURLConnection) u.openConnection(proxy);
+ switch (HttpSupport.response(c)) {
+ case HttpURLConnection.HTTP_OK:
+ final InputStream in = c.getInputStream();
+ final int len = c.getContentLength();
+ return new FileStream(in, len);
+ case HttpURLConnection.HTTP_NOT_FOUND:
+ throw new FileNotFoundException(u.toString());
+ default:
+ throw new IOException(u.toString() + ": "
+ + HttpSupport.response(c) + " "
+ + c.getResponseMessage());
+ }
+ }
+
+ Map<String, Ref> readAdvertisedRefs() throws TransportException {
+ try {
+ final BufferedReader br = openReader(INFO_REFS);
+ try {
+ return readAdvertisedImpl(br);
+ } finally {
+ br.close();
+ }
+ } catch (IOException err) {
+ try {
+ throw new TransportException(new URL(objectsUrl, INFO_REFS)
+ + ": cannot read available refs", err);
+ } catch (MalformedURLException mue) {
+ throw new TransportException(objectsUrl + INFO_REFS
+ + ": cannot read available refs", err);
+ }
+ }
+ }
+
+ private Map<String, Ref> readAdvertisedImpl(final BufferedReader br)
+ throws IOException, PackProtocolException {
+ final TreeMap<String, Ref> avail = new TreeMap<String, Ref>();
+ for (;;) {
+ String line = br.readLine();
+ if (line == null)
+ break;
+
+ final int tab = line.indexOf('\t');
+ if (tab < 0)
+ throw invalidAdvertisement(line);
+
+ String name;
+ final ObjectId id;
+
+ name = line.substring(tab + 1);
+ id = ObjectId.fromString(line.substring(0, tab));
+ if (name.endsWith("^{}")) {
+ name = name.substring(0, name.length() - 3);
+ final Ref prior = avail.get(name);
+ if (prior == null)
+ throw outOfOrderAdvertisement(name);
+
+ if (prior.getPeeledObjectId() != null)
+ throw duplicateAdvertisement(name + "^{}");
+
+ avail.put(name, new Ref(Ref.Storage.NETWORK, name, prior
+ .getObjectId(), id, true));
+ } else {
+ final Ref prior = avail.put(name, new Ref(
+ Ref.Storage.NETWORK, name, id));
+ if (prior != null)
+ throw duplicateAdvertisement(name);
+ }
+ }
+ return avail;
+ }
+
+ private PackProtocolException outOfOrderAdvertisement(final String n) {
+ return new PackProtocolException("advertisement of " + n
+ + "^{} came before " + n);
+ }
+
+ private PackProtocolException invalidAdvertisement(final String n) {
+ return new PackProtocolException("invalid advertisement of " + n);
+ }
+
+ private PackProtocolException duplicateAdvertisement(final String n) {
+ return new PackProtocolException("duplicate advertisements of " + n);
+ }
+
+ @Override
+ void close() {
+ // We do not maintain persistent connections.
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
new file mode 100644
index 0000000000..8bb22275b5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import org.eclipse.jgit.errors.NotSupportedException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+
+/**
+ * Transport to access a local directory as though it were a remote peer.
+ * <p>
+ * This transport is suitable for use on the local system, where the caller has
+ * direct read or write access to the "remote" repository.
+ * <p>
+ * By default this transport works by spawning a helper thread within the same
+ * JVM, and processes the data transfer using a shared memory buffer between the
+ * calling thread and the helper thread. This is a pure-Java implementation
+ * which does not require forking an external process.
+ * <p>
+ * However, during {@link #openFetch()}, if the Transport has configured
+ * {@link Transport#getOptionUploadPack()} to be anything other than
+ * <code>"git-upload-pack"</code> or <code>"git upload-pack"</code>, this
+ * implementation will fork and execute the external process, using an operating
+ * system pipe to transfer data.
+ * <p>
+ * Similarly, during {@link #openPush()}, if the Transport has configured
+ * {@link Transport#getOptionReceivePack()} to be anything other than
+ * <code>"git-receive-pack"</code> or <code>"git receive-pack"</code>, this
+ * implementation will fork and execute the external process, using an operating
+ * system pipe to transfer data.
+ */
+class TransportLocal extends Transport implements PackTransport {
+ private static final String PWD = ".";
+
+ static boolean canHandle(final URIish uri) {
+ if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
+ || uri.getPass() != null || uri.getPath() == null)
+ return false;
+
+ if ("file".equals(uri.getScheme()) || uri.getScheme() == null)
+ return FS.resolve(new File(PWD), uri.getPath()).isDirectory();
+ return false;
+ }
+
+ private final File remoteGitDir;
+
+ TransportLocal(final Repository local, final URIish uri) {
+ super(local, uri);
+
+ File d = FS.resolve(new File(PWD), uri.getPath()).getAbsoluteFile();
+ if (new File(d, ".git").isDirectory())
+ d = new File(d, ".git");
+ remoteGitDir = d;
+ }
+
+ @Override
+ public FetchConnection openFetch() throws TransportException {
+ final String up = getOptionUploadPack();
+ if ("git-upload-pack".equals(up) || "git upload-pack".equals(up))
+ return new InternalLocalFetchConnection();
+ return new ForkLocalFetchConnection();
+ }
+
+ @Override
+ public PushConnection openPush() throws NotSupportedException,
+ TransportException {
+ final String rp = getOptionReceivePack();
+ if ("git-receive-pack".equals(rp) || "git receive-pack".equals(rp))
+ return new InternalLocalPushConnection();
+ return new ForkLocalPushConnection();
+ }
+
+ @Override
+ public void close() {
+ // Resources must be established per-connection.
+ }
+
+ protected Process startProcessWithErrStream(final String cmd)
+ throws TransportException {
+ try {
+ final String[] args;
+ final Process proc;
+
+ if (cmd.startsWith("git-")) {
+ args = new String[] { "git", cmd.substring(4), PWD };
+ } else {
+ final int gitspace = cmd.indexOf("git ");
+ if (gitspace >= 0) {
+ final String git = cmd.substring(0, gitspace + 3);
+ final String subcmd = cmd.substring(gitspace + 4);
+ args = new String[] { git, subcmd, PWD };
+ } else {
+ args = new String[] { cmd, PWD };
+ }
+ }
+
+ proc = Runtime.getRuntime().exec(args, null, remoteGitDir);
+ new StreamRewritingThread(cmd, proc.getErrorStream()).start();
+ return proc;
+ } catch (IOException err) {
+ throw new TransportException(uri, err.getMessage(), err);
+ }
+ }
+
+ class InternalLocalFetchConnection extends BasePackFetchConnection {
+ private Thread worker;
+
+ InternalLocalFetchConnection() throws TransportException {
+ super(TransportLocal.this);
+
+ final Repository dst;
+ try {
+ dst = new Repository(remoteGitDir);
+ } catch (IOException err) {
+ throw new TransportException(uri, "not a git directory");
+ }
+
+ final PipedInputStream in_r;
+ final PipedOutputStream in_w;
+
+ final PipedInputStream out_r;
+ final PipedOutputStream out_w;
+ try {
+ in_r = new PipedInputStream();
+ in_w = new PipedOutputStream(in_r);
+
+ out_r = new PipedInputStream() {
+ // The client (BasePackFetchConnection) can write
+ // a huge burst before it reads again. We need to
+ // force the buffer to be big enough, otherwise it
+ // will deadlock both threads.
+ {
+ buffer = new byte[MIN_CLIENT_BUFFER];
+ }
+ };
+ out_w = new PipedOutputStream(out_r);
+ } catch (IOException err) {
+ dst.close();
+ throw new TransportException(uri, "cannot connect pipes", err);
+ }
+
+ worker = new Thread("JGit-Upload-Pack") {
+ public void run() {
+ try {
+ final UploadPack rp = new UploadPack(dst);
+ rp.upload(out_r, in_w, null);
+ } catch (IOException err) {
+ // Client side of the pipes should report the problem.
+ err.printStackTrace();
+ } catch (RuntimeException err) {
+ // Clients side will notice we went away, and report.
+ err.printStackTrace();
+ } finally {
+ try {
+ out_r.close();
+ } catch (IOException e2) {
+ // Ignore close failure, we probably crashed above.
+ }
+
+ try {
+ in_w.close();
+ } catch (IOException e2) {
+ // Ignore close failure, we probably crashed above.
+ }
+
+ dst.close();
+ }
+ }
+ };
+ worker.start();
+
+ init(in_r, out_w);
+ readAdvertisedRefs();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+
+ if (worker != null) {
+ try {
+ worker.join();
+ } catch (InterruptedException ie) {
+ // Stop waiting and return anyway.
+ } finally {
+ worker = null;
+ }
+ }
+ }
+ }
+
+ class ForkLocalFetchConnection extends BasePackFetchConnection {
+ private Process uploadPack;
+
+ ForkLocalFetchConnection() throws TransportException {
+ super(TransportLocal.this);
+ uploadPack = startProcessWithErrStream(getOptionUploadPack());
+ final InputStream upIn = uploadPack.getInputStream();
+ final OutputStream upOut = uploadPack.getOutputStream();
+ init(upIn, upOut);
+ readAdvertisedRefs();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+
+ if (uploadPack != null) {
+ try {
+ uploadPack.waitFor();
+ } catch (InterruptedException ie) {
+ // Stop waiting and return anyway.
+ } finally {
+ uploadPack = null;
+ }
+ }
+ }
+ }
+
+ class InternalLocalPushConnection extends BasePackPushConnection {
+ private Thread worker;
+
+ InternalLocalPushConnection() throws TransportException {
+ super(TransportLocal.this);
+
+ final Repository dst;
+ try {
+ dst = new Repository(remoteGitDir);
+ } catch (IOException err) {
+ throw new TransportException(uri, "not a git directory");
+ }
+
+ final PipedInputStream in_r;
+ final PipedOutputStream in_w;
+
+ final PipedInputStream out_r;
+ final PipedOutputStream out_w;
+ try {
+ in_r = new PipedInputStream();
+ in_w = new PipedOutputStream(in_r);
+
+ out_r = new PipedInputStream();
+ out_w = new PipedOutputStream(out_r);
+ } catch (IOException err) {
+ dst.close();
+ throw new TransportException(uri, "cannot connect pipes", err);
+ }
+
+ worker = new Thread("JGit-Receive-Pack") {
+ public void run() {
+ try {
+ final ReceivePack rp = new ReceivePack(dst);
+ rp.receive(out_r, in_w, System.err);
+ } catch (IOException err) {
+ // Client side of the pipes should report the problem.
+ } catch (RuntimeException err) {
+ // Clients side will notice we went away, and report.
+ } finally {
+ try {
+ out_r.close();
+ } catch (IOException e2) {
+ // Ignore close failure, we probably crashed above.
+ }
+
+ try {
+ in_w.close();
+ } catch (IOException e2) {
+ // Ignore close failure, we probably crashed above.
+ }
+
+ dst.close();
+ }
+ }
+ };
+ worker.start();
+
+ init(in_r, out_w);
+ readAdvertisedRefs();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+
+ if (worker != null) {
+ try {
+ worker.join();
+ } catch (InterruptedException ie) {
+ // Stop waiting and return anyway.
+ } finally {
+ worker = null;
+ }
+ }
+ }
+ }
+
+ class ForkLocalPushConnection extends BasePackPushConnection {
+ private Process receivePack;
+
+ ForkLocalPushConnection() throws TransportException {
+ super(TransportLocal.this);
+ receivePack = startProcessWithErrStream(getOptionReceivePack());
+ final InputStream rpIn = receivePack.getInputStream();
+ final OutputStream rpOut = receivePack.getOutputStream();
+ init(rpIn, rpOut);
+ readAdvertisedRefs();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+
+ if (receivePack != null) {
+ try {
+ receivePack.waitFor();
+ } catch (InterruptedException ie) {
+ // Stop waiting and return anyway.
+ } finally {
+ receivePack = null;
+ }
+ }
+ }
+ }
+
+ static class StreamRewritingThread extends Thread {
+ private final InputStream in;
+
+ StreamRewritingThread(final String cmd, final InputStream in) {
+ super("JGit " + cmd + " Errors");
+ this.in = in;
+ }
+
+ public void run() {
+ final byte[] tmp = new byte[512];
+ try {
+ for (;;) {
+ final int n = in.read(tmp);
+ if (n < 0)
+ break;
+ System.err.write(tmp, 0, n);
+ System.err.flush();
+ }
+ } catch (IOException err) {
+ // Ignore errors reading errors.
+ } finally {
+ try {
+ in.close();
+ } catch (IOException err2) {
+ // Ignore errors closing the pipe.
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
new file mode 100644
index 0000000000..8243ddabb2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Ref.Storage;
+
+import com.jcraft.jsch.Channel;
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.SftpATTRS;
+import com.jcraft.jsch.SftpException;
+
+/**
+ * Transport over the non-Git aware SFTP (SSH based FTP) protocol.
+ * <p>
+ * The SFTP transport does not require any specialized Git support on the remote
+ * (server side) repository. Object files are retrieved directly through secure
+ * shell's FTP protocol, making it possible to copy objects from a remote
+ * repository that is available over SSH, but whose remote host does not have
+ * Git installed.
+ * <p>
+ * Unlike the HTTP variant (see {@link TransportHttp}) we rely upon being able
+ * to list files in directories, as the SFTP protocol supports this function. By
+ * listing files through SFTP we can avoid needing to have current
+ * <code>objects/info/packs</code> or <code>info/refs</code> files on the
+ * remote repository and access the data directly, much as Git itself would.
+ * <p>
+ * Concurrent pushing over this transport is not supported. Multiple concurrent
+ * push operations may cause confusion in the repository state.
+ *
+ * @see WalkFetchConnection
+ */
+public class TransportSftp extends SshTransport implements WalkTransport {
+ static boolean canHandle(final URIish uri) {
+ return uri.isRemote() && "sftp".equals(uri.getScheme());
+ }
+
+ TransportSftp(final Repository local, final URIish uri) {
+ super(local, uri);
+ }
+
+ @Override
+ public FetchConnection openFetch() throws TransportException {
+ final SftpObjectDB c = new SftpObjectDB(uri.getPath());
+ final WalkFetchConnection r = new WalkFetchConnection(this, c);
+ r.available(c.readAdvertisedRefs());
+ return r;
+ }
+
+ @Override
+ public PushConnection openPush() throws TransportException {
+ final SftpObjectDB c = new SftpObjectDB(uri.getPath());
+ final WalkPushConnection r = new WalkPushConnection(this, c);
+ r.available(c.readAdvertisedRefs());
+ return r;
+ }
+
+ ChannelSftp newSftp() throws TransportException {
+ initSession();
+
+ final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;
+ try {
+ final Channel channel = sock.openChannel("sftp");
+ channel.connect(tms);
+ return (ChannelSftp) channel;
+ } catch (JSchException je) {
+ throw new TransportException(uri, je.getMessage(), je);
+ }
+ }
+
+ class SftpObjectDB extends WalkRemoteObjectDatabase {
+ private final String objectsPath;
+
+ private ChannelSftp ftp;
+
+ SftpObjectDB(String path) throws TransportException {
+ if (path.startsWith("/~"))
+ path = path.substring(1);
+ if (path.startsWith("~/"))
+ path = path.substring(2);
+ try {
+ ftp = newSftp();
+ ftp.cd(path);
+ ftp.cd("objects");
+ objectsPath = ftp.pwd();
+ } catch (TransportException err) {
+ close();
+ throw err;
+ } catch (SftpException je) {
+ throw new TransportException("Can't enter " + path + "/objects"
+ + ": " + je.getMessage(), je);
+ }
+ }
+
+ SftpObjectDB(final SftpObjectDB parent, final String p)
+ throws TransportException {
+ try {
+ ftp = newSftp();
+ ftp.cd(parent.objectsPath);
+ ftp.cd(p);
+ objectsPath = ftp.pwd();
+ } catch (TransportException err) {
+ close();
+ throw err;
+ } catch (SftpException je) {
+ throw new TransportException("Can't enter " + p + " from "
+ + parent.objectsPath + ": " + je.getMessage(), je);
+ }
+ }
+
+ @Override
+ URIish getURI() {
+ return uri.setPath(objectsPath);
+ }
+
+ @Override
+ Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
+ try {
+ return readAlternates(INFO_ALTERNATES);
+ } catch (FileNotFoundException err) {
+ return null;
+ }
+ }
+
+ @Override
+ WalkRemoteObjectDatabase openAlternate(final String location)
+ throws IOException {
+ return new SftpObjectDB(this, location);
+ }
+
+ @Override
+ Collection<String> getPackNames() throws IOException {
+ final List<String> packs = new ArrayList<String>();
+ try {
+ final Collection<ChannelSftp.LsEntry> list = ftp.ls("pack");
+ final HashMap<String, ChannelSftp.LsEntry> files;
+ final HashMap<String, Integer> mtimes;
+
+ files = new HashMap<String, ChannelSftp.LsEntry>();
+ mtimes = new HashMap<String, Integer>();
+
+ for (final ChannelSftp.LsEntry ent : list)
+ files.put(ent.getFilename(), ent);
+ for (final ChannelSftp.LsEntry ent : list) {
+ final String n = ent.getFilename();
+ if (!n.startsWith("pack-") || !n.endsWith(".pack"))
+ continue;
+
+ final String in = n.substring(0, n.length() - 5) + ".idx";
+ if (!files.containsKey(in))
+ continue;
+
+ mtimes.put(n, ent.getAttrs().getMTime());
+ packs.add(n);
+ }
+
+ Collections.sort(packs, new Comparator<String>() {
+ public int compare(final String o1, final String o2) {
+ return mtimes.get(o2) - mtimes.get(o1);
+ }
+ });
+ } catch (SftpException je) {
+ throw new TransportException("Can't ls " + objectsPath
+ + "/pack: " + je.getMessage(), je);
+ }
+ return packs;
+ }
+
+ @Override
+ FileStream open(final String path) throws IOException {
+ try {
+ final SftpATTRS a = ftp.lstat(path);
+ return new FileStream(ftp.get(path), a.getSize());
+ } catch (SftpException je) {
+ if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
+ throw new FileNotFoundException(path);
+ throw new TransportException("Can't get " + objectsPath + "/"
+ + path + ": " + je.getMessage(), je);
+ }
+ }
+
+ @Override
+ void deleteFile(final String path) throws IOException {
+ try {
+ ftp.rm(path);
+ } catch (SftpException je) {
+ if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
+ return;
+ throw new TransportException("Can't delete " + objectsPath
+ + "/" + path + ": " + je.getMessage(), je);
+ }
+
+ // Prune any now empty directories.
+ //
+ String dir = path;
+ int s = dir.lastIndexOf('/');
+ while (s > 0) {
+ try {
+ dir = dir.substring(0, s);
+ ftp.rmdir(dir);
+ s = dir.lastIndexOf('/');
+ } catch (SftpException je) {
+ // If we cannot delete it, leave it alone. It may have
+ // entries still in it, or maybe we lack write access on
+ // the parent. Either way it isn't a fatal error.
+ //
+ break;
+ }
+ }
+ }
+
+ @Override
+ OutputStream writeFile(final String path,
+ final ProgressMonitor monitor, final String monitorTask)
+ throws IOException {
+ try {
+ return ftp.put(path);
+ } catch (SftpException je) {
+ if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
+ mkdir_p(path);
+ try {
+ return ftp.put(path);
+ } catch (SftpException je2) {
+ je = je2;
+ }
+ }
+
+ throw new TransportException("Can't write " + objectsPath + "/"
+ + path + ": " + je.getMessage(), je);
+ }
+ }
+
+ @Override
+ void writeFile(final String path, final byte[] data) throws IOException {
+ final String lock = path + ".lock";
+ try {
+ super.writeFile(lock, data);
+ try {
+ ftp.rename(lock, path);
+ } catch (SftpException je) {
+ throw new TransportException("Can't write " + objectsPath
+ + "/" + path + ": " + je.getMessage(), je);
+ }
+ } catch (IOException err) {
+ try {
+ ftp.rm(lock);
+ } catch (SftpException e) {
+ // Ignore deletion failure, we are already
+ // failing anyway.
+ }
+ throw err;
+ }
+ }
+
+ private void mkdir_p(String path) throws IOException {
+ final int s = path.lastIndexOf('/');
+ if (s <= 0)
+ return;
+
+ path = path.substring(0, s);
+ try {
+ ftp.mkdir(path);
+ } catch (SftpException je) {
+ if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
+ mkdir_p(path);
+ try {
+ ftp.mkdir(path);
+ return;
+ } catch (SftpException je2) {
+ je = je2;
+ }
+ }
+
+ throw new TransportException("Can't mkdir " + objectsPath + "/"
+ + path + ": " + je.getMessage(), je);
+ }
+ }
+
+ Map<String, Ref> readAdvertisedRefs() throws TransportException {
+ final TreeMap<String, Ref> avail = new TreeMap<String, Ref>();
+ readPackedRefs(avail);
+ readRef(avail, ROOT_DIR + Constants.HEAD, Constants.HEAD);
+ readLooseRefs(avail, ROOT_DIR + "refs", "refs/");
+ return avail;
+ }
+
+ private void readLooseRefs(final TreeMap<String, Ref> avail,
+ final String dir, final String prefix)
+ throws TransportException {
+ final Collection<ChannelSftp.LsEntry> list;
+ try {
+ list = ftp.ls(dir);
+ } catch (SftpException je) {
+ throw new TransportException("Can't ls " + objectsPath + "/"
+ + dir + ": " + je.getMessage(), je);
+ }
+
+ for (final ChannelSftp.LsEntry ent : list) {
+ final String n = ent.getFilename();
+ if (".".equals(n) || "..".equals(n))
+ continue;
+
+ final String nPath = dir + "/" + n;
+ if (ent.getAttrs().isDir())
+ readLooseRefs(avail, nPath, prefix + n + "/");
+ else
+ readRef(avail, nPath, prefix + n);
+ }
+ }
+
+ private Ref readRef(final TreeMap<String, Ref> avail,
+ final String path, final String name) throws TransportException {
+ final String line;
+ try {
+ final BufferedReader br = openReader(path);
+ try {
+ line = br.readLine();
+ } finally {
+ br.close();
+ }
+ } catch (FileNotFoundException noRef) {
+ return null;
+ } catch (IOException err) {
+ throw new TransportException("Cannot read " + objectsPath + "/"
+ + path + ": " + err.getMessage(), err);
+ }
+
+ if (line == null)
+ throw new TransportException("Empty ref: " + name);
+
+ if (line.startsWith("ref: ")) {
+ final String p = line.substring("ref: ".length());
+ Ref r = readRef(avail, ROOT_DIR + p, p);
+ if (r == null)
+ r = avail.get(p);
+ if (r != null) {
+ r = new Ref(loose(r), name, r.getObjectId(), r
+ .getPeeledObjectId(), true);
+ avail.put(name, r);
+ }
+ return r;
+ }
+
+ if (ObjectId.isId(line)) {
+ final Ref r = new Ref(loose(avail.get(name)), name, ObjectId
+ .fromString(line));
+ avail.put(r.getName(), r);
+ return r;
+ }
+
+ throw new TransportException("Bad ref: " + name + ": " + line);
+ }
+
+ private Storage loose(final Ref r) {
+ if (r != null && r.getStorage() == Storage.PACKED)
+ return Storage.LOOSE_PACKED;
+ return Storage.LOOSE;
+ }
+
+ @Override
+ void close() {
+ if (ftp != null) {
+ try {
+ if (ftp.isConnected())
+ ftp.disconnect();
+ } finally {
+ ftp = null;
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
new file mode 100644
index 0000000000..cfdf47c0c4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This URI like construct used for referencing Git archives over the net, as
+ * well as locally stored archives. The most important difference compared to
+ * RFC 2396 URI's is that no URI encoding/decoding ever takes place. A space or
+ * any special character is written as-is.
+ */
+public class URIish {
+ private static final Pattern FULL_URI = Pattern
+ .compile("^(?:([a-z][a-z0-9+-]+)://(?:([^/]+?)(?::([^/]+?))?@)?(?:([^/]+?))?(?::(\\d+))?)?((?:[A-Za-z]:)?/.+)$");
+
+ private static final Pattern SCP_URI = Pattern
+ .compile("^(?:([^@]+?)@)?([^:]+?):(.+)$");
+
+ private String scheme;
+
+ private String path;
+
+ private String user;
+
+ private String pass;
+
+ private int port = -1;
+
+ private String host;
+
+ /**
+ * Parse and construct an {@link URIish} from a string
+ *
+ * @param s
+ * @throws URISyntaxException
+ */
+ public URIish(String s) throws URISyntaxException {
+ s = s.replace('\\', '/');
+ Matcher matcher = FULL_URI.matcher(s);
+ if (matcher.matches()) {
+ scheme = matcher.group(1);
+ user = matcher.group(2);
+ pass = matcher.group(3);
+ host = matcher.group(4);
+ if (matcher.group(5) != null)
+ port = Integer.parseInt(matcher.group(5));
+ path = matcher.group(6);
+ if (path.length() >= 3
+ && path.charAt(0) == '/'
+ && path.charAt(2) == ':'
+ && (path.charAt(1) >= 'A' && path.charAt(1) <= 'Z'
+ || path.charAt(1) >= 'a' && path.charAt(1) <= 'z'))
+ path = path.substring(1);
+ } else {
+ matcher = SCP_URI.matcher(s);
+ if (matcher.matches()) {
+ user = matcher.group(1);
+ host = matcher.group(2);
+ path = matcher.group(3);
+ } else
+ throw new URISyntaxException(s, "Cannot parse Git URI-ish");
+ }
+ }
+
+ /**
+ * Construct a URIish from a standard URL.
+ *
+ * @param u
+ * the source URL to convert from.
+ */
+ public URIish(final URL u) {
+ scheme = u.getProtocol();
+ path = u.getPath();
+
+ final String ui = u.getUserInfo();
+ if (ui != null) {
+ final int d = ui.indexOf(':');
+ user = d < 0 ? ui : ui.substring(0, d);
+ pass = d < 0 ? null : ui.substring(d + 1);
+ }
+
+ port = u.getPort();
+ host = u.getHost();
+ }
+
+ /** Create an empty, non-configured URI. */
+ public URIish() {
+ // Configure nothing.
+ }
+
+ private URIish(final URIish u) {
+ this.scheme = u.scheme;
+ this.path = u.path;
+ this.user = u.user;
+ this.pass = u.pass;
+ this.port = u.port;
+ this.host = u.host;
+ }
+
+ /**
+ * @return true if this URI references a repository on another system.
+ */
+ public boolean isRemote() {
+ return getHost() != null;
+ }
+
+ /**
+ * @return host name part or null
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Return a new URI matching this one, but with a different host.
+ *
+ * @param n
+ * the new value for host.
+ * @return a new URI with the updated value.
+ */
+ public URIish setHost(final String n) {
+ final URIish r = new URIish(this);
+ r.host = n;
+ return r;
+ }
+
+ /**
+ * @return protocol name or null for local references
+ */
+ public String getScheme() {
+ return scheme;
+ }
+
+ /**
+ * Return a new URI matching this one, but with a different scheme.
+ *
+ * @param n
+ * the new value for scheme.
+ * @return a new URI with the updated value.
+ */
+ public URIish setScheme(final String n) {
+ final URIish r = new URIish(this);
+ r.scheme = n;
+ return r;
+ }
+
+ /**
+ * @return path name component
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Return a new URI matching this one, but with a different path.
+ *
+ * @param n
+ * the new value for path.
+ * @return a new URI with the updated value.
+ */
+ public URIish setPath(final String n) {
+ final URIish r = new URIish(this);
+ r.path = n;
+ return r;
+ }
+
+ /**
+ * @return user name requested for transfer or null
+ */
+ public String getUser() {
+ return user;
+ }
+
+ /**
+ * Return a new URI matching this one, but with a different user.
+ *
+ * @param n
+ * the new value for user.
+ * @return a new URI with the updated value.
+ */
+ public URIish setUser(final String n) {
+ final URIish r = new URIish(this);
+ r.user = n;
+ return r;
+ }
+
+ /**
+ * @return password requested for transfer or null
+ */
+ public String getPass() {
+ return pass;
+ }
+
+ /**
+ * Return a new URI matching this one, but with a different password.
+ *
+ * @param n
+ * the new value for password.
+ * @return a new URI with the updated value.
+ */
+ public URIish setPass(final String n) {
+ final URIish r = new URIish(this);
+ r.pass = n;
+ return r;
+ }
+
+ /**
+ * @return port number requested for transfer or -1 if not explicit
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Return a new URI matching this one, but with a different port.
+ *
+ * @param n
+ * the new value for port.
+ * @return a new URI with the updated value.
+ */
+ public URIish setPort(final int n) {
+ final URIish r = new URIish(this);
+ r.port = n > 0 ? n : -1;
+ return r;
+ }
+
+ public int hashCode() {
+ int hc = 0;
+ if (getScheme() != null)
+ hc = hc * 31 + getScheme().hashCode();
+ if (getUser() != null)
+ hc = hc * 31 + getUser().hashCode();
+ if (getPass() != null)
+ hc = hc * 31 + getPass().hashCode();
+ if (getHost() != null)
+ hc = hc * 31 + getHost().hashCode();
+ if (getPort() > 0)
+ hc = hc * 31 + getPort();
+ if (getPath() != null)
+ hc = hc * 31 + getPath().hashCode();
+ return hc;
+ }
+
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof URIish))
+ return false;
+ final URIish b = (URIish) obj;
+ if (!eq(getScheme(), b.getScheme()))
+ return false;
+ if (!eq(getUser(), b.getUser()))
+ return false;
+ if (!eq(getPass(), b.getPass()))
+ return false;
+ if (!eq(getHost(), b.getHost()))
+ return false;
+ if (getPort() != b.getPort())
+ return false;
+ if (!eq(getPath(), b.getPath()))
+ return false;
+ return true;
+ }
+
+ private static boolean eq(final String a, final String b) {
+ if (a == b)
+ return true;
+ if (a == null || b == null)
+ return false;
+ return a.equals(b);
+ }
+
+ /**
+ * Obtain the string form of the URI, with the password included.
+ *
+ * @return the URI, including its password field, if any.
+ */
+ public String toPrivateString() {
+ return format(true);
+ }
+
+ public String toString() {
+ return format(false);
+ }
+
+ private String format(final boolean includePassword) {
+ final StringBuilder r = new StringBuilder();
+ if (getScheme() != null) {
+ r.append(getScheme());
+ r.append("://");
+ }
+
+ if (getUser() != null) {
+ r.append(getUser());
+ if (includePassword && getPass() != null) {
+ r.append(':');
+ r.append(getPass());
+ }
+ }
+
+ if (getHost() != null) {
+ if (getUser() != null)
+ r.append('@');
+ r.append(getHost());
+ if (getScheme() != null && getPort() > 0) {
+ r.append(':');
+ r.append(getPort());
+ }
+ }
+
+ if (getPath() != null) {
+ if (getScheme() != null) {
+ if (!getPath().startsWith("/"))
+ r.append('/');
+ } else if (getHost() != null)
+ r.append(':');
+ r.append(getPath());
+ }
+
+ return r.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
new file mode 100644
index 0000000000..7e534a39c9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PackWriter;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevFlagSet;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.io.InterruptTimer;
+import org.eclipse.jgit.util.io.TimeoutInputStream;
+import org.eclipse.jgit.util.io.TimeoutOutputStream;
+
+/**
+ * Implements the server side of a fetch connection, transmitting objects.
+ */
+public class UploadPack {
+ static final String OPTION_INCLUDE_TAG = BasePackFetchConnection.OPTION_INCLUDE_TAG;
+
+ static final String OPTION_MULTI_ACK = BasePackFetchConnection.OPTION_MULTI_ACK;
+
+ static final String OPTION_THIN_PACK = BasePackFetchConnection.OPTION_THIN_PACK;
+
+ static final String OPTION_SIDE_BAND = BasePackFetchConnection.OPTION_SIDE_BAND;
+
+ static final String OPTION_SIDE_BAND_64K = BasePackFetchConnection.OPTION_SIDE_BAND_64K;
+
+ static final String OPTION_OFS_DELTA = BasePackFetchConnection.OPTION_OFS_DELTA;
+
+ static final String OPTION_NO_PROGRESS = BasePackFetchConnection.OPTION_NO_PROGRESS;
+
+ /** Database we read the objects from. */
+ private final Repository db;
+
+ /** Revision traversal support over {@link #db}. */
+ private final RevWalk walk;
+
+ /** Timeout in seconds to wait for client interaction. */
+ private int timeout;
+
+ /** Timer to manage {@link #timeout}. */
+ private InterruptTimer timer;
+
+ private InputStream rawIn;
+
+ private OutputStream rawOut;
+
+ private PacketLineIn pckIn;
+
+ private PacketLineOut pckOut;
+
+ /** The refs we advertised as existing at the start of the connection. */
+ private Map<String, Ref> refs;
+
+ /** Capabilities requested by the client. */
+ private final Set<String> options = new HashSet<String>();
+
+ /** Objects the client wants to obtain. */
+ private final List<RevObject> wantAll = new ArrayList<RevObject>();
+
+ /** Objects the client wants to obtain. */
+ private final List<RevCommit> wantCommits = new ArrayList<RevCommit>();
+
+ /** Objects on both sides, these don't have to be sent. */
+ private final List<RevObject> commonBase = new ArrayList<RevObject>();
+
+ /** null if {@link #commonBase} should be examined again. */
+ private Boolean okToGiveUp;
+
+ /** Marked on objects we sent in our advertisement list. */
+ private final RevFlag ADVERTISED;
+
+ /** Marked on objects the client has asked us to give them. */
+ private final RevFlag WANT;
+
+ /** Marked on objects both we and the client have. */
+ private final RevFlag PEER_HAS;
+
+ /** Marked on objects in {@link #commonBase}. */
+ private final RevFlag COMMON;
+
+ private final RevFlagSet SAVE;
+
+ private boolean multiAck;
+
+ /**
+ * Create a new pack upload for an open repository.
+ *
+ * @param copyFrom
+ * the source repository.
+ */
+ public UploadPack(final Repository copyFrom) {
+ db = copyFrom;
+ walk = new RevWalk(db);
+ walk.setRetainBody(false);
+
+ ADVERTISED = walk.newFlag("ADVERTISED");
+ WANT = walk.newFlag("WANT");
+ PEER_HAS = walk.newFlag("PEER_HAS");
+ COMMON = walk.newFlag("COMMON");
+ walk.carry(PEER_HAS);
+
+ SAVE = new RevFlagSet();
+ SAVE.add(ADVERTISED);
+ SAVE.add(WANT);
+ SAVE.add(PEER_HAS);
+ }
+
+ /** @return the repository this receive completes into. */
+ public final Repository getRepository() {
+ return db;
+ }
+
+ /** @return the RevWalk instance used by this connection. */
+ public final RevWalk getRevWalk() {
+ return walk;
+ }
+
+ /** @return timeout (in seconds) before aborting an IO operation. */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Set the timeout before willing to abort an IO call.
+ *
+ * @param seconds
+ * number of seconds to wait (with no data transfer occurring)
+ * before aborting an IO read or write operation with the
+ * connected client.
+ */
+ public void setTimeout(final int seconds) {
+ timeout = seconds;
+ }
+
+ /**
+ * Execute the upload task on the socket.
+ *
+ * @param input
+ * raw input to read client commands from. Caller must ensure the
+ * input is buffered, otherwise read performance may suffer.
+ * @param output
+ * response back to the Git network client, to write the pack
+ * data onto. Caller must ensure the output is buffered,
+ * otherwise write performance may suffer.
+ * @param messages
+ * secondary "notice" channel to send additional messages out
+ * through. When run over SSH this should be tied back to the
+ * standard error channel of the command execution. For most
+ * other network connections this should be null.
+ * @throws IOException
+ */
+ public void upload(final InputStream input, final OutputStream output,
+ final OutputStream messages) throws IOException {
+ try {
+ rawIn = input;
+ rawOut = output;
+
+ if (timeout > 0) {
+ final Thread caller = Thread.currentThread();
+ timer = new InterruptTimer(caller.getName() + "-Timer");
+ TimeoutInputStream i = new TimeoutInputStream(rawIn, timer);
+ TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);
+ i.setTimeout(timeout * 1000);
+ o.setTimeout(timeout * 1000);
+ rawIn = i;
+ rawOut = o;
+ }
+
+ pckIn = new PacketLineIn(rawIn);
+ pckOut = new PacketLineOut(rawOut);
+ service();
+ } finally {
+ if (timer != null) {
+ try {
+ timer.terminate();
+ } finally {
+ timer = null;
+ }
+ }
+ }
+ }
+
+ private void service() throws IOException {
+ sendAdvertisedRefs();
+ recvWants();
+ if (wantAll.isEmpty())
+ return;
+ multiAck = options.contains(OPTION_MULTI_ACK);
+ negotiate();
+ sendPack();
+ }
+
+ private void sendAdvertisedRefs() throws IOException {
+ final RefAdvertiser adv = new RefAdvertiser(pckOut, walk, ADVERTISED);
+ adv.advertiseCapability(OPTION_INCLUDE_TAG);
+ adv.advertiseCapability(OPTION_MULTI_ACK);
+ adv.advertiseCapability(OPTION_OFS_DELTA);
+ adv.advertiseCapability(OPTION_SIDE_BAND);
+ adv.advertiseCapability(OPTION_SIDE_BAND_64K);
+ adv.advertiseCapability(OPTION_THIN_PACK);
+ adv.advertiseCapability(OPTION_NO_PROGRESS);
+ adv.setDerefTags(true);
+ refs = db.getAllRefs();
+ adv.send(refs.values());
+ pckOut.end();
+ }
+
+ private void recvWants() throws IOException {
+ boolean isFirst = true;
+ for (;; isFirst = false) {
+ String line;
+ try {
+ line = pckIn.readString();
+ } catch (EOFException eof) {
+ if (isFirst)
+ break;
+ throw eof;
+ }
+
+ if (line == PacketLineIn.END)
+ break;
+ if (!line.startsWith("want ") || line.length() < 45)
+ throw new PackProtocolException("expected want; got " + line);
+
+ if (isFirst && line.length() > 45) {
+ String opt = line.substring(45);
+ if (opt.startsWith(" "))
+ opt = opt.substring(1);
+ for (String c : opt.split(" "))
+ options.add(c);
+ line = line.substring(0, 45);
+ }
+
+ final ObjectId id = ObjectId.fromString(line.substring(5));
+ final RevObject o;
+ try {
+ o = walk.parseAny(id);
+ } catch (IOException e) {
+ throw new PackProtocolException(id.name() + " not valid", e);
+ }
+ if (!o.has(ADVERTISED))
+ throw new PackProtocolException(id.name() + " not valid");
+ want(o);
+ }
+ }
+
+ private void want(RevObject o) {
+ if (!o.has(WANT)) {
+ o.add(WANT);
+ wantAll.add(o);
+
+ if (o instanceof RevCommit)
+ wantCommits.add((RevCommit) o);
+
+ else if (o instanceof RevTag) {
+ do {
+ o = ((RevTag) o).getObject();
+ } while (o instanceof RevTag);
+ if (o instanceof RevCommit)
+ want(o);
+ }
+ }
+ }
+
+ private void negotiate() throws IOException {
+ ObjectId last = ObjectId.zeroId();
+ for (;;) {
+ String line;
+ try {
+ line = pckIn.readString();
+ } catch (EOFException eof) {
+ throw eof;
+ }
+
+ if (line == PacketLineIn.END) {
+ if (commonBase.isEmpty() || multiAck)
+ pckOut.writeString("NAK\n");
+ pckOut.flush();
+ } else if (line.startsWith("have ") && line.length() == 45) {
+ final ObjectId id = ObjectId.fromString(line.substring(5));
+ if (matchHave(id)) {
+ // Both sides have the same object; let the client know.
+ //
+ if (multiAck) {
+ last = id;
+ pckOut.writeString("ACK " + id.name() + " continue\n");
+ } else if (commonBase.size() == 1)
+ pckOut.writeString("ACK " + id.name() + "\n");
+ } else {
+ // They have this object; we don't.
+ //
+ if (multiAck && okToGiveUp())
+ pckOut.writeString("ACK " + id.name() + " continue\n");
+ }
+
+ } else if (line.equals("done")) {
+ if (commonBase.isEmpty())
+ pckOut.writeString("NAK\n");
+
+ else if (multiAck)
+ pckOut.writeString("ACK " + last.name() + "\n");
+ break;
+
+ } else {
+ throw new PackProtocolException("expected have; got " + line);
+ }
+ }
+ }
+
+ private boolean matchHave(final ObjectId id) {
+ final RevObject o;
+ try {
+ o = walk.parseAny(id);
+ } catch (IOException err) {
+ return false;
+ }
+
+ if (!o.has(PEER_HAS)) {
+ o.add(PEER_HAS);
+ if (o instanceof RevCommit)
+ ((RevCommit) o).carry(PEER_HAS);
+ addCommonBase(o);
+ }
+ return true;
+ }
+
+ private void addCommonBase(final RevObject o) {
+ if (!o.has(COMMON)) {
+ o.add(COMMON);
+ commonBase.add(o);
+ okToGiveUp = null;
+ }
+ }
+
+ private boolean okToGiveUp() throws PackProtocolException {
+ if (okToGiveUp == null)
+ okToGiveUp = Boolean.valueOf(okToGiveUpImp());
+ return okToGiveUp.booleanValue();
+ }
+
+ private boolean okToGiveUpImp() throws PackProtocolException {
+ if (commonBase.isEmpty())
+ return false;
+
+ try {
+ for (final Iterator<RevCommit> i = wantCommits.iterator(); i
+ .hasNext();) {
+ final RevCommit want = i.next();
+ if (wantSatisfied(want))
+ i.remove();
+ }
+ } catch (IOException e) {
+ throw new PackProtocolException("internal revision error", e);
+ }
+ return wantCommits.isEmpty();
+ }
+
+ private boolean wantSatisfied(final RevCommit want) throws IOException {
+ walk.resetRetain(SAVE);
+ walk.markStart(want);
+ for (;;) {
+ final RevCommit c = walk.next();
+ if (c == null)
+ break;
+ if (c.has(PEER_HAS)) {
+ addCommonBase(c);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void sendPack() throws IOException {
+ final boolean thin = options.contains(OPTION_THIN_PACK);
+ final boolean progress = !options.contains(OPTION_NO_PROGRESS);
+ final boolean sideband = options.contains(OPTION_SIDE_BAND)
+ || options.contains(OPTION_SIDE_BAND_64K);
+
+ ProgressMonitor pm = NullProgressMonitor.INSTANCE;
+ OutputStream packOut = rawOut;
+
+ if (sideband) {
+ int bufsz = SideBandOutputStream.SMALL_BUF;
+ if (options.contains(OPTION_SIDE_BAND_64K))
+ bufsz = SideBandOutputStream.MAX_BUF;
+ bufsz -= SideBandOutputStream.HDR_SIZE;
+
+ packOut = new BufferedOutputStream(new SideBandOutputStream(
+ SideBandOutputStream.CH_DATA, pckOut), bufsz);
+
+ if (progress)
+ pm = new SideBandProgressMonitor(pckOut);
+ }
+
+ final PackWriter pw;
+ pw = new PackWriter(db, pm, NullProgressMonitor.INSTANCE);
+ pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
+ pw.setThin(thin);
+ pw.preparePack(wantAll, commonBase);
+ if (options.contains(OPTION_INCLUDE_TAG)) {
+ for (final Ref r : refs.values()) {
+ final RevObject o;
+ try {
+ o = walk.parseAny(r.getObjectId());
+ } catch (IOException e) {
+ continue;
+ }
+ if (o.has(WANT) || !(o instanceof RevTag))
+ continue;
+ final RevTag t = (RevTag) o;
+ if (!pw.willInclude(t) && pw.willInclude(t.getObject()))
+ pw.addObject(t);
+ }
+ }
+ pw.writePack(packOut);
+
+ if (sideband) {
+ packOut.flush();
+ pckOut.end();
+ } else {
+ rawOut.flush();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
new file mode 100644
index 0000000000..d368fb2cd7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+abstract class WalkEncryption {
+ static final WalkEncryption NONE = new NoEncryption();
+
+ static final String JETS3T_CRYPTO_VER = "jets3t-crypto-ver";
+
+ static final String JETS3T_CRYPTO_ALG = "jets3t-crypto-alg";
+
+ abstract OutputStream encrypt(OutputStream os) throws IOException;
+
+ abstract InputStream decrypt(InputStream in) throws IOException;
+
+ abstract void request(HttpURLConnection u, String prefix);
+
+ abstract void validate(HttpURLConnection u, String p) throws IOException;
+
+ protected void validateImpl(final HttpURLConnection u, final String p,
+ final String version, final String name) throws IOException {
+ String v;
+
+ v = u.getHeaderField(p + JETS3T_CRYPTO_VER);
+ if (v == null)
+ v = "";
+ if (!version.equals(v))
+ throw new IOException("Unsupported encryption version: " + v);
+
+ v = u.getHeaderField(p + JETS3T_CRYPTO_ALG);
+ if (v == null)
+ v = "";
+ if (!name.equals(v))
+ throw new IOException("Unsupported encryption algorithm: " + v);
+ }
+
+ IOException error(final Throwable why) {
+ final IOException e;
+ e = new IOException("Encryption error: " + why.getMessage());
+ e.initCause(why);
+ return e;
+ }
+
+ private static class NoEncryption extends WalkEncryption {
+ @Override
+ void request(HttpURLConnection u, String prefix) {
+ // Don't store any request properties.
+ }
+
+ @Override
+ void validate(final HttpURLConnection u, final String p)
+ throws IOException {
+ validateImpl(u, p, "", "");
+ }
+
+ @Override
+ InputStream decrypt(InputStream in) {
+ return in;
+ }
+
+ @Override
+ OutputStream encrypt(OutputStream os) {
+ return os;
+ }
+ }
+
+ static class ObjectEncryptionV2 extends WalkEncryption {
+ private static int ITERATION_COUNT = 5000;
+
+ private static byte[] salt = { (byte) 0xA4, (byte) 0x0B, (byte) 0xC8,
+ (byte) 0x34, (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 };
+
+ private final String algorithmName;
+
+ private final SecretKey skey;
+
+ private final PBEParameterSpec aspec;
+
+ ObjectEncryptionV2(final String algo, final String key)
+ throws InvalidKeySpecException, NoSuchAlgorithmException {
+ algorithmName = algo;
+
+ final PBEKeySpec s;
+ s = new PBEKeySpec(key.toCharArray(), salt, ITERATION_COUNT, 32);
+ skey = SecretKeyFactory.getInstance(algo).generateSecret(s);
+ aspec = new PBEParameterSpec(salt, ITERATION_COUNT);
+ }
+
+ @Override
+ void request(final HttpURLConnection u, final String prefix) {
+ u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, "2");
+ u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, algorithmName);
+ }
+
+ @Override
+ void validate(final HttpURLConnection u, final String p)
+ throws IOException {
+ validateImpl(u, p, "2", algorithmName);
+ }
+
+ @Override
+ OutputStream encrypt(final OutputStream os) throws IOException {
+ try {
+ final Cipher c = Cipher.getInstance(algorithmName);
+ c.init(Cipher.ENCRYPT_MODE, skey, aspec);
+ return new CipherOutputStream(os, c);
+ } catch (NoSuchAlgorithmException e) {
+ throw error(e);
+ } catch (NoSuchPaddingException e) {
+ throw error(e);
+ } catch (InvalidKeyException e) {
+ throw error(e);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw error(e);
+ }
+ }
+
+ @Override
+ InputStream decrypt(final InputStream in) throws IOException {
+ try {
+ final Cipher c = Cipher.getInstance(algorithmName);
+ c.init(Cipher.DECRYPT_MODE, skey, aspec);
+ return new CipherInputStream(in, c);
+ } catch (NoSuchAlgorithmException e) {
+ throw error(e);
+ } catch (NoSuchPaddingException e) {
+ throw error(e);
+ } catch (InvalidKeyException e) {
+ throw error(e);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw error(e);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
new file mode 100644
index 0000000000..8660a195d5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.CompoundException;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.ObjectWritingException;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PackIndex;
+import org.eclipse.jgit.lib.PackLock;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.UnpackedObjectLoader;
+import org.eclipse.jgit.revwalk.DateRevQueue;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+/**
+ * Generic fetch support for dumb transport protocols.
+ * <p>
+ * Since there are no Git-specific smarts on the remote side of the connection
+ * the client side must determine which objects it needs to copy in order to
+ * completely fetch the requested refs and their history. The generic walk
+ * support in this class parses each individual object (once it has been copied
+ * to the local repository) and examines the list of objects that must also be
+ * copied to create a complete history. Objects which are already available
+ * locally are retained (and not copied), saving bandwidth for incremental
+ * fetches. Pack files are copied from the remote repository only as a last
+ * resort, as the entire pack must be copied locally in order to access any
+ * single object.
+ * <p>
+ * This fetch connection does not actually perform the object data transfer.
+ * Instead it delegates the transfer to a {@link WalkRemoteObjectDatabase},
+ * which knows how to read individual files from the remote repository and
+ * supply the data as a standard Java InputStream.
+ *
+ * @see WalkRemoteObjectDatabase
+ */
+class WalkFetchConnection extends BaseFetchConnection {
+ /** The repository this transport fetches into, or pushes out of. */
+ private final Repository local;
+
+ /** If not null the validator for received objects. */
+ private final ObjectChecker objCheck;
+
+ /**
+ * List of all remote repositories we may need to get objects out of.
+ * <p>
+ * The first repository in the list is the one we were asked to fetch from;
+ * the remaining repositories point to the alternate locations we can fetch
+ * objects through.
+ */
+ private final List<WalkRemoteObjectDatabase> remotes;
+
+ /** Most recently used item in {@link #remotes}. */
+ private int lastRemoteIdx;
+
+ private final RevWalk revWalk;
+
+ private final TreeWalk treeWalk;
+
+ /** Objects whose direct dependents we know we have (or will have). */
+ private final RevFlag COMPLETE;
+
+ /** Objects that have already entered {@link #workQueue}. */
+ private final RevFlag IN_WORK_QUEUE;
+
+ /** Commits that have already entered {@link #localCommitQueue}. */
+ private final RevFlag LOCALLY_SEEN;
+
+ /** Commits already reachable from all local refs. */
+ private final DateRevQueue localCommitQueue;
+
+ /** Objects we need to copy from the remote repository. */
+ private LinkedList<ObjectId> workQueue;
+
+ /** Databases we have not yet obtained the list of packs from. */
+ private final LinkedList<WalkRemoteObjectDatabase> noPacksYet;
+
+ /** Databases we have not yet obtained the alternates from. */
+ private final LinkedList<WalkRemoteObjectDatabase> noAlternatesYet;
+
+ /** Packs we have discovered, but have not yet fetched locally. */
+ private final LinkedList<RemotePack> unfetchedPacks;
+
+ /**
+ * Packs whose indexes we have looked at in {@link #unfetchedPacks}.
+ * <p>
+ * We try to avoid getting duplicate copies of the same pack through
+ * multiple alternates by only looking at packs whose names are not yet in
+ * this collection.
+ */
+ private final Set<String> packsConsidered;
+
+ private final MutableObjectId idBuffer = new MutableObjectId();
+
+ private final MessageDigest objectDigest = Constants.newMessageDigest();
+
+ /**
+ * Errors received while trying to obtain an object.
+ * <p>
+ * If the fetch winds up failing because we cannot locate a specific object
+ * then we need to report all errors related to that object back to the
+ * caller as there may be cascading failures.
+ */
+ private final HashMap<ObjectId, List<Throwable>> fetchErrors;
+
+ private String lockMessage;
+
+ private final List<PackLock> packLocks;
+
+ WalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) {
+ Transport wt = (Transport)t;
+ local = wt.local;
+ objCheck = wt.isCheckFetchedObjects() ? new ObjectChecker() : null;
+
+ remotes = new ArrayList<WalkRemoteObjectDatabase>();
+ remotes.add(w);
+
+ unfetchedPacks = new LinkedList<RemotePack>();
+ packsConsidered = new HashSet<String>();
+
+ noPacksYet = new LinkedList<WalkRemoteObjectDatabase>();
+ noPacksYet.add(w);
+
+ noAlternatesYet = new LinkedList<WalkRemoteObjectDatabase>();
+ noAlternatesYet.add(w);
+
+ fetchErrors = new HashMap<ObjectId, List<Throwable>>();
+ packLocks = new ArrayList<PackLock>(4);
+
+ revWalk = new RevWalk(local);
+ revWalk.setRetainBody(false);
+ treeWalk = new TreeWalk(local);
+ COMPLETE = revWalk.newFlag("COMPLETE");
+ IN_WORK_QUEUE = revWalk.newFlag("IN_WORK_QUEUE");
+ LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN");
+
+ localCommitQueue = new DateRevQueue();
+ workQueue = new LinkedList<ObjectId>();
+ }
+
+ public boolean didFetchTestConnectivity() {
+ return true;
+ }
+
+ @Override
+ protected void doFetch(final ProgressMonitor monitor,
+ final Collection<Ref> want, final Set<ObjectId> have)
+ throws TransportException {
+ markLocalRefsComplete(have);
+ queueWants(want);
+
+ while (!monitor.isCancelled() && !workQueue.isEmpty()) {
+ final ObjectId id = workQueue.removeFirst();
+ if (!(id instanceof RevObject) || !((RevObject) id).has(COMPLETE))
+ downloadObject(monitor, id);
+ process(id);
+ }
+ }
+
+ public Collection<PackLock> getPackLocks() {
+ return packLocks;
+ }
+
+ public void setPackLockMessage(final String message) {
+ lockMessage = message;
+ }
+
+ @Override
+ public void close() {
+ for (final RemotePack p : unfetchedPacks)
+ p.tmpIdx.delete();
+ for (final WalkRemoteObjectDatabase r : remotes)
+ r.close();
+ }
+
+ private void queueWants(final Collection<Ref> want)
+ throws TransportException {
+ final HashSet<ObjectId> inWorkQueue = new HashSet<ObjectId>();
+ for (final Ref r : want) {
+ final ObjectId id = r.getObjectId();
+ try {
+ final RevObject obj = revWalk.parseAny(id);
+ if (obj.has(COMPLETE))
+ continue;
+ if (inWorkQueue.add(id)) {
+ obj.add(IN_WORK_QUEUE);
+ workQueue.add(obj);
+ }
+ } catch (MissingObjectException e) {
+ if (inWorkQueue.add(id))
+ workQueue.add(id);
+ } catch (IOException e) {
+ throw new TransportException("Cannot read " + id.name(), e);
+ }
+ }
+ }
+
+ private void process(final ObjectId id) throws TransportException {
+ final RevObject obj;
+ try {
+ if (id instanceof RevObject) {
+ obj = (RevObject) id;
+ if (obj.has(COMPLETE))
+ return;
+ revWalk.parseHeaders(obj);
+ } else {
+ obj = revWalk.parseAny(id);
+ if (obj.has(COMPLETE))
+ return;
+ }
+ } catch (IOException e) {
+ throw new TransportException("Cannot read " + id.name(), e);
+ }
+
+ switch (obj.getType()) {
+ case Constants.OBJ_BLOB:
+ processBlob(obj);
+ break;
+ case Constants.OBJ_TREE:
+ processTree(obj);
+ break;
+ case Constants.OBJ_COMMIT:
+ processCommit(obj);
+ break;
+ case Constants.OBJ_TAG:
+ processTag(obj);
+ break;
+ default:
+ throw new TransportException("Unknown object type " + id.name());
+ }
+
+ // If we had any prior errors fetching this object they are
+ // now resolved, as the object was parsed successfully.
+ //
+ fetchErrors.remove(id.copy());
+ }
+
+ private void processBlob(final RevObject obj) throws TransportException {
+ if (!local.hasObject(obj))
+ throw new TransportException("Cannot read blob " + obj.name(),
+ new MissingObjectException(obj, Constants.TYPE_BLOB));
+ obj.add(COMPLETE);
+ }
+
+ private void processTree(final RevObject obj) throws TransportException {
+ try {
+ treeWalk.reset(obj);
+ while (treeWalk.next()) {
+ final FileMode mode = treeWalk.getFileMode(0);
+ final int sType = mode.getObjectType();
+
+ switch (sType) {
+ case Constants.OBJ_BLOB:
+ case Constants.OBJ_TREE:
+ treeWalk.getObjectId(idBuffer, 0);
+ needs(revWalk.lookupAny(idBuffer, sType));
+ continue;
+
+ default:
+ if (FileMode.GITLINK.equals(mode))
+ continue;
+ treeWalk.getObjectId(idBuffer, 0);
+ throw new CorruptObjectException("Invalid mode " + mode
+ + " for " + idBuffer.name() + " "
+ + treeWalk.getPathString() + " in "
+ + obj.getId().name() + ".");
+ }
+ }
+ } catch (IOException ioe) {
+ throw new TransportException("Cannot read tree " + obj.name(), ioe);
+ }
+ obj.add(COMPLETE);
+ }
+
+ private void processCommit(final RevObject obj) throws TransportException {
+ final RevCommit commit = (RevCommit) obj;
+ markLocalCommitsComplete(commit.getCommitTime());
+ needs(commit.getTree());
+ for (final RevCommit p : commit.getParents())
+ needs(p);
+ obj.add(COMPLETE);
+ }
+
+ private void processTag(final RevObject obj) {
+ final RevTag tag = (RevTag) obj;
+ needs(tag.getObject());
+ obj.add(COMPLETE);
+ }
+
+ private void needs(final RevObject obj) {
+ if (obj.has(COMPLETE))
+ return;
+ if (!obj.has(IN_WORK_QUEUE)) {
+ obj.add(IN_WORK_QUEUE);
+ workQueue.add(obj);
+ }
+ }
+
+ private void downloadObject(final ProgressMonitor pm, final AnyObjectId id)
+ throws TransportException {
+ if (local.hasObject(id))
+ return;
+
+ for (;;) {
+ // Try a pack file we know about, but don't have yet. Odds are
+ // that if it has this object, it has others related to it so
+ // getting the pack is a good bet.
+ //
+ if (downloadPackedObject(pm, id))
+ return;
+
+ // Search for a loose object over all alternates, starting
+ // from the one we last successfully located an object through.
+ //
+ final String idStr = id.name();
+ final String subdir = idStr.substring(0, 2);
+ final String file = idStr.substring(2);
+ final String looseName = subdir + "/" + file;
+
+ for (int i = lastRemoteIdx; i < remotes.size(); i++) {
+ if (downloadLooseObject(id, looseName, remotes.get(i))) {
+ lastRemoteIdx = i;
+ return;
+ }
+ }
+ for (int i = 0; i < lastRemoteIdx; i++) {
+ if (downloadLooseObject(id, looseName, remotes.get(i))) {
+ lastRemoteIdx = i;
+ return;
+ }
+ }
+
+ // Try to obtain more pack information and search those.
+ //
+ while (!noPacksYet.isEmpty()) {
+ final WalkRemoteObjectDatabase wrr = noPacksYet.removeFirst();
+ final Collection<String> packNameList;
+ try {
+ pm.beginTask("Listing packs", ProgressMonitor.UNKNOWN);
+ packNameList = wrr.getPackNames();
+ } catch (IOException e) {
+ // Try another repository.
+ //
+ recordError(id, e);
+ continue;
+ } finally {
+ pm.endTask();
+ }
+
+ if (packNameList == null || packNameList.isEmpty())
+ continue;
+ for (final String packName : packNameList) {
+ if (packsConsidered.add(packName))
+ unfetchedPacks.add(new RemotePack(wrr, packName));
+ }
+ if (downloadPackedObject(pm, id))
+ return;
+ }
+
+ // Try to expand the first alternate we haven't expanded yet.
+ //
+ Collection<WalkRemoteObjectDatabase> al = expandOneAlternate(id, pm);
+ if (al != null && !al.isEmpty()) {
+ for (final WalkRemoteObjectDatabase alt : al) {
+ remotes.add(alt);
+ noPacksYet.add(alt);
+ noAlternatesYet.add(alt);
+ }
+ continue;
+ }
+
+ // We could not obtain the object. There may be reasons why.
+ //
+ List<Throwable> failures = fetchErrors.get(id.copy());
+ final TransportException te;
+
+ te = new TransportException("Cannot get " + id.name() + ".");
+ if (failures != null && !failures.isEmpty()) {
+ if (failures.size() == 1)
+ te.initCause(failures.get(0));
+ else
+ te.initCause(new CompoundException(failures));
+ }
+ throw te;
+ }
+ }
+
+ private boolean downloadPackedObject(final ProgressMonitor monitor,
+ final AnyObjectId id) throws TransportException {
+ // Search for the object in a remote pack whose index we have,
+ // but whose pack we do not yet have.
+ //
+ final Iterator<RemotePack> packItr = unfetchedPacks.iterator();
+ while (packItr.hasNext() && !monitor.isCancelled()) {
+ final RemotePack pack = packItr.next();
+ try {
+ pack.openIndex(monitor);
+ } catch (IOException err) {
+ // If the index won't open its either not found or
+ // its a format we don't recognize. In either case
+ // we may still be able to obtain the object from
+ // another source, so don't consider it a failure.
+ //
+ recordError(id, err);
+ packItr.remove();
+ continue;
+ }
+
+ if (monitor.isCancelled()) {
+ // If we were cancelled while the index was opening
+ // the open may have aborted. We can't search an
+ // unopen index.
+ //
+ return false;
+ }
+
+ if (!pack.index.hasObject(id)) {
+ // Not in this pack? Try another.
+ //
+ continue;
+ }
+
+ // It should be in the associated pack. Download that
+ // and attach it to the local repository so we can use
+ // all of the contained objects.
+ //
+ try {
+ pack.downloadPack(monitor);
+ } catch (IOException err) {
+ // If the pack failed to download, index correctly,
+ // or open in the local repository we may still be
+ // able to obtain this object from another pack or
+ // an alternate.
+ //
+ recordError(id, err);
+ continue;
+ } finally {
+ // If the pack was good its in the local repository
+ // and Repository.hasObject(id) will succeed in the
+ // future, so we do not need this data anymore. If
+ // it failed the index and pack are unusable and we
+ // shouldn't consult them again.
+ //
+ pack.tmpIdx.delete();
+ packItr.remove();
+ }
+
+ if (!local.hasObject(id)) {
+ // What the hell? This pack claimed to have
+ // the object, but after indexing we didn't
+ // actually find it in the pack.
+ //
+ recordError(id, new FileNotFoundException("Object " + id.name()
+ + " not found in " + pack.packName + "."));
+ continue;
+ }
+
+ // Complete any other objects that we can.
+ //
+ final Iterator<ObjectId> pending = swapFetchQueue();
+ while (pending.hasNext()) {
+ final ObjectId p = pending.next();
+ if (pack.index.hasObject(p)) {
+ pending.remove();
+ process(p);
+ } else {
+ workQueue.add(p);
+ }
+ }
+ return true;
+
+ }
+ return false;
+ }
+
+ private Iterator<ObjectId> swapFetchQueue() {
+ final Iterator<ObjectId> r = workQueue.iterator();
+ workQueue = new LinkedList<ObjectId>();
+ return r;
+ }
+
+ private boolean downloadLooseObject(final AnyObjectId id,
+ final String looseName, final WalkRemoteObjectDatabase remote)
+ throws TransportException {
+ try {
+ final byte[] compressed = remote.open(looseName).toArray();
+ verifyLooseObject(id, compressed);
+ saveLooseObject(id, compressed);
+ return true;
+ } catch (FileNotFoundException e) {
+ // Not available in a loose format from this alternate?
+ // Try another strategy to get the object.
+ //
+ recordError(id, e);
+ return false;
+ } catch (IOException e) {
+ throw new TransportException("Cannot download " + id.name(), e);
+ }
+ }
+
+ private void verifyLooseObject(final AnyObjectId id, final byte[] compressed)
+ throws IOException {
+ final UnpackedObjectLoader uol;
+ try {
+ uol = new UnpackedObjectLoader(compressed);
+ } catch (CorruptObjectException parsingError) {
+ // Some HTTP servers send back a "200 OK" status with an HTML
+ // page that explains the requested file could not be found.
+ // These servers are most certainly misconfigured, but many
+ // of them exist in the world, and many of those are hosting
+ // Git repositories.
+ //
+ // Since an HTML page is unlikely to hash to one of our loose
+ // objects we treat this condition as a FileNotFoundException
+ // and attempt to recover by getting the object from another
+ // source.
+ //
+ final FileNotFoundException e;
+ e = new FileNotFoundException(id.name());
+ e.initCause(parsingError);
+ throw e;
+ }
+
+ objectDigest.reset();
+ objectDigest.update(Constants.encodedTypeString(uol.getType()));
+ objectDigest.update((byte) ' ');
+ objectDigest.update(Constants.encodeASCII(uol.getSize()));
+ objectDigest.update((byte) 0);
+ objectDigest.update(uol.getCachedBytes());
+ idBuffer.fromRaw(objectDigest.digest(), 0);
+
+ if (!AnyObjectId.equals(id, idBuffer)) {
+ throw new TransportException("Incorrect hash for " + id.name()
+ + "; computed " + idBuffer.name() + " as a "
+ + Constants.typeString(uol.getType()) + " from "
+ + compressed.length + " bytes.");
+ }
+ if (objCheck != null) {
+ try {
+ objCheck.check(uol.getType(), uol.getCachedBytes());
+ } catch (CorruptObjectException e) {
+ throw new TransportException("Invalid "
+ + Constants.typeString(uol.getType()) + " "
+ + id.name() + ":" + e.getMessage());
+ }
+ }
+ }
+
+ private void saveLooseObject(final AnyObjectId id, final byte[] compressed)
+ throws IOException, ObjectWritingException {
+ final File tmp;
+
+ tmp = File.createTempFile("noz", null, local.getObjectsDirectory());
+ try {
+ final FileOutputStream out = new FileOutputStream(tmp);
+ try {
+ out.write(compressed);
+ } finally {
+ out.close();
+ }
+ tmp.setReadOnly();
+ } catch (IOException e) {
+ tmp.delete();
+ throw e;
+ }
+
+ final File o = local.toFile(id);
+ if (tmp.renameTo(o))
+ return;
+
+ // Maybe the directory doesn't exist yet as the object
+ // directories are always lazily created. Note that we
+ // try the rename first as the directory likely does exist.
+ //
+ o.getParentFile().mkdir();
+ if (tmp.renameTo(o))
+ return;
+
+ tmp.delete();
+ if (local.hasObject(id))
+ return;
+ throw new ObjectWritingException("Unable to store " + id.name() + ".");
+ }
+
+ private Collection<WalkRemoteObjectDatabase> expandOneAlternate(
+ final AnyObjectId id, final ProgressMonitor pm) {
+ while (!noAlternatesYet.isEmpty()) {
+ final WalkRemoteObjectDatabase wrr = noAlternatesYet.removeFirst();
+ try {
+ pm.beginTask("Listing alternates", ProgressMonitor.UNKNOWN);
+ Collection<WalkRemoteObjectDatabase> altList = wrr
+ .getAlternates();
+ if (altList != null && !altList.isEmpty())
+ return altList;
+ } catch (IOException e) {
+ // Try another repository.
+ //
+ recordError(id, e);
+ } finally {
+ pm.endTask();
+ }
+ }
+ return null;
+ }
+
+ private void markLocalRefsComplete(final Set<ObjectId> have) throws TransportException {
+ for (final Ref r : local.getAllRefs().values()) {
+ try {
+ markLocalObjComplete(revWalk.parseAny(r.getObjectId()));
+ } catch (IOException readError) {
+ throw new TransportException("Local ref " + r.getName()
+ + " is missing object(s).", readError);
+ }
+ }
+ for (final ObjectId id : have) {
+ try {
+ markLocalObjComplete(revWalk.parseAny(id));
+ } catch (IOException readError) {
+ throw new TransportException("Missing assumed "+id.name(), readError);
+ }
+ }
+ }
+
+ private void markLocalObjComplete(RevObject obj) throws IOException {
+ while (obj.getType() == Constants.OBJ_TAG) {
+ obj.add(COMPLETE);
+ obj = ((RevTag) obj).getObject();
+ revWalk.parseHeaders(obj);
+ }
+
+ switch (obj.getType()) {
+ case Constants.OBJ_BLOB:
+ obj.add(COMPLETE);
+ break;
+ case Constants.OBJ_COMMIT:
+ pushLocalCommit((RevCommit) obj);
+ break;
+ case Constants.OBJ_TREE:
+ markTreeComplete((RevTree) obj);
+ break;
+ }
+ }
+
+ private void markLocalCommitsComplete(final int until)
+ throws TransportException {
+ try {
+ for (;;) {
+ final RevCommit c = localCommitQueue.peek();
+ if (c == null || c.getCommitTime() < until)
+ return;
+ localCommitQueue.next();
+
+ markTreeComplete(c.getTree());
+ for (final RevCommit p : c.getParents())
+ pushLocalCommit(p);
+ }
+ } catch (IOException err) {
+ throw new TransportException("Local objects incomplete.", err);
+ }
+ }
+
+ private void pushLocalCommit(final RevCommit p)
+ throws MissingObjectException, IOException {
+ if (p.has(LOCALLY_SEEN))
+ return;
+ revWalk.parseHeaders(p);
+ p.add(LOCALLY_SEEN);
+ p.add(COMPLETE);
+ p.carry(COMPLETE);
+ localCommitQueue.add(p);
+ }
+
+ private void markTreeComplete(final RevTree tree) throws IOException {
+ if (tree.has(COMPLETE))
+ return;
+ tree.add(COMPLETE);
+ treeWalk.reset(tree);
+ while (treeWalk.next()) {
+ final FileMode mode = treeWalk.getFileMode(0);
+ final int sType = mode.getObjectType();
+
+ switch (sType) {
+ case Constants.OBJ_BLOB:
+ treeWalk.getObjectId(idBuffer, 0);
+ revWalk.lookupAny(idBuffer, sType).add(COMPLETE);
+ continue;
+
+ case Constants.OBJ_TREE: {
+ treeWalk.getObjectId(idBuffer, 0);
+ final RevObject o = revWalk.lookupAny(idBuffer, sType);
+ if (!o.has(COMPLETE)) {
+ o.add(COMPLETE);
+ treeWalk.enterSubtree();
+ }
+ continue;
+ }
+ default:
+ if (FileMode.GITLINK.equals(mode))
+ continue;
+ treeWalk.getObjectId(idBuffer, 0);
+ throw new CorruptObjectException("Invalid mode " + mode
+ + " for " + idBuffer.name() + " "
+ + treeWalk.getPathString() + " in " + tree.name() + ".");
+ }
+ }
+ }
+
+ private void recordError(final AnyObjectId id, final Throwable what) {
+ final ObjectId objId = id.copy();
+ List<Throwable> errors = fetchErrors.get(objId);
+ if (errors == null) {
+ errors = new ArrayList<Throwable>(2);
+ fetchErrors.put(objId, errors);
+ }
+ errors.add(what);
+ }
+
+ private class RemotePack {
+ final WalkRemoteObjectDatabase connection;
+
+ final String packName;
+
+ final String idxName;
+
+ final File tmpIdx;
+
+ PackIndex index;
+
+ RemotePack(final WalkRemoteObjectDatabase c, final String pn) {
+ final File objdir = local.getObjectsDirectory();
+ connection = c;
+ packName = pn;
+ idxName = packName.substring(0, packName.length() - 5) + ".idx";
+
+ String tn = idxName;
+ if (tn.startsWith("pack-"))
+ tn = tn.substring(5);
+ if (tn.endsWith(".idx"))
+ tn = tn.substring(0, tn.length() - 4);
+ tmpIdx = new File(objdir, "walk-" + tn + ".walkidx");
+ }
+
+ void openIndex(final ProgressMonitor pm) throws IOException {
+ if (index != null)
+ return;
+ if (tmpIdx.isFile()) {
+ try {
+ index = PackIndex.open(tmpIdx);
+ return;
+ } catch (FileNotFoundException err) {
+ // Fall through and get the file.
+ }
+ }
+
+ final WalkRemoteObjectDatabase.FileStream s;
+ s = connection.open("pack/" + idxName);
+ pm.beginTask("Get " + idxName.substring(0, 12) + "..idx",
+ s.length < 0 ? ProgressMonitor.UNKNOWN
+ : (int) (s.length / 1024));
+ try {
+ final FileOutputStream fos = new FileOutputStream(tmpIdx);
+ try {
+ final byte[] buf = new byte[2048];
+ int cnt;
+ while (!pm.isCancelled() && (cnt = s.in.read(buf)) >= 0) {
+ fos.write(buf, 0, cnt);
+ pm.update(cnt / 1024);
+ }
+ } finally {
+ fos.close();
+ }
+ } catch (IOException err) {
+ tmpIdx.delete();
+ throw err;
+ } finally {
+ s.in.close();
+ }
+ pm.endTask();
+
+ if (pm.isCancelled()) {
+ tmpIdx.delete();
+ return;
+ }
+
+ try {
+ index = PackIndex.open(tmpIdx);
+ } catch (IOException e) {
+ tmpIdx.delete();
+ throw e;
+ }
+ }
+
+ void downloadPack(final ProgressMonitor monitor) throws IOException {
+ final WalkRemoteObjectDatabase.FileStream s;
+ final IndexPack ip;
+
+ s = connection.open("pack/" + packName);
+ ip = IndexPack.create(local, s.in);
+ ip.setFixThin(false);
+ ip.setObjectChecker(objCheck);
+ ip.index(monitor);
+ final PackLock keep = ip.renameAndOpenPack(lockMessage);
+ if (keep != null)
+ packLocks.add(keep);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
new file mode 100644
index 0000000000..56f73c50b2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import static org.eclipse.jgit.transport.WalkRemoteObjectDatabase.ROOT_DIR;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PackWriter;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefWriter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+
+/**
+ * Generic push support for dumb transport protocols.
+ * <p>
+ * Since there are no Git-specific smarts on the remote side of the connection
+ * the client side must handle everything on its own. The generic push support
+ * requires being able to delete, create and overwrite files on the remote side,
+ * as well as create any missing directories (if necessary). Typically this can
+ * be handled through an FTP style protocol.
+ * <p>
+ * Objects not on the remote side are uploaded as pack files, using one pack
+ * file per invocation. This simplifies the implementation as only two data
+ * files need to be written to the remote repository.
+ * <p>
+ * Push support supplied by this class is not multiuser safe. Concurrent pushes
+ * to the same repository may yield an inconsistent reference database which may
+ * confuse fetch clients.
+ * <p>
+ * A single push is concurrently safe with multiple fetch requests, due to the
+ * careful order of operations used to update the repository. Clients fetching
+ * may receive transient failures due to short reads on certain files if the
+ * protocol does not support atomic file replacement.
+ *
+ * @see WalkRemoteObjectDatabase
+ */
+class WalkPushConnection extends BaseConnection implements PushConnection {
+ /** The repository this transport pushes out of. */
+ private final Repository local;
+
+ /** Location of the remote repository we are writing to. */
+ private final URIish uri;
+
+ /** Database connection to the remote repository. */
+ private final WalkRemoteObjectDatabase dest;
+
+ /**
+ * Packs already known to reside in the remote repository.
+ * <p>
+ * This is a LinkedHashMap to maintain the original order.
+ */
+ private LinkedHashMap<String, String> packNames;
+
+ /** Complete listing of refs the remote will have after our push. */
+ private Map<String, Ref> newRefs;
+
+ /**
+ * Updates which require altering the packed-refs file to complete.
+ * <p>
+ * If this collection is non-empty then any refs listed in {@link #newRefs}
+ * with a storage class of {@link Storage#PACKED} will be written.
+ */
+ private Collection<RemoteRefUpdate> packedRefUpdates;
+
+ WalkPushConnection(final WalkTransport walkTransport,
+ final WalkRemoteObjectDatabase w) {
+ Transport t = (Transport)walkTransport;
+ local = t.local;
+ uri = t.getURI();
+ dest = w;
+ }
+
+ public void push(final ProgressMonitor monitor,
+ final Map<String, RemoteRefUpdate> refUpdates)
+ throws TransportException {
+ markStartedOperation();
+ packNames = null;
+ newRefs = new TreeMap<String, Ref>(getRefsMap());
+ packedRefUpdates = new ArrayList<RemoteRefUpdate>(refUpdates.size());
+
+ // Filter the commands and issue all deletes first. This way we
+ // can correctly handle a directory being cleared out and a new
+ // ref using the directory name being created.
+ //
+ final List<RemoteRefUpdate> updates = new ArrayList<RemoteRefUpdate>();
+ for (final RemoteRefUpdate u : refUpdates.values()) {
+ final String n = u.getRemoteName();
+ if (!n.startsWith("refs/") || !Repository.isValidRefName(n)) {
+ u.setStatus(Status.REJECTED_OTHER_REASON);
+ u.setMessage("funny refname");
+ continue;
+ }
+
+ if (AnyObjectId.equals(ObjectId.zeroId(), u.getNewObjectId()))
+ deleteCommand(u);
+ else
+ updates.add(u);
+ }
+
+ // If we have any updates we need to upload the objects first, to
+ // prevent creating refs pointing at non-existent data. Then we
+ // can update the refs, and the info-refs file for dumb transports.
+ //
+ if (!updates.isEmpty())
+ sendpack(updates, monitor);
+ for (final RemoteRefUpdate u : updates)
+ updateCommand(u);
+
+ // Is this a new repository? If so we should create additional
+ // metadata files so it is properly initialized during the push.
+ //
+ if (!updates.isEmpty() && isNewRepository())
+ createNewRepository(updates);
+
+ RefWriter refWriter = new RefWriter(newRefs.values()) {
+ @Override
+ protected void writeFile(String file, byte[] content)
+ throws IOException {
+ dest.writeFile(ROOT_DIR + file, content);
+ }
+ };
+ if (!packedRefUpdates.isEmpty()) {
+ try {
+ refWriter.writePackedRefs();
+ for (final RemoteRefUpdate u : packedRefUpdates)
+ u.setStatus(Status.OK);
+ } catch (IOException err) {
+ for (final RemoteRefUpdate u : packedRefUpdates) {
+ u.setStatus(Status.REJECTED_OTHER_REASON);
+ u.setMessage(err.getMessage());
+ }
+ throw new TransportException(uri, "failed updating refs", err);
+ }
+ }
+
+ try {
+ refWriter.writeInfoRefs();
+ } catch (IOException err) {
+ throw new TransportException(uri, "failed updating refs", err);
+ }
+ }
+
+ @Override
+ public void close() {
+ dest.close();
+ }
+
+ private void sendpack(final List<RemoteRefUpdate> updates,
+ final ProgressMonitor monitor) throws TransportException {
+ String pathPack = null;
+ String pathIdx = null;
+
+ try {
+ final PackWriter pw = new PackWriter(local, monitor);
+ final List<ObjectId> need = new ArrayList<ObjectId>();
+ final List<ObjectId> have = new ArrayList<ObjectId>();
+ for (final RemoteRefUpdate r : updates)
+ need.add(r.getNewObjectId());
+ for (final Ref r : getRefs()) {
+ have.add(r.getObjectId());
+ if (r.getPeeledObjectId() != null)
+ have.add(r.getPeeledObjectId());
+ }
+ pw.preparePack(need, have);
+
+ // We don't have to continue further if the pack will
+ // be an empty pack, as the remote has all objects it
+ // needs to complete this change.
+ //
+ if (pw.getObjectsNumber() == 0)
+ return;
+
+ packNames = new LinkedHashMap<String, String>();
+ for (final String n : dest.getPackNames())
+ packNames.put(n, n);
+
+ final String base = "pack-" + pw.computeName().name();
+ final String packName = base + ".pack";
+ pathPack = "pack/" + packName;
+ pathIdx = "pack/" + base + ".idx";
+
+ if (packNames.remove(packName) != null) {
+ // The remote already contains this pack. We should
+ // remove the index before overwriting to prevent bad
+ // offsets from appearing to clients.
+ //
+ dest.writeInfoPacks(packNames.keySet());
+ dest.deleteFile(pathIdx);
+ }
+
+ // Write the pack file, then the index, as readers look the
+ // other direction (index, then pack file).
+ //
+ final String wt = "Put " + base.substring(0, 12);
+ OutputStream os = dest.writeFile(pathPack, monitor, wt + "..pack");
+ try {
+ pw.writePack(os);
+ } finally {
+ os.close();
+ }
+
+ os = dest.writeFile(pathIdx, monitor, wt + "..idx");
+ try {
+ pw.writeIndex(os);
+ } finally {
+ os.close();
+ }
+
+ // Record the pack at the start of the pack info list. This
+ // way clients are likely to consult the newest pack first,
+ // and discover the most recent objects there.
+ //
+ final ArrayList<String> infoPacks = new ArrayList<String>();
+ infoPacks.add(packName);
+ infoPacks.addAll(packNames.keySet());
+ dest.writeInfoPacks(infoPacks);
+
+ } catch (IOException err) {
+ safeDelete(pathIdx);
+ safeDelete(pathPack);
+
+ throw new TransportException(uri, "cannot store objects", err);
+ }
+ }
+
+ private void safeDelete(final String path) {
+ if (path != null) {
+ try {
+ dest.deleteFile(path);
+ } catch (IOException cleanupFailure) {
+ // Ignore the deletion failure. We probably are
+ // already failing and were just trying to pick
+ // up after ourselves.
+ }
+ }
+ }
+
+ private void deleteCommand(final RemoteRefUpdate u) {
+ final Ref r = newRefs.remove(u.getRemoteName());
+ if (r == null) {
+ // Already gone.
+ //
+ u.setStatus(Status.OK);
+ return;
+ }
+
+ if (r.getStorage().isPacked())
+ packedRefUpdates.add(u);
+
+ if (r.getStorage().isLoose()) {
+ try {
+ dest.deleteRef(u.getRemoteName());
+ u.setStatus(Status.OK);
+ } catch (IOException e) {
+ u.setStatus(Status.REJECTED_OTHER_REASON);
+ u.setMessage(e.getMessage());
+ }
+ }
+
+ try {
+ dest.deleteRefLog(u.getRemoteName());
+ } catch (IOException e) {
+ u.setStatus(Status.REJECTED_OTHER_REASON);
+ u.setMessage(e.getMessage());
+ }
+ }
+
+ private void updateCommand(final RemoteRefUpdate u) {
+ try {
+ dest.writeRef(u.getRemoteName(), u.getNewObjectId());
+ newRefs.put(u.getRemoteName(), new Ref(Storage.LOOSE, u
+ .getRemoteName(), u.getNewObjectId()));
+ u.setStatus(Status.OK);
+ } catch (IOException e) {
+ u.setStatus(Status.REJECTED_OTHER_REASON);
+ u.setMessage(e.getMessage());
+ }
+ }
+
+ private boolean isNewRepository() {
+ return getRefsMap().isEmpty() && packNames != null
+ && packNames.isEmpty();
+ }
+
+ private void createNewRepository(final List<RemoteRefUpdate> updates)
+ throws TransportException {
+ try {
+ final String ref = "ref: " + pickHEAD(updates) + "\n";
+ final byte[] bytes = Constants.encode(ref);
+ dest.writeFile(ROOT_DIR + Constants.HEAD, bytes);
+ } catch (IOException e) {
+ throw new TransportException(uri, "cannot create HEAD", e);
+ }
+
+ try {
+ final String config = "[core]\n"
+ + "\trepositoryformatversion = 0\n";
+ final byte[] bytes = Constants.encode(config);
+ dest.writeFile(ROOT_DIR + "config", bytes);
+ } catch (IOException e) {
+ throw new TransportException(uri, "cannot create config", e);
+ }
+ }
+
+ private static String pickHEAD(final List<RemoteRefUpdate> updates) {
+ // Try to use master if the user is pushing that, it is the
+ // default branch and is likely what they want to remain as
+ // the default on the new remote.
+ //
+ for (final RemoteRefUpdate u : updates) {
+ final String n = u.getRemoteName();
+ if (n.equals(Constants.R_HEADS + Constants.MASTER))
+ return n;
+ }
+
+ // Pick any branch, under the assumption the user pushed only
+ // one to the remote side.
+ //
+ for (final RemoteRefUpdate u : updates) {
+ final String n = u.getRemoteName();
+ if (n.startsWith(Constants.R_HEADS))
+ return n;
+ }
+ return updates.get(0).getRemoteName();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
new file mode 100644
index 0000000000..6a557dfd3f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Transfers object data through a dumb transport.
+ * <p>
+ * Implementations are responsible for resolving path names relative to the
+ * <code>objects/</code> subdirectory of a single remote Git repository or
+ * naked object database and make the content available as a Java input stream
+ * for reading during fetch. The actual object traversal logic to determine the
+ * names of files to retrieve is handled through the generic, protocol
+ * independent {@link WalkFetchConnection}.
+ */
+abstract class WalkRemoteObjectDatabase {
+ static final String ROOT_DIR = "../";
+
+ static final String INFO_PACKS = "info/packs";
+
+ static final String INFO_ALTERNATES = "info/alternates";
+
+ static final String INFO_HTTP_ALTERNATES = "info/http-alternates";
+
+ static final String INFO_REFS = ROOT_DIR + Constants.INFO_REFS;
+
+ abstract URIish getURI();
+
+ /**
+ * Obtain the list of available packs (if any).
+ * <p>
+ * Pack names should be the file name in the packs directory, that is
+ * <code>pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>. Index
+ * names should not be included in the returned collection.
+ *
+ * @return list of pack names; null or empty list if none are available.
+ * @throws IOException
+ * The connection is unable to read the remote repository's list
+ * of available pack files.
+ */
+ abstract Collection<String> getPackNames() throws IOException;
+
+ /**
+ * Obtain alternate connections to alternate object databases (if any).
+ * <p>
+ * Alternates are typically read from the file {@link #INFO_ALTERNATES} or
+ * {@link #INFO_HTTP_ALTERNATES}. The content of each line must be resolved
+ * by the implementation and a new database reference should be returned to
+ * represent the additional location.
+ * <p>
+ * Alternates may reuse the same network connection handle, however the
+ * fetch connection will {@link #close()} each created alternate.
+ *
+ * @return list of additional object databases the caller could fetch from;
+ * null or empty list if none are configured.
+ * @throws IOException
+ * The connection is unable to read the remote repository's list
+ * of configured alternates.
+ */
+ abstract Collection<WalkRemoteObjectDatabase> getAlternates()
+ throws IOException;
+
+ /**
+ * Open a single file for reading.
+ * <p>
+ * Implementors should make every attempt possible to ensure
+ * {@link FileNotFoundException} is used when the remote object does not
+ * exist. However when fetching over HTTP some misconfigured servers may
+ * generate a 200 OK status message (rather than a 404 Not Found) with an
+ * HTML formatted message explaining the requested resource does not exist.
+ * Callers such as {@link WalkFetchConnection} are prepared to handle this
+ * by validating the content received, and assuming content that fails to
+ * match its hash is an incorrectly phrased FileNotFoundException.
+ *
+ * @param path
+ * location of the file to read, relative to this objects
+ * directory (e.g.
+ * <code>cb/95df6ab7ae9e57571511ef451cf33767c26dd2</code> or
+ * <code>pack/pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>).
+ * @return a stream to read from the file. Never null.
+ * @throws FileNotFoundException
+ * the requested file does not exist at the given location.
+ * @throws IOException
+ * The connection is unable to read the remote's file, and the
+ * failure occurred prior to being able to determine if the file
+ * exists, or after it was determined to exist but before the
+ * stream could be created.
+ */
+ abstract FileStream open(String path) throws FileNotFoundException,
+ IOException;
+
+ /**
+ * Create a new connection for a discovered alternate object database
+ * <p>
+ * This method is typically called by {@link #readAlternates(String)} when
+ * subclasses us the generic alternate parsing logic for their
+ * implementation of {@link #getAlternates()}.
+ *
+ * @param location
+ * the location of the new alternate, relative to the current
+ * object database.
+ * @return a new database connection that can read from the specified
+ * alternate.
+ * @throws IOException
+ * The database connection cannot be established with the
+ * alternate, such as if the alternate location does not
+ * actually exist and the connection's constructor attempts to
+ * verify that.
+ */
+ abstract WalkRemoteObjectDatabase openAlternate(String location)
+ throws IOException;
+
+ /**
+ * Close any resources used by this connection.
+ * <p>
+ * If the remote repository is contacted by a network socket this method
+ * must close that network socket, disconnecting the two peers. If the
+ * remote repository is actually local (same system) this method must close
+ * any open file handles used to read the "remote" repository.
+ */
+ abstract void close();
+
+ /**
+ * Delete a file from the object database.
+ * <p>
+ * Path may start with <code>../</code> to request deletion of a file that
+ * resides in the repository itself.
+ * <p>
+ * When possible empty directories must be removed, up to but not including
+ * the current object database directory itself.
+ * <p>
+ * This method does not support deletion of directories.
+ *
+ * @param path
+ * name of the item to be removed, relative to the current object
+ * database.
+ * @throws IOException
+ * deletion is not supported, or deletion failed.
+ */
+ void deleteFile(final String path) throws IOException {
+ throw new IOException("Deleting '" + path + "' not supported.");
+ }
+
+ /**
+ * Open a remote file for writing.
+ * <p>
+ * Path may start with <code>../</code> to request writing of a file that
+ * resides in the repository itself.
+ * <p>
+ * The requested path may or may not exist. If the path already exists as a
+ * file the file should be truncated and completely replaced.
+ * <p>
+ * This method creates any missing parent directories, if necessary.
+ *
+ * @param path
+ * name of the file to write, relative to the current object
+ * database.
+ * @return stream to write into this file. Caller must close the stream to
+ * complete the write request. The stream is not buffered and each
+ * write may cause a network request/response so callers should
+ * buffer to smooth out small writes.
+ * @param monitor
+ * (optional) progress monitor to post write completion to during
+ * the stream's close method.
+ * @param monitorTask
+ * (optional) task name to display during the close method.
+ * @throws IOException
+ * writing is not supported, or attempting to write the file
+ * failed, possibly due to permissions or remote disk full, etc.
+ */
+ OutputStream writeFile(final String path, final ProgressMonitor monitor,
+ final String monitorTask) throws IOException {
+ throw new IOException("Writing of '" + path + "' not supported.");
+ }
+
+ /**
+ * Atomically write a remote file.
+ * <p>
+ * This method attempts to perform as atomic of an update as it can,
+ * reducing (or eliminating) the time that clients might be able to see
+ * partial file content. This method is not suitable for very large
+ * transfers as the complete content must be passed as an argument.
+ * <p>
+ * Path may start with <code>../</code> to request writing of a file that
+ * resides in the repository itself.
+ * <p>
+ * The requested path may or may not exist. If the path already exists as a
+ * file the file should be truncated and completely replaced.
+ * <p>
+ * This method creates any missing parent directories, if necessary.
+ *
+ * @param path
+ * name of the file to write, relative to the current object
+ * database.
+ * @param data
+ * complete new content of the file.
+ * @throws IOException
+ * writing is not supported, or attempting to write the file
+ * failed, possibly due to permissions or remote disk full, etc.
+ */
+ void writeFile(final String path, final byte[] data) throws IOException {
+ final OutputStream os = writeFile(path, null, null);
+ try {
+ os.write(data);
+ } finally {
+ os.close();
+ }
+ }
+
+ /**
+ * Delete a loose ref from the remote repository.
+ *
+ * @param name
+ * name of the ref within the ref space, for example
+ * <code>refs/heads/pu</code>.
+ * @throws IOException
+ * deletion is not supported, or deletion failed.
+ */
+ void deleteRef(final String name) throws IOException {
+ deleteFile(ROOT_DIR + name);
+ }
+
+ /**
+ * Delete a reflog from the remote repository.
+ *
+ * @param name
+ * name of the ref within the ref space, for example
+ * <code>refs/heads/pu</code>.
+ * @throws IOException
+ * deletion is not supported, or deletion failed.
+ */
+ void deleteRefLog(final String name) throws IOException {
+ deleteFile(ROOT_DIR + Constants.LOGS + "/" + name);
+ }
+
+ /**
+ * Overwrite (or create) a loose ref in the remote repository.
+ * <p>
+ * This method creates any missing parent directories, if necessary.
+ *
+ * @param name
+ * name of the ref within the ref space, for example
+ * <code>refs/heads/pu</code>.
+ * @param value
+ * new value to store in this ref. Must not be null.
+ * @throws IOException
+ * writing is not supported, or attempting to write the file
+ * failed, possibly due to permissions or remote disk full, etc.
+ */
+ void writeRef(final String name, final ObjectId value) throws IOException {
+ final ByteArrayOutputStream b;
+
+ b = new ByteArrayOutputStream(Constants.OBJECT_ID_LENGTH * 2 + 1);
+ value.copyTo(b);
+ b.write('\n');
+
+ writeFile(ROOT_DIR + name, b.toByteArray());
+ }
+
+ /**
+ * Rebuild the {@link #INFO_PACKS} for dumb transport clients.
+ * <p>
+ * This method rebuilds the contents of the {@link #INFO_PACKS} file to
+ * match the passed list of pack names.
+ *
+ * @param packNames
+ * names of available pack files, in the order they should appear
+ * in the file. Valid pack name strings are of the form
+ * <code>pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>.
+ * @throws IOException
+ * writing is not supported, or attempting to write the file
+ * failed, possibly due to permissions or remote disk full, etc.
+ */
+ void writeInfoPacks(final Collection<String> packNames) throws IOException {
+ final StringBuilder w = new StringBuilder();
+ for (final String n : packNames) {
+ w.append("P ");
+ w.append(n);
+ w.append('\n');
+ }
+ writeFile(INFO_PACKS, Constants.encodeASCII(w.toString()));
+ }
+
+ /**
+ * Open a buffered reader around a file.
+ * <p>
+ * This is shorthand for calling {@link #open(String)} and then wrapping it
+ * in a reader suitable for line oriented files like the alternates list.
+ *
+ * @return a stream to read from the file. Never null.
+ * @param path
+ * location of the file to read, relative to this objects
+ * directory (e.g. <code>info/packs</code>).
+ * @throws FileNotFoundException
+ * the requested file does not exist at the given location.
+ * @throws IOException
+ * The connection is unable to read the remote's file, and the
+ * failure occurred prior to being able to determine if the file
+ * exists, or after it was determined to exist but before the
+ * stream could be created.
+ */
+ BufferedReader openReader(final String path) throws IOException {
+ final InputStream is = open(path).in;
+ return new BufferedReader(new InputStreamReader(is, Constants.CHARSET));
+ }
+
+ /**
+ * Read a standard Git alternates file to discover other object databases.
+ * <p>
+ * This method is suitable for reading the standard formats of the
+ * alternates file, such as found in <code>objects/info/alternates</code>
+ * or <code>objects/info/http-alternates</code> within a Git repository.
+ * <p>
+ * Alternates appear one per line, with paths expressed relative to this
+ * object database.
+ *
+ * @param listPath
+ * location of the alternate file to read, relative to this
+ * object database (e.g. <code>info/alternates</code>).
+ * @return the list of discovered alternates. Empty list if the file exists,
+ * but no entries were discovered.
+ * @throws FileNotFoundException
+ * the requested file does not exist at the given location.
+ * @throws IOException
+ * The connection is unable to read the remote's file, and the
+ * failure occurred prior to being able to determine if the file
+ * exists, or after it was determined to exist but before the
+ * stream could be created.
+ */
+ Collection<WalkRemoteObjectDatabase> readAlternates(final String listPath)
+ throws IOException {
+ final BufferedReader br = openReader(listPath);
+ try {
+ final Collection<WalkRemoteObjectDatabase> alts = new ArrayList<WalkRemoteObjectDatabase>();
+ for (;;) {
+ String line = br.readLine();
+ if (line == null)
+ break;
+ if (!line.endsWith("/"))
+ line += "/";
+ alts.add(openAlternate(line));
+ }
+ return alts;
+ } finally {
+ br.close();
+ }
+ }
+
+ /**
+ * Read a standard Git packed-refs file to discover known references.
+ *
+ * @param avail
+ * return collection of references. Any existing entries will be
+ * replaced if they are found in the packed-refs file.
+ * @throws TransportException
+ * an error occurred reading from the packed refs file.
+ */
+ protected void readPackedRefs(final Map<String, Ref> avail)
+ throws TransportException {
+ try {
+ final BufferedReader br = openReader(ROOT_DIR
+ + Constants.PACKED_REFS);
+ try {
+ readPackedRefsImpl(avail, br);
+ } finally {
+ br.close();
+ }
+ } catch (FileNotFoundException notPacked) {
+ // Perhaps it wasn't worthwhile, or is just an older repository.
+ } catch (IOException e) {
+ throw new TransportException(getURI(), "error in packed-refs", e);
+ }
+ }
+
+ private void readPackedRefsImpl(final Map<String, Ref> avail,
+ final BufferedReader br) throws IOException {
+ Ref last = null;
+ for (;;) {
+ String line = br.readLine();
+ if (line == null)
+ break;
+ if (line.charAt(0) == '#')
+ continue;
+ if (line.charAt(0) == '^') {
+ if (last == null)
+ throw new TransportException("Peeled line before ref.");
+ final ObjectId id = ObjectId.fromString(line.substring(1));
+ last = new Ref(Ref.Storage.PACKED, last.getName(), last
+ .getObjectId(), id, true);
+ avail.put(last.getName(), last);
+ continue;
+ }
+
+ final int sp = line.indexOf(' ');
+ if (sp < 0)
+ throw new TransportException("Unrecognized ref: " + line);
+ final ObjectId id = ObjectId.fromString(line.substring(0, sp));
+ final String name = line.substring(sp + 1);
+ last = new Ref(Ref.Storage.PACKED, name, id);
+ avail.put(last.getName(), last);
+ }
+ }
+
+ static final class FileStream {
+ final InputStream in;
+
+ final long length;
+
+ /**
+ * Create a new stream of unknown length.
+ *
+ * @param i
+ * stream containing the file data. This stream will be
+ * closed by the caller when reading is complete.
+ */
+ FileStream(final InputStream i) {
+ in = i;
+ length = -1;
+ }
+
+ /**
+ * Create a new stream of known length.
+ *
+ * @param i
+ * stream containing the file data. This stream will be
+ * closed by the caller when reading is complete.
+ * @param n
+ * total number of bytes available for reading through
+ * <code>i</code>.
+ */
+ FileStream(final InputStream i, final long n) {
+ in = i;
+ length = n;
+ }
+
+ byte[] toArray() throws IOException {
+ try {
+ if (length >= 0) {
+ final byte[] r = new byte[(int) length];
+ NB.readFully(in, r, 0, r.length);
+ return r;
+ }
+
+ final ByteArrayOutputStream r = new ByteArrayOutputStream();
+ final byte[] buf = new byte[2048];
+ int n;
+ while ((n = in.read(buf)) >= 0)
+ r.write(buf, 0, n);
+ return r.toByteArray();
+ } finally {
+ in.close();
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkTransport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkTransport.java
new file mode 100644
index 0000000000..8b440041a2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkTransport.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * Copyright (C) 2008, Mike Ralphson <mike@abacus.co.uk>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+/**
+ * Marker interface for an object transport walking transport.
+ * <p>
+ * Implementations of WalkTransport transfer individual objects one at a time
+ * from the loose objects directory, or entire packs if the source side does not
+ * have the object as a loose object.
+ * <p>
+ * WalkTransports are not as efficient as {@link PackTransport} instances, but
+ * can be useful in situations where a pack transport is not acceptable.
+ *
+ * @see WalkFetchConnection
+ */
+public interface WalkTransport {
+ // no methods in marker interface
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
new file mode 100644
index 0000000000..102974f449
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+
+/**
+ * Walks a Git tree (directory) in Git sort order.
+ * <p>
+ * A new iterator instance should be positioned on the first entry, or at eof.
+ * Data for the first entry (if not at eof) should be available immediately.
+ * <p>
+ * Implementors must walk a tree in the Git sort order, which has the following
+ * odd sorting:
+ * <ol>
+ * <li>A.c</li>
+ * <li>A/c</li>
+ * <li>A0c</li>
+ * </ol>
+ * <p>
+ * In the second item, <code>A</code> is the name of a subtree and
+ * <code>c</code> is a file within that subtree. The other two items are files
+ * in the root level tree.
+ *
+ * @see CanonicalTreeParser
+ */
+public abstract class AbstractTreeIterator {
+ /** Default size for the {@link #path} buffer. */
+ protected static final int DEFAULT_PATH_SIZE = 128;
+
+ /** A dummy object id buffer that matches the zero ObjectId. */
+ protected static final byte[] zeroid = new byte[Constants.OBJECT_ID_LENGTH];
+
+ /** Iterator for the parent tree; null if we are the root iterator. */
+ final AbstractTreeIterator parent;
+
+ /** The iterator this current entry is path equal to. */
+ AbstractTreeIterator matches;
+
+ /**
+ * Number of entries we moved forward to force a D/F conflict match.
+ *
+ * @see NameConflictTreeWalk
+ */
+ int matchShift;
+
+ /**
+ * Mode bits for the current entry.
+ * <p>
+ * A numerical value from FileMode is usually faster for an iterator to
+ * obtain from its data source so this is the preferred representation.
+ *
+ * @see org.eclipse.jgit.lib.FileMode
+ */
+ protected int mode;
+
+ /**
+ * Path buffer for the current entry.
+ * <p>
+ * This buffer is pre-allocated at the start of walking and is shared from
+ * parent iterators down into their subtree iterators. The sharing allows
+ * the current entry to always be a full path from the root, while each
+ * subtree only needs to populate the part that is under their control.
+ */
+ protected byte[] path;
+
+ /**
+ * Position within {@link #path} this iterator starts writing at.
+ * <p>
+ * This is the first offset in {@link #path} that this iterator must
+ * populate during {@link #next}. At the root level (when {@link #parent}
+ * is null) this is 0. For a subtree iterator the index before this position
+ * should have the value '/'.
+ */
+ protected final int pathOffset;
+
+ /**
+ * Total length of the current entry's complete path from the root.
+ * <p>
+ * This is the number of bytes within {@link #path} that pertain to the
+ * current entry. Values at this index through the end of the array are
+ * garbage and may be randomly populated from prior entries.
+ */
+ protected int pathLen;
+
+ /** Create a new iterator with no parent. */
+ protected AbstractTreeIterator() {
+ parent = null;
+ path = new byte[DEFAULT_PATH_SIZE];
+ pathOffset = 0;
+ }
+
+ /**
+ * Create a new iterator with no parent and a prefix.
+ * <p>
+ * The prefix path supplied is inserted in front of all paths generated by
+ * this iterator. It is intended to be used when an iterator is being
+ * created for a subsection of an overall repository and needs to be
+ * combined with other iterators that are created to run over the entire
+ * repository namespace.
+ *
+ * @param prefix
+ * position of this iterator in the repository tree. The value
+ * may be null or the empty string to indicate the prefix is the
+ * root of the repository. A trailing slash ('/') is
+ * automatically appended if the prefix does not end in '/'.
+ */
+ protected AbstractTreeIterator(final String prefix) {
+ parent = null;
+
+ if (prefix != null && prefix.length() > 0) {
+ final ByteBuffer b;
+
+ b = Constants.CHARSET.encode(CharBuffer.wrap(prefix));
+ pathLen = b.limit();
+ path = new byte[Math.max(DEFAULT_PATH_SIZE, pathLen + 1)];
+ b.get(path, 0, pathLen);
+ if (path[pathLen - 1] != '/')
+ path[pathLen++] = '/';
+ pathOffset = pathLen;
+ } else {
+ path = new byte[DEFAULT_PATH_SIZE];
+ pathOffset = 0;
+ }
+ }
+
+ /**
+ * Create a new iterator with no parent and a prefix.
+ * <p>
+ * The prefix path supplied is inserted in front of all paths generated by
+ * this iterator. It is intended to be used when an iterator is being
+ * created for a subsection of an overall repository and needs to be
+ * combined with other iterators that are created to run over the entire
+ * repository namespace.
+ *
+ * @param prefix
+ * position of this iterator in the repository tree. The value
+ * may be null or the empty array to indicate the prefix is the
+ * root of the repository. A trailing slash ('/') is
+ * automatically appended if the prefix does not end in '/'.
+ */
+ protected AbstractTreeIterator(final byte[] prefix) {
+ parent = null;
+
+ if (prefix != null && prefix.length > 0) {
+ pathLen = prefix.length;
+ path = new byte[Math.max(DEFAULT_PATH_SIZE, pathLen + 1)];
+ System.arraycopy(prefix, 0, path, 0, pathLen);
+ if (path[pathLen - 1] != '/')
+ path[pathLen++] = '/';
+ pathOffset = pathLen;
+ } else {
+ path = new byte[DEFAULT_PATH_SIZE];
+ pathOffset = 0;
+ }
+ }
+
+ /**
+ * Create an iterator for a subtree of an existing iterator.
+ *
+ * @param p
+ * parent tree iterator.
+ */
+ protected AbstractTreeIterator(final AbstractTreeIterator p) {
+ parent = p;
+ path = p.path;
+ pathOffset = p.pathLen + 1;
+ try {
+ path[pathOffset - 1] = '/';
+ } catch (ArrayIndexOutOfBoundsException e) {
+ growPath(p.pathLen);
+ path[pathOffset - 1] = '/';
+ }
+ }
+
+ /**
+ * Create an iterator for a subtree of an existing iterator.
+ * <p>
+ * The caller is responsible for setting up the path of the child iterator.
+ *
+ * @param p
+ * parent tree iterator.
+ * @param childPath
+ * path array to be used by the child iterator. This path must
+ * contain the path from the top of the walk to the first child
+ * and must end with a '/'.
+ * @param childPathOffset
+ * position within <code>childPath</code> where the child can
+ * insert its data. The value at
+ * <code>childPath[childPathOffset-1]</code> must be '/'.
+ */
+ protected AbstractTreeIterator(final AbstractTreeIterator p,
+ final byte[] childPath, final int childPathOffset) {
+ parent = p;
+ path = childPath;
+ pathOffset = childPathOffset;
+ }
+
+ /**
+ * Grow the path buffer larger.
+ *
+ * @param len
+ * number of live bytes in the path buffer. This many bytes will
+ * be moved into the larger buffer.
+ */
+ protected void growPath(final int len) {
+ setPathCapacity(path.length << 1, len);
+ }
+
+ /**
+ * Ensure that path is capable to hold at least {@code capacity} bytes
+ *
+ * @param capacity
+ * the amount of bytes to hold
+ * @param len
+ * the amount of live bytes in path buffer
+ */
+ protected void ensurePathCapacity(final int capacity, final int len) {
+ if (path.length >= capacity)
+ return;
+ final byte[] o = path;
+ int current = o.length;
+ int newCapacity = current;
+ while (newCapacity < capacity && newCapacity > 0)
+ newCapacity <<= 1;
+ setPathCapacity(newCapacity, len);
+ }
+
+ /**
+ * Set path buffer capacity to the specified size
+ *
+ * @param capacity
+ * the new size
+ * @param len
+ * the amount of bytes to copy
+ */
+ private void setPathCapacity(int capacity, int len) {
+ final byte[] o = path;
+ final byte[] n = new byte[capacity];
+ System.arraycopy(o, 0, n, 0, len);
+ for (AbstractTreeIterator p = this; p != null && p.path == o; p = p.parent)
+ p.path = n;
+ }
+
+ /**
+ * Compare the path of this current entry to another iterator's entry.
+ *
+ * @param p
+ * the other iterator to compare the path against.
+ * @return -1 if this entry sorts first; 0 if the entries are equal; 1 if
+ * p's entry sorts first.
+ */
+ public int pathCompare(final AbstractTreeIterator p) {
+ return pathCompare(p, p.mode);
+ }
+
+ int pathCompare(final AbstractTreeIterator p, final int pMode) {
+ final byte[] a = path;
+ final byte[] b = p.path;
+ final int aLen = pathLen;
+ final int bLen = p.pathLen;
+ int cPos;
+
+ // Its common when we are a subtree for both parents to match;
+ // when this happens everything in path[0..cPos] is known to
+ // be equal and does not require evaluation again.
+ //
+ cPos = alreadyMatch(this, p);
+
+ for (; cPos < aLen && cPos < bLen; cPos++) {
+ final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
+ if (cmp != 0)
+ return cmp;
+ }
+
+ if (cPos < aLen)
+ return (a[cPos] & 0xff) - lastPathChar(pMode);
+ if (cPos < bLen)
+ return lastPathChar(mode) - (b[cPos] & 0xff);
+ return lastPathChar(mode) - lastPathChar(pMode);
+ }
+
+ private static int alreadyMatch(AbstractTreeIterator a,
+ AbstractTreeIterator b) {
+ for (;;) {
+ final AbstractTreeIterator ap = a.parent;
+ final AbstractTreeIterator bp = b.parent;
+ if (ap == null || bp == null)
+ return 0;
+ if (ap.matches == bp.matches)
+ return a.pathOffset;
+ a = ap;
+ b = bp;
+ }
+ }
+
+ private static int lastPathChar(final int mode) {
+ return FileMode.TREE.equals(mode) ? '/' : '\0';
+ }
+
+ /**
+ * Check if the current entry of both iterators has the same id.
+ * <p>
+ * This method is faster than {@link #getEntryObjectId()} as it does not
+ * require copying the bytes out of the buffers. A direct {@link #idBuffer}
+ * compare operation is performed.
+ *
+ * @param otherIterator
+ * the other iterator to test against.
+ * @return true if both iterators have the same object id; false otherwise.
+ */
+ public boolean idEqual(final AbstractTreeIterator otherIterator) {
+ return ObjectId.equals(idBuffer(), idOffset(),
+ otherIterator.idBuffer(), otherIterator.idOffset());
+ }
+
+ /**
+ * Get the object id of the current entry.
+ *
+ * @return an object id for the current entry.
+ */
+ public ObjectId getEntryObjectId() {
+ return ObjectId.fromRaw(idBuffer(), idOffset());
+ }
+
+ /**
+ * Obtain the ObjectId for the current entry.
+ *
+ * @param out
+ * buffer to copy the object id into.
+ */
+ public void getEntryObjectId(final MutableObjectId out) {
+ out.fromRaw(idBuffer(), idOffset());
+ }
+
+ /** @return the file mode of the current entry. */
+ public FileMode getEntryFileMode() {
+ return FileMode.fromBits(mode);
+ }
+
+ /** @return the file mode of the current entry as bits */
+ public int getEntryRawMode() {
+ return mode;
+ }
+
+ /** @return path of the current entry, as a string. */
+ public String getEntryPathString() {
+ return TreeWalk.pathOf(this);
+ }
+
+ /**
+ * Get the byte array buffer object IDs must be copied out of.
+ * <p>
+ * The id buffer contains the bytes necessary to construct an ObjectId for
+ * the current entry of this iterator. The buffer can be the same buffer for
+ * all entries, or it can be a unique buffer per-entry. Implementations are
+ * encouraged to expose their private buffer whenever possible to reduce
+ * garbage generation and copying costs.
+ *
+ * @return byte array the implementation stores object IDs within.
+ * @see #getEntryObjectId()
+ */
+ public abstract byte[] idBuffer();
+
+ /**
+ * Get the position within {@link #idBuffer()} of this entry's ObjectId.
+ *
+ * @return offset into the array returned by {@link #idBuffer()} where the
+ * ObjectId must be copied out of.
+ */
+ public abstract int idOffset();
+
+ /**
+ * Create a new iterator for the current entry's subtree.
+ * <p>
+ * The parent reference of the iterator must be <code>this</code>,
+ * otherwise the caller would not be able to exit out of the subtree
+ * iterator correctly and return to continue walking <code>this</code>.
+ *
+ * @param repo
+ * repository to load the tree data from.
+ * @return a new parser that walks over the current subtree.
+ * @throws IncorrectObjectTypeException
+ * the current entry is not actually a tree and cannot be parsed
+ * as though it were a tree.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public abstract AbstractTreeIterator createSubtreeIterator(Repository repo)
+ throws IncorrectObjectTypeException, IOException;
+
+ /**
+ * Create a new iterator as though the current entry were a subtree.
+ *
+ * @return a new empty tree iterator.
+ */
+ public EmptyTreeIterator createEmptyTreeIterator() {
+ return new EmptyTreeIterator(this);
+ }
+
+ /**
+ * Create a new iterator for the current entry's subtree.
+ * <p>
+ * The parent reference of the iterator must be <code>this</code>, otherwise
+ * the caller would not be able to exit out of the subtree iterator
+ * correctly and return to continue walking <code>this</code>.
+ *
+ * @param repo
+ * repository to load the tree data from.
+ * @param idBuffer
+ * temporary ObjectId buffer for use by this method.
+ * @param curs
+ * window cursor to use during repository access.
+ * @return a new parser that walks over the current subtree.
+ * @throws IncorrectObjectTypeException
+ * the current entry is not actually a tree and cannot be parsed
+ * as though it were a tree.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public AbstractTreeIterator createSubtreeIterator(final Repository repo,
+ final MutableObjectId idBuffer, final WindowCursor curs)
+ throws IncorrectObjectTypeException, IOException {
+ return createSubtreeIterator(repo);
+ }
+
+ /**
+ * Is this tree iterator positioned on its first entry?
+ * <p>
+ * An iterator is positioned on the first entry if <code>back(1)</code>
+ * would be an invalid request as there is no entry before the current one.
+ * <p>
+ * An empty iterator (one with no entries) will be
+ * <code>first() &amp;&amp; eof()</code>.
+ *
+ * @return true if the iterator is positioned on the first entry.
+ */
+ public abstract boolean first();
+
+ /**
+ * Is this tree iterator at its EOF point (no more entries)?
+ * <p>
+ * An iterator is at EOF if there is no current entry.
+ *
+ * @return true if we have walked all entries and have none left.
+ */
+ public abstract boolean eof();
+
+ /**
+ * Move to next entry, populating this iterator with the entry data.
+ * <p>
+ * The delta indicates how many moves forward should occur. The most common
+ * delta is 1 to move to the next entry.
+ * <p>
+ * Implementations must populate the following members:
+ * <ul>
+ * <li>{@link #mode}</li>
+ * <li>{@link #path} (from {@link #pathOffset} to {@link #pathLen})</li>
+ * <li>{@link #pathLen}</li>
+ * </ul>
+ * as well as any implementation dependent information necessary to
+ * accurately return data from {@link #idBuffer()} and {@link #idOffset()}
+ * when demanded.
+ *
+ * @param delta
+ * number of entries to move the iterator by. Must be a positive,
+ * non-zero integer.
+ * @throws CorruptObjectException
+ * the tree is invalid.
+ */
+ public abstract void next(int delta) throws CorruptObjectException;
+
+ /**
+ * Move to prior entry, populating this iterator with the entry data.
+ * <p>
+ * The delta indicates how many moves backward should occur.The most common
+ * delta is 1 to move to the prior entry.
+ * <p>
+ * Implementations must populate the following members:
+ * <ul>
+ * <li>{@link #mode}</li>
+ * <li>{@link #path} (from {@link #pathOffset} to {@link #pathLen})</li>
+ * <li>{@link #pathLen}</li>
+ * </ul>
+ * as well as any implementation dependent information necessary to
+ * accurately return data from {@link #idBuffer()} and {@link #idOffset()}
+ * when demanded.
+ *
+ * @param delta
+ * number of entries to move the iterator by. Must be a positive,
+ * non-zero integer.
+ * @throws CorruptObjectException
+ * the tree is invalid.
+ */
+ public abstract void back(int delta) throws CorruptObjectException;
+
+ /**
+ * Advance to the next tree entry, populating this iterator with its data.
+ * <p>
+ * This method behaves like <code>seek(1)</code> but is called by
+ * {@link TreeWalk} only if a {@link TreeFilter} was used and ruled out the
+ * current entry from the results. In such cases this tree iterator may
+ * perform special behavior.
+ *
+ * @throws CorruptObjectException
+ * the tree is invalid.
+ */
+ public void skip() throws CorruptObjectException {
+ next(1);
+ }
+
+ /**
+ * Indicates to the iterator that no more entries will be read.
+ * <p>
+ * This is only invoked by TreeWalk when the iteration is aborted early due
+ * to a {@link org.eclipse.jgit.errors.StopWalkException} being thrown from
+ * within a TreeFilter.
+ */
+ public void stopWalk() {
+ // Do nothing by default. Most iterators do not care.
+ }
+
+ /**
+ * @return the length of the name component of the path for the current entry
+ */
+ public int getNameLength() {
+ return pathLen - pathOffset;
+ }
+
+ /**
+ * Get the name component of the current entry path into the provided buffer.
+ *
+ * @param buffer the buffer to get the name into, it is assumed that buffer can hold the name
+ * @param offset the offset of the name in the buffer
+ * @see #getNameLength()
+ */
+ public void getName(byte[] buffer, int offset) {
+ System.arraycopy(path, pathOffset, buffer, offset, pathLen - pathOffset);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
new file mode 100644
index 0000000000..6705ad992b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.WindowCursor;
+
+/** Parses raw Git trees from the canonical semi-text/semi-binary format. */
+public class CanonicalTreeParser extends AbstractTreeIterator {
+ private static final byte[] EMPTY = {};
+
+ private byte[] raw;
+
+ /** First offset within {@link #raw} of the prior entry. */
+ private int prevPtr;
+
+ /** First offset within {@link #raw} of the current entry's data. */
+ private int currPtr;
+
+ /** Offset one past the current entry (first byte of next entry). */
+ private int nextPtr;
+
+ /** Create a new parser. */
+ public CanonicalTreeParser() {
+ reset(EMPTY);
+ }
+
+ /**
+ * Create a new parser for a tree appearing in a subset of a repository.
+ *
+ * @param prefix
+ * position of this iterator in the repository tree. The value
+ * may be null or the empty array to indicate the prefix is the
+ * root of the repository. A trailing slash ('/') is
+ * automatically appended if the prefix does not end in '/'.
+ * @param repo
+ * repository to load the tree data from.
+ * @param treeId
+ * identity of the tree being parsed; used only in exception
+ * messages if data corruption is found.
+ * @param curs
+ * a window cursor to use during data access from the repository.
+ * @throws MissingObjectException
+ * the object supplied is not available from the repository.
+ * @throws IncorrectObjectTypeException
+ * the object supplied as an argument is not actually a tree and
+ * cannot be parsed as though it were a tree.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public CanonicalTreeParser(final byte[] prefix, final Repository repo,
+ final AnyObjectId treeId, final WindowCursor curs)
+ throws IncorrectObjectTypeException, IOException {
+ super(prefix);
+ reset(repo, treeId, curs);
+ }
+
+ private CanonicalTreeParser(final CanonicalTreeParser p) {
+ super(p);
+ }
+
+ /**
+ * Reset this parser to walk through the given tree data.
+ *
+ * @param treeData
+ * the raw tree content.
+ */
+ public void reset(final byte[] treeData) {
+ raw = treeData;
+ prevPtr = -1;
+ currPtr = 0;
+ if (!eof())
+ parseEntry();
+ }
+
+ /**
+ * Reset this parser to walk through the given tree.
+ *
+ * @param repo
+ * repository to load the tree data from.
+ * @param id
+ * identity of the tree being parsed; used only in exception
+ * messages if data corruption is found.
+ * @param curs
+ * window cursor to use during repository access.
+ * @return the root level parser.
+ * @throws MissingObjectException
+ * the object supplied is not available from the repository.
+ * @throws IncorrectObjectTypeException
+ * the object supplied as an argument is not actually a tree and
+ * cannot be parsed as though it were a tree.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public CanonicalTreeParser resetRoot(final Repository repo,
+ final AnyObjectId id, final WindowCursor curs)
+ throws IncorrectObjectTypeException, IOException {
+ CanonicalTreeParser p = this;
+ while (p.parent != null)
+ p = (CanonicalTreeParser) p.parent;
+ p.reset(repo, id, curs);
+ return p;
+ }
+
+ /** @return this iterator, or its parent, if the tree is at eof. */
+ public CanonicalTreeParser next() {
+ CanonicalTreeParser p = this;
+ for (;;) {
+ p.next(1);
+ if (p.eof() && p.parent != null) {
+ // Parent was left pointing at the entry for us; advance
+ // the parent to the next entry, possibly unwinding many
+ // levels up the tree.
+ //
+ p = (CanonicalTreeParser) p.parent;
+ continue;
+ }
+ return p;
+ }
+ }
+
+ /**
+ * Reset this parser to walk through the given tree.
+ *
+ * @param repo
+ * repository to load the tree data from.
+ * @param id
+ * identity of the tree being parsed; used only in exception
+ * messages if data corruption is found.
+ * @param curs
+ * window cursor to use during repository access.
+ * @throws MissingObjectException
+ * the object supplied is not available from the repository.
+ * @throws IncorrectObjectTypeException
+ * the object supplied as an argument is not actually a tree and
+ * cannot be parsed as though it were a tree.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public void reset(final Repository repo, final AnyObjectId id,
+ final WindowCursor curs)
+ throws IncorrectObjectTypeException, IOException {
+ final ObjectLoader ldr = repo.openObject(curs, id);
+ if (ldr == null) {
+ final ObjectId me = id.toObjectId();
+ throw new MissingObjectException(me, Constants.TYPE_TREE);
+ }
+ final byte[] subtreeData = ldr.getCachedBytes();
+ if (ldr.getType() != Constants.OBJ_TREE) {
+ final ObjectId me = id.toObjectId();
+ throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE);
+ }
+ reset(subtreeData);
+ }
+
+ @Override
+ public CanonicalTreeParser createSubtreeIterator(final Repository repo,
+ final MutableObjectId idBuffer, final WindowCursor curs)
+ throws IncorrectObjectTypeException, IOException {
+ idBuffer.fromRaw(idBuffer(), idOffset());
+ if (!FileMode.TREE.equals(mode)) {
+ final ObjectId me = idBuffer.toObjectId();
+ throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE);
+ }
+ return createSubtreeIterator0(repo, idBuffer, curs);
+ }
+
+ /**
+ * Back door to quickly create a subtree iterator for any subtree.
+ * <p>
+ * Don't use this unless you are ObjectWalk. The method is meant to be
+ * called only once the current entry has been identified as a tree and its
+ * identity has been converted into an ObjectId.
+ *
+ * @param repo
+ * repository to load the tree data from.
+ * @param id
+ * ObjectId of the tree to open.
+ * @param curs
+ * window cursor to use during repository access.
+ * @return a new parser that walks over the current subtree.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public final CanonicalTreeParser createSubtreeIterator0(
+ final Repository repo, final AnyObjectId id, final WindowCursor curs)
+ throws IOException {
+ final CanonicalTreeParser p = new CanonicalTreeParser(this);
+ p.reset(repo, id, curs);
+ return p;
+ }
+
+ public CanonicalTreeParser createSubtreeIterator(final Repository repo)
+ throws IncorrectObjectTypeException, IOException {
+ final WindowCursor curs = new WindowCursor();
+ try {
+ return createSubtreeIterator(repo, new MutableObjectId(), curs);
+ } finally {
+ curs.release();
+ }
+ }
+
+ @Override
+ public byte[] idBuffer() {
+ return raw;
+ }
+
+ @Override
+ public int idOffset() {
+ return nextPtr - Constants.OBJECT_ID_LENGTH;
+ }
+
+ @Override
+ public boolean first() {
+ return currPtr == 0;
+ }
+
+ public boolean eof() {
+ return currPtr == raw.length;
+ }
+
+ @Override
+ public void next(int delta) {
+ if (delta == 1) {
+ // Moving forward one is the most common case.
+ //
+ prevPtr = currPtr;
+ currPtr = nextPtr;
+ if (!eof())
+ parseEntry();
+ return;
+ }
+
+ // Fast skip over records, then parse the last one.
+ //
+ final int end = raw.length;
+ int ptr = nextPtr;
+ while (--delta > 0 && ptr != end) {
+ prevPtr = ptr;
+ while (raw[ptr] != 0)
+ ptr++;
+ ptr += Constants.OBJECT_ID_LENGTH + 1;
+ }
+ if (delta != 0)
+ throw new ArrayIndexOutOfBoundsException(delta);
+ currPtr = ptr;
+ if (!eof())
+ parseEntry();
+ }
+
+ @Override
+ public void back(int delta) {
+ if (delta == 1 && 0 <= prevPtr) {
+ // Moving back one is common in NameTreeWalk, as the average tree
+ // won't have D/F type conflicts to study.
+ //
+ currPtr = prevPtr;
+ prevPtr = -1;
+ if (!eof())
+ parseEntry();
+ return;
+ } else if (delta <= 0)
+ throw new ArrayIndexOutOfBoundsException(delta);
+
+ // Fast skip through the records, from the beginning of the tree.
+ // There is no reliable way to read the tree backwards, so we must
+ // parse all over again from the beginning. We hold the last "delta"
+ // positions in a buffer, so we can find the correct position later.
+ //
+ final int[] trace = new int[delta + 1];
+ Arrays.fill(trace, -1);
+ int ptr = 0;
+ while (ptr != currPtr) {
+ System.arraycopy(trace, 1, trace, 0, delta);
+ trace[delta] = ptr;
+ while (raw[ptr] != 0)
+ ptr++;
+ ptr += Constants.OBJECT_ID_LENGTH + 1;
+ }
+ if (trace[1] == -1)
+ throw new ArrayIndexOutOfBoundsException(delta);
+ prevPtr = trace[0];
+ currPtr = trace[1];
+ parseEntry();
+ }
+
+ private void parseEntry() {
+ int ptr = currPtr;
+ byte c = raw[ptr++];
+ int tmp = c - '0';
+ for (;;) {
+ c = raw[ptr++];
+ if (' ' == c)
+ break;
+ tmp <<= 3;
+ tmp += c - '0';
+ }
+ mode = tmp;
+
+ tmp = pathOffset;
+ for (;; tmp++) {
+ c = raw[ptr++];
+ if (c == 0)
+ break;
+ try {
+ path[tmp] = c;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ growPath(tmp);
+ path[tmp] = c;
+ }
+ }
+ pathLen = tmp;
+ nextPtr = ptr + Constants.OBJECT_ID_LENGTH;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
new file mode 100644
index 0000000000..1776b50887
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+/** Iterator over an empty tree (a directory with no files). */
+public class EmptyTreeIterator extends AbstractTreeIterator {
+ /** Create a new iterator with no parent. */
+ public EmptyTreeIterator() {
+ // Create a root empty tree.
+ }
+
+ EmptyTreeIterator(final AbstractTreeIterator p) {
+ super(p);
+ pathLen = pathOffset;
+ }
+
+ /**
+ * Create an iterator for a subtree of an existing iterator.
+ * <p>
+ * The caller is responsible for setting up the path of the child iterator.
+ *
+ * @param p
+ * parent tree iterator.
+ * @param childPath
+ * path array to be used by the child iterator. This path must
+ * contain the path from the top of the walk to the first child
+ * and must end with a '/'.
+ * @param childPathOffset
+ * position within <code>childPath</code> where the child can
+ * insert its data. The value at
+ * <code>childPath[childPathOffset-1]</code> must be '/'.
+ */
+ public EmptyTreeIterator(final AbstractTreeIterator p,
+ final byte[] childPath, final int childPathOffset) {
+ super(p, childPath, childPathOffset);
+ pathLen = childPathOffset - 1;
+ }
+
+ @Override
+ public AbstractTreeIterator createSubtreeIterator(final Repository repo)
+ throws IncorrectObjectTypeException, IOException {
+ return new EmptyTreeIterator(this);
+ }
+
+ @Override
+ public ObjectId getEntryObjectId() {
+ return ObjectId.zeroId();
+ }
+
+ @Override
+ public byte[] idBuffer() {
+ return zeroid;
+ }
+
+ @Override
+ public int idOffset() {
+ return 0;
+ }
+
+ @Override
+ public boolean first() {
+ return true;
+ }
+
+ @Override
+ public boolean eof() {
+ return true;
+ }
+
+ @Override
+ public void next(final int delta) throws CorruptObjectException {
+ // Do nothing.
+ }
+
+ @Override
+ public void back(final int delta) throws CorruptObjectException {
+ // Do nothing.
+ }
+
+ @Override
+ public void stopWalk() {
+ if (parent != null)
+ parent.stopWalk();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
new file mode 100644
index 0000000000..3ef050e7c3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2009, Tor Arne Vestbø <torarnv@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+
+/**
+ * Working directory iterator for standard Java IO.
+ * <p>
+ * This iterator uses the standard <code>java.io</code> package to read the
+ * specified working directory as part of a {@link TreeWalk}.
+ */
+public class FileTreeIterator extends WorkingTreeIterator {
+ private final File directory;
+
+ /**
+ * Create a new iterator to traverse the given directory and its children.
+ *
+ * @param root
+ * the starting directory. This directory should correspond to
+ * the root of the repository.
+ */
+ public FileTreeIterator(final File root) {
+ directory = root;
+ init(entries());
+ }
+
+ /**
+ * Create a new iterator to traverse a subdirectory.
+ *
+ * @param p
+ * the parent iterator we were created from.
+ * @param root
+ * the subdirectory. This should be a directory contained within
+ * the parent directory.
+ */
+ protected FileTreeIterator(final FileTreeIterator p, final File root) {
+ super(p);
+ directory = root;
+ init(entries());
+ }
+
+ @Override
+ public AbstractTreeIterator createSubtreeIterator(final Repository repo)
+ throws IncorrectObjectTypeException, IOException {
+ return new FileTreeIterator(this, ((FileEntry) current()).file);
+ }
+
+ private Entry[] entries() {
+ final File[] all = directory.listFiles();
+ if (all == null)
+ return EOF;
+ final Entry[] r = new Entry[all.length];
+ for (int i = 0; i < r.length; i++)
+ r[i] = new FileEntry(all[i]);
+ return r;
+ }
+
+ /**
+ * Wrapper for a standard Java IO file
+ */
+ static public class FileEntry extends Entry {
+ final File file;
+
+ private final FileMode mode;
+
+ private long length = -1;
+
+ private long lastModified;
+
+ FileEntry(final File f) {
+ file = f;
+
+ if (f.isDirectory()) {
+ if (new File(f, ".git").isDirectory())
+ mode = FileMode.GITLINK;
+ else
+ mode = FileMode.TREE;
+ } else if (FS.INSTANCE.canExecute(file))
+ mode = FileMode.EXECUTABLE_FILE;
+ else
+ mode = FileMode.REGULAR_FILE;
+ }
+
+ @Override
+ public FileMode getMode() {
+ return mode;
+ }
+
+ @Override
+ public String getName() {
+ return file.getName();
+ }
+
+ @Override
+ public long getLength() {
+ if (length < 0)
+ length = file.length();
+ return length;
+ }
+
+ @Override
+ public long getLastModified() {
+ if (lastModified == 0)
+ lastModified = file.lastModified();
+ return lastModified;
+ }
+
+ @Override
+ public InputStream openInputStream() throws IOException {
+ return new FileInputStream(file);
+ }
+
+ /**
+ * Get the underlying file of this entry.
+ *
+ * @return the underlying file of this entry
+ */
+ public File getFile() {
+ return file;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
new file mode 100644
index 0000000000..b569174bdb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk;
+
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Specialized TreeWalk to detect directory-file (D/F) name conflicts.
+ * <p>
+ * Due to the way a Git tree is organized the standard {@link TreeWalk} won't
+ * easily find a D/F conflict when merging two or more trees together. In the
+ * standard TreeWalk the file will be returned first, and then much later the
+ * directory will be returned. This makes it impossible for the application to
+ * efficiently detect and handle the conflict.
+ * <p>
+ * Using this walk implementation causes the directory to report earlier than
+ * usual, at the same time as the non-directory entry. This permits the
+ * application to handle the D/F conflict in a single step. The directory is
+ * returned only once, so it does not get returned later in the iteration.
+ * <p>
+ * When a D/F conflict is detected {@link TreeWalk#isSubtree()} will return true
+ * and {@link TreeWalk#enterSubtree()} will recurse into the subtree, no matter
+ * which iterator originally supplied the subtree.
+ * <p>
+ * Because conflicted directories report early, using this walk implementation
+ * to populate a {@link DirCacheBuilder} may cause the automatic resorting to
+ * run and fix the entry ordering.
+ * <p>
+ * This walk implementation requires more CPU to implement a look-ahead and a
+ * look-behind to merge a D/F pair together, or to skip a previously reported
+ * directory. In typical Git repositories the look-ahead cost is 0 and the
+ * look-behind doesn't trigger, as users tend not to create trees which contain
+ * both "foo" as a directory and "foo.c" as a file.
+ * <p>
+ * In the worst-case however several thousand look-ahead steps per walk step may
+ * be necessary, making the overhead quite significant. Since this worst-case
+ * should never happen this walk implementation has made the time/space tradeoff
+ * in favor of more-time/less-space, as that better suits the typical case.
+ */
+public class NameConflictTreeWalk extends TreeWalk {
+ private static final int TREE_MODE = FileMode.TREE.getBits();
+
+ private boolean fastMinHasMatch;
+
+ /**
+ * Create a new tree walker for a given repository.
+ *
+ * @param repo
+ * the repository the walker will obtain data from.
+ */
+ public NameConflictTreeWalk(final Repository repo) {
+ super(repo);
+ }
+
+ @Override
+ AbstractTreeIterator min() throws CorruptObjectException {
+ for (;;) {
+ final AbstractTreeIterator minRef = fastMin();
+ if (fastMinHasMatch)
+ return minRef;
+
+ if (isTree(minRef)) {
+ if (skipEntry(minRef)) {
+ for (final AbstractTreeIterator t : trees) {
+ if (t.matches == minRef) {
+ t.next(1);
+ t.matches = null;
+ }
+ }
+ continue;
+ }
+ return minRef;
+ }
+
+ return combineDF(minRef);
+ }
+ }
+
+ private AbstractTreeIterator fastMin() {
+ fastMinHasMatch = true;
+
+ int i = 0;
+ AbstractTreeIterator minRef = trees[i];
+ while (minRef.eof() && ++i < trees.length)
+ minRef = trees[i];
+ if (minRef.eof())
+ return minRef;
+
+ minRef.matches = minRef;
+ while (++i < trees.length) {
+ final AbstractTreeIterator t = trees[i];
+ if (t.eof())
+ continue;
+
+ final int cmp = t.pathCompare(minRef);
+ if (cmp < 0) {
+ if (fastMinHasMatch && isTree(minRef) && !isTree(t)
+ && nameEqual(minRef, t)) {
+ // We used to be at a tree, but now we are at a file
+ // with the same name. Allow the file to match the
+ // tree anyway.
+ //
+ t.matches = minRef;
+ } else {
+ fastMinHasMatch = false;
+ t.matches = t;
+ minRef = t;
+ }
+ } else if (cmp == 0) {
+ // Exact name/mode match is best.
+ //
+ t.matches = minRef;
+ } else if (fastMinHasMatch && isTree(t) && !isTree(minRef)
+ && nameEqual(t, minRef)) {
+ // The minimum is a file (non-tree) but the next entry
+ // of this iterator is a tree whose name matches our file.
+ // This is a classic D/F conflict and commonly occurs like
+ // this, with no gaps in between the file and directory.
+ //
+ // Use the tree as the minimum instead (see combineDF).
+ //
+
+ for (int k = 0; k < i; k++) {
+ final AbstractTreeIterator p = trees[k];
+ if (p.matches == minRef)
+ p.matches = t;
+ }
+ t.matches = t;
+ minRef = t;
+ } else
+ fastMinHasMatch = false;
+ }
+
+ return minRef;
+ }
+
+ private static boolean nameEqual(final AbstractTreeIterator a,
+ final AbstractTreeIterator b) {
+ return a.pathCompare(b, TREE_MODE) == 0;
+ }
+
+ private static boolean isTree(final AbstractTreeIterator p) {
+ return FileMode.TREE.equals(p.mode);
+ }
+
+ private boolean skipEntry(final AbstractTreeIterator minRef)
+ throws CorruptObjectException {
+ // A tree D/F may have been handled earlier. We need to
+ // not report this path if it has already been reported.
+ //
+ for (final AbstractTreeIterator t : trees) {
+ if (t.matches == minRef || t.first())
+ continue;
+
+ int stepsBack = 0;
+ for (;;) {
+ stepsBack++;
+ t.back(1);
+
+ final int cmp = t.pathCompare(minRef, 0);
+ if (cmp == 0) {
+ // We have already seen this "$path" before. Skip it.
+ //
+ t.next(stepsBack);
+ return true;
+ } else if (cmp < 0 || t.first()) {
+ // We cannot find "$path" in t; it will never appear.
+ //
+ t.next(stepsBack);
+ break;
+ }
+ }
+ }
+
+ // We have never seen the current path before.
+ //
+ return false;
+ }
+
+ private AbstractTreeIterator combineDF(final AbstractTreeIterator minRef)
+ throws CorruptObjectException {
+ // Look for a possible D/F conflict forward in the tree(s)
+ // as there may be a "$path/" which matches "$path". Make
+ // such entries match this entry.
+ //
+ AbstractTreeIterator treeMatch = null;
+ for (final AbstractTreeIterator t : trees) {
+ if (t.matches == minRef || t.eof())
+ continue;
+
+ for (;;) {
+ final int cmp = t.pathCompare(minRef, TREE_MODE);
+ if (cmp < 0) {
+ // The "$path/" may still appear later.
+ //
+ t.matchShift++;
+ t.next(1);
+ if (t.eof()) {
+ t.back(t.matchShift);
+ t.matchShift = 0;
+ break;
+ }
+ } else if (cmp == 0) {
+ // We have a conflict match here.
+ //
+ t.matches = minRef;
+ treeMatch = t;
+ break;
+ } else {
+ // A conflict match is not possible.
+ //
+ if (t.matchShift != 0) {
+ t.back(t.matchShift);
+ t.matchShift = 0;
+ }
+ break;
+ }
+ }
+ }
+
+ if (treeMatch != null) {
+ // If we do have a conflict use one of the directory
+ // matching iterators instead of the file iterator.
+ // This way isSubtree is true and isRecursive works.
+ //
+ for (final AbstractTreeIterator t : trees)
+ if (t.matches == minRef)
+ t.matches = treeMatch;
+ return treeMatch;
+ }
+
+ return minRef;
+ }
+
+ @Override
+ void popEntriesEqual() throws CorruptObjectException {
+ final AbstractTreeIterator ch = currentHead;
+ for (int i = 0; i < trees.length; i++) {
+ final AbstractTreeIterator t = trees[i];
+ if (t.matches == ch) {
+ if (t.matchShift == 0)
+ t.next(1);
+ else {
+ t.back(t.matchShift);
+ t.matchShift = 0;
+ }
+ t.matches = null;
+ }
+ }
+ }
+
+ @Override
+ void skipEntriesEqual() throws CorruptObjectException {
+ final AbstractTreeIterator ch = currentHead;
+ for (int i = 0; i < trees.length; i++) {
+ final AbstractTreeIterator t = trees[i];
+ if (t.matches == ch) {
+ if (t.matchShift == 0)
+ t.skip();
+ else {
+ t.back(t.matchShift);
+ t.matchShift = 0;
+ }
+ t.matches = null;
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
new file mode 100644
index 0000000000..245bea8dcf
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -0,0 +1,921 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.StopWalkException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.WindowCursor;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Walks one or more {@link AbstractTreeIterator}s in parallel.
+ * <p>
+ * This class can perform n-way differences across as many trees as necessary.
+ * <p>
+ * Each tree added must have the same root as existing trees in the walk.
+ * <p>
+ * A TreeWalk instance can only be used once to generate results. Running a
+ * second time requires creating a new TreeWalk instance, or invoking
+ * {@link #reset()} and adding new trees before starting again. Resetting an
+ * existing instance may be faster for some applications as some internal
+ * buffers may be recycled.
+ * <p>
+ * TreeWalk instances are not thread-safe. Applications must either restrict
+ * usage of a TreeWalk instance to a single thread, or implement their own
+ * synchronization at a higher level.
+ * <p>
+ * Multiple simultaneous TreeWalk instances per {@link Repository} are
+ * permitted, even from concurrent threads.
+ */
+public class TreeWalk {
+ /**
+ * Open a tree walk and filter to exactly one path.
+ * <p>
+ * The returned tree walk is already positioned on the requested path, so
+ * the caller should not need to invoke {@link #next()} unless they are
+ * looking for a possible directory/file name conflict.
+ *
+ * @param db
+ * repository to read tree object data from.
+ * @param path
+ * single path to advance the tree walk instance into.
+ * @param trees
+ * one or more trees to walk through, all with the same root.
+ * @return a new tree walk configured for exactly this one path; null if no
+ * path was found in any of the trees.
+ * @throws IOException
+ * reading a pack file or loose object failed.
+ * @throws CorruptObjectException
+ * an tree object could not be read as its data stream did not
+ * appear to be a tree, or could not be inflated.
+ * @throws IncorrectObjectTypeException
+ * an object we expected to be a tree was not a tree.
+ * @throws MissingObjectException
+ * a tree object was not found.
+ */
+ public static TreeWalk forPath(final Repository db, final String path,
+ final AnyObjectId... trees) throws MissingObjectException,
+ IncorrectObjectTypeException, CorruptObjectException, IOException {
+ final TreeWalk r = new TreeWalk(db);
+ r.setFilter(PathFilterGroup.createFromStrings(Collections
+ .singleton(path)));
+ r.setRecursive(r.getFilter().shouldBeRecursive());
+ r.reset(trees);
+ return r.next() ? r : null;
+ }
+
+ /**
+ * Open a tree walk and filter to exactly one path.
+ * <p>
+ * The returned tree walk is already positioned on the requested path, so
+ * the caller should not need to invoke {@link #next()} unless they are
+ * looking for a possible directory/file name conflict.
+ *
+ * @param db
+ * repository to read tree object data from.
+ * @param path
+ * single path to advance the tree walk instance into.
+ * @param tree
+ * the single tree to walk through.
+ * @return a new tree walk configured for exactly this one path; null if no
+ * path was found in any of the trees.
+ * @throws IOException
+ * reading a pack file or loose object failed.
+ * @throws CorruptObjectException
+ * an tree object could not be read as its data stream did not
+ * appear to be a tree, or could not be inflated.
+ * @throws IncorrectObjectTypeException
+ * an object we expected to be a tree was not a tree.
+ * @throws MissingObjectException
+ * a tree object was not found.
+ */
+ public static TreeWalk forPath(final Repository db, final String path,
+ final RevTree tree) throws MissingObjectException,
+ IncorrectObjectTypeException, CorruptObjectException, IOException {
+ return forPath(db, path, new ObjectId[] { tree });
+ }
+
+ private final Repository db;
+
+ private final MutableObjectId idBuffer = new MutableObjectId();
+
+ private final WindowCursor curs = new WindowCursor();
+
+ private TreeFilter filter;
+
+ AbstractTreeIterator[] trees;
+
+ private boolean recursive;
+
+ private boolean postOrderTraversal;
+
+ private int depth;
+
+ private boolean advance;
+
+ private boolean postChildren;
+
+ AbstractTreeIterator currentHead;
+
+ /**
+ * Create a new tree walker for a given repository.
+ *
+ * @param repo
+ * the repository the walker will obtain data from.
+ */
+ public TreeWalk(final Repository repo) {
+ db = repo;
+ filter = TreeFilter.ALL;
+ trees = new AbstractTreeIterator[] { new EmptyTreeIterator() };
+ }
+
+ /**
+ * Get the repository this tree walker is reading from.
+ *
+ * @return the repository configured when the walker was created.
+ */
+ public Repository getRepository() {
+ return db;
+ }
+
+ /**
+ * Get the currently configured filter.
+ *
+ * @return the current filter. Never null as a filter is always needed.
+ */
+ public TreeFilter getFilter() {
+ return filter;
+ }
+
+ /**
+ * Set the tree entry filter for this walker.
+ * <p>
+ * Multiple filters may be combined by constructing an arbitrary tree of
+ * <code>AndTreeFilter</code> or <code>OrTreeFilter</code> instances to
+ * describe the boolean expression required by the application. Custom
+ * filter implementations may also be constructed by applications.
+ * <p>
+ * Note that filters are not thread-safe and may not be shared by concurrent
+ * TreeWalk instances. Every TreeWalk must be supplied its own unique
+ * filter, unless the filter implementation specifically states it is (and
+ * always will be) thread-safe. Callers may use {@link TreeFilter#clone()}
+ * to create a unique filter tree for this TreeWalk instance.
+ *
+ * @param newFilter
+ * the new filter. If null the special {@link TreeFilter#ALL}
+ * filter will be used instead, as it matches every entry.
+ * @see org.eclipse.jgit.treewalk.filter.AndTreeFilter
+ * @see org.eclipse.jgit.treewalk.filter.OrTreeFilter
+ */
+ public void setFilter(final TreeFilter newFilter) {
+ filter = newFilter != null ? newFilter : TreeFilter.ALL;
+ }
+
+ /**
+ * Is this walker automatically entering into subtrees?
+ * <p>
+ * If the walker is recursive then the caller will not see a subtree node
+ * and instead will only receive file nodes in all relevant subtrees.
+ *
+ * @return true if automatically entering subtrees is enabled.
+ */
+ public boolean isRecursive() {
+ return recursive;
+ }
+
+ /**
+ * Set the walker to enter (or not enter) subtrees automatically.
+ * <p>
+ * If recursive mode is enabled the walker will hide subtree nodes from the
+ * calling application and will produce only file level nodes. If a tree
+ * (directory) is deleted then all of the file level nodes will appear to be
+ * deleted, recursively, through as many levels as necessary to account for
+ * all entries.
+ *
+ * @param b
+ * true to skip subtree nodes and only obtain files nodes.
+ */
+ public void setRecursive(final boolean b) {
+ recursive = b;
+ }
+
+ /**
+ * Does this walker return a tree entry after it exits the subtree?
+ * <p>
+ * If post order traversal is enabled then the walker will return a subtree
+ * after it has returned the last entry within that subtree. This may cause
+ * a subtree to be seen by the application twice if {@link #isRecursive()}
+ * is false, as the application will see it once, call
+ * {@link #enterSubtree()}, and then see it again as it leaves the subtree.
+ * <p>
+ * If an application does not enable {@link #isRecursive()} and it does not
+ * call {@link #enterSubtree()} then the tree is returned only once as none
+ * of the children were processed.
+ *
+ * @return true if subtrees are returned after entries within the subtree.
+ */
+ public boolean isPostOrderTraversal() {
+ return postOrderTraversal;
+ }
+
+ /**
+ * Set the walker to return trees after their children.
+ *
+ * @param b
+ * true to get trees after their children.
+ * @see #isPostOrderTraversal()
+ */
+ public void setPostOrderTraversal(final boolean b) {
+ postOrderTraversal = b;
+ }
+
+ /** Reset this walker so new tree iterators can be added to it. */
+ public void reset() {
+ trees = new AbstractTreeIterator[0];
+ advance = false;
+ depth = 0;
+ }
+
+ /**
+ * Reset this walker to run over a single existing tree.
+ *
+ * @param id
+ * the tree we need to parse. The walker will execute over this
+ * single tree if the reset is successful.
+ * @throws MissingObjectException
+ * the given tree object does not exist in this repository.
+ * @throws IncorrectObjectTypeException
+ * the given object id does not denote a tree, but instead names
+ * some other non-tree type of object. Note that commits are not
+ * trees, even if they are sometimes called a "tree-ish".
+ * @throws CorruptObjectException
+ * the object claimed to be a tree, but its contents did not
+ * appear to be a tree. The repository may have data corruption.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public void reset(final AnyObjectId id) throws MissingObjectException,
+ IncorrectObjectTypeException, CorruptObjectException, IOException {
+ if (trees.length == 1) {
+ AbstractTreeIterator o = trees[0];
+ while (o.parent != null)
+ o = o.parent;
+ if (o instanceof CanonicalTreeParser) {
+ o.matches = null;
+ o.matchShift = 0;
+ ((CanonicalTreeParser) o).reset(db, id, curs);
+ trees[0] = o;
+ } else {
+ trees[0] = parserFor(id);
+ }
+ } else {
+ trees = new AbstractTreeIterator[] { parserFor(id) };
+ }
+
+ advance = false;
+ depth = 0;
+ }
+
+ /**
+ * Reset this walker to run over a set of existing trees.
+ *
+ * @param ids
+ * the trees we need to parse. The walker will execute over this
+ * many parallel trees if the reset is successful.
+ * @throws MissingObjectException
+ * the given tree object does not exist in this repository.
+ * @throws IncorrectObjectTypeException
+ * the given object id does not denote a tree, but instead names
+ * some other non-tree type of object. Note that commits are not
+ * trees, even if they are sometimes called a "tree-ish".
+ * @throws CorruptObjectException
+ * the object claimed to be a tree, but its contents did not
+ * appear to be a tree. The repository may have data corruption.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public void reset(final AnyObjectId[] ids) throws MissingObjectException,
+ IncorrectObjectTypeException, CorruptObjectException, IOException {
+ final int oldLen = trees.length;
+ final int newLen = ids.length;
+ final AbstractTreeIterator[] r = newLen == oldLen ? trees
+ : new AbstractTreeIterator[newLen];
+ for (int i = 0; i < newLen; i++) {
+ AbstractTreeIterator o;
+
+ if (i < oldLen) {
+ o = trees[i];
+ while (o.parent != null)
+ o = o.parent;
+ if (o instanceof CanonicalTreeParser && o.pathOffset == 0) {
+ o.matches = null;
+ o.matchShift = 0;
+ ((CanonicalTreeParser) o).reset(db, ids[i], curs);
+ r[i] = o;
+ continue;
+ }
+ }
+
+ o = parserFor(ids[i]);
+ r[i] = o;
+ }
+
+ trees = r;
+ advance = false;
+ depth = 0;
+ }
+
+ /**
+ * Add an already existing tree object for walking.
+ * <p>
+ * The position of this tree is returned to the caller, in case the caller
+ * has lost track of the order they added the trees into the walker.
+ * <p>
+ * The tree must have the same root as existing trees in the walk.
+ *
+ * @param id
+ * identity of the tree object the caller wants walked.
+ * @return position of this tree within the walker.
+ * @throws MissingObjectException
+ * the given tree object does not exist in this repository.
+ * @throws IncorrectObjectTypeException
+ * the given object id does not denote a tree, but instead names
+ * some other non-tree type of object. Note that commits are not
+ * trees, even if they are sometimes called a "tree-ish".
+ * @throws CorruptObjectException
+ * the object claimed to be a tree, but its contents did not
+ * appear to be a tree. The repository may have data corruption.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public int addTree(final ObjectId id) throws MissingObjectException,
+ IncorrectObjectTypeException, CorruptObjectException, IOException {
+ return addTree(parserFor(id));
+ }
+
+ /**
+ * Add an already created tree iterator for walking.
+ * <p>
+ * The position of this tree is returned to the caller, in case the caller
+ * has lost track of the order they added the trees into the walker.
+ * <p>
+ * The tree which the iterator operates on must have the same root as
+ * existing trees in the walk.
+ *
+ * @param p
+ * an iterator to walk over. The iterator should be new, with no
+ * parent, and should still be positioned before the first entry.
+ * The tree which the iterator operates on must have the same root
+ * as other trees in the walk.
+ *
+ * @return position of this tree within the walker.
+ * @throws CorruptObjectException
+ * the iterator was unable to obtain its first entry, due to
+ * possible data corruption within the backing data store.
+ */
+ public int addTree(final AbstractTreeIterator p)
+ throws CorruptObjectException {
+ final int n = trees.length;
+ final AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1];
+
+ System.arraycopy(trees, 0, newTrees, 0, n);
+ newTrees[n] = p;
+ p.matches = null;
+ p.matchShift = 0;
+
+ trees = newTrees;
+ return n;
+ }
+
+ /**
+ * Get the number of trees known to this walker.
+ *
+ * @return the total number of trees this walker is iterating over.
+ */
+ public int getTreeCount() {
+ return trees.length;
+ }
+
+ /**
+ * Advance this walker to the next relevant entry.
+ *
+ * @return true if there is an entry available; false if all entries have
+ * been walked and the walk of this set of tree iterators is over.
+ * @throws MissingObjectException
+ * {@link #isRecursive()} was enabled, a subtree was found, but
+ * the subtree object does not exist in this repository. The
+ * repository may be missing objects.
+ * @throws IncorrectObjectTypeException
+ * {@link #isRecursive()} was enabled, a subtree was found, and
+ * the subtree id does not denote a tree, but instead names some
+ * other non-tree type of object. The repository may have data
+ * corruption.
+ * @throws CorruptObjectException
+ * the contents of a tree did not appear to be a tree. The
+ * repository may have data corruption.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public boolean next() throws MissingObjectException,
+ IncorrectObjectTypeException, CorruptObjectException, IOException {
+ try {
+ if (advance) {
+ advance = false;
+ postChildren = false;
+ popEntriesEqual();
+ }
+
+ for (;;) {
+ final AbstractTreeIterator t = min();
+ if (t.eof()) {
+ if (depth > 0) {
+ exitSubtree();
+ if (postOrderTraversal) {
+ advance = true;
+ postChildren = true;
+ return true;
+ }
+ popEntriesEqual();
+ continue;
+ }
+ return false;
+ }
+
+ currentHead = t;
+ if (!filter.include(this)) {
+ skipEntriesEqual();
+ continue;
+ }
+
+ if (recursive && FileMode.TREE.equals(t.mode)) {
+ enterSubtree();
+ continue;
+ }
+
+ advance = true;
+ return true;
+ }
+ } catch (StopWalkException stop) {
+ for (final AbstractTreeIterator t : trees)
+ t.stopWalk();
+ return false;
+ }
+ }
+
+ /**
+ * Obtain the tree iterator for the current entry.
+ * <p>
+ * Entering into (or exiting out of) a subtree causes the current tree
+ * iterator instance to be changed for the nth tree. This allows the tree
+ * iterators to manage only one list of items, with the diving handled by
+ * recursive trees.
+ *
+ * @param <T>
+ * type of the tree iterator expected by the caller.
+ * @param nth
+ * tree to obtain the current iterator of.
+ * @param clazz
+ * type of the tree iterator expected by the caller.
+ * @return r the current iterator of the requested type; null if the tree
+ * has no entry to match the current path.
+ */
+ public <T extends AbstractTreeIterator> T getTree(final int nth,
+ final Class<T> clazz) {
+ final AbstractTreeIterator t = trees[nth];
+ return t.matches == currentHead ? (T) t : null;
+ }
+
+ /**
+ * Obtain the raw {@link FileMode} bits for the current entry.
+ * <p>
+ * Every added tree supplies mode bits, even if the tree does not contain
+ * the current entry. In the latter case {@link FileMode#MISSING}'s mode
+ * bits (0) are returned.
+ *
+ * @param nth
+ * tree to obtain the mode bits from.
+ * @return mode bits for the current entry of the nth tree.
+ * @see FileMode#fromBits(int)
+ */
+ public int getRawMode(final int nth) {
+ final AbstractTreeIterator t = trees[nth];
+ return t.matches == currentHead ? t.mode : 0;
+ }
+
+ /**
+ * Obtain the {@link FileMode} for the current entry.
+ * <p>
+ * Every added tree supplies a mode, even if the tree does not contain the
+ * current entry. In the latter case {@link FileMode#MISSING} is returned.
+ *
+ * @param nth
+ * tree to obtain the mode from.
+ * @return mode for the current entry of the nth tree.
+ */
+ public FileMode getFileMode(final int nth) {
+ return FileMode.fromBits(getRawMode(nth));
+ }
+
+ /**
+ * Obtain the ObjectId for the current entry.
+ * <p>
+ * Using this method to compare ObjectId values between trees of this walker
+ * is very inefficient. Applications should try to use
+ * {@link #idEqual(int, int)} or {@link #getObjectId(MutableObjectId, int)}
+ * whenever possible.
+ * <p>
+ * Every tree supplies an object id, even if the tree does not contain the
+ * current entry. In the latter case {@link ObjectId#zeroId()} is returned.
+ *
+ * @param nth
+ * tree to obtain the object identifier from.
+ * @return object identifier for the current tree entry.
+ * @see #getObjectId(MutableObjectId, int)
+ * @see #idEqual(int, int)
+ */
+ public ObjectId getObjectId(final int nth) {
+ final AbstractTreeIterator t = trees[nth];
+ return t.matches == currentHead ? t.getEntryObjectId() : ObjectId
+ .zeroId();
+ }
+
+ /**
+ * Obtain the ObjectId for the current entry.
+ * <p>
+ * Every tree supplies an object id, even if the tree does not contain the
+ * current entry. In the latter case {@link ObjectId#zeroId()} is supplied.
+ * <p>
+ * Applications should try to use {@link #idEqual(int, int)} when possible
+ * as it avoids conversion overheads.
+ *
+ * @param out
+ * buffer to copy the object id into.
+ * @param nth
+ * tree to obtain the object identifier from.
+ * @see #idEqual(int, int)
+ */
+ public void getObjectId(final MutableObjectId out, final int nth) {
+ final AbstractTreeIterator t = trees[nth];
+ if (t.matches == currentHead)
+ t.getEntryObjectId(out);
+ else
+ out.clear();
+ }
+
+ /**
+ * Compare two tree's current ObjectId values for equality.
+ *
+ * @param nthA
+ * first tree to compare the object id from.
+ * @param nthB
+ * second tree to compare the object id from.
+ * @return result of
+ * <code>getObjectId(nthA).equals(getObjectId(nthB))</code>.
+ * @see #getObjectId(int)
+ */
+ public boolean idEqual(final int nthA, final int nthB) {
+ final AbstractTreeIterator ch = currentHead;
+ final AbstractTreeIterator a = trees[nthA];
+ final AbstractTreeIterator b = trees[nthB];
+ if (a.matches == ch && b.matches == ch)
+ return a.idEqual(b);
+ if (a.matches != ch && b.matches != ch) {
+ // If neither tree matches the current path node then neither
+ // tree has this entry. In such case the ObjectId is zero(),
+ // and zero() is always equal to zero().
+ //
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the current entry's name within its parent tree.
+ * <p>
+ * This method is not very efficient and is primarily meant for debugging
+ * and final output generation. Applications should try to avoid calling it,
+ * and if invoked do so only once per interesting entry, where the name is
+ * absolutely required for correct function.
+ *
+ * @return name of the current entry within the parent tree (or directory).
+ * The name never includes a '/'.
+ */
+ public String getNameString() {
+ final AbstractTreeIterator t = currentHead;
+ final int off = t.pathOffset;
+ final int end = t.pathLen;
+ return RawParseUtils.decode(Constants.CHARSET, t.path, off, end);
+ }
+
+ /**
+ * Get the current entry's complete path.
+ * <p>
+ * This method is not very efficient and is primarily meant for debugging
+ * and final output generation. Applications should try to avoid calling it,
+ * and if invoked do so only once per interesting entry, where the name is
+ * absolutely required for correct function.
+ *
+ * @return complete path of the current entry, from the root of the
+ * repository. If the current entry is in a subtree there will be at
+ * least one '/' in the returned string.
+ */
+ public String getPathString() {
+ return pathOf(currentHead);
+ }
+
+ /**
+ * Get the current entry's complete path as a UTF-8 byte array.
+ *
+ * @return complete path of the current entry, from the root of the
+ * repository. If the current entry is in a subtree there will be at
+ * least one '/' in the returned string.
+ */
+ public byte[] getRawPath() {
+ final AbstractTreeIterator t = currentHead;
+ final int n = t.pathLen;
+ final byte[] r = new byte[n];
+ System.arraycopy(t.path, 0, r, 0, n);
+ return r;
+ }
+
+ /**
+ * Test if the supplied path matches the current entry's path.
+ * <p>
+ * This method tests that the supplied path is exactly equal to the current
+ * entry, or is one of its parent directories. It is faster to use this
+ * method then to use {@link #getPathString()} to first create a String
+ * object, then test <code>startsWith</code> or some other type of string
+ * match function.
+ *
+ * @param p
+ * path buffer to test. Callers should ensure the path does not
+ * end with '/' prior to invocation.
+ * @param pLen
+ * number of bytes from <code>buf</code> to test.
+ * @return < 0 if p is before the current path; 0 if p matches the current
+ * path; 1 if the current path is past p and p will never match
+ * again on this tree walk.
+ */
+ public int isPathPrefix(final byte[] p, final int pLen) {
+ final AbstractTreeIterator t = currentHead;
+ final byte[] c = t.path;
+ final int cLen = t.pathLen;
+ int ci;
+
+ for (ci = 0; ci < cLen && ci < pLen; ci++) {
+ final int c_value = (c[ci] & 0xff) - (p[ci] & 0xff);
+ if (c_value != 0)
+ return c_value;
+ }
+
+ if (ci < cLen) {
+ // Ran out of pattern but we still had current data.
+ // If c[ci] == '/' then pattern matches the subtree.
+ // Otherwise we cannot be certain so we return -1.
+ //
+ return c[ci] == '/' ? 0 : -1;
+ }
+
+ if (ci < pLen) {
+ // Ran out of current, but we still have pattern data.
+ // If p[ci] == '/' then pattern matches this subtree,
+ // otherwise we cannot be certain so we return -1.
+ //
+ return p[ci] == '/' ? 0 : -1;
+ }
+
+ // Both strings are identical.
+ //
+ return 0;
+ }
+
+ /**
+ * Test if the supplied path matches (being suffix of) the current entry's
+ * path.
+ * <p>
+ * This method tests that the supplied path is exactly equal to the current
+ * entry, or is relative to one of entry's parent directories. It is faster
+ * to use this method then to use {@link #getPathString()} to first create
+ * a String object, then test <code>endsWith</code> or some other type of
+ * string match function.
+ *
+ * @param p
+ * path buffer to test.
+ * @param pLen
+ * number of bytes from <code>buf</code> to test.
+ * @return true if p is suffix of the current path;
+ * false if otherwise
+ */
+ public boolean isPathSuffix(final byte[] p, final int pLen) {
+ final AbstractTreeIterator t = currentHead;
+ final byte[] c = t.path;
+ final int cLen = t.pathLen;
+ int ci;
+
+ for (ci = 1; ci < cLen && ci < pLen; ci++) {
+ if (c[cLen-ci] != p[pLen-ci])
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the current subtree depth of this walker.
+ *
+ * @return the current subtree depth of this walker.
+ */
+ public int getDepth() {
+ return depth;
+ }
+
+ /**
+ * Is the current entry a subtree?
+ * <p>
+ * This method is faster then testing the raw mode bits of all trees to see
+ * if any of them are a subtree. If at least one is a subtree then this
+ * method will return true.
+ *
+ * @return true if {@link #enterSubtree()} will work on the current node.
+ */
+ public boolean isSubtree() {
+ return FileMode.TREE.equals(currentHead.mode);
+ }
+
+ /**
+ * Is the current entry a subtree returned after its children?
+ *
+ * @return true if the current node is a tree that has been returned after
+ * its children were already processed.
+ * @see #isPostOrderTraversal()
+ */
+ public boolean isPostChildren() {
+ return postChildren && isSubtree();
+ }
+
+ /**
+ * Enter into the current subtree.
+ * <p>
+ * If the current entry is a subtree this method arranges for its children
+ * to be returned before the next sibling following the subtree is returned.
+ *
+ * @throws MissingObjectException
+ * a subtree was found, but the subtree object does not exist in
+ * this repository. The repository may be missing objects.
+ * @throws IncorrectObjectTypeException
+ * a subtree was found, and the subtree id does not denote a
+ * tree, but instead names some other non-tree type of object.
+ * The repository may have data corruption.
+ * @throws CorruptObjectException
+ * the contents of a tree did not appear to be a tree. The
+ * repository may have data corruption.
+ * @throws IOException
+ * a loose object or pack file could not be read.
+ */
+ public void enterSubtree() throws MissingObjectException,
+ IncorrectObjectTypeException, CorruptObjectException, IOException {
+ final AbstractTreeIterator ch = currentHead;
+ final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
+ for (int i = 0; i < trees.length; i++) {
+ final AbstractTreeIterator t = trees[i];
+ final AbstractTreeIterator n;
+ if (t.matches == ch && !t.eof() && FileMode.TREE.equals(t.mode))
+ n = t.createSubtreeIterator(db, idBuffer, curs);
+ else
+ n = t.createEmptyTreeIterator();
+ tmp[i] = n;
+ }
+ depth++;
+ advance = false;
+ System.arraycopy(tmp, 0, trees, 0, trees.length);
+ }
+
+ AbstractTreeIterator min() throws CorruptObjectException {
+ int i = 0;
+ AbstractTreeIterator minRef = trees[i];
+ while (minRef.eof() && ++i < trees.length)
+ minRef = trees[i];
+ if (minRef.eof())
+ return minRef;
+
+ minRef.matches = minRef;
+ while (++i < trees.length) {
+ final AbstractTreeIterator t = trees[i];
+ if (t.eof())
+ continue;
+ final int cmp = t.pathCompare(minRef);
+ if (cmp < 0) {
+ t.matches = t;
+ minRef = t;
+ } else if (cmp == 0) {
+ t.matches = minRef;
+ }
+ }
+
+ return minRef;
+ }
+
+ void popEntriesEqual() throws CorruptObjectException {
+ final AbstractTreeIterator ch = currentHead;
+ for (int i = 0; i < trees.length; i++) {
+ final AbstractTreeIterator t = trees[i];
+ if (t.matches == ch) {
+ t.next(1);
+ t.matches = null;
+ }
+ }
+ }
+
+ void skipEntriesEqual() throws CorruptObjectException {
+ final AbstractTreeIterator ch = currentHead;
+ for (int i = 0; i < trees.length; i++) {
+ final AbstractTreeIterator t = trees[i];
+ if (t.matches == ch) {
+ t.skip();
+ t.matches = null;
+ }
+ }
+ }
+
+ private void exitSubtree() {
+ depth--;
+ for (int i = 0; i < trees.length; i++)
+ trees[i] = trees[i].parent;
+
+ AbstractTreeIterator minRef = null;
+ for (final AbstractTreeIterator t : trees) {
+ if (t.matches != t)
+ continue;
+ if (minRef == null || t.pathCompare(minRef) < 0)
+ minRef = t;
+ }
+ currentHead = minRef;
+ }
+
+ private CanonicalTreeParser parserFor(final AnyObjectId id)
+ throws IncorrectObjectTypeException, IOException {
+ final CanonicalTreeParser p = new CanonicalTreeParser();
+ p.reset(db, id, curs);
+ return p;
+ }
+
+ static String pathOf(final AbstractTreeIterator t) {
+ return RawParseUtils.decode(Constants.CHARSET, t.path, 0, t.pathLen);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
new file mode 100644
index 0000000000..18ceef8d91
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+
+/**
+ * Walks a working directory tree as part of a {@link TreeWalk}.
+ * <p>
+ * Most applications will want to use the standard implementation of this
+ * iterator, {@link FileTreeIterator}, as that does all IO through the standard
+ * <code>java.io</code> package. Plugins for a Java based IDE may however wish
+ * to create their own implementations of this class to allow traversal of the
+ * IDE's project space, as well as benefit from any caching the IDE may have.
+ *
+ * @see FileTreeIterator
+ */
+public abstract class WorkingTreeIterator extends AbstractTreeIterator {
+ /** An empty entry array, suitable for {@link #init(Entry[])}. */
+ protected static final Entry[] EOF = {};
+
+ /** Size we perform file IO in if we have to read and hash a file. */
+ private static final int BUFFER_SIZE = 2048;
+
+ /** The {@link #idBuffer()} for the current entry. */
+ private byte[] contentId;
+
+ /** Index within {@link #entries} that {@link #contentId} came from. */
+ private int contentIdFromPtr;
+
+ /** Buffer used to perform {@link #contentId} computations. */
+ private byte[] contentReadBuffer;
+
+ /** Digest computer for {@link #contentId} computations. */
+ private MessageDigest contentDigest;
+
+ /** File name character encoder. */
+ private final CharsetEncoder nameEncoder;
+
+ /** List of entries obtained from the subclass. */
+ private Entry[] entries;
+
+ /** Total number of entries in {@link #entries} that are valid. */
+ private int entryCnt;
+
+ /** Current position within {@link #entries}. */
+ private int ptr;
+
+ /** Create a new iterator with no parent. */
+ protected WorkingTreeIterator() {
+ super();
+ nameEncoder = Constants.CHARSET.newEncoder();
+ }
+
+ /**
+ * Create a new iterator with no parent and a prefix.
+ * <p>
+ * The prefix path supplied is inserted in front of all paths generated by
+ * this iterator. It is intended to be used when an iterator is being
+ * created for a subsection of an overall repository and needs to be
+ * combined with other iterators that are created to run over the entire
+ * repository namespace.
+ *
+ * @param prefix
+ * position of this iterator in the repository tree. The value
+ * may be null or the empty string to indicate the prefix is the
+ * root of the repository. A trailing slash ('/') is
+ * automatically appended if the prefix does not end in '/'.
+ */
+ protected WorkingTreeIterator(final String prefix) {
+ super(prefix);
+ nameEncoder = Constants.CHARSET.newEncoder();
+ }
+
+ /**
+ * Create an iterator for a subtree of an existing iterator.
+ *
+ * @param p
+ * parent tree iterator.
+ */
+ protected WorkingTreeIterator(final WorkingTreeIterator p) {
+ super(p);
+ nameEncoder = p.nameEncoder;
+ }
+
+ @Override
+ public byte[] idBuffer() {
+ if (contentIdFromPtr == ptr)
+ return contentId;
+ switch (mode & FileMode.TYPE_MASK) {
+ case FileMode.TYPE_FILE:
+ contentIdFromPtr = ptr;
+ return contentId = idBufferBlob(entries[ptr]);
+ case FileMode.TYPE_SYMLINK:
+ // Java does not support symbolic links, so we should not
+ // have reached this particular part of the walk code.
+ //
+ return zeroid;
+ case FileMode.TYPE_GITLINK:
+ // TODO: Support obtaining current HEAD SHA-1 from nested repository
+ //
+ return zeroid;
+ }
+ return zeroid;
+ }
+
+ private void initializeDigest() {
+ if (contentDigest != null)
+ return;
+
+ if (parent == null) {
+ contentReadBuffer = new byte[BUFFER_SIZE];
+ contentDigest = Constants.newMessageDigest();
+ } else {
+ final WorkingTreeIterator p = (WorkingTreeIterator) parent;
+ p.initializeDigest();
+ contentReadBuffer = p.contentReadBuffer;
+ contentDigest = p.contentDigest;
+ }
+ }
+
+ private static final byte[] digits = { '0', '1', '2', '3', '4', '5', '6',
+ '7', '8', '9' };
+
+ private static final byte[] hblob = Constants
+ .encodedTypeString(Constants.OBJ_BLOB);
+
+ private byte[] idBufferBlob(final Entry e) {
+ try {
+ final InputStream is = e.openInputStream();
+ if (is == null)
+ return zeroid;
+ try {
+ initializeDigest();
+
+ contentDigest.reset();
+ contentDigest.update(hblob);
+ contentDigest.update((byte) ' ');
+
+ final long blobLength = e.getLength();
+ long sz = blobLength;
+ if (sz == 0) {
+ contentDigest.update((byte) '0');
+ } else {
+ final int bufn = contentReadBuffer.length;
+ int p = bufn;
+ do {
+ contentReadBuffer[--p] = digits[(int) (sz % 10)];
+ sz /= 10;
+ } while (sz > 0);
+ contentDigest.update(contentReadBuffer, p, bufn - p);
+ }
+ contentDigest.update((byte) 0);
+
+ for (;;) {
+ final int r = is.read(contentReadBuffer);
+ if (r <= 0)
+ break;
+ contentDigest.update(contentReadBuffer, 0, r);
+ sz += r;
+ }
+ if (sz != blobLength)
+ return zeroid;
+ return contentDigest.digest();
+ } finally {
+ try {
+ is.close();
+ } catch (IOException err2) {
+ // Suppress any error related to closing an input
+ // stream. We don't care, we should not have any
+ // outstanding data to flush or anything like that.
+ }
+ }
+ } catch (IOException err) {
+ // Can't read the file? Don't report the failure either.
+ //
+ return zeroid;
+ }
+ }
+
+ @Override
+ public int idOffset() {
+ return 0;
+ }
+
+ @Override
+ public boolean first() {
+ return ptr == 0;
+ }
+
+ @Override
+ public boolean eof() {
+ return ptr == entryCnt;
+ }
+
+ @Override
+ public void next(final int delta) throws CorruptObjectException {
+ ptr += delta;
+ if (!eof())
+ parseEntry();
+ }
+
+ @Override
+ public void back(final int delta) throws CorruptObjectException {
+ ptr -= delta;
+ parseEntry();
+ }
+
+ private void parseEntry() {
+ final Entry e = entries[ptr];
+ mode = e.getMode().getBits();
+
+ final int nameLen = e.encodedNameLen;
+ ensurePathCapacity(pathOffset + nameLen, pathOffset);
+ System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
+ pathLen = pathOffset + nameLen;
+ }
+
+ /**
+ * Get the byte length of this entry.
+ *
+ * @return size of this file, in bytes.
+ */
+ public long getEntryLength() {
+ return current().getLength();
+ }
+
+ /**
+ * Get the last modified time of this entry.
+ *
+ * @return last modified time of this file, in milliseconds since the epoch
+ * (Jan 1, 1970 UTC).
+ */
+ public long getEntryLastModified() {
+ return current().getLastModified();
+ }
+
+ private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
+ public int compare(final Entry o1, final Entry o2) {
+ final byte[] a = o1.encodedName;
+ final byte[] b = o2.encodedName;
+ final int aLen = o1.encodedNameLen;
+ final int bLen = o2.encodedNameLen;
+ int cPos;
+
+ for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
+ final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
+ if (cmp != 0)
+ return cmp;
+ }
+
+ if (cPos < aLen)
+ return (a[cPos] & 0xff) - lastPathChar(o2);
+ if (cPos < bLen)
+ return lastPathChar(o1) - (b[cPos] & 0xff);
+ return lastPathChar(o1) - lastPathChar(o2);
+ }
+ };
+
+ static int lastPathChar(final Entry e) {
+ return e.getMode() == FileMode.TREE ? '/' : '\0';
+ }
+
+ /**
+ * Constructor helper.
+ *
+ * @param list
+ * files in the subtree of the work tree this iterator operates
+ * on
+ */
+ protected void init(final Entry[] list) {
+ // Filter out nulls, . and .. as these are not valid tree entries,
+ // also cache the encoded forms of the path names for efficient use
+ // later on during sorting and iteration.
+ //
+ entries = list;
+ int i, o;
+
+ for (i = 0, o = 0; i < entries.length; i++) {
+ final Entry e = entries[i];
+ if (e == null)
+ continue;
+ final String name = e.getName();
+ if (".".equals(name) || "..".equals(name))
+ continue;
+ if (".git".equals(name))
+ continue;
+ if (i != o)
+ entries[o] = e;
+ e.encodeName(nameEncoder);
+ o++;
+ }
+ entryCnt = o;
+ Arrays.sort(entries, 0, entryCnt, ENTRY_CMP);
+
+ contentIdFromPtr = -1;
+ ptr = 0;
+ if (!eof())
+ parseEntry();
+ }
+
+ /**
+ * Obtain the current entry from this iterator.
+ *
+ * @return the currently selected entry.
+ */
+ protected Entry current() {
+ return entries[ptr];
+ }
+
+ /** A single entry within a working directory tree. */
+ protected static abstract class Entry {
+ byte[] encodedName;
+
+ int encodedNameLen;
+
+ void encodeName(final CharsetEncoder enc) {
+ final ByteBuffer b;
+ try {
+ b = enc.encode(CharBuffer.wrap(getName()));
+ } catch (CharacterCodingException e) {
+ // This should so never happen.
+ throw new RuntimeException("Unencodeable file: " + getName());
+ }
+
+ encodedNameLen = b.limit();
+ if (b.hasArray() && b.arrayOffset() == 0)
+ encodedName = b.array();
+ else
+ b.get(encodedName = new byte[encodedNameLen]);
+ }
+
+ public String toString() {
+ return getMode().toString() + " " + getName();
+ }
+
+ /**
+ * Get the type of this entry.
+ * <p>
+ * <b>Note: Efficient implementation required.</b>
+ * <p>
+ * The implementation of this method must be efficient. If a subclass
+ * needs to compute the value they should cache the reference within an
+ * instance member instead.
+ *
+ * @return a file mode constant from {@link FileMode}.
+ */
+ public abstract FileMode getMode();
+
+ /**
+ * Get the byte length of this entry.
+ * <p>
+ * <b>Note: Efficient implementation required.</b>
+ * <p>
+ * The implementation of this method must be efficient. If a subclass
+ * needs to compute the value they should cache the reference within an
+ * instance member instead.
+ *
+ * @return size of this file, in bytes.
+ */
+ public abstract long getLength();
+
+ /**
+ * Get the last modified time of this entry.
+ * <p>
+ * <b>Note: Efficient implementation required.</b>
+ * <p>
+ * The implementation of this method must be efficient. If a subclass
+ * needs to compute the value they should cache the reference within an
+ * instance member instead.
+ *
+ * @return time since the epoch (in ms) of the last change.
+ */
+ public abstract long getLastModified();
+
+ /**
+ * Get the name of this entry within its directory.
+ * <p>
+ * Efficient implementations are not required. The caller will obtain
+ * the name only once and cache it once obtained.
+ *
+ * @return name of the entry.
+ */
+ public abstract String getName();
+
+ /**
+ * Obtain an input stream to read the file content.
+ * <p>
+ * Efficient implementations are not required. The caller will usually
+ * obtain the stream only once per entry, if at all.
+ * <p>
+ * The input stream should not use buffering if the implementation can
+ * avoid it. The caller will buffer as necessary to perform efficient
+ * block IO operations.
+ * <p>
+ * The caller will close the stream once complete.
+ *
+ * @return a stream to read from the file.
+ * @throws IOException
+ * the file could not be opened for reading.
+ */
+ public abstract InputStream openInputStream() throws IOException;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java
new file mode 100644
index 0000000000..2c3983b015
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk.filter;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+/**
+ * Includes a tree entry only if all subfilters include the same tree entry.
+ * <p>
+ * Classic shortcut behavior is used, so evaluation of the
+ * {@link TreeFilter#include(TreeWalk)} method stops as soon as a false result
+ * is obtained. Applications can improve filtering performance by placing faster
+ * filters that are more likely to reject a result earlier in the list.
+ */
+public abstract class AndTreeFilter extends TreeFilter {
+ /**
+ * Create a filter with two filters, both of which must match.
+ *
+ * @param a
+ * first filter to test.
+ * @param b
+ * second filter to test.
+ * @return a filter that must match both input filters.
+ */
+ public static TreeFilter create(final TreeFilter a, final TreeFilter b) {
+ if (a == ALL)
+ return b;
+ if (b == ALL)
+ return a;
+ return new Binary(a, b);
+ }
+
+ /**
+ * Create a filter around many filters, all of which must match.
+ *
+ * @param list
+ * list of filters to match against. Must contain at least 2
+ * filters.
+ * @return a filter that must match all input filters.
+ */
+ public static TreeFilter create(final TreeFilter[] list) {
+ if (list.length == 2)
+ return create(list[0], list[1]);
+ if (list.length < 2)
+ throw new IllegalArgumentException("At least two filters needed.");
+ final TreeFilter[] subfilters = new TreeFilter[list.length];
+ System.arraycopy(list, 0, subfilters, 0, list.length);
+ return new List(subfilters);
+ }
+
+ /**
+ * Create a filter around many filters, all of which must match.
+ *
+ * @param list
+ * list of filters to match against. Must contain at least 2
+ * filters.
+ * @return a filter that must match all input filters.
+ */
+ public static TreeFilter create(final Collection<TreeFilter> list) {
+ if (list.size() < 2)
+ throw new IllegalArgumentException("At least two filters needed.");
+ final TreeFilter[] subfilters = new TreeFilter[list.size()];
+ list.toArray(subfilters);
+ if (subfilters.length == 2)
+ return create(subfilters[0], subfilters[1]);
+ return new List(subfilters);
+ }
+
+ private static class Binary extends AndTreeFilter {
+ private final TreeFilter a;
+
+ private final TreeFilter b;
+
+ Binary(final TreeFilter one, final TreeFilter two) {
+ a = one;
+ b = two;
+ }
+
+ @Override
+ public boolean include(final TreeWalk walker)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return a.include(walker) && b.include(walker);
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ return a.shouldBeRecursive() || b.shouldBeRecursive();
+ }
+
+ @Override
+ public TreeFilter clone() {
+ return new Binary(a.clone(), b.clone());
+ }
+
+ @Override
+ public String toString() {
+ return "(" + a.toString() + " AND " + b.toString() + ")";
+ }
+ }
+
+ private static class List extends AndTreeFilter {
+ private final TreeFilter[] subfilters;
+
+ List(final TreeFilter[] list) {
+ subfilters = list;
+ }
+
+ @Override
+ public boolean include(final TreeWalk walker)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ for (final TreeFilter f : subfilters) {
+ if (!f.include(walker))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ for (final TreeFilter f : subfilters)
+ if (f.shouldBeRecursive())
+ return true;
+ return false;
+ }
+
+ @Override
+ public TreeFilter clone() {
+ final TreeFilter[] s = new TreeFilter[subfilters.length];
+ for (int i = 0; i < s.length; i++)
+ s[i] = subfilters[i].clone();
+ return new List(s);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer r = new StringBuffer();
+ r.append("(");
+ for (int i = 0; i < subfilters.length; i++) {
+ if (i > 0)
+ r.append(" AND ");
+ r.append(subfilters[i].toString());
+ }
+ r.append(")");
+ return r.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java
new file mode 100644
index 0000000000..33e4415a97
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk.filter;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+/** Includes an entry only if the subfilter does not include the entry. */
+public class NotTreeFilter extends TreeFilter {
+ /**
+ * Create a filter that negates the result of another filter.
+ *
+ * @param a
+ * filter to negate.
+ * @return a filter that does the reverse of <code>a</code>.
+ */
+ public static TreeFilter create(final TreeFilter a) {
+ return new NotTreeFilter(a);
+ }
+
+ private final TreeFilter a;
+
+ private NotTreeFilter(final TreeFilter one) {
+ a = one;
+ }
+
+ @Override
+ public TreeFilter negate() {
+ return a;
+ }
+
+ @Override
+ public boolean include(final TreeWalk walker)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return !a.include(walker);
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ return a.shouldBeRecursive();
+ }
+
+ @Override
+ public TreeFilter clone() {
+ final TreeFilter n = a.clone();
+ return n == a ? this : new NotTreeFilter(n);
+ }
+
+ @Override
+ public String toString() {
+ return "NOT " + a.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java
new file mode 100644
index 0000000000..55005446e5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk.filter;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+/**
+ * Includes a tree entry if any subfilters include the same tree entry.
+ * <p>
+ * Classic shortcut behavior is used, so evaluation of the
+ * {@link TreeFilter#include(TreeWalk)} method stops as soon as a true result is
+ * obtained. Applications can improve filtering performance by placing faster
+ * filters that are more likely to accept a result earlier in the list.
+ */
+public abstract class OrTreeFilter extends TreeFilter {
+ /**
+ * Create a filter with two filters, one of which must match.
+ *
+ * @param a
+ * first filter to test.
+ * @param b
+ * second filter to test.
+ * @return a filter that must match at least one input filter.
+ */
+ public static TreeFilter create(final TreeFilter a, final TreeFilter b) {
+ if (a == ALL || b == ALL)
+ return ALL;
+ return new Binary(a, b);
+ }
+
+ /**
+ * Create a filter around many filters, one of which must match.
+ *
+ * @param list
+ * list of filters to match against. Must contain at least 2
+ * filters.
+ * @return a filter that must match at least one input filter.
+ */
+ public static TreeFilter create(final TreeFilter[] list) {
+ if (list.length == 2)
+ return create(list[0], list[1]);
+ if (list.length < 2)
+ throw new IllegalArgumentException("At least two filters needed.");
+ final TreeFilter[] subfilters = new TreeFilter[list.length];
+ System.arraycopy(list, 0, subfilters, 0, list.length);
+ return new List(subfilters);
+ }
+
+ /**
+ * Create a filter around many filters, one of which must match.
+ *
+ * @param list
+ * list of filters to match against. Must contain at least 2
+ * filters.
+ * @return a filter that must match at least one input filter.
+ */
+ public static TreeFilter create(final Collection<TreeFilter> list) {
+ if (list.size() < 2)
+ throw new IllegalArgumentException("At least two filters needed.");
+ final TreeFilter[] subfilters = new TreeFilter[list.size()];
+ list.toArray(subfilters);
+ if (subfilters.length == 2)
+ return create(subfilters[0], subfilters[1]);
+ return new List(subfilters);
+ }
+
+ private static class Binary extends OrTreeFilter {
+ private final TreeFilter a;
+
+ private final TreeFilter b;
+
+ Binary(final TreeFilter one, final TreeFilter two) {
+ a = one;
+ b = two;
+ }
+
+ @Override
+ public boolean include(final TreeWalk walker)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ return a.include(walker) || b.include(walker);
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ return a.shouldBeRecursive() || b.shouldBeRecursive();
+ }
+
+ @Override
+ public TreeFilter clone() {
+ return new Binary(a.clone(), b.clone());
+ }
+
+ @Override
+ public String toString() {
+ return "(" + a.toString() + " OR " + b.toString() + ")";
+ }
+ }
+
+ private static class List extends OrTreeFilter {
+ private final TreeFilter[] subfilters;
+
+ List(final TreeFilter[] list) {
+ subfilters = list;
+ }
+
+ @Override
+ public boolean include(final TreeWalk walker)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ for (final TreeFilter f : subfilters) {
+ if (f.include(walker))
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ for (final TreeFilter f : subfilters)
+ if (f.shouldBeRecursive())
+ return true;
+ return false;
+ }
+
+ @Override
+ public TreeFilter clone() {
+ final TreeFilter[] s = new TreeFilter[subfilters.length];
+ for (int i = 0; i < s.length; i++)
+ s[i] = subfilters[i].clone();
+ return new List(s);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer r = new StringBuffer();
+ r.append("(");
+ for (int i = 0; i < subfilters.length; i++) {
+ if (i > 0)
+ r.append(" OR ");
+ r.append(subfilters[i].toString());
+ }
+ r.append(")");
+ return r.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java
new file mode 100644
index 0000000000..5883d655ef
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk.filter;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+/**
+ * Includes tree entries only if they match the configured path.
+ * <p>
+ * Applications should use {@link PathFilterGroup} to connect these into a tree
+ * filter graph, as the group supports breaking out of traversal once it is
+ * known the path can never match.
+ */
+public class PathFilter extends TreeFilter {
+ /**
+ * Create a new tree filter for a user supplied path.
+ * <p>
+ * Path strings are relative to the root of the repository. If the user's
+ * input should be assumed relative to a subdirectory of the repository the
+ * caller must prepend the subdirectory's path prior to creating the filter.
+ * <p>
+ * Path strings use '/' to delimit directories on all platforms.
+ *
+ * @param path
+ * the path to filter on. Must not be the empty string. All
+ * trailing '/' characters will be trimmed before string's length
+ * is checked or is used as part of the constructed filter.
+ * @return a new filter for the requested path.
+ * @throws IllegalArgumentException
+ * the path supplied was the empty string.
+ */
+ public static PathFilter create(String path) {
+ while (path.endsWith("/"))
+ path = path.substring(0, path.length() - 1);
+ if (path.length() == 0)
+ throw new IllegalArgumentException("Empty path not permitted.");
+ return new PathFilter(path);
+ }
+
+ final String pathStr;
+
+ final byte[] pathRaw;
+
+ private PathFilter(final String s) {
+ pathStr = s;
+ pathRaw = Constants.encode(pathStr);
+ }
+
+ @Override
+ public boolean include(final TreeWalk walker) {
+ return walker.isPathPrefix(pathRaw, pathRaw.length) == 0;
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ for (final byte b : pathRaw)
+ if (b == '/')
+ return true;
+ return false;
+ }
+
+ @Override
+ public TreeFilter clone() {
+ return this;
+ }
+
+ public String toString() {
+ return "PATH(\"" + pathStr + "\")";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
new file mode 100644
index 0000000000..cd11f8123b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk.filter;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+
+import org.eclipse.jgit.errors.StopWalkException;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+/**
+ * Includes tree entries only if they match one or more configured paths.
+ * <p>
+ * Operates like {@link PathFilter} but causes the walk to abort as soon as the
+ * tree can no longer match any of the paths within the group. This may bypass
+ * the boolean logic of a higher level AND or OR group, but does improve
+ * performance for the common case of examining one or more modified paths.
+ * <p>
+ * This filter is effectively an OR group around paths, with the early abort
+ * feature described above.
+ */
+public class PathFilterGroup {
+ /**
+ * Create a collection of path filters from Java strings.
+ * <p>
+ * Path strings are relative to the root of the repository. If the user's
+ * input should be assumed relative to a subdirectory of the repository the
+ * caller must prepend the subdirectory's path prior to creating the filter.
+ * <p>
+ * Path strings use '/' to delimit directories on all platforms.
+ * <p>
+ * Paths may appear in any order within the collection. Sorting may be done
+ * internally when the group is constructed if doing so will improve path
+ * matching performance.
+ *
+ * @param paths
+ * the paths to test against. Must have at least one entry.
+ * @return a new filter for the list of paths supplied.
+ */
+ public static TreeFilter createFromStrings(final Collection<String> paths) {
+ if (paths.isEmpty())
+ throw new IllegalArgumentException("At least one path is required.");
+ final PathFilter[] p = new PathFilter[paths.size()];
+ int i = 0;
+ for (final String s : paths)
+ p[i++] = PathFilter.create(s);
+ return create(p);
+ }
+
+ /**
+ * Create a collection of path filters.
+ * <p>
+ * Paths may appear in any order within the collection. Sorting may be done
+ * internally when the group is constructed if doing so will improve path
+ * matching performance.
+ *
+ * @param paths
+ * the paths to test against. Must have at least one entry.
+ * @return a new filter for the list of paths supplied.
+ */
+ public static TreeFilter create(final Collection<PathFilter> paths) {
+ if (paths.isEmpty())
+ throw new IllegalArgumentException("At least one path is required.");
+ final PathFilter[] p = new PathFilter[paths.size()];
+ paths.toArray(p);
+ return create(p);
+ }
+
+ private static TreeFilter create(final PathFilter[] p) {
+ if (p.length == 1)
+ return new Single(p[0]);
+ return new Group(p);
+ }
+
+ static class Single extends TreeFilter {
+ private final PathFilter path;
+
+ private final byte[] raw;
+
+ private Single(final PathFilter p) {
+ path = p;
+ raw = path.pathRaw;
+ }
+
+ @Override
+ public boolean include(final TreeWalk walker) {
+ final int cmp = walker.isPathPrefix(raw, raw.length);
+ if (cmp > 0)
+ throw StopWalkException.INSTANCE;
+ return cmp == 0;
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ return path.shouldBeRecursive();
+ }
+
+ @Override
+ public TreeFilter clone() {
+ return this;
+ }
+
+ public String toString() {
+ return "FAST_" + path.toString();
+ }
+ }
+
+ static class Group extends TreeFilter {
+ private static final Comparator<PathFilter> PATH_SORT = new Comparator<PathFilter>() {
+ public int compare(final PathFilter o1, final PathFilter o2) {
+ return o1.pathStr.compareTo(o2.pathStr);
+ }
+ };
+
+ private final PathFilter[] paths;
+
+ private Group(final PathFilter[] p) {
+ paths = p;
+ Arrays.sort(paths, PATH_SORT);
+ }
+
+ @Override
+ public boolean include(final TreeWalk walker) {
+ final int n = paths.length;
+ for (int i = 0;;) {
+ final byte[] r = paths[i].pathRaw;
+ final int cmp = walker.isPathPrefix(r, r.length);
+ if (cmp == 0)
+ return true;
+ if (++i < n)
+ continue;
+ if (cmp > 0)
+ throw StopWalkException.INSTANCE;
+ return false;
+ }
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ for (final PathFilter p : paths)
+ if (p.shouldBeRecursive())
+ return true;
+ return false;
+ }
+
+ @Override
+ public TreeFilter clone() {
+ return this;
+ }
+
+ public String toString() {
+ final StringBuffer r = new StringBuffer();
+ r.append("FAST(");
+ for (int i = 0; i < paths.length; i++) {
+ if (i > 0)
+ r.append(" OR ");
+ r.append(paths[i].toString());
+ }
+ r.append(")");
+ return r.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java
new file mode 100644
index 0000000000..3721ec646f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk.filter;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+/**
+ * Includes tree entries only if they match the configured path.
+ */
+public class PathSuffixFilter extends TreeFilter {
+
+ /**
+ * Create a new tree filter for a user supplied path.
+ * <p>
+ * Path strings use '/' to delimit directories on all platforms.
+ *
+ * @param path
+ * the path (suffix) to filter on. Must not be the empty string.
+ * @return a new filter for the requested path.
+ * @throws IllegalArgumentException
+ * the path supplied was the empty string.
+ */
+ public static PathSuffixFilter create(String path) {
+ if (path.length() == 0)
+ throw new IllegalArgumentException("Empty path not permitted.");
+ return new PathSuffixFilter(path);
+ }
+
+ final String pathStr;
+ final byte[] pathRaw;
+
+ private PathSuffixFilter(final String s) {
+ pathStr = s;
+ pathRaw = Constants.encode(pathStr);
+ }
+
+ @Override
+ public TreeFilter clone() {
+ return this;
+ }
+
+ @Override
+ public boolean include(TreeWalk walker) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ if (walker.isSubtree())
+ return true;
+ else
+ return walker.isPathSuffix(pathRaw, pathRaw.length);
+
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ return true;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java
new file mode 100644
index 0000000000..5d0fb12f51
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.treewalk.filter;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+/**
+ * Selects interesting tree entries during walking.
+ * <p>
+ * This is an abstract interface. Applications may implement a subclass, or use
+ * one of the predefined implementations already available within this package.
+ * <p>
+ * Unless specifically noted otherwise a TreeFilter implementation is not thread
+ * safe and may not be shared by different TreeWalk instances at the same time.
+ * This restriction allows TreeFilter implementations to cache state within
+ * their instances during {@link #include(TreeWalk)} if it is beneficial to
+ * their implementation. Deep clones created by {@link #clone()} may be used to
+ * construct a thread-safe copy of an existing filter.
+ *
+ * <p>
+ * <b>Path filters:</b>
+ * <ul>
+ * <li>Matching pathname: {@link PathFilter}</li>
+ * </ul>
+ *
+ * <p>
+ * <b>Difference filters:</b>
+ * <ul>
+ * <li>Only select differences: {@link #ANY_DIFF}.</li>
+ * </ul>
+ *
+ * <p>
+ * <b>Boolean modifiers:</b>
+ * <ul>
+ * <li>AND: {@link AndTreeFilter}</li>
+ * <li>OR: {@link OrTreeFilter}</li>
+ * <li>NOT: {@link NotTreeFilter}</li>
+ * </ul>
+ */
+public abstract class TreeFilter {
+ /** Selects all tree entries. */
+ public static final TreeFilter ALL = new TreeFilter() {
+ @Override
+ public boolean include(final TreeWalk walker) {
+ return true;
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ return false;
+ }
+
+ @Override
+ public TreeFilter clone() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "ALL";
+ }
+ };
+
+ /**
+ * Selects only tree entries which differ between at least 2 trees.
+ * <p>
+ * This filter also prevents a TreeWalk from recursing into a subtree if all
+ * parent trees have the identical subtree at the same path. This
+ * dramatically improves walk performance as only the changed subtrees are
+ * entered into.
+ * <p>
+ * If this filter is applied to a walker with only one tree it behaves like
+ * {@link #ALL}, or as though the walker was matching a virtual empty tree
+ * against the single tree it was actually given. Applications may wish to
+ * treat such a difference as "all names added".
+ */
+ public static final TreeFilter ANY_DIFF = new TreeFilter() {
+ private static final int baseTree = 0;
+
+ @Override
+ public boolean include(final TreeWalk walker) {
+ final int n = walker.getTreeCount();
+ if (n == 1) // Assume they meant difference to empty tree.
+ return true;
+
+ final int m = walker.getRawMode(baseTree);
+ for (int i = 1; i < n; i++)
+ if (walker.getRawMode(i) != m || !walker.idEqual(i, baseTree))
+ return true;
+ return false;
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ return false;
+ }
+
+ @Override
+ public TreeFilter clone() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "ANY_DIFF";
+ }
+ };
+
+ /**
+ * Create a new filter that does the opposite of this filter.
+ *
+ * @return a new filter that includes tree entries this filter rejects.
+ */
+ public TreeFilter negate() {
+ return NotTreeFilter.create(this);
+ }
+
+ /**
+ * Determine if the current entry is interesting to report.
+ * <p>
+ * This method is consulted for subtree entries even if
+ * {@link TreeWalk#isRecursive()} is enabled. The consultation allows the
+ * filter to bypass subtree recursion on a case-by-case basis, even when
+ * recursion is enabled at the application level.
+ *
+ * @param walker
+ * the walker the filter needs to examine.
+ * @return true if the current entry should be seen by the application;
+ * false to hide the entry.
+ * @throws MissingObjectException
+ * an object the filter needs to consult to determine its answer
+ * does not exist in the Git repository the walker is operating
+ * on. Filtering this current walker entry is impossible without
+ * the object.
+ * @throws IncorrectObjectTypeException
+ * an object the filter needed to consult was not of the
+ * expected object type. This usually indicates a corrupt
+ * repository, as an object link is referencing the wrong type.
+ * @throws IOException
+ * a loose object or pack file could not be read to obtain data
+ * necessary for the filter to make its decision.
+ */
+ public abstract boolean include(TreeWalk walker)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException;
+
+ /**
+ * Does this tree filter require a recursive walk to match everything?
+ * <p>
+ * If this tree filter is matching on full entry path names and its pattern
+ * is looking for a '/' then the filter would require a recursive TreeWalk
+ * to accurately make its decisions. The walker is not required to enable
+ * recursive behavior for any particular filter, this is only a hint.
+ *
+ * @return true if the filter would like to have the walker recurse into
+ * subtrees to make sure it matches everything correctly; false if
+ * the filter does not require entering subtrees.
+ */
+ public abstract boolean shouldBeRecursive();
+
+ /**
+ * Clone this tree filter, including its parameters.
+ * <p>
+ * This is a deep clone. If this filter embeds objects or other filters it
+ * must also clone those, to ensure the instances do not share mutable data.
+ *
+ * @return another copy of this filter, suitable for another thread.
+ */
+ public abstract TreeFilter clone();
+
+ @Override
+ public String toString() {
+ String n = getClass().getName();
+ int lastDot = n.lastIndexOf('.');
+ if (lastDot >= 0) {
+ n = n.substring(lastDot + 1);
+ }
+ return n.replace('$', '.');
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
new file mode 100644
index 0000000000..53c7beced8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
@@ -0,0 +1,1475 @@
+//
+// NOTE: The following source code is the iHarder.net public domain
+// Base64 library and is provided here as a convenience. For updates,
+// problems, questions, etc. regarding this code, please visit:
+// http://iharder.sourceforge.net/current/java/base64/
+//
+
+package org.eclipse.jgit.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+
+/**
+ * Encodes and decodes to and from Base64 notation.
+ *
+ * <p>
+ * Change Log:
+ * </p>
+ * <ul>
+ * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
+ * some convenience methods for reading and writing to and from files.</li>
+ * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
+ * with other encodings (like EBCDIC).</li>
+ * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the
+ * encoded data was a single byte.</li>
+ * <li>v2.0 - I got rid of methods that used booleans to set options.
+ * Now everything is more consolidated and cleaner. The code now detects
+ * when data that's being decoded is gzip-compressed and will decompress it
+ * automatically. Generally things are cleaner. You'll probably have to
+ * change some method calls that you were making to support the new
+ * options format (<tt>int</tt>s that you "OR" together).</li>
+ * <li>v1.5.1 - Fixed bug when decompressing and decoding to a
+ * byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>.
+ * Added the ability to "suspend" encoding in the Output Stream so
+ * you can turn on and off the encoding if you need to embed base64
+ * data in an otherwise "normal" stream (like an XML file).</li>
+ * <li>v1.5 - Output stream passes on flush() command but doesn't do anything itself.
+ * This helps when using GZIP streams.
+ * Added the ability to GZip-compress objects before encoding them.</li>
+ * <li>v1.4 - Added helper methods to read/write files.</li>
+ * <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
+ * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
+ * where last buffer being read, if not completely full, was not returned.</li>
+ * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
+ * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
+ * </ul>
+ *
+ * <p>
+ * I am placing this code in the Public Domain. Do with it as you will.
+ * This software comes with no guarantees or warranties but with
+ * plenty of well-wishing instead!
+ * Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a>
+ * periodically to check for updates or to contribute improvements.
+ * </p>
+ *
+ * @author Robert Harder
+ * @author rob@iharder.net
+ * @version 2.1
+ */
+public class Base64
+{
+
+/* ******** P U B L I C F I E L D S ******** */
+
+
+ /** No options specified. Value is zero. */
+ public final static int NO_OPTIONS = 0;
+
+ /** Specify encoding. */
+ public final static int ENCODE = 1;
+
+
+ /** Specify decoding. */
+ public final static int DECODE = 0;
+
+
+ /** Specify that data should be gzip-compressed. */
+ public final static int GZIP = 2;
+
+
+ /** Don't break lines when encoding (violates strict Base64 specification) */
+ public final static int DONT_BREAK_LINES = 8;
+
+
+/* ******** P R I V A T E F I E L D S ******** */
+
+
+ /** Maximum line length (76) of Base64 output. */
+ private final static int MAX_LINE_LENGTH = 76;
+
+
+ /** The equals sign (=) as a byte. */
+ private final static byte EQUALS_SIGN = (byte)'=';
+
+
+ /** The new line character (\n) as a byte. */
+ private final static byte NEW_LINE = (byte)'\n';
+
+
+ /** Preferred encoding. */
+ private final static String PREFERRED_ENCODING = "UTF-8";
+
+
+ /** The 64 valid Base64 values. */
+ private final static byte[] ALPHABET;
+ private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */
+ {
+ (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+ (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+ (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+ (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+ (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+ (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+ (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+ (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
+ (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/'
+ };
+
+ /** Determine which ALPHABET to use. */
+ static
+ {
+ byte[] __bytes;
+ try
+ {
+ __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes( PREFERRED_ENCODING );
+ } // end try
+ catch (java.io.UnsupportedEncodingException use)
+ {
+ __bytes = _NATIVE_ALPHABET; // Fall back to native encoding
+ } // end catch
+ ALPHABET = __bytes;
+ } // end static
+
+
+ /**
+ * Translates a Base64 value to either its 6-bit reconstruction value
+ * or a negative number indicating some other meaning.
+ **/
+ private final static byte[] DECODABET =
+ {
+ -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
+ -5,-5, // Whitespace: Tab and Linefeed
+ -9,-9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
+ -9,-9,-9,-9,-9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
+ 62, // Plus sign at decimal 43
+ -9,-9,-9, // Decimal 44 - 46
+ 63, // Slash at decimal 47
+ 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine
+ -9,-9,-9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9,-9,-9, // Decimal 62 - 64
+ 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N'
+ 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z'
+ -9,-9,-9,-9,-9,-9, // Decimal 91 - 96
+ 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm'
+ 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z'
+ -9,-9,-9,-9 // Decimal 123 - 126
+ /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
+ };
+
+ // I think I end up not using the BAD_ENCODING indicator.
+ //private final static byte BAD_ENCODING = -9; // Indicates error in encoding
+ private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
+ private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
+
+ private static void closeStream(Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /** Defeats instantiation. */
+ private Base64() {
+ //suppress empty block warning
+ }
+
+/* ******** E N C O D I N G M E T H O D S ******** */
+
+
+ /**
+ * Encodes up to the first three bytes of array <var>threeBytes</var>
+ * and returns a four-byte array in Base64 notation.
+ * The actual number of significant bytes in your array is
+ * given by <var>numSigBytes</var>.
+ * The array <var>threeBytes</var> needs only be as big as
+ * <var>numSigBytes</var>.
+ * Code can reuse a byte array by passing a four-byte array as <var>b4</var>.
+ *
+ * @param b4 A reusable byte array to reduce array instantiation
+ * @param threeBytes the array to convert
+ * @param numSigBytes the number of significant bytes in your array
+ * @return four byte array in Base64 notation.
+ * @since 1.5.1
+ */
+ private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes )
+ {
+ encode3to4( threeBytes, 0, numSigBytes, b4, 0 );
+ return b4;
+ } // end encode3to4
+
+
+ /**
+ * Encodes up to three bytes of the array <var>source</var>
+ * and writes the resulting four Base64 bytes to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accommodate <var>srcOffset</var> + 3 for
+ * the <var>source</var> array or <var>destOffset</var> + 4 for
+ * the <var>destination</var> array.
+ * The actual number of significant bytes in your array is
+ * given by <var>numSigBytes</var>.
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param numSigBytes the number of significant bytes in your array
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @return the <var>destination</var> array
+ * @since 1.3
+ */
+ private static byte[] encode3to4(
+ byte[] source, int srcOffset, int numSigBytes,
+ byte[] destination, int destOffset )
+ {
+ // 1 2 3
+ // 01234567890123456789012345678901 Bit position
+ // --------000000001111111122222222 Array position from threeBytes
+ // --------| || || || | Six bit groups to index ALPHABET
+ // >>18 >>12 >> 6 >> 0 Right shift necessary
+ // 0x3f 0x3f 0x3f Additional AND
+
+ // Create buffer with zero-padding if there are only one or two
+ // significant bytes passed in the array.
+ // We have to shift left 24 in order to flush out the 1's that appear
+ // when Java treats a value as negative that is cast from a byte to an int.
+ int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 )
+ | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 )
+ | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 );
+
+ switch( numSigBytes )
+ {
+ case 3:
+ destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
+ destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+ destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
+ destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ];
+ return destination;
+
+ case 2:
+ destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
+ destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+ destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
+ destination[ destOffset + 3 ] = EQUALS_SIGN;
+ return destination;
+
+ case 1:
+ destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
+ destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+ destination[ destOffset + 2 ] = EQUALS_SIGN;
+ destination[ destOffset + 3 ] = EQUALS_SIGN;
+ return destination;
+
+ default:
+ return destination;
+ } // end switch
+ } // end encode3to4
+
+
+
+ /**
+ * Serializes an object and returns the Base64-encoded
+ * version of that serialized object. If the object
+ * cannot be serialized or there is another error,
+ * the method will return <tt>null</tt>.
+ * The object is not GZip-compressed before being encoded.
+ *
+ * @param serializableObject The object to encode
+ * @return The Base64-encoded object
+ * @since 1.4
+ */
+ public static String encodeObject( java.io.Serializable serializableObject )
+ {
+ return encodeObject( serializableObject, NO_OPTIONS );
+ } // end encodeObject
+
+
+
+ /**
+ * Serializes an object and returns the Base64-encoded
+ * version of that serialized object. If the object
+ * cannot be serialized or there is another error,
+ * the method will return <tt>null</tt>.
+ * <p>
+ * Valid options:<pre>
+ * GZIP: gzip-compresses object before encoding it.
+ * DONT_BREAK_LINES: don't break lines at 76 characters
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
+ * <p>
+ * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+ *
+ * @param serializableObject The object to encode
+ * @param options Specified options
+ * @return The Base64-encoded object
+ * @see Base64#GZIP
+ * @see Base64#DONT_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeObject( java.io.Serializable serializableObject, int options )
+ {
+ // Streams
+ java.io.ByteArrayOutputStream baos = null;
+ java.io.OutputStream b64os = null;
+ java.io.ObjectOutputStream oos = null;
+ java.util.zip.GZIPOutputStream gzos = null;
+
+ // Isolate options
+ int gzip = (options & GZIP);
+ int dontBreakLines = (options & DONT_BREAK_LINES);
+
+ try
+ {
+ // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
+ baos = new java.io.ByteArrayOutputStream();
+ b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
+
+ // GZip?
+ if( gzip == GZIP )
+ {
+ gzos = new java.util.zip.GZIPOutputStream( b64os );
+ oos = new java.io.ObjectOutputStream( gzos );
+ } // end if: gzip
+ else
+ oos = new java.io.ObjectOutputStream( b64os );
+
+ oos.writeObject( serializableObject );
+ } // end try
+ catch( java.io.IOException e )
+ {
+ e.printStackTrace();
+ return null;
+ } // end catch
+ finally
+ {
+ closeStream(oos);
+ closeStream(gzos);
+ closeStream(b64os);
+ closeStream(baos);
+ } // end finally
+
+ // Return value according to relevant encoding.
+ try
+ {
+ return new String( baos.toByteArray(), PREFERRED_ENCODING );
+ } // end try
+ catch (java.io.UnsupportedEncodingException uue)
+ {
+ return new String( baos.toByteArray() );
+ } // end catch
+
+ } // end encode
+
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * Does not GZip-compress data.
+ *
+ * @param source The data to convert
+ * @return encoded base64 representation of source.
+ * @since 1.4
+ */
+ public static String encodeBytes( byte[] source )
+ {
+ return encodeBytes( source, 0, source.length, NO_OPTIONS );
+ } // end encodeBytes
+
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * <p>
+ * Valid options:<pre>
+ * GZIP: gzip-compresses object before encoding it.
+ * DONT_BREAK_LINES: don't break lines at 76 characters
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+ *
+ *
+ * @param source The data to convert
+ * @param options Specified options
+ * @return encoded base64 representation of source.
+ * @see Base64#GZIP
+ * @see Base64#DONT_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeBytes( byte[] source, int options )
+ {
+ return encodeBytes( source, 0, source.length, options );
+ } // end encodeBytes
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * Does not GZip-compress data.
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @return encoded base64 representation of source.
+ * @since 1.4
+ */
+ public static String encodeBytes( byte[] source, int off, int len )
+ {
+ return encodeBytes( source, off, len, NO_OPTIONS );
+ } // end encodeBytes
+
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * <p>
+ * Valid options:<pre>
+ * GZIP: gzip-compresses object before encoding it.
+ * DONT_BREAK_LINES: don't break lines at 76 characters
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+ *
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @param options Specified options
+ * @return encoded base64 representation of source.
+ * @see Base64#GZIP
+ * @see Base64#DONT_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeBytes( byte[] source, int off, int len, int options )
+ {
+ // Isolate options
+ int dontBreakLines = ( options & DONT_BREAK_LINES );
+ int gzip = ( options & GZIP );
+
+ // Compress?
+ if( gzip == GZIP )
+ {
+ java.io.ByteArrayOutputStream baos = null;
+ java.util.zip.GZIPOutputStream gzos = null;
+ Base64.OutputStream b64os = null;
+
+
+ try
+ {
+ // GZip -> Base64 -> ByteArray
+ baos = new java.io.ByteArrayOutputStream();
+ b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
+ gzos = new java.util.zip.GZIPOutputStream( b64os );
+
+ gzos.write( source, off, len );
+ gzos.close();
+ } // end try
+ catch( java.io.IOException e )
+ {
+ e.printStackTrace();
+ return null;
+ } // end catch
+ finally
+ {
+ closeStream(gzos);
+ closeStream(b64os);
+ closeStream(baos);
+ } // end finally
+
+ // Return value according to relevant encoding.
+ try
+ {
+ return new String( baos.toByteArray(), PREFERRED_ENCODING );
+ } // end try
+ catch (java.io.UnsupportedEncodingException uue)
+ {
+ return new String( baos.toByteArray() );
+ } // end catch
+ } // end if: compress
+
+ // Else, don't compress. Better not to use streams at all then.
+ else
+ {
+ // Convert option to boolean in way that code likes it.
+ boolean breakLines = dontBreakLines == 0;
+
+ int len43 = len * 4 / 3;
+ byte[] outBuff = new byte[ ( len43 ) // Main 4:3
+ + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
+ + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
+ int d = 0;
+ int e = 0;
+ int len2 = len - 2;
+ int lineLength = 0;
+ for( ; d < len2; d+=3, e+=4 )
+ {
+ encode3to4( source, d+off, 3, outBuff, e );
+
+ lineLength += 4;
+ if( breakLines && lineLength == MAX_LINE_LENGTH )
+ {
+ outBuff[e+4] = NEW_LINE;
+ e++;
+ lineLength = 0;
+ } // end if: end of line
+ } // end for: each piece of array
+
+ if( d < len )
+ {
+ encode3to4( source, d+off, len - d, outBuff, e );
+ e += 4;
+ } // end if: some padding needed
+
+
+ // Return value according to relevant encoding.
+ try
+ {
+ return new String( outBuff, 0, e, PREFERRED_ENCODING );
+ } // end try
+ catch (java.io.UnsupportedEncodingException uue)
+ {
+ return new String( outBuff, 0, e );
+ } // end catch
+
+ } // end else: don't compress
+
+ } // end encodeBytes
+
+
+
+
+
+/* ******** D E C O D I N G M E T H O D S ******** */
+
+
+ /**
+ * Decodes four bytes from array <var>source</var>
+ * and writes the resulting bytes (up to three of them)
+ * to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accommodate <var>srcOffset</var> + 4 for
+ * the <var>source</var> array or <var>destOffset</var> + 3 for
+ * the <var>destination</var> array.
+ * This method returns the actual number of bytes that
+ * were converted from the Base64 encoding.
+ *
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @return the number of decoded bytes converted
+ * @since 1.3
+ */
+ private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset )
+ {
+ // Example: Dk==
+ if( source[ srcOffset + 2] == EQUALS_SIGN )
+ {
+ // Two ways to do the same thing. Don't know which way I like best.
+ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
+ int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
+ | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
+
+ destination[ destOffset ] = (byte)( outBuff >>> 16 );
+ return 1;
+ }
+
+ // Example: DkL=
+ else if( source[ srcOffset + 3 ] == EQUALS_SIGN )
+ {
+ // Two ways to do the same thing. Don't know which way I like best.
+ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+ // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
+ int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
+ | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
+ | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 );
+
+ destination[ destOffset ] = (byte)( outBuff >>> 16 );
+ destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 );
+ return 2;
+ }
+
+ // Example: DkLE
+ else
+ {
+ try{
+ // Two ways to do the same thing. Don't know which way I like best.
+ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+ // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
+ // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
+ int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
+ | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
+ | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6)
+ | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) );
+
+
+ destination[ destOffset ] = (byte)( outBuff >> 16 );
+ destination[ destOffset + 1 ] = (byte)( outBuff >> 8 );
+ destination[ destOffset + 2 ] = (byte)( outBuff );
+
+ return 3;
+ }catch( Exception e){
+ System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) );
+ System.out.println(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) );
+ System.out.println(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) );
+ System.out.println(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) );
+ return -1;
+ } //e nd catch
+ }
+ } // end decodeToBytes
+
+
+
+
+ /**
+ * Very low-level access to decoding ASCII characters in
+ * the form of a byte array. Does not support automatically
+ * gunzipping or any other "fancy" features.
+ *
+ * @param source The Base64 encoded data
+ * @param off The offset of where to begin decoding
+ * @param len The length of characters to decode
+ * @return decoded data
+ * @since 1.3
+ */
+ public static byte[] decode( byte[] source, int off, int len )
+ {
+ int len34 = len * 3 / 4;
+ byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
+ int outBuffPosn = 0;
+
+ byte[] b4 = new byte[4];
+ int b4Posn = 0;
+ int i = 0;
+ byte sbiCrop = 0;
+ byte sbiDecode = 0;
+ for( i = off; i < off+len; i++ )
+ {
+ sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits
+ sbiDecode = DECODABET[ sbiCrop ];
+
+ if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better
+ {
+ if( sbiDecode >= EQUALS_SIGN_ENC )
+ {
+ b4[ b4Posn++ ] = sbiCrop;
+ if( b4Posn > 3 )
+ {
+ outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn );
+ b4Posn = 0;
+
+ // If that was the equals sign, break out of 'for' loop
+ if( sbiCrop == EQUALS_SIGN )
+ break;
+ } // end if: quartet built
+
+ } // end if: equals sign or better
+
+ } // end if: white space, equals sign or better
+ else
+ {
+ System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" );
+ return null;
+ } // end else:
+ } // each input character
+
+ byte[] out = new byte[ outBuffPosn ];
+ System.arraycopy( outBuff, 0, out, 0, outBuffPosn );
+ return out;
+ } // end decode
+
+
+
+
+ /**
+ * Decodes data from Base64 notation, automatically
+ * detecting gzip-compressed data and decompressing it.
+ *
+ * @param s the string to decode
+ * @return the decoded data
+ * @since 1.4
+ */
+ public static byte[] decode( String s )
+ {
+ byte[] bytes;
+ try
+ {
+ bytes = s.getBytes( PREFERRED_ENCODING );
+ } // end try
+ catch( java.io.UnsupportedEncodingException uee )
+ {
+ bytes = s.getBytes();
+ } // end catch
+ //</change>
+
+ // Decode
+ bytes = decode( bytes, 0, bytes.length );
+
+
+ // Check to see if it's gzip-compressed
+ // GZIP Magic Two-Byte Number: 0x8b1f (35615)
+ if( bytes != null && bytes.length >= 4 )
+ {
+
+ int head = (bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
+ if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head )
+ {
+ java.io.ByteArrayInputStream bais = null;
+ java.util.zip.GZIPInputStream gzis = null;
+ java.io.ByteArrayOutputStream baos = null;
+ byte[] buffer = new byte[2048];
+ int length = 0;
+
+ try
+ {
+ baos = new java.io.ByteArrayOutputStream();
+ bais = new java.io.ByteArrayInputStream( bytes );
+ gzis = new java.util.zip.GZIPInputStream( bais );
+
+ while( ( length = gzis.read( buffer ) ) >= 0 )
+ {
+ baos.write(buffer,0,length);
+ } // end while: reading input
+
+ // No error? Get new bytes.
+ bytes = baos.toByteArray();
+
+ } // end try
+ catch( java.io.IOException e )
+ {
+ // Just return originally-decoded bytes
+ } // end catch
+ finally
+ {
+ closeStream(baos);
+ closeStream(gzis);
+ closeStream(bais);
+ } // end finally
+
+ } // end if: gzipped
+ } // end if: bytes.length >= 2
+
+ return bytes;
+ } // end decode
+
+
+
+
+ /**
+ * Attempts to decode Base64 data and deserialize a Java
+ * Object within. Returns <tt>null</tt> if there was an error.
+ *
+ * @param encodedObject The Base64 data to decode
+ * @return The decoded and deserialized object
+ * @since 1.5
+ */
+ public static Object decodeToObject( String encodedObject )
+ {
+ // Decode and gunzip if necessary
+ byte[] objBytes = decode( encodedObject );
+
+ java.io.ByteArrayInputStream bais = null;
+ java.io.ObjectInputStream ois = null;
+ Object obj = null;
+
+ try
+ {
+ bais = new java.io.ByteArrayInputStream( objBytes );
+ ois = new java.io.ObjectInputStream( bais );
+
+ obj = ois.readObject();
+ } // end try
+ catch( java.io.IOException e )
+ {
+ e.printStackTrace();
+ } // end catch
+ catch( java.lang.ClassNotFoundException e )
+ {
+ e.printStackTrace();
+ } // end catch
+ finally
+ {
+ closeStream(bais);
+ closeStream(ois);
+ } // end finally
+
+ return obj;
+ } // end decodeObject
+
+
+
+ /**
+ * Convenience method for encoding data to a file.
+ *
+ * @param dataToEncode byte array of data to encode in base64 form
+ * @param filename Filename for saving encoded data
+ * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
+ *
+ * @since 2.1
+ */
+ public static boolean encodeToFile( byte[] dataToEncode, String filename )
+ {
+ boolean success = false;
+ Base64.OutputStream bos = null;
+ try
+ {
+ bos = new Base64.OutputStream(
+ new java.io.FileOutputStream( filename ), Base64.ENCODE );
+ bos.write( dataToEncode );
+ success = true;
+ } // end try
+ catch( java.io.IOException e )
+ {
+
+ success = false;
+ } // end catch: IOException
+ finally
+ {
+ closeStream(bos);
+ } // end finally
+
+ return success;
+ } // end encodeToFile
+
+
+ /**
+ * Convenience method for decoding data to a file.
+ *
+ * @param dataToDecode Base64-encoded data as a string
+ * @param filename Filename for saving decoded data
+ * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
+ *
+ * @since 2.1
+ */
+ public static boolean decodeToFile( String dataToDecode, String filename )
+ {
+ boolean success = false;
+ Base64.OutputStream bos = null;
+ try
+ {
+ bos = new Base64.OutputStream(
+ new java.io.FileOutputStream( filename ), Base64.DECODE );
+ bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) );
+ success = true;
+ } // end try
+ catch( java.io.IOException e )
+ {
+ success = false;
+ } // end catch: IOException
+ finally
+ {
+ closeStream(bos);
+ } // end finally
+
+ return success;
+ } // end decodeToFile
+
+
+
+
+ /**
+ * Convenience method for reading a base64-encoded
+ * file and decoding it.
+ *
+ * @param filename Filename for reading encoded data
+ * @return decoded byte array or null if unsuccessful
+ *
+ * @since 2.1
+ */
+ public static byte[] decodeFromFile( String filename )
+ {
+ byte[] decodedData = null;
+ Base64.InputStream bis = null;
+ try
+ {
+ // Set up some useful variables
+ java.io.File file = new java.io.File( filename );
+ byte[] buffer = null;
+ int length = 0;
+ int numBytes = 0;
+
+ // Check for size of file
+ if( file.length() > Integer.MAX_VALUE )
+ {
+ System.err.println( "File is too big for this convenience method (" + file.length() + " bytes)." );
+ return null;
+ } // end if: file too big for int index
+ buffer = new byte[ (int)file.length() ];
+
+ // Open a stream
+ bis = new Base64.InputStream(
+ new java.io.BufferedInputStream(
+ new java.io.FileInputStream( file ) ), Base64.DECODE );
+
+ // Read until done
+ while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )
+ length += numBytes;
+
+ // Save in a variable to return
+ decodedData = new byte[ length ];
+ System.arraycopy( buffer, 0, decodedData, 0, length );
+
+ } // end try
+ catch( java.io.IOException e )
+ {
+ System.err.println( "Error decoding from file " + filename );
+ } // end catch: IOException
+ finally
+ {
+ closeStream(bis);
+ } // end finally
+
+ return decodedData;
+ } // end decodeFromFile
+
+
+
+ /**
+ * Convenience method for reading a binary file
+ * and base64-encoding it.
+ *
+ * @param filename Filename for reading binary data
+ * @return base64-encoded string or null if unsuccessful
+ *
+ * @since 2.1
+ */
+ public static String encodeFromFile( String filename )
+ {
+ String encodedData = null;
+ Base64.InputStream bis = null;
+ try
+ {
+ // Set up some useful variables
+ java.io.File file = new java.io.File( filename );
+ byte[] buffer = new byte[ (int)(file.length() * 1.4) ];
+ int length = 0;
+ int numBytes = 0;
+
+ // Open a stream
+ bis = new Base64.InputStream(
+ new java.io.BufferedInputStream(
+ new java.io.FileInputStream( file ) ), Base64.ENCODE );
+
+ // Read until done
+ while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )
+ length += numBytes;
+
+ // Save in a variable to return
+ encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING );
+
+ } // end try
+ catch( java.io.IOException e )
+ {
+ System.err.println( "Error encoding from file " + filename );
+ } // end catch: IOException
+ finally
+ {
+ closeStream(bis);
+ } // end finally
+
+ return encodedData;
+ } // end encodeFromFile
+
+
+
+
+ /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
+
+
+
+ /**
+ * A {@link Base64.InputStream} will read data from another
+ * <tt>java.io.InputStream</tt>, given in the constructor,
+ * and encode/decode to/from Base64 notation on the fly.
+ *
+ * @see Base64
+ * @since 1.3
+ */
+ public static class InputStream extends java.io.FilterInputStream
+ {
+ private boolean encode; // Encoding or decoding
+ private int position; // Current position in the buffer
+ private byte[] buffer; // Small buffer holding converted data
+ private int bufferLength; // Length of buffer (3 or 4)
+ private int numSigBytes; // Number of meaningful bytes in the buffer
+ private int lineLength;
+ private boolean breakLines; // Break lines at less than 80 characters
+
+
+ /**
+ * Constructs a {@link Base64.InputStream} in DECODE mode.
+ *
+ * @param in the <tt>java.io.InputStream</tt> from which to read data.
+ * @since 1.3
+ */
+ public InputStream( java.io.InputStream in )
+ {
+ this( in, DECODE );
+ } // end constructor
+
+
+ /**
+ * Constructs a {@link Base64.InputStream} in
+ * either ENCODE or DECODE mode.
+ * <p>
+ * Valid options:<pre>
+ * ENCODE or DECODE: Encode or Decode as data is read.
+ * DONT_BREAK_LINES: don't break lines at 76 characters
+ * (only meaningful when encoding)
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
+ *
+ *
+ * @param in the <tt>java.io.InputStream</tt> from which to read data.
+ * @param options Specified options
+ * @see Base64#ENCODE
+ * @see Base64#DECODE
+ * @see Base64#DONT_BREAK_LINES
+ * @since 2.0
+ */
+ public InputStream( java.io.InputStream in, int options )
+ {
+ super( in );
+ this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
+ this.encode = (options & ENCODE) == ENCODE;
+ this.bufferLength = encode ? 4 : 3;
+ this.buffer = new byte[ bufferLength ];
+ this.position = -1;
+ this.lineLength = 0;
+ } // end constructor
+
+ /**
+ * Reads enough of the input stream to convert
+ * to/from Base64 and returns the next byte.
+ *
+ * @return next byte
+ * @since 1.3
+ */
+ public int read() throws java.io.IOException
+ {
+ // Do we need to get data?
+ if( position < 0 )
+ {
+ if( encode )
+ {
+ byte[] b3 = new byte[3];
+ int numBinaryBytes = 0;
+ for( int i = 0; i < 3; i++ )
+ {
+ try
+ {
+ int b = in.read();
+
+ // If end of stream, b is -1.
+ if( b >= 0 )
+ {
+ b3[i] = (byte)b;
+ numBinaryBytes++;
+ } // end if: not end of stream
+
+ } // end try: read
+ catch( java.io.IOException e )
+ {
+ // Only a problem if we got no data at all.
+ if( i == 0 )
+ throw e;
+
+ } // end catch
+ } // end for: each needed input byte
+
+ if( numBinaryBytes > 0 )
+ {
+ encode3to4( b3, 0, numBinaryBytes, buffer, 0 );
+ position = 0;
+ numSigBytes = 4;
+ } // end if: got data
+ else
+ {
+ return -1;
+ } // end else
+ } // end if: encoding
+
+ // Else decoding
+ else
+ {
+ byte[] b4 = new byte[4];
+ int i = 0;
+ for( i = 0; i < 4; i++ )
+ {
+ // Read four "meaningful" bytes:
+ int b = 0;
+ do{ b = in.read(); }
+ while( b >= 0 && DECODABET[ b & 0x7f ] <= WHITE_SPACE_ENC );
+
+ if( b < 0 )
+ break; // Reads a -1 if end of stream
+
+ b4[i] = (byte)b;
+ } // end for: each needed input byte
+
+ if( i == 4 )
+ {
+ numSigBytes = decode4to3( b4, 0, buffer, 0 );
+ position = 0;
+ } // end if: got four characters
+ else if( i == 0 ){
+ return -1;
+ } // end else if: also padded correctly
+ else
+ {
+ // Must have broken out from above.
+ throw new java.io.IOException( "Improperly padded Base64 input." );
+ } // end
+
+ } // end else: decode
+ } // end else: get data
+
+ // Got data?
+ if( position >= 0 )
+ {
+ // End of relevant data?
+ if( /*!encode &&*/ position >= numSigBytes )
+ return -1;
+
+ if( encode && breakLines && lineLength >= MAX_LINE_LENGTH )
+ {
+ lineLength = 0;
+ return '\n';
+ } // end if
+ else
+ {
+ lineLength++; // This isn't important when decoding
+ // but throwing an extra "if" seems
+ // just as wasteful.
+
+ int b = buffer[ position++ ];
+
+ if( position >= bufferLength )
+ position = -1;
+
+ return b & 0xFF; // This is how you "cast" a byte that's
+ // intended to be unsigned.
+ } // end else
+ } // end if: position >= 0
+
+ // Else error
+ else
+ {
+ // When JDK1.4 is more accepted, use an assertion here.
+ throw new java.io.IOException( "Error in Base64 code reading stream." );
+ } // end else
+ } // end read
+
+
+ /**
+ * Calls {@link #read()} repeatedly until the end of stream
+ * is reached or <var>len</var> bytes are read.
+ * Returns number of bytes read into array or -1 if
+ * end of stream is encountered.
+ *
+ * @param dest array to hold values
+ * @param off offset for array
+ * @param len max number of bytes to read into array
+ * @return bytes read into array or -1 if end of stream is encountered.
+ * @since 1.3
+ */
+ public int read( byte[] dest, int off, int len ) throws java.io.IOException
+ {
+ int i;
+ int b;
+ for( i = 0; i < len; i++ )
+ {
+ b = read();
+
+ //if( b < 0 && i == 0 )
+ // return -1;
+
+ if( b >= 0 )
+ dest[off + i] = (byte)b;
+ else if( i == 0 )
+ return -1;
+ else
+ break; // Out of 'for' loop
+ } // end for: each byte read
+ return i;
+ } // end read
+
+ } // end inner class InputStream
+
+
+
+
+
+
+ /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
+
+
+
+ /**
+ * A {@link Base64.OutputStream} will write data to another
+ * <tt>java.io.OutputStream</tt>, given in the constructor,
+ * and encode/decode to/from Base64 notation on the fly.
+ *
+ * @see Base64
+ * @since 1.3
+ */
+ public static class OutputStream extends java.io.FilterOutputStream
+ {
+ private boolean encode;
+ private int position;
+ private byte[] buffer;
+ private int bufferLength;
+ private int lineLength;
+ private boolean breakLines;
+ private byte[] b4; // Scratch used in a few places
+ private boolean suspendEncoding;
+
+ /**
+ * Constructs a {@link Base64.OutputStream} in ENCODE mode.
+ *
+ * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
+ * @since 1.3
+ */
+ public OutputStream( java.io.OutputStream out )
+ {
+ this( out, ENCODE );
+ } // end constructor
+
+
+ /**
+ * Constructs a {@link Base64.OutputStream} in
+ * either ENCODE or DECODE mode.
+ * <p>
+ * Valid options:<pre>
+ * ENCODE or DECODE: Encode or Decode as data is read.
+ * DONT_BREAK_LINES: don't break lines at 76 characters
+ * (only meaningful when encoding)
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
+ *
+ * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
+ * @param options Specified options.
+ * @see Base64#ENCODE
+ * @see Base64#DECODE
+ * @see Base64#DONT_BREAK_LINES
+ * @since 1.3
+ */
+ public OutputStream( java.io.OutputStream out, int options )
+ {
+ super( out );
+ this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
+ this.encode = (options & ENCODE) == ENCODE;
+ this.bufferLength = encode ? 3 : 4;
+ this.buffer = new byte[ bufferLength ];
+ this.position = 0;
+ this.lineLength = 0;
+ this.suspendEncoding = false;
+ this.b4 = new byte[4];
+ } // end constructor
+
+
+ /**
+ * Writes the byte to the output stream after
+ * converting to/from Base64 notation.
+ * When encoding, bytes are buffered three
+ * at a time before the output stream actually
+ * gets a write() call.
+ * When decoding, bytes are buffered four
+ * at a time.
+ *
+ * @param theByte the byte to write
+ * @since 1.3
+ */
+ public void write(int theByte) throws java.io.IOException
+ {
+ // Encoding suspended?
+ if( suspendEncoding )
+ {
+ super.out.write( theByte );
+ return;
+ } // end if: suspended
+
+ // Encode?
+ if( encode )
+ {
+ buffer[ position++ ] = (byte)theByte;
+ if( position >= bufferLength ) // Enough to encode.
+ {
+ out.write( encode3to4( b4, buffer, bufferLength ) );
+
+ lineLength += 4;
+ if( breakLines && lineLength >= MAX_LINE_LENGTH )
+ {
+ out.write( NEW_LINE );
+ lineLength = 0;
+ } // end if: end of line
+
+ position = 0;
+ } // end if: enough to output
+ } // end if: encoding
+
+ // Else, Decoding
+ else
+ {
+ // Meaningful Base64 character?
+ if( DECODABET[ theByte & 0x7f ] > WHITE_SPACE_ENC )
+ {
+ buffer[ position++ ] = (byte)theByte;
+ if( position >= bufferLength ) // Enough to output.
+ {
+ int len = Base64.decode4to3( buffer, 0, b4, 0 );
+ out.write( b4, 0, len );
+ //out.write( Base64.decode4to3( buffer ) );
+ position = 0;
+ } // end if: enough to output
+ } // end if: meaningful base64 character
+ else if( DECODABET[ theByte & 0x7f ] != WHITE_SPACE_ENC )
+ {
+ throw new java.io.IOException( "Invalid character in Base64 data." );
+ } // end else: not white space either
+ } // end else: decoding
+ } // end write
+
+
+
+ /**
+ * Calls {@link #write(int)} repeatedly until <var>len</var>
+ * bytes are written.
+ *
+ * @param theBytes array from which to read bytes
+ * @param off offset for array
+ * @param len max number of bytes to read into array
+ * @since 1.3
+ */
+ public void write( byte[] theBytes, int off, int len ) throws java.io.IOException
+ {
+ // Encoding suspended?
+ if( suspendEncoding )
+ {
+ super.out.write( theBytes, off, len );
+ return;
+ } // end if: suspended
+
+ for( int i = 0; i < len; i++ )
+ {
+ write( theBytes[ off + i ] );
+ } // end for: each byte written
+
+ } // end write
+
+
+
+ /**
+ * Method added by PHIL. [Thanks, PHIL. -Rob]
+ * This pads the buffer without closing the stream.
+ * @throws java.io.IOException input was not properly padded.
+ */
+ public void flushBase64() throws java.io.IOException
+ {
+ if( position > 0 )
+ {
+ if( encode )
+ {
+ out.write( encode3to4( b4, buffer, position ) );
+ position = 0;
+ } // end if: encoding
+ else
+ {
+ throw new java.io.IOException( "Base64 input not properly padded." );
+ } // end else: decoding
+ } // end if: buffer partially full
+
+ } // end flush
+
+
+ /**
+ * Flushes and closes (I think, in the superclass) the stream.
+ *
+ * @since 1.3
+ */
+ public void close() throws java.io.IOException
+ {
+ // 1. Ensure that pending characters are written
+ flushBase64();
+
+ // 2. Actually close the stream
+ // Base class both flushes and closes.
+ super.close();
+
+ buffer = null;
+ out = null;
+ } // end close
+
+
+
+ /**
+ * Suspends encoding of the stream.
+ * May be helpful if you need to embed a piece of
+ * base640-encoded data in a stream.
+ *
+ * @throws java.io.IOException input was not properly padded.
+ * @since 1.5.1
+ */
+ public void suspendEncoding() throws java.io.IOException
+ {
+ flushBase64();
+ this.suspendEncoding = true;
+ } // end suspendEncoding
+
+
+ /**
+ * Resumes encoding of the stream.
+ * May be helpful if you need to embed a piece of
+ * base640-encoded data in a stream.
+ *
+ * @since 1.5.1
+ */
+ public void resumeEncoding()
+ {
+ this.suspendEncoding = false;
+ } // end resumeEncoding
+
+
+
+ } // end inner class OutputStream
+
+
+} // end class Base64
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
new file mode 100644
index 0000000000..a52a6530ef
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.io.File;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/** Abstraction to support various file system operations not in Java. */
+public abstract class FS {
+ /** The implementation selected for this operating system and JRE. */
+ public static final FS INSTANCE;
+
+ static {
+ if (FS_Win32.detect()) {
+ if (FS_Win32_Cygwin.detect())
+ INSTANCE = new FS_Win32_Cygwin();
+ else
+ INSTANCE = new FS_Win32();
+ } else if (FS_POSIX_Java6.detect())
+ INSTANCE = new FS_POSIX_Java6();
+ else
+ INSTANCE = new FS_POSIX_Java5();
+ }
+
+ /**
+ * Does this operating system and JRE support the execute flag on files?
+ *
+ * @return true if this implementation can provide reasonably accurate
+ * executable bit information; false otherwise.
+ */
+ public abstract boolean supportsExecute();
+
+ /**
+ * Determine if the file is executable (or not).
+ * <p>
+ * Not all platforms and JREs support executable flags on files. If the
+ * feature is unsupported this method will always return false.
+ *
+ * @param f
+ * abstract path to test.
+ * @return true if the file is believed to be executable by the user.
+ */
+ public abstract boolean canExecute(File f);
+
+ /**
+ * Set a file to be executable by the user.
+ * <p>
+ * Not all platforms and JREs support executable flags on files. If the
+ * feature is unsupported this method will always return false and no
+ * changes will be made to the file specified.
+ *
+ * @param f
+ * path to modify the executable status of.
+ * @param canExec
+ * true to enable execution; false to disable it.
+ * @return true if the change succeeded; false otherwise.
+ */
+ public abstract boolean setExecute(File f, boolean canExec);
+
+ /**
+ * Resolve this file to its actual path name that the JRE can use.
+ * <p>
+ * This method can be relatively expensive. Computing a translation may
+ * require forking an external process per path name translated. Callers
+ * should try to minimize the number of translations necessary by caching
+ * the results.
+ * <p>
+ * Not all platforms and JREs require path name translation. Currently only
+ * Cygwin on Win32 require translation for Cygwin based paths.
+ *
+ * @param dir
+ * directory relative to which the path name is.
+ * @param name
+ * path name to translate.
+ * @return the translated path. <code>new File(dir,name)</code> if this
+ * platform does not require path name translation.
+ */
+ public static File resolve(final File dir, final String name) {
+ return INSTANCE.resolveImpl(dir, name);
+ }
+
+ /**
+ * Resolve this file to its actual path name that the JRE can use.
+ * <p>
+ * This method can be relatively expensive. Computing a translation may
+ * require forking an external process per path name translated. Callers
+ * should try to minimize the number of translations necessary by caching
+ * the results.
+ * <p>
+ * Not all platforms and JREs require path name translation. Currently only
+ * Cygwin on Win32 require translation for Cygwin based paths.
+ *
+ * @param dir
+ * directory relative to which the path name is.
+ * @param name
+ * path name to translate.
+ * @return the translated path. <code>new File(dir,name)</code> if this
+ * platform does not require path name translation.
+ */
+ protected File resolveImpl(final File dir, final String name) {
+ final File abspn = new File(name);
+ if (abspn.isAbsolute())
+ return abspn;
+ return new File(dir, name);
+ }
+
+ /**
+ * Determine the user's home directory (location where preferences are).
+ * <p>
+ * This method can be expensive on the first invocation if path name
+ * translation is required. Subsequent invocations return a cached result.
+ * <p>
+ * Not all platforms and JREs require path name translation. Currently only
+ * Cygwin on Win32 requires translation of the Cygwin HOME directory.
+ *
+ * @return the user's home directory; null if the user does not have one.
+ */
+ public static File userHome() {
+ return USER_HOME.home;
+ }
+
+ private static class USER_HOME {
+ static final File home = INSTANCE.userHomeImpl();
+ }
+
+ /**
+ * Determine the user's home directory (location where preferences are).
+ *
+ * @return the user's home directory; null if the user does not have one.
+ */
+ protected File userHomeImpl() {
+ final String home = AccessController
+ .doPrivileged(new PrivilegedAction<String>() {
+ public String run() {
+ return System.getProperty("user.home");
+ }
+ });
+ if (home == null || home.length() == 0)
+ return null;
+ return new File(home).getAbsoluteFile();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java5.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java5.java
new file mode 100644
index 0000000000..4ce0366fc8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java5.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.io.File;
+
+class FS_POSIX_Java5 extends FS {
+ public boolean supportsExecute() {
+ return false;
+ }
+
+ public boolean canExecute(final File f) {
+ return false;
+ }
+
+ public boolean setExecute(final File f, final boolean canExec) {
+ return false;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java6.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java6.java
new file mode 100644
index 0000000000..8a86d2e65f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java6.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class FS_POSIX_Java6 extends FS {
+ private static final Method canExecute;
+
+ private static final Method setExecute;
+
+ static {
+ canExecute = needMethod(File.class, "canExecute");
+ setExecute = needMethod(File.class, "setExecutable", Boolean.TYPE);
+ }
+
+ static boolean detect() {
+ return canExecute != null && setExecute != null;
+ }
+
+ private static Method needMethod(final Class<?> on, final String name,
+ final Class<?>... args) {
+ try {
+ return on.getMethod(name, args);
+ } catch (SecurityException e) {
+ return null;
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+
+ public boolean supportsExecute() {
+ return true;
+ }
+
+ public boolean canExecute(final File f) {
+ try {
+ final Object r = canExecute.invoke(f, (Object[]) null);
+ return ((Boolean) r).booleanValue();
+ } catch (IllegalArgumentException e) {
+ throw new Error(e);
+ } catch (IllegalAccessException e) {
+ throw new Error(e);
+ } catch (InvocationTargetException e) {
+ throw new Error(e);
+ }
+ }
+
+ public boolean setExecute(final File f, final boolean canExec) {
+ try {
+ final Object r;
+ r = setExecute.invoke(f, new Object[] { Boolean.valueOf(canExec) });
+ return ((Boolean) r).booleanValue();
+ } catch (IllegalArgumentException e) {
+ throw new Error(e);
+ } catch (IllegalAccessException e) {
+ throw new Error(e);
+ } catch (InvocationTargetException e) {
+ throw new Error(e);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
new file mode 100644
index 0000000000..79bf1e82e8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.io.File;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+class FS_Win32 extends FS {
+ static boolean detect() {
+ final String osDotName = AccessController
+ .doPrivileged(new PrivilegedAction<String>() {
+ public String run() {
+ return System.getProperty("os.name");
+ }
+ });
+ return osDotName != null
+ && StringUtils.toLowerCase(osDotName).indexOf("windows") != -1;
+ }
+
+ public boolean supportsExecute() {
+ return false;
+ }
+
+ public boolean canExecute(final File f) {
+ return false;
+ }
+
+ public boolean setExecute(final File f, final boolean canExec) {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
new file mode 100644
index 0000000000..f727084860
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+class FS_Win32_Cygwin extends FS_Win32 {
+ private static String cygpath;
+
+ static boolean detect() {
+ final String path = AccessController
+ .doPrivileged(new PrivilegedAction<String>() {
+ public String run() {
+ return System.getProperty("java.library.path");
+ }
+ });
+ if (path == null)
+ return false;
+ for (final String p : path.split(";")) {
+ final File e = new File(p, "cygpath.exe");
+ if (e.isFile()) {
+ cygpath = e.getAbsolutePath();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected File resolveImpl(final File dir, final String pn) {
+ try {
+ final Process p;
+
+ p = Runtime.getRuntime().exec(
+ new String[] { cygpath, "--windows", "--absolute", pn },
+ null, dir);
+ p.getOutputStream().close();
+
+ final BufferedReader lineRead = new BufferedReader(
+ new InputStreamReader(p.getInputStream(), "UTF-8"));
+ String r = null;
+ try {
+ r = lineRead.readLine();
+ } finally {
+ lineRead.close();
+ }
+
+ for (;;) {
+ try {
+ if (p.waitFor() == 0 && r != null && r.length() > 0)
+ return new File(r);
+ break;
+ } catch (InterruptedException ie) {
+ // Stop bothering me, I have a zombie to reap.
+ }
+ }
+ } catch (IOException ioe) {
+ // Fall through and use the default return.
+ //
+ }
+ return super.resolveImpl(dir, pn);
+ }
+
+ @Override
+ protected File userHomeImpl() {
+ final String home = AccessController
+ .doPrivileged(new PrivilegedAction<String>() {
+ public String run() {
+ return System.getenv("HOME");
+ }
+ });
+ if (home == null || home.length() == 0)
+ return super.userHomeImpl();
+ return resolveImpl(new File("."), home);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
new file mode 100644
index 0000000000..40134d0e4f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLEncoder;
+
+import org.eclipse.jgit.awtui.AwtAuthenticator;
+
+/** Extra utilities to support usage of HTTP. */
+public class HttpSupport {
+ /**
+ * Configure the JRE's standard HTTP based on <code>http_proxy</code>.
+ * <p>
+ * The popular libcurl library honors the <code>http_proxy</code>
+ * environment variable as a means of specifying an HTTP proxy for requests
+ * made behind a firewall. This is not natively recognized by the JRE, so
+ * this method can be used by command line utilities to configure the JRE
+ * before the first request is sent.
+ *
+ * @throws MalformedURLException
+ * the value in <code>http_proxy</code> is unsupportable.
+ */
+ public static void configureHttpProxy() throws MalformedURLException {
+ final String s = System.getenv("http_proxy");
+ if (s == null || s.equals(""))
+ return;
+
+ final URL u = new URL((s.indexOf("://") == -1) ? "http://" + s : s);
+ if (!"http".equals(u.getProtocol()))
+ throw new MalformedURLException("Invalid http_proxy: " + s
+ + ": Only http supported.");
+
+ final String proxyHost = u.getHost();
+ final int proxyPort = u.getPort();
+
+ System.setProperty("http.proxyHost", proxyHost);
+ if (proxyPort > 0)
+ System.setProperty("http.proxyPort", String.valueOf(proxyPort));
+
+ final String userpass = u.getUserInfo();
+ if (userpass != null && userpass.contains(":")) {
+ final int c = userpass.indexOf(':');
+ final String user = userpass.substring(0, c);
+ final String pass = userpass.substring(c + 1);
+ AwtAuthenticator.add(new AwtAuthenticator.CachedAuthentication(
+ proxyHost, proxyPort, user, pass));
+ }
+ }
+
+ /**
+ * URL encode a value string into an output buffer.
+ *
+ * @param urlstr
+ * the output buffer.
+ * @param key
+ * value which must be encoded to protected special characters.
+ */
+ public static void encode(final StringBuilder urlstr, final String key) {
+ if (key == null || key.length() == 0)
+ return;
+ try {
+ urlstr.append(URLEncoder.encode(key, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Could not URL encode to UTF-8", e);
+ }
+ }
+
+ /**
+ * Get the HTTP response code from the request.
+ * <p>
+ * Roughly the same as <code>c.getResponseCode()</code> but the
+ * ConnectException is translated to be more understandable.
+ *
+ * @param c
+ * connection the code should be obtained from.
+ * @return r HTTP status code, usually 200 to indicate success. See
+ * {@link HttpURLConnection} for other defined constants.
+ * @throws IOException
+ * communications error prevented obtaining the response code.
+ */
+ public static int response(final HttpURLConnection c) throws IOException {
+ try {
+ return c.getResponseCode();
+ } catch (ConnectException ce) {
+ final String host = c.getURL().getHost();
+ // The standard J2SE error message is not very useful.
+ //
+ if ("Connection timed out: connect".equals(ce.getMessage()))
+ throw new ConnectException("Connection time out: " + host);
+ throw new ConnectException(ce.getMessage() + " " + host);
+ }
+ }
+
+ /**
+ * Determine the proxy server (if any) needed to obtain a URL.
+ *
+ * @param proxySelector
+ * proxy support for the caller.
+ * @param u
+ * location of the server caller wants to talk to.
+ * @return proxy to communicate with the supplied URL.
+ * @throws ConnectException
+ * the proxy could not be computed as the supplied URL could not
+ * be read. This failure should never occur.
+ */
+ public static Proxy proxyFor(final ProxySelector proxySelector, final URL u)
+ throws ConnectException {
+ try {
+ return proxySelector.select(u.toURI()).get(0);
+ } catch (URISyntaxException e) {
+ final ConnectException err;
+ err = new ConnectException("Cannot determine proxy for " + u);
+ err.initCause(e);
+ throw err;
+ }
+ }
+
+ private HttpSupport() {
+ // Utility class only.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
new file mode 100644
index 0000000000..510f2a4db9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+/** A more efficient List<Integer> using a primitive integer array. */
+public class IntList {
+ private int[] entries;
+
+ private int count;
+
+ /** Create an empty list with a default capacity. */
+ public IntList() {
+ this(10);
+ }
+
+ /**
+ * Create an empty list with the specified capacity.
+ *
+ * @param capacity
+ * number of entries the list can initially hold.
+ */
+ public IntList(final int capacity) {
+ entries = new int[capacity];
+ }
+
+ /** @return number of entries in this list */
+ public int size() {
+ return count;
+ }
+
+ /**
+ * @param i
+ * index to read, must be in the range [0, {@link #size()}).
+ * @return the number at the specified index
+ * @throws ArrayIndexOutOfBoundsException
+ * the index outside the valid range
+ */
+ public int get(final int i) {
+ if (count <= i)
+ throw new ArrayIndexOutOfBoundsException(i);
+ return entries[i];
+ }
+
+ /** Empty this list */
+ public void clear() {
+ count = 0;
+ }
+
+ /**
+ * Add an entry to the end of the list.
+ *
+ * @param n
+ * the number to add.
+ */
+ public void add(final int n) {
+ if (count == entries.length)
+ grow();
+ entries[count++] = n;
+ }
+
+ /**
+ * Pad the list with entries.
+ *
+ * @param toIndex
+ * index position to stop filling at. 0 inserts no filler. 1
+ * ensures the list has a size of 1, adding <code>val</code> if
+ * the list is currently empty.
+ * @param val
+ * value to insert into padded positions.
+ */
+ public void fillTo(int toIndex, final int val) {
+ while (count < toIndex)
+ add(val);
+ }
+
+ private void grow() {
+ final int[] n = new int[(entries.length + 16) * 3 / 2];
+ System.arraycopy(entries, 0, n, 0, count);
+ entries = n;
+ }
+
+ public String toString() {
+ final StringBuilder r = new StringBuilder();
+ r.append('[');
+ for (int i = 0; i < count; i++) {
+ if (i > 0)
+ r.append(", ");
+ r.append(entries[i]);
+ }
+ r.append(']');
+ return r.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/MutableInteger.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/MutableInteger.java
new file mode 100644
index 0000000000..cbe321086c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/MutableInteger.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+/** A boxed integer that can be modified. */
+public final class MutableInteger {
+ /** Current value of this boxed value. */
+ public int value;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java
new file mode 100644
index 0000000000..a42871dbc3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/** Conversion utilities for network byte order handling. */
+public final class NB {
+ /**
+ * Read an entire local file into memory as a byte array.
+ *
+ * @param path
+ * location of the file to read.
+ * @return complete contents of the requested local file.
+ * @throws FileNotFoundException
+ * the file does not exist.
+ * @throws IOException
+ * the file exists, but its contents cannot be read.
+ */
+ public static final byte[] readFully(final File path)
+ throws FileNotFoundException, IOException {
+ return readFully(path, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Read an entire local file into memory as a byte array.
+ *
+ * @param path
+ * location of the file to read.
+ * @param max
+ * maximum number of bytes to read, if the file is larger than
+ * this limit an IOException is thrown.
+ * @return complete contents of the requested local file.
+ * @throws FileNotFoundException
+ * the file does not exist.
+ * @throws IOException
+ * the file exists, but its contents cannot be read.
+ */
+ public static final byte[] readFully(final File path, final int max)
+ throws FileNotFoundException, IOException {
+ final FileInputStream in = new FileInputStream(path);
+ try {
+ final long sz = in.getChannel().size();
+ if (sz > max)
+ throw new IOException("File is too large: " + path);
+ final byte[] buf = new byte[(int) sz];
+ readFully(in, buf, 0, buf.length);
+ return buf;
+ } finally {
+ try {
+ in.close();
+ } catch (IOException ignored) {
+ // ignore any close errors, this was a read only stream
+ }
+ }
+ }
+
+ /**
+ * Read the entire byte array into memory, or throw an exception.
+ *
+ * @param fd
+ * input stream to read the data from.
+ * @param dst
+ * buffer that must be fully populated, [off, off+len).
+ * @param off
+ * position within the buffer to start writing to.
+ * @param len
+ * number of bytes that must be read.
+ * @throws EOFException
+ * the stream ended before dst was fully populated.
+ * @throws IOException
+ * there was an error reading from the stream.
+ */
+ public static void readFully(final InputStream fd, final byte[] dst,
+ int off, int len) throws IOException {
+ while (len > 0) {
+ final int r = fd.read(dst, off, len);
+ if (r <= 0)
+ throw new EOFException("Short read of block.");
+ off += r;
+ len -= r;
+ }
+ }
+
+ /**
+ * Read the entire byte array into memory, or throw an exception.
+ *
+ * @param fd
+ * file to read the data from.
+ * @param pos
+ * position to read from the file at.
+ * @param dst
+ * buffer that must be fully populated, [off, off+len).
+ * @param off
+ * position within the buffer to start writing to.
+ * @param len
+ * number of bytes that must be read.
+ * @throws EOFException
+ * the stream ended before dst was fully populated.
+ * @throws IOException
+ * there was an error reading from the stream.
+ */
+ public static void readFully(final FileChannel fd, long pos,
+ final byte[] dst, int off, int len) throws IOException {
+ while (len > 0) {
+ final int r = fd.read(ByteBuffer.wrap(dst, off, len), pos);
+ if (r <= 0)
+ throw new EOFException("Short read of block.");
+ pos += r;
+ off += r;
+ len -= r;
+ }
+ }
+
+ /**
+ * Skip an entire region of an input stream.
+ * <p>
+ * The input stream's position is moved forward by the number of requested
+ * bytes, discarding them from the input. This method does not return until
+ * the exact number of bytes requested has been skipped.
+ *
+ * @param fd
+ * the stream to skip bytes from.
+ * @param toSkip
+ * total number of bytes to be discarded. Must be >= 0.
+ * @throws EOFException
+ * the stream ended before the requested number of bytes were
+ * skipped.
+ * @throws IOException
+ * there was an error reading from the stream.
+ */
+ public static void skipFully(final InputStream fd, long toSkip)
+ throws IOException {
+ while (toSkip > 0) {
+ final long r = fd.skip(toSkip);
+ if (r <= 0)
+ throw new EOFException("Short skip of block");
+ toSkip -= r;
+ }
+ }
+
+ /**
+ * Compare a 32 bit unsigned integer stored in a 32 bit signed integer.
+ * <p>
+ * This function performs an unsigned compare operation, even though Java
+ * does not natively support unsigned integer values. Negative numbers are
+ * treated as larger than positive ones.
+ *
+ * @param a
+ * the first value to compare.
+ * @param b
+ * the second value to compare.
+ * @return < 0 if a < b; 0 if a == b; > 0 if a > b.
+ */
+ public static int compareUInt32(final int a, final int b) {
+ final int cmp = (a >>> 1) - (b >>> 1);
+ if (cmp != 0)
+ return cmp;
+ return (a & 1) - (b & 1);
+ }
+
+ /**
+ * Convert sequence of 2 bytes (network byte order) into unsigned value.
+ *
+ * @param intbuf
+ * buffer to acquire the 2 bytes of data from.
+ * @param offset
+ * position within the buffer to begin reading from. This
+ * position and the next byte after it (for a total of 2 bytes)
+ * will be read.
+ * @return unsigned integer value that matches the 16 bits read.
+ */
+ public static int decodeUInt16(final byte[] intbuf, final int offset) {
+ int r = (intbuf[offset] & 0xff) << 8;
+ return r | (intbuf[offset + 1] & 0xff);
+ }
+
+ /**
+ * Convert sequence of 4 bytes (network byte order) into signed value.
+ *
+ * @param intbuf
+ * buffer to acquire the 4 bytes of data from.
+ * @param offset
+ * position within the buffer to begin reading from. This
+ * position and the next 3 bytes after it (for a total of 4
+ * bytes) will be read.
+ * @return signed integer value that matches the 32 bits read.
+ */
+ public static int decodeInt32(final byte[] intbuf, final int offset) {
+ int r = intbuf[offset] << 8;
+
+ r |= intbuf[offset + 1] & 0xff;
+ r <<= 8;
+
+ r |= intbuf[offset + 2] & 0xff;
+ return (r << 8) | (intbuf[offset + 3] & 0xff);
+ }
+
+ /**
+ * Convert sequence of 4 bytes (network byte order) into unsigned value.
+ *
+ * @param intbuf
+ * buffer to acquire the 4 bytes of data from.
+ * @param offset
+ * position within the buffer to begin reading from. This
+ * position and the next 3 bytes after it (for a total of 4
+ * bytes) will be read.
+ * @return unsigned integer value that matches the 32 bits read.
+ */
+ public static long decodeUInt32(final byte[] intbuf, final int offset) {
+ int low = (intbuf[offset + 1] & 0xff) << 8;
+ low |= (intbuf[offset + 2] & 0xff);
+ low <<= 8;
+
+ low |= (intbuf[offset + 3] & 0xff);
+ return ((long) (intbuf[offset] & 0xff)) << 24 | low;
+ }
+
+ /**
+ * Convert sequence of 8 bytes (network byte order) into unsigned value.
+ *
+ * @param intbuf
+ * buffer to acquire the 8 bytes of data from.
+ * @param offset
+ * position within the buffer to begin reading from. This
+ * position and the next 7 bytes after it (for a total of 8
+ * bytes) will be read.
+ * @return unsigned integer value that matches the 64 bits read.
+ */
+ public static long decodeUInt64(final byte[] intbuf, final int offset) {
+ return (decodeUInt32(intbuf, offset) << 32)
+ | decodeUInt32(intbuf, offset + 4);
+ }
+
+ /**
+ * Write a 16 bit integer as a sequence of 2 bytes (network byte order).
+ *
+ * @param intbuf
+ * buffer to write the 2 bytes of data into.
+ * @param offset
+ * position within the buffer to begin writing to. This position
+ * and the next byte after it (for a total of 2 bytes) will be
+ * replaced.
+ * @param v
+ * the value to write.
+ */
+ public static void encodeInt16(final byte[] intbuf, final int offset, int v) {
+ intbuf[offset + 1] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset] = (byte) v;
+ }
+
+ /**
+ * Write a 32 bit integer as a sequence of 4 bytes (network byte order).
+ *
+ * @param intbuf
+ * buffer to write the 4 bytes of data into.
+ * @param offset
+ * position within the buffer to begin writing to. This position
+ * and the next 3 bytes after it (for a total of 4 bytes) will be
+ * replaced.
+ * @param v
+ * the value to write.
+ */
+ public static void encodeInt32(final byte[] intbuf, final int offset, int v) {
+ intbuf[offset + 3] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset + 2] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset + 1] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset] = (byte) v;
+ }
+
+ /**
+ * Write a 64 bit integer as a sequence of 8 bytes (network byte order).
+ *
+ * @param intbuf
+ * buffer to write the 48bytes of data into.
+ * @param offset
+ * position within the buffer to begin writing to. This position
+ * and the next 7 bytes after it (for a total of 8 bytes) will be
+ * replaced.
+ * @param v
+ * the value to write.
+ */
+ public static void encodeInt64(final byte[] intbuf, final int offset, long v) {
+ intbuf[offset + 7] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset + 6] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset + 5] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset + 4] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset + 3] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset + 2] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset + 1] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset] = (byte) v;
+ }
+
+ private NB() {
+ // Don't create instances of a static only utility.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java
new file mode 100644
index 0000000000..7e5bde7582
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.util.Arrays;
+
+import org.eclipse.jgit.lib.Constants;
+
+/** Utility functions related to quoted string handling. */
+public abstract class QuotedString {
+ /** Quoting style that obeys the rules Git applies to file names */
+ public static final GitPathStyle GIT_PATH = new GitPathStyle();
+
+ /**
+ * Quoting style used by the Bourne shell.
+ * <p>
+ * Quotes are unconditionally inserted during {@link #quote(String)}. This
+ * protects shell meta-characters like <code>$</code> or <code>~</code> from
+ * being recognized as special.
+ */
+ public static final BourneStyle BOURNE = new BourneStyle();
+
+ /** Bourne style, but permits <code>~user</code> at the start of the string. */
+ public static final BourneUserPathStyle BOURNE_USER_PATH = new BourneUserPathStyle();
+
+ /**
+ * Quote an input string by the quoting rules.
+ * <p>
+ * If the input string does not require any quoting, the same String
+ * reference is returned to the caller.
+ * <p>
+ * Otherwise a quoted string is returned, including the opening and closing
+ * quotation marks at the start and end of the string. If the style does not
+ * permit raw Unicode characters then the string will first be encoded in
+ * UTF-8, with unprintable sequences possibly escaped by the rules.
+ *
+ * @param in
+ * any non-null Unicode string.
+ * @return a quoted string. See above for details.
+ */
+ public abstract String quote(String in);
+
+ /**
+ * Clean a previously quoted input, decoding the result via UTF-8.
+ * <p>
+ * This method must match quote such that:
+ *
+ * <pre>
+ * a.equals(dequote(quote(a)));
+ * </pre>
+ *
+ * is true for any <code>a</code>.
+ *
+ * @param in
+ * a Unicode string to remove quoting from.
+ * @return the cleaned string.
+ * @see #dequote(byte[], int, int)
+ */
+ public String dequote(final String in) {
+ final byte[] b = Constants.encode(in);
+ return dequote(b, 0, b.length);
+ }
+
+ /**
+ * Decode a previously quoted input, scanning a UTF-8 encoded buffer.
+ * <p>
+ * This method must match quote such that:
+ *
+ * <pre>
+ * a.equals(dequote(Constants.encode(quote(a))));
+ * </pre>
+ *
+ * is true for any <code>a</code>.
+ * <p>
+ * This method removes any opening/closing quotation marks added by
+ * {@link #quote(String)}.
+ *
+ * @param in
+ * the input buffer to parse.
+ * @param offset
+ * first position within <code>in</code> to scan.
+ * @param end
+ * one position past in <code>in</code> to scan.
+ * @return the cleaned string.
+ */
+ public abstract String dequote(byte[] in, int offset, int end);
+
+ /**
+ * Quoting style used by the Bourne shell.
+ * <p>
+ * Quotes are unconditionally inserted during {@link #quote(String)}. This
+ * protects shell meta-characters like <code>$</code> or <code>~</code> from
+ * being recognized as special.
+ */
+ public static class BourneStyle extends QuotedString {
+ @Override
+ public String quote(final String in) {
+ final StringBuilder r = new StringBuilder();
+ r.append('\'');
+ int start = 0, i = 0;
+ for (; i < in.length(); i++) {
+ switch (in.charAt(i)) {
+ case '\'':
+ case '!':
+ r.append(in, start, i);
+ r.append('\'');
+ r.append('\\');
+ r.append(in.charAt(i));
+ r.append('\'');
+ start = i + 1;
+ break;
+ }
+ }
+ r.append(in, start, i);
+ r.append('\'');
+ return r.toString();
+ }
+
+ @Override
+ public String dequote(final byte[] in, int ip, final int ie) {
+ boolean inquote = false;
+ final byte[] r = new byte[ie - ip];
+ int rPtr = 0;
+ while (ip < ie) {
+ final byte b = in[ip++];
+ switch (b) {
+ case '\'':
+ inquote = !inquote;
+ continue;
+ case '\\':
+ if (inquote || ip == ie)
+ r[rPtr++] = b; // literal within a quote
+ else
+ r[rPtr++] = in[ip++];
+ continue;
+ default:
+ r[rPtr++] = b;
+ continue;
+ }
+ }
+ return RawParseUtils.decode(Constants.CHARSET, r, 0, rPtr);
+ }
+ }
+
+ /** Bourne style, but permits <code>~user</code> at the start of the string. */
+ public static class BourneUserPathStyle extends BourneStyle {
+ @Override
+ public String quote(final String in) {
+ if (in.matches("^~[A-Za-z0-9_-]+$")) {
+ // If the string is just "~user" we can assume they
+ // mean "~user/".
+ //
+ return in + "/";
+ }
+
+ if (in.matches("^~[A-Za-z0-9_-]*/.*$")) {
+ // If the string is of "~/path" or "~user/path"
+ // we must not escape ~/ or ~user/ from the shell.
+ //
+ final int i = in.indexOf('/') + 1;
+ if (i == in.length())
+ return in;
+ return in.substring(0, i) + super.quote(in.substring(i));
+ }
+
+ return super.quote(in);
+ }
+ }
+
+ /** Quoting style that obeys the rules Git applies to file names */
+ public static final class GitPathStyle extends QuotedString {
+ private static final byte[] quote;
+ static {
+ quote = new byte[128];
+ Arrays.fill(quote, (byte) -1);
+
+ for (int i = '0'; i <= '9'; i++)
+ quote[i] = 0;
+ for (int i = 'a'; i <= 'z'; i++)
+ quote[i] = 0;
+ for (int i = 'A'; i <= 'Z'; i++)
+ quote[i] = 0;
+ quote[' '] = 0;
+ quote['+'] = 0;
+ quote[','] = 0;
+ quote['-'] = 0;
+ quote['.'] = 0;
+ quote['/'] = 0;
+ quote['='] = 0;
+ quote['_'] = 0;
+ quote['^'] = 0;
+
+ quote['\u0007'] = 'a';
+ quote['\b'] = 'b';
+ quote['\f'] = 'f';
+ quote['\n'] = 'n';
+ quote['\r'] = 'r';
+ quote['\t'] = 't';
+ quote['\u000B'] = 'v';
+ quote['\\'] = '\\';
+ quote['"'] = '"';
+ }
+
+ @Override
+ public String quote(final String instr) {
+ if (instr.length() == 0)
+ return "\"\"";
+ boolean reuse = true;
+ final byte[] in = Constants.encode(instr);
+ final StringBuilder r = new StringBuilder(2 + in.length);
+ r.append('"');
+ for (int i = 0; i < in.length; i++) {
+ final int c = in[i] & 0xff;
+ if (c < quote.length) {
+ final byte style = quote[c];
+ if (style == 0) {
+ r.append((char) c);
+ continue;
+ }
+ if (style > 0) {
+ reuse = false;
+ r.append('\\');
+ r.append((char) style);
+ continue;
+ }
+ }
+
+ reuse = false;
+ r.append('\\');
+ r.append((char) (((c >> 6) & 03) + '0'));
+ r.append((char) (((c >> 3) & 07) + '0'));
+ r.append((char) (((c >> 0) & 07) + '0'));
+ }
+ if (reuse)
+ return instr;
+ r.append('"');
+ return r.toString();
+ }
+
+ @Override
+ public String dequote(final byte[] in, final int inPtr, final int inEnd) {
+ if (2 <= inEnd - inPtr && in[inPtr] == '"' && in[inEnd - 1] == '"')
+ return dq(in, inPtr + 1, inEnd - 1);
+ return RawParseUtils.decode(Constants.CHARSET, in, inPtr, inEnd);
+ }
+
+ private static String dq(final byte[] in, int inPtr, final int inEnd) {
+ final byte[] r = new byte[inEnd - inPtr];
+ int rPtr = 0;
+ while (inPtr < inEnd) {
+ final byte b = in[inPtr++];
+ if (b != '\\') {
+ r[rPtr++] = b;
+ continue;
+ }
+
+ if (inPtr == inEnd) {
+ // Lone trailing backslash. Treat it as a literal.
+ //
+ r[rPtr++] = '\\';
+ break;
+ }
+
+ switch (in[inPtr++]) {
+ case 'a':
+ r[rPtr++] = 0x07 /* \a = BEL */;
+ continue;
+ case 'b':
+ r[rPtr++] = '\b';
+ continue;
+ case 'f':
+ r[rPtr++] = '\f';
+ continue;
+ case 'n':
+ r[rPtr++] = '\n';
+ continue;
+ case 'r':
+ r[rPtr++] = '\r';
+ continue;
+ case 't':
+ r[rPtr++] = '\t';
+ continue;
+ case 'v':
+ r[rPtr++] = 0x0B/* \v = VT */;
+ continue;
+
+ case '\\':
+ case '"':
+ r[rPtr++] = in[inPtr - 1];
+ continue;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3': {
+ int cp = in[inPtr - 1] - '0';
+ while (inPtr < inEnd) {
+ final byte c = in[inPtr];
+ if ('0' <= c && c <= '7') {
+ cp <<= 3;
+ cp |= c - '0';
+ inPtr++;
+ } else {
+ break;
+ }
+ }
+ r[rPtr++] = (byte) cp;
+ continue;
+ }
+
+ default:
+ // Any other code is taken literally.
+ //
+ r[rPtr++] = '\\';
+ r[rPtr++] = in[inPtr - 1];
+ continue;
+ }
+ }
+
+ return RawParseUtils.decode(Constants.CHARSET, r, 0, rPtr);
+ }
+
+ private GitPathStyle() {
+ // Singleton
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java
new file mode 100644
index 0000000000..c89705cb6d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+/**
+ * A rough character sequence around a raw byte buffer.
+ * <p>
+ * Characters are assumed to be 8-bit US-ASCII.
+ */
+public final class RawCharSequence implements CharSequence {
+ /** A zero-length character sequence. */
+ public static final RawCharSequence EMPTY = new RawCharSequence(null, 0, 0);
+
+ final byte[] buffer;
+
+ final int startPtr;
+
+ final int endPtr;
+
+ /**
+ * Create a rough character sequence around the raw byte buffer.
+ *
+ * @param buf
+ * buffer to scan.
+ * @param start
+ * starting position for the sequence.
+ * @param end
+ * ending position for the sequence.
+ */
+ public RawCharSequence(final byte[] buf, final int start, final int end) {
+ buffer = buf;
+ startPtr = start;
+ endPtr = end;
+ }
+
+ public char charAt(final int index) {
+ return (char) (buffer[startPtr + index] & 0xff);
+ }
+
+ public int length() {
+ return endPtr - startPtr;
+ }
+
+ public CharSequence subSequence(final int start, final int end) {
+ return new RawCharSequence(buffer, startPtr + start, startPtr + end);
+ }
+
+ @Override
+ public String toString() {
+ final int n = length();
+ final StringBuilder b = new StringBuilder(n);
+ for (int i = 0; i < n; i++)
+ b.append(charAt(i));
+ return b.toString();
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
new file mode 100644
index 0000000000..9254eb3d79
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
@@ -0,0 +1,1016 @@
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import static org.eclipse.jgit.lib.ObjectChecker.author;
+import static org.eclipse.jgit.lib.ObjectChecker.committer;
+import static org.eclipse.jgit.lib.ObjectChecker.encoding;
+import static org.eclipse.jgit.lib.ObjectChecker.tagger;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+import java.util.Arrays;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.PersonIdent;
+
+/** Handy utility functions to parse raw object contents. */
+public final class RawParseUtils {
+ private static final byte[] digits10;
+
+ private static final byte[] digits16;
+
+ private static final byte[] footerLineKeyChars;
+
+ static {
+ digits10 = new byte['9' + 1];
+ Arrays.fill(digits10, (byte) -1);
+ for (char i = '0'; i <= '9'; i++)
+ digits10[i] = (byte) (i - '0');
+
+ digits16 = new byte['f' + 1];
+ Arrays.fill(digits16, (byte) -1);
+ for (char i = '0'; i <= '9'; i++)
+ digits16[i] = (byte) (i - '0');
+ for (char i = 'a'; i <= 'f'; i++)
+ digits16[i] = (byte) ((i - 'a') + 10);
+ for (char i = 'A'; i <= 'F'; i++)
+ digits16[i] = (byte) ((i - 'A') + 10);
+
+ footerLineKeyChars = new byte['z' + 1];
+ footerLineKeyChars['-'] = 1;
+ for (char i = '0'; i <= '9'; i++)
+ footerLineKeyChars[i] = 1;
+ for (char i = 'A'; i <= 'Z'; i++)
+ footerLineKeyChars[i] = 1;
+ for (char i = 'a'; i <= 'z'; i++)
+ footerLineKeyChars[i] = 1;
+ }
+
+ /**
+ * Determine if b[ptr] matches src.
+ *
+ * @param b
+ * the buffer to scan.
+ * @param ptr
+ * first position within b, this should match src[0].
+ * @param src
+ * the buffer to test for equality with b.
+ * @return ptr + src.length if b[ptr..src.length] == src; else -1.
+ */
+ public static final int match(final byte[] b, int ptr, final byte[] src) {
+ if (ptr + src.length > b.length)
+ return -1;
+ for (int i = 0; i < src.length; i++, ptr++)
+ if (b[ptr] != src[i])
+ return -1;
+ return ptr;
+ }
+
+ private static final byte[] base10byte = { '0', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9' };
+
+ /**
+ * Format a base 10 numeric into a temporary buffer.
+ * <p>
+ * Formatting is performed backwards. The method starts at offset
+ * <code>o-1</code> and ends at <code>o-1-digits</code>, where
+ * <code>digits</code> is the number of positions necessary to store the
+ * base 10 value.
+ * <p>
+ * The argument and return values from this method make it easy to chain
+ * writing, for example:
+ * </p>
+ *
+ * <pre>
+ * final byte[] tmp = new byte[64];
+ * int ptr = tmp.length;
+ * tmp[--ptr] = '\n';
+ * ptr = RawParseUtils.formatBase10(tmp, ptr, 32);
+ * tmp[--ptr] = ' ';
+ * ptr = RawParseUtils.formatBase10(tmp, ptr, 18);
+ * tmp[--ptr] = 0;
+ * final String str = new String(tmp, ptr, tmp.length - ptr);
+ * </pre>
+ *
+ * @param b
+ * buffer to write into.
+ * @param o
+ * one offset past the location where writing will begin; writing
+ * proceeds towards lower index values.
+ * @param value
+ * the value to store.
+ * @return the new offset value <code>o</code>. This is the position of
+ * the last byte written. Additional writing should start at one
+ * position earlier.
+ */
+ public static int formatBase10(final byte[] b, int o, int value) {
+ if (value == 0) {
+ b[--o] = '0';
+ return o;
+ }
+ final boolean isneg = value < 0;
+ while (value != 0) {
+ b[--o] = base10byte[value % 10];
+ value /= 10;
+ }
+ if (isneg)
+ b[--o] = '-';
+ return o;
+ }
+
+ /**
+ * Parse a base 10 numeric from a sequence of ASCII digits into an int.
+ * <p>
+ * Digit sequences can begin with an optional run of spaces before the
+ * sequence, and may start with a '+' or a '-' to indicate sign position.
+ * Any other characters will cause the method to stop and return the current
+ * result to the caller.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start parsing digits at.
+ * @param ptrResult
+ * optional location to return the new ptr value through. If null
+ * the ptr value will be discarded.
+ * @return the value at this location; 0 if the location is not a valid
+ * numeric.
+ */
+ public static final int parseBase10(final byte[] b, int ptr,
+ final MutableInteger ptrResult) {
+ int r = 0;
+ int sign = 0;
+ try {
+ final int sz = b.length;
+ while (ptr < sz && b[ptr] == ' ')
+ ptr++;
+ if (ptr >= sz)
+ return 0;
+
+ switch (b[ptr]) {
+ case '-':
+ sign = -1;
+ ptr++;
+ break;
+ case '+':
+ ptr++;
+ break;
+ }
+
+ while (ptr < sz) {
+ final byte v = digits10[b[ptr]];
+ if (v < 0)
+ break;
+ r = (r * 10) + v;
+ ptr++;
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // Not a valid digit.
+ }
+ if (ptrResult != null)
+ ptrResult.value = ptr;
+ return sign < 0 ? -r : r;
+ }
+
+ /**
+ * Parse a base 10 numeric from a sequence of ASCII digits into a long.
+ * <p>
+ * Digit sequences can begin with an optional run of spaces before the
+ * sequence, and may start with a '+' or a '-' to indicate sign position.
+ * Any other characters will cause the method to stop and return the current
+ * result to the caller.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start parsing digits at.
+ * @param ptrResult
+ * optional location to return the new ptr value through. If null
+ * the ptr value will be discarded.
+ * @return the value at this location; 0 if the location is not a valid
+ * numeric.
+ */
+ public static final long parseLongBase10(final byte[] b, int ptr,
+ final MutableInteger ptrResult) {
+ long r = 0;
+ int sign = 0;
+ try {
+ final int sz = b.length;
+ while (ptr < sz && b[ptr] == ' ')
+ ptr++;
+ if (ptr >= sz)
+ return 0;
+
+ switch (b[ptr]) {
+ case '-':
+ sign = -1;
+ ptr++;
+ break;
+ case '+':
+ ptr++;
+ break;
+ }
+
+ while (ptr < sz) {
+ final byte v = digits10[b[ptr]];
+ if (v < 0)
+ break;
+ r = (r * 10) + v;
+ ptr++;
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // Not a valid digit.
+ }
+ if (ptrResult != null)
+ ptrResult.value = ptr;
+ return sign < 0 ? -r : r;
+ }
+
+ /**
+ * Parse 4 character base 16 (hex) formatted string to unsigned integer.
+ * <p>
+ * The number is read in network byte order, that is, most significant
+ * nybble first.
+ *
+ * @param bs
+ * buffer to parse digits from; positions {@code [p, p+4)} will
+ * be parsed.
+ * @param p
+ * first position within the buffer to parse.
+ * @return the integer value.
+ * @throws ArrayIndexOutOfBoundsException
+ * if the string is not hex formatted.
+ */
+ public static final int parseHexInt16(final byte[] bs, final int p) {
+ int r = digits16[bs[p]] << 4;
+
+ r |= digits16[bs[p + 1]];
+ r <<= 4;
+
+ r |= digits16[bs[p + 2]];
+ r <<= 4;
+
+ r |= digits16[bs[p + 3]];
+ if (r < 0)
+ throw new ArrayIndexOutOfBoundsException();
+ return r;
+ }
+
+ /**
+ * Parse 8 character base 16 (hex) formatted string to unsigned integer.
+ * <p>
+ * The number is read in network byte order, that is, most significant
+ * nybble first.
+ *
+ * @param bs
+ * buffer to parse digits from; positions {@code [p, p+8)} will
+ * be parsed.
+ * @param p
+ * first position within the buffer to parse.
+ * @return the integer value.
+ * @throws ArrayIndexOutOfBoundsException
+ * if the string is not hex formatted.
+ */
+ public static final int parseHexInt32(final byte[] bs, final int p) {
+ int r = digits16[bs[p]] << 4;
+
+ r |= digits16[bs[p + 1]];
+ r <<= 4;
+
+ r |= digits16[bs[p + 2]];
+ r <<= 4;
+
+ r |= digits16[bs[p + 3]];
+ r <<= 4;
+
+ r |= digits16[bs[p + 4]];
+ r <<= 4;
+
+ r |= digits16[bs[p + 5]];
+ r <<= 4;
+
+ r |= digits16[bs[p + 6]];
+
+ final int last = digits16[bs[p + 7]];
+ if (r < 0 || last < 0)
+ throw new ArrayIndexOutOfBoundsException();
+ return (r << 4) | last;
+ }
+
+ /**
+ * Parse a single hex digit to its numeric value (0-15).
+ *
+ * @param digit
+ * hex character to parse.
+ * @return numeric value, in the range 0-15.
+ * @throws ArrayIndexOutOfBoundsException
+ * if the input digit is not a valid hex digit.
+ */
+ public static final int parseHexInt4(final byte digit) {
+ final byte r = digits16[digit];
+ if (r < 0)
+ throw new ArrayIndexOutOfBoundsException();
+ return r;
+ }
+
+ /**
+ * Parse a Git style timezone string.
+ * <p>
+ * The sequence "-0315" will be parsed as the numeric value -195, as the
+ * lower two positions count minutes, not 100ths of an hour.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start parsing digits at.
+ * @return the timezone at this location, expressed in minutes.
+ */
+ public static final int parseTimeZoneOffset(final byte[] b, int ptr) {
+ final int v = parseBase10(b, ptr, null);
+ final int tzMins = v % 100;
+ final int tzHours = v / 100;
+ return tzHours * 60 + tzMins;
+ }
+
+ /**
+ * Locate the first position after a given character.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start looking for chrA at.
+ * @param chrA
+ * character to find.
+ * @return new position just after chrA.
+ */
+ public static final int next(final byte[] b, int ptr, final char chrA) {
+ final int sz = b.length;
+ while (ptr < sz) {
+ if (b[ptr++] == chrA)
+ return ptr;
+ }
+ return ptr;
+ }
+
+ /**
+ * Locate the first position after the next LF.
+ * <p>
+ * This method stops on the first '\n' it finds.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start looking for LF at.
+ * @return new position just after the first LF found.
+ */
+ public static final int nextLF(final byte[] b, int ptr) {
+ return next(b, ptr, '\n');
+ }
+
+ /**
+ * Locate the first position after either the given character or LF.
+ * <p>
+ * This method stops on the first match it finds from either chrA or '\n'.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start looking for chrA or LF at.
+ * @param chrA
+ * character to find.
+ * @return new position just after the first chrA or LF to be found.
+ */
+ public static final int nextLF(final byte[] b, int ptr, final char chrA) {
+ final int sz = b.length;
+ while (ptr < sz) {
+ final byte c = b[ptr++];
+ if (c == chrA || c == '\n')
+ return ptr;
+ }
+ return ptr;
+ }
+
+ /**
+ * Locate the first position before a given character.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start looking for chrA at.
+ * @param chrA
+ * character to find.
+ * @return new position just before chrA, -1 for not found
+ */
+ public static final int prev(final byte[] b, int ptr, final char chrA) {
+ if (ptr == b.length)
+ --ptr;
+ while (ptr >= 0) {
+ if (b[ptr--] == chrA)
+ return ptr;
+ }
+ return ptr;
+ }
+
+ /**
+ * Locate the first position before the previous LF.
+ * <p>
+ * This method stops on the first '\n' it finds.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start looking for LF at.
+ * @return new position just before the first LF found, -1 for not found
+ */
+ public static final int prevLF(final byte[] b, int ptr) {
+ return prev(b, ptr, '\n');
+ }
+
+ /**
+ * Locate the previous position before either the given character or LF.
+ * <p>
+ * This method stops on the first match it finds from either chrA or '\n'.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start looking for chrA or LF at.
+ * @param chrA
+ * character to find.
+ * @return new position just before the first chrA or LF to be found, -1 for
+ * not found
+ */
+ public static final int prevLF(final byte[] b, int ptr, final char chrA) {
+ if (ptr == b.length)
+ --ptr;
+ while (ptr >= 0) {
+ final byte c = b[ptr--];
+ if (c == chrA || c == '\n')
+ return ptr;
+ }
+ return ptr;
+ }
+
+ /**
+ * Index the region between <code>[ptr, end)</code> to find line starts.
+ * <p>
+ * The returned list is 1 indexed. Index 0 contains
+ * {@link Integer#MIN_VALUE} to pad the list out.
+ * <p>
+ * Using a 1 indexed list means that line numbers can be directly accessed
+ * from the list, so <code>list.get(1)</code> (aka get line 1) returns
+ * <code>ptr</code>.
+ * <p>
+ * The last element (index <code>map.size()-1</code>) always contains
+ * <code>end</code>.
+ *
+ * @param buf
+ * buffer to scan.
+ * @param ptr
+ * position within the buffer corresponding to the first byte of
+ * line 1.
+ * @param end
+ * 1 past the end of the content within <code>buf</code>.
+ * @return a line map indexing the start position of each line.
+ */
+ public static final IntList lineMap(final byte[] buf, int ptr, int end) {
+ // Experimentally derived from multiple source repositories
+ // the average number of bytes/line is 36. Its a rough guess
+ // to initially size our map close to the target.
+ //
+ final IntList map = new IntList((end - ptr) / 36);
+ map.fillTo(1, Integer.MIN_VALUE);
+ for (; ptr < end; ptr = nextLF(buf, ptr))
+ map.add(ptr);
+ map.add(end);
+ return map;
+ }
+
+ /**
+ * Locate the "author " header line data.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position in buffer to start the scan at. Most callers should
+ * pass 0 to ensure the scan starts from the beginning of the
+ * commit buffer and does not accidentally look at message body.
+ * @return position just after the space in "author ", so the first
+ * character of the author's name. If no author header can be
+ * located -1 is returned.
+ */
+ public static final int author(final byte[] b, int ptr) {
+ final int sz = b.length;
+ if (ptr == 0)
+ ptr += 46; // skip the "tree ..." line.
+ while (ptr < sz && b[ptr] == 'p')
+ ptr += 48; // skip this parent.
+ return match(b, ptr, author);
+ }
+
+ /**
+ * Locate the "committer " header line data.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position in buffer to start the scan at. Most callers should
+ * pass 0 to ensure the scan starts from the beginning of the
+ * commit buffer and does not accidentally look at message body.
+ * @return position just after the space in "committer ", so the first
+ * character of the committer's name. If no committer header can be
+ * located -1 is returned.
+ */
+ public static final int committer(final byte[] b, int ptr) {
+ final int sz = b.length;
+ if (ptr == 0)
+ ptr += 46; // skip the "tree ..." line.
+ while (ptr < sz && b[ptr] == 'p')
+ ptr += 48; // skip this parent.
+ if (ptr < sz && b[ptr] == 'a')
+ ptr = nextLF(b, ptr);
+ return match(b, ptr, committer);
+ }
+
+ /**
+ * Locate the "tagger " header line data.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position in buffer to start the scan at. Most callers should
+ * pass 0 to ensure the scan starts from the beginning of the tag
+ * buffer and does not accidentally look at message body.
+ * @return position just after the space in "tagger ", so the first
+ * character of the tagger's name. If no tagger header can be
+ * located -1 is returned.
+ */
+ public static final int tagger(final byte[] b, int ptr) {
+ final int sz = b.length;
+ if (ptr == 0)
+ ptr += 48; // skip the "object ..." line.
+ while (ptr < sz) {
+ if (b[ptr] == '\n')
+ return -1;
+ final int m = match(b, ptr, tagger);
+ if (m >= 0)
+ return m;
+ ptr = nextLF(b, ptr);
+ }
+ return -1;
+ }
+
+ /**
+ * Locate the "encoding " header line.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position in buffer to start the scan at. Most callers should
+ * pass 0 to ensure the scan starts from the beginning of the
+ * buffer and does not accidentally look at the message body.
+ * @return position just after the space in "encoding ", so the first
+ * character of the encoding's name. If no encoding header can be
+ * located -1 is returned (and UTF-8 should be assumed).
+ */
+ public static final int encoding(final byte[] b, int ptr) {
+ final int sz = b.length;
+ while (ptr < sz) {
+ if (b[ptr] == '\n')
+ return -1;
+ if (b[ptr] == 'e')
+ break;
+ ptr = nextLF(b, ptr);
+ }
+ return match(b, ptr, encoding);
+ }
+
+ /**
+ * Parse the "encoding " header into a character set reference.
+ * <p>
+ * Locates the "encoding " header (if present) by first calling
+ * {@link #encoding(byte[], int)} and then returns the proper character set
+ * to apply to this buffer to evaluate its contents as character data.
+ * <p>
+ * If no encoding header is present, {@link Constants#CHARSET} is assumed.
+ *
+ * @param b
+ * buffer to scan.
+ * @return the Java character set representation. Never null.
+ */
+ public static Charset parseEncoding(final byte[] b) {
+ final int enc = encoding(b, 0);
+ if (enc < 0)
+ return Constants.CHARSET;
+ final int lf = nextLF(b, enc);
+ return Charset.forName(decode(Constants.CHARSET, b, enc, lf - 1));
+ }
+
+ /**
+ * Parse a name line (e.g. author, committer, tagger) into a PersonIdent.
+ * <p>
+ * When passing in a value for <code>nameB</code> callers should use the
+ * return value of {@link #author(byte[], int)} or
+ * {@link #committer(byte[], int)}, as these methods provide the proper
+ * position within the buffer.
+ *
+ * @param raw
+ * the buffer to parse character data from.
+ * @param nameB
+ * first position of the identity information. This should be the
+ * first position after the space which delimits the header field
+ * name (e.g. "author" or "committer") from the rest of the
+ * identity line.
+ * @return the parsed identity. Never null.
+ */
+ public static PersonIdent parsePersonIdent(final byte[] raw, final int nameB) {
+ final Charset cs = parseEncoding(raw);
+ final int emailB = nextLF(raw, nameB, '<');
+ final int emailE = nextLF(raw, emailB, '>');
+
+ final String name = decode(cs, raw, nameB, emailB - 2);
+ final String email = decode(cs, raw, emailB, emailE - 1);
+
+ final MutableInteger ptrout = new MutableInteger();
+ final long when = parseLongBase10(raw, emailE + 1, ptrout);
+ final int tz = parseTimeZoneOffset(raw, ptrout.value);
+
+ return new PersonIdent(name, email, when * 1000L, tz);
+ }
+
+ /**
+ * Parse a name data (e.g. as within a reflog) into a PersonIdent.
+ * <p>
+ * When passing in a value for <code>nameB</code> callers should use the
+ * return value of {@link #author(byte[], int)} or
+ * {@link #committer(byte[], int)}, as these methods provide the proper
+ * position within the buffer.
+ *
+ * @param raw
+ * the buffer to parse character data from.
+ * @param nameB
+ * first position of the identity information. This should be the
+ * first position after the space which delimits the header field
+ * name (e.g. "author" or "committer") from the rest of the
+ * identity line.
+ * @return the parsed identity. Never null.
+ */
+ public static PersonIdent parsePersonIdentOnly(final byte[] raw, final int nameB) {
+ int stop = nextLF(raw, nameB);
+ int emailB = nextLF(raw, nameB, '<');
+ int emailE = nextLF(raw, emailB, '>');
+ final String name;
+ final String email;
+ if (emailE < stop) {
+ email = decode(raw, emailB, emailE - 1);
+ } else {
+ email = "invalid";
+ }
+ if (emailB < stop)
+ name = decode(raw, nameB, emailB - 2);
+ else
+ name = decode(raw, nameB, stop);
+
+ final MutableInteger ptrout = new MutableInteger();
+ long when;
+ int tz;
+ if (emailE < stop) {
+ when = parseLongBase10(raw, emailE + 1, ptrout);
+ tz = parseTimeZoneOffset(raw, ptrout.value);
+ } else {
+ when = 0;
+ tz = 0;
+ }
+ return new PersonIdent(name, email, when * 1000L, tz);
+ }
+
+ /**
+ * Locate the end of a footer line key string.
+ * <p>
+ * If the region at {@code raw[ptr]} matches {@code ^[A-Za-z0-9-]+:} (e.g.
+ * "Signed-off-by: A. U. Thor\n") then this method returns the position of
+ * the first ':'.
+ * <p>
+ * If the region at {@code raw[ptr]} does not match {@code ^[A-Za-z0-9-]+:}
+ * then this method returns -1.
+ *
+ * @param raw
+ * buffer to scan.
+ * @param ptr
+ * first position within raw to consider as a footer line key.
+ * @return position of the ':' which terminates the footer line key if this
+ * is otherwise a valid footer line key; otherwise -1.
+ */
+ public static int endOfFooterLineKey(final byte[] raw, int ptr) {
+ try {
+ for (;;) {
+ final byte c = raw[ptr];
+ if (footerLineKeyChars[c] == 0) {
+ if (c == ':')
+ return ptr;
+ return -1;
+ }
+ ptr++;
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Decode a buffer under UTF-8, if possible.
+ *
+ * If the byte stream cannot be decoded that way, the platform default is tried
+ * and if that too fails, the fail-safe ISO-8859-1 encoding is tried.
+ *
+ * @param buffer
+ * buffer to pull raw bytes from.
+ * @return a string representation of the range <code>[start,end)</code>,
+ * after decoding the region through the specified character set.
+ */
+ public static String decode(final byte[] buffer) {
+ return decode(buffer, 0, buffer.length);
+ }
+
+ /**
+ * Decode a buffer under UTF-8, if possible.
+ *
+ * If the byte stream cannot be decoded that way, the platform default is
+ * tried and if that too fails, the fail-safe ISO-8859-1 encoding is tried.
+ *
+ * @param buffer
+ * buffer to pull raw bytes from.
+ * @param start
+ * start position in buffer
+ * @param end
+ * one position past the last location within the buffer to take
+ * data from.
+ * @return a string representation of the range <code>[start,end)</code>,
+ * after decoding the region through the specified character set.
+ */
+ public static String decode(final byte[] buffer, final int start,
+ final int end) {
+ return decode(Constants.CHARSET, buffer, start, end);
+ }
+
+ /**
+ * Decode a buffer under the specified character set if possible.
+ *
+ * If the byte stream cannot be decoded that way, the platform default is tried
+ * and if that too fails, the fail-safe ISO-8859-1 encoding is tried.
+ *
+ * @param cs
+ * character set to use when decoding the buffer.
+ * @param buffer
+ * buffer to pull raw bytes from.
+ * @return a string representation of the range <code>[start,end)</code>,
+ * after decoding the region through the specified character set.
+ */
+ public static String decode(final Charset cs, final byte[] buffer) {
+ return decode(cs, buffer, 0, buffer.length);
+ }
+
+ /**
+ * Decode a region of the buffer under the specified character set if possible.
+ *
+ * If the byte stream cannot be decoded that way, the platform default is tried
+ * and if that too fails, the fail-safe ISO-8859-1 encoding is tried.
+ *
+ * @param cs
+ * character set to use when decoding the buffer.
+ * @param buffer
+ * buffer to pull raw bytes from.
+ * @param start
+ * first position within the buffer to take data from.
+ * @param end
+ * one position past the last location within the buffer to take
+ * data from.
+ * @return a string representation of the range <code>[start,end)</code>,
+ * after decoding the region through the specified character set.
+ */
+ public static String decode(final Charset cs, final byte[] buffer,
+ final int start, final int end) {
+ try {
+ return decodeNoFallback(cs, buffer, start, end);
+ } catch (CharacterCodingException e) {
+ // Fall back to an ISO-8859-1 style encoding. At least all of
+ // the bytes will be present in the output.
+ //
+ return extractBinaryString(buffer, start, end);
+ }
+ }
+
+ /**
+ * Decode a region of the buffer under the specified character set if
+ * possible.
+ *
+ * If the byte stream cannot be decoded that way, the platform default is
+ * tried and if that too fails, an exception is thrown.
+ *
+ * @param cs
+ * character set to use when decoding the buffer.
+ * @param buffer
+ * buffer to pull raw bytes from.
+ * @param start
+ * first position within the buffer to take data from.
+ * @param end
+ * one position past the last location within the buffer to take
+ * data from.
+ * @return a string representation of the range <code>[start,end)</code>,
+ * after decoding the region through the specified character set.
+ * @throws CharacterCodingException
+ * the input is not in any of the tested character sets.
+ */
+ public static String decodeNoFallback(final Charset cs,
+ final byte[] buffer, final int start, final int end)
+ throws CharacterCodingException {
+ final ByteBuffer b = ByteBuffer.wrap(buffer, start, end - start);
+ b.mark();
+
+ // Try our built-in favorite. The assumption here is that
+ // decoding will fail if the data is not actually encoded
+ // using that encoder.
+ //
+ try {
+ return decode(b, Constants.CHARSET);
+ } catch (CharacterCodingException e) {
+ b.reset();
+ }
+
+ if (!cs.equals(Constants.CHARSET)) {
+ // Try the suggested encoding, it might be right since it was
+ // provided by the caller.
+ //
+ try {
+ return decode(b, cs);
+ } catch (CharacterCodingException e) {
+ b.reset();
+ }
+ }
+
+ // Try the default character set. A small group of people
+ // might actually use the same (or very similar) locale.
+ //
+ final Charset defcs = Charset.defaultCharset();
+ if (!defcs.equals(cs) && !defcs.equals(Constants.CHARSET)) {
+ try {
+ return decode(b, defcs);
+ } catch (CharacterCodingException e) {
+ b.reset();
+ }
+ }
+
+ throw new CharacterCodingException();
+ }
+
+ /**
+ * Decode a region of the buffer under the ISO-8859-1 encoding.
+ *
+ * Each byte is treated as a single character in the 8859-1 character
+ * encoding, performing a raw binary->char conversion.
+ *
+ * @param buffer
+ * buffer to pull raw bytes from.
+ * @param start
+ * first position within the buffer to take data from.
+ * @param end
+ * one position past the last location within the buffer to take
+ * data from.
+ * @return a string representation of the range <code>[start,end)</code>.
+ */
+ public static String extractBinaryString(final byte[] buffer,
+ final int start, final int end) {
+ final StringBuilder r = new StringBuilder(end - start);
+ for (int i = start; i < end; i++)
+ r.append((char) (buffer[i] & 0xff));
+ return r.toString();
+ }
+
+ private static String decode(final ByteBuffer b, final Charset charset)
+ throws CharacterCodingException {
+ final CharsetDecoder d = charset.newDecoder();
+ d.onMalformedInput(CodingErrorAction.REPORT);
+ d.onUnmappableCharacter(CodingErrorAction.REPORT);
+ return d.decode(b).toString();
+ }
+
+ /**
+ * Locate the position of the commit message body.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position in buffer to start the scan at. Most callers should
+ * pass 0 to ensure the scan starts from the beginning of the
+ * commit buffer.
+ * @return position of the user's message buffer.
+ */
+ public static final int commitMessage(final byte[] b, int ptr) {
+ final int sz = b.length;
+ if (ptr == 0)
+ ptr += 46; // skip the "tree ..." line.
+ while (ptr < sz && b[ptr] == 'p')
+ ptr += 48; // skip this parent.
+
+ // Skip any remaining header lines, ignoring what their actual
+ // header line type is. This is identical to the logic for a tag.
+ //
+ return tagMessage(b, ptr);
+ }
+
+ /**
+ * Locate the position of the tag message body.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position in buffer to start the scan at. Most callers should
+ * pass 0 to ensure the scan starts from the beginning of the tag
+ * buffer.
+ * @return position of the user's message buffer.
+ */
+ public static final int tagMessage(final byte[] b, int ptr) {
+ final int sz = b.length;
+ if (ptr == 0)
+ ptr += 48; // skip the "object ..." line.
+ while (ptr < sz && b[ptr] != '\n')
+ ptr = nextLF(b, ptr);
+ if (ptr < sz && b[ptr] == '\n')
+ return ptr + 1;
+ return -1;
+ }
+
+ /**
+ * Locate the end of a paragraph.
+ * <p>
+ * A paragraph is ended by two consecutive LF bytes.
+ *
+ * @param b
+ * buffer to scan.
+ * @param start
+ * position in buffer to start the scan at. Most callers will
+ * want to pass the first position of the commit message (as
+ * found by {@link #commitMessage(byte[], int)}.
+ * @return position of the LF at the end of the paragraph;
+ * <code>b.length</code> if no paragraph end could be located.
+ */
+ public static final int endOfParagraph(final byte[] b, final int start) {
+ int ptr = start;
+ final int sz = b.length;
+ while (ptr < sz && b[ptr] != '\n')
+ ptr = nextLF(b, ptr);
+ while (0 < ptr && start < ptr && b[ptr - 1] == '\n')
+ ptr--;
+ return ptr;
+ }
+
+ private RawParseUtils() {
+ // Don't create instances of a static only utility.
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java
new file mode 100644
index 0000000000..ae135afab7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import org.eclipse.jgit.lib.Constants;
+
+/**
+ * Searches text using only substring search.
+ * <p>
+ * Instances are thread-safe. Multiple concurrent threads may perform matches on
+ * different character sequences at the same time.
+ */
+public class RawSubStringPattern {
+ private final String needleString;
+
+ private final byte[] needle;
+
+ /**
+ * Construct a new substring pattern.
+ *
+ * @param patternText
+ * text to locate. This should be a literal string, as no
+ * meta-characters are supported by this implementation. The
+ * string may not be the empty string.
+ */
+ public RawSubStringPattern(final String patternText) {
+ if (patternText.length() == 0)
+ throw new IllegalArgumentException("Cannot match on empty string.");
+ needleString = patternText;
+
+ final byte[] b = Constants.encode(patternText);
+ needle = new byte[b.length];
+ for (int i = 0; i < b.length; i++)
+ needle[i] = lc(b[i]);
+ }
+
+ /**
+ * Match a character sequence against this pattern.
+ *
+ * @param rcs
+ * the sequence to match. Must not be null but the length of the
+ * sequence is permitted to be 0.
+ * @return offset within <code>rcs</code> of the first occurrence of this
+ * pattern; -1 if this pattern does not appear at any position of
+ * <code>rcs</code>.
+ */
+ public int match(final RawCharSequence rcs) {
+ final int needleLen = needle.length;
+ final byte first = needle[0];
+
+ final byte[] text = rcs.buffer;
+ int matchPos = rcs.startPtr;
+ final int maxPos = rcs.endPtr - needleLen;
+
+ OUTER: for (; matchPos < maxPos; matchPos++) {
+ if (neq(first, text[matchPos])) {
+ while (++matchPos < maxPos && neq(first, text[matchPos])) {
+ /* skip */
+ }
+ if (matchPos == maxPos)
+ return -1;
+ }
+
+ int si = ++matchPos;
+ for (int j = 1; j < needleLen; j++, si++) {
+ if (neq(needle[j], text[si]))
+ continue OUTER;
+ }
+ return matchPos - 1;
+ }
+ return -1;
+ }
+
+ private static final boolean neq(final byte a, final byte b) {
+ return a != b && a != lc(b);
+ }
+
+ private static final byte lc(final byte q) {
+ return (byte) StringUtils.toLowerCase((char) (q & 0xff));
+ }
+
+ /**
+ * Get the literal pattern string this instance searches for.
+ *
+ * @return the pattern string given to our constructor.
+ */
+ public String pattern() {
+ return needleString;
+ }
+
+ @Override
+ public String toString() {
+ return pattern();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
new file mode 100644
index 0000000000..91f03f095e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+/** Miscellaneous string comparison utility methods. */
+public final class StringUtils {
+ private static final char[] LC;
+
+ static {
+ LC = new char['Z' + 1];
+ for (char c = 0; c < LC.length; c++)
+ LC[c] = c;
+ for (char c = 'A'; c <= 'Z'; c++)
+ LC[c] = (char) ('a' + (c - 'A'));
+ }
+
+ /**
+ * Convert the input to lowercase.
+ * <p>
+ * This method does not honor the JVM locale, but instead always behaves as
+ * though it is in the US-ASCII locale. Only characters in the range 'A'
+ * through 'Z' are converted. All other characters are left as-is, even if
+ * they otherwise would have a lowercase character equivilant.
+ *
+ * @param c
+ * the input character.
+ * @return lowercase version of the input.
+ */
+ public static char toLowerCase(final char c) {
+ return c <= 'Z' ? LC[c] : c;
+ }
+
+ /**
+ * Convert the input string to lower case, according to the "C" locale.
+ * <p>
+ * This method does not honor the JVM locale, but instead always behaves as
+ * though it is in the US-ASCII locale. Only characters in the range 'A'
+ * through 'Z' are converted, all other characters are left as-is, even if
+ * they otherwise would have a lowercase character equivilant.
+ *
+ * @param in
+ * the input string. Must not be null.
+ * @return a copy of the input string, after converting characters in the
+ * range 'A'..'Z' to 'a'..'z'.
+ */
+ public static String toLowerCase(final String in) {
+ final StringBuilder r = new StringBuilder(in.length());
+ for (int i = 0; i < in.length(); i++)
+ r.append(toLowerCase(in.charAt(i)));
+ return r.toString();
+ }
+
+ /**
+ * Test if two strings are equal, ignoring case.
+ * <p>
+ * This method does not honor the JVM locale, but instead always behaves as
+ * though it is in the US-ASCII locale.
+ *
+ * @param a
+ * first string to compare.
+ * @param b
+ * second string to compare.
+ * @return true if a equals b
+ */
+ public static boolean equalsIgnoreCase(final String a, final String b) {
+ if (a == b)
+ return true;
+ if (a.length() != b.length())
+ return false;
+ for (int i = 0; i < a.length(); i++) {
+ if (toLowerCase(a.charAt(i)) != toLowerCase(b.charAt(i)))
+ return false;
+ }
+ return true;
+ }
+
+ private StringUtils() {
+ // Do not create instances
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
new file mode 100644
index 0000000000..771e77058a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.TimeZone;
+
+import org.eclipse.jgit.lib.FileBasedConfig;
+
+/**
+ * Interface to read values from the system.
+ * <p>
+ * When writing unit tests, extending this interface with a custom class
+ * permits to simulate an access to a system variable or property and
+ * permits to control the user's global configuration.
+ * </p>
+ */
+public abstract class SystemReader {
+ private static SystemReader INSTANCE = new SystemReader() {
+ private volatile String hostname;
+
+ public String getenv(String variable) {
+ return System.getenv(variable);
+ }
+
+ public String getProperty(String key) {
+ return System.getProperty(key);
+ }
+
+ public FileBasedConfig openUserConfig() {
+ final File home = FS.userHome();
+ return new FileBasedConfig(new File(home, ".gitconfig"));
+ }
+
+ public String getHostname() {
+ if (hostname == null) {
+ try {
+ InetAddress localMachine = InetAddress.getLocalHost();
+ hostname = localMachine.getCanonicalHostName();
+ } catch (UnknownHostException e) {
+ // we do nothing
+ hostname = "localhost";
+ }
+ assert hostname != null;
+ }
+ return hostname;
+ }
+
+ @Override
+ public long getCurrentTime() {
+ return System.currentTimeMillis();
+ }
+
+ @Override
+ public int getTimezone(long when) {
+ return TimeZone.getDefault().getOffset(when) / (60 * 1000);
+ }
+ };
+
+ /** @return the live instance to read system properties. */
+ public static SystemReader getInstance() {
+ return INSTANCE;
+ }
+
+ /**
+ * @param newReader
+ * the new instance to use when accessing properties.
+ */
+ public static void setInstance(SystemReader newReader) {
+ INSTANCE = newReader;
+ }
+
+ /**
+ * Gets the hostname of the local host. If no hostname can be found, the
+ * hostname is set to the default value "localhost".
+ *
+ * @return the canonical hostname
+ */
+ public abstract String getHostname();
+
+ /**
+ * @param variable system variable to read
+ * @return value of the system variable
+ */
+ public abstract String getenv(String variable);
+
+ /**
+ * @param key of the system property to read
+ * @return value of the system property
+ */
+ public abstract String getProperty(String key);
+
+ /**
+ * @return the git configuration found in the user home
+ */
+ public abstract FileBasedConfig openUserConfig();
+
+ /**
+ * @return the current system time
+ */
+ public abstract long getCurrentTime();
+
+ /**
+ * @param when TODO
+ * @return the local time zone
+ */
+ public abstract int getTimezone(long when);
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
new file mode 100644
index 0000000000..9c6addebd8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ProgressMonitor;
+
+/**
+ * A fully buffered output stream using local disk storage for large data.
+ * <p>
+ * Initially this output stream buffers to memory, like ByteArrayOutputStream
+ * might do, but it shifts to using an on disk temporary file if the output gets
+ * too large.
+ * <p>
+ * The content of this buffered stream may be sent to another OutputStream only
+ * after this stream has been properly closed by {@link #close()}.
+ */
+public class TemporaryBuffer extends OutputStream {
+ static final int DEFAULT_IN_CORE_LIMIT = 1024 * 1024;
+
+ /** Chain of data, if we are still completely in-core; otherwise null. */
+ private ArrayList<Block> blocks;
+
+ /**
+ * Maximum number of bytes we will permit storing in memory.
+ * <p>
+ * When this limit is reached the data will be shifted to a file on disk,
+ * preventing the JVM heap from growing out of control.
+ */
+ private int inCoreLimit;
+
+ /**
+ * Location of our temporary file if we are on disk; otherwise null.
+ * <p>
+ * If we exceeded the {@link #inCoreLimit} we nulled out {@link #blocks} and
+ * created this file instead. All output goes here through {@link #diskOut}.
+ */
+ private File onDiskFile;
+
+ /** If writing to {@link #onDiskFile} this is a buffered stream to it. */
+ private OutputStream diskOut;
+
+ /** Create a new empty temporary buffer. */
+ public TemporaryBuffer() {
+ inCoreLimit = DEFAULT_IN_CORE_LIMIT;
+ blocks = new ArrayList<Block>(inCoreLimit / Block.SZ);
+ blocks.add(new Block());
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ if (blocks == null) {
+ diskOut.write(b);
+ return;
+ }
+
+ Block s = last();
+ if (s.isFull()) {
+ if (reachedInCoreLimit()) {
+ diskOut.write(b);
+ return;
+ }
+
+ s = new Block();
+ blocks.add(s);
+ }
+ s.buffer[s.count++] = (byte) b;
+ }
+
+ @Override
+ public void write(final byte[] b, int off, int len) throws IOException {
+ if (blocks != null) {
+ while (len > 0) {
+ Block s = last();
+ if (s.isFull()) {
+ if (reachedInCoreLimit())
+ break;
+
+ s = new Block();
+ blocks.add(s);
+ }
+
+ final int n = Math.min(Block.SZ - s.count, len);
+ System.arraycopy(b, off, s.buffer, s.count, n);
+ s.count += n;
+ len -= n;
+ off += n;
+ }
+ }
+
+ if (len > 0)
+ diskOut.write(b, off, len);
+ }
+
+ /**
+ * Copy all bytes remaining on the input stream into this buffer.
+ *
+ * @param in
+ * the stream to read from, until EOF is reached.
+ * @throws IOException
+ * an error occurred reading from the input stream, or while
+ * writing to a local temporary file.
+ */
+ public void copy(final InputStream in) throws IOException {
+ if (blocks != null) {
+ for (;;) {
+ Block s = last();
+ if (s.isFull()) {
+ if (reachedInCoreLimit())
+ break;
+ s = new Block();
+ blocks.add(s);
+ }
+
+ final int n = in.read(s.buffer, s.count, Block.SZ - s.count);
+ if (n < 1)
+ return;
+ s.count += n;
+ }
+ }
+
+ final byte[] tmp = new byte[Block.SZ];
+ int n;
+ while ((n = in.read(tmp)) > 0)
+ diskOut.write(tmp, 0, n);
+ }
+
+ private Block last() {
+ return blocks.get(blocks.size() - 1);
+ }
+
+ private boolean reachedInCoreLimit() throws IOException {
+ if (blocks.size() * Block.SZ < inCoreLimit)
+ return false;
+
+ onDiskFile = File.createTempFile("jgit_", ".buffer");
+ diskOut = new FileOutputStream(onDiskFile);
+
+ final Block last = blocks.remove(blocks.size() - 1);
+ for (final Block b : blocks)
+ diskOut.write(b.buffer, 0, b.count);
+ blocks = null;
+
+ diskOut = new BufferedOutputStream(diskOut, Block.SZ);
+ diskOut.write(last.buffer, 0, last.count);
+ return true;
+ }
+
+ public void close() throws IOException {
+ if (diskOut != null) {
+ try {
+ diskOut.close();
+ } finally {
+ diskOut = null;
+ }
+ }
+ }
+
+ /**
+ * Obtain the length (in bytes) of the buffer.
+ * <p>
+ * The length is only accurate after {@link #close()} has been invoked.
+ *
+ * @return total length of the buffer, in bytes.
+ */
+ public long length() {
+ if (onDiskFile != null)
+ return onDiskFile.length();
+
+ final Block last = last();
+ return ((long) blocks.size()) * Block.SZ - (Block.SZ - last.count);
+ }
+
+ /**
+ * Convert this buffer's contents into a contiguous byte array.
+ * <p>
+ * The buffer is only complete after {@link #close()} has been invoked.
+ *
+ * @return the complete byte array; length matches {@link #length()}.
+ * @throws IOException
+ * an error occurred reading from a local temporary file
+ * @throws OutOfMemoryError
+ * the buffer cannot fit in memory
+ */
+ public byte[] toByteArray() throws IOException {
+ final long len = length();
+ if (Integer.MAX_VALUE < len)
+ throw new OutOfMemoryError("Length exceeds maximum array size");
+
+ final byte[] out = new byte[(int) len];
+ if (blocks != null) {
+ int outPtr = 0;
+ for (final Block b : blocks) {
+ System.arraycopy(b.buffer, 0, out, outPtr, b.count);
+ outPtr += b.count;
+ }
+ } else {
+ final FileInputStream in = new FileInputStream(onDiskFile);
+ try {
+ NB.readFully(in, out, 0, (int) len);
+ } finally {
+ in.close();
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Send this buffer to an output stream.
+ * <p>
+ * This method may only be invoked after {@link #close()} has completed
+ * normally, to ensure all data is completely transferred.
+ *
+ * @param os
+ * stream to send this buffer's complete content to.
+ * @param pm
+ * if not null progress updates are sent here. Caller should
+ * initialize the task and the number of work units to
+ * <code>{@link #length()}/1024</code>.
+ * @throws IOException
+ * an error occurred reading from a temporary file on the local
+ * system, or writing to the output stream.
+ */
+ public void writeTo(final OutputStream os, ProgressMonitor pm)
+ throws IOException {
+ if (pm == null)
+ pm = NullProgressMonitor.INSTANCE;
+ if (blocks != null) {
+ // Everything is in core so we can stream directly to the output.
+ //
+ for (final Block b : blocks) {
+ os.write(b.buffer, 0, b.count);
+ pm.update(b.count / 1024);
+ }
+ } else {
+ // Reopen the temporary file and copy the contents.
+ //
+ final FileInputStream in = new FileInputStream(onDiskFile);
+ try {
+ int cnt;
+ final byte[] buf = new byte[Block.SZ];
+ while ((cnt = in.read(buf)) >= 0) {
+ os.write(buf, 0, cnt);
+ pm.update(cnt / 1024);
+ }
+ } finally {
+ in.close();
+ }
+ }
+ }
+
+ /** Clear this buffer so it has no data, and cannot be used again. */
+ public void destroy() {
+ blocks = null;
+
+ if (diskOut != null) {
+ try {
+ diskOut.close();
+ } catch (IOException err) {
+ // We shouldn't encounter an error closing the file.
+ } finally {
+ diskOut = null;
+ }
+ }
+
+ if (onDiskFile != null) {
+ if (!onDiskFile.delete())
+ onDiskFile.deleteOnExit();
+ onDiskFile = null;
+ }
+ }
+
+ static class Block {
+ static final int SZ = 8 * 1024;
+
+ final byte[] buffer = new byte[SZ];
+
+ int count;
+
+ boolean isFull() {
+ return count == SZ;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java
new file mode 100644
index 0000000000..91aa1cb6d2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util.io;
+
+/**
+ * Triggers an interrupt on the calling thread if it doesn't complete a block.
+ * <p>
+ * Classes can use this to trip an alarm interrupting the calling thread if it
+ * doesn't complete a block within the specified timeout. Typical calling
+ * pattern is:
+ *
+ * <pre>
+ * private InterruptTimer myTimer = ...;
+ * void foo() {
+ * try {
+ * myTimer.begin(timeout);
+ * // work
+ * } finally {
+ * myTimer.end();
+ * }
+ * }
+ * </pre>
+ * <p>
+ * An InterruptTimer is not recursive. To implement recursive timers,
+ * independent InterruptTimer instances are required. A single InterruptTimer
+ * may be shared between objects which won't recursively call each other.
+ * <p>
+ * Each InterruptTimer spawns one background thread to sleep the specified time
+ * and interrupt the thread which called {@link #begin(int)}. It is up to the
+ * caller to ensure that the operations within the work block between the
+ * matched begin and end calls tests the interrupt flag (most IO operations do).
+ * <p>
+ * To terminate the background thread, use {@link #terminate()}. If the
+ * application fails to terminate the thread, it will (eventually) terminate
+ * itself when the InterruptTimer instance is garbage collected.
+ *
+ * @see TimeoutInputStream
+ */
+public final class InterruptTimer {
+ private final AlarmState state;
+
+ private final AlarmThread thread;
+
+ final AutoKiller autoKiller;
+
+ /** Create a new timer with a default thread name. */
+ public InterruptTimer() {
+ this("JGit-InterruptTimer");
+ }
+
+ /**
+ * Create a new timer to signal on interrupt on the caller.
+ * <p>
+ * The timer thread is created in the calling thread's ThreadGroup.
+ *
+ * @param threadName
+ * name of the timer thread.
+ */
+ public InterruptTimer(final String threadName) {
+ state = new AlarmState();
+ autoKiller = new AutoKiller(state);
+ thread = new AlarmThread(threadName, state);
+ thread.start();
+ }
+
+ /**
+ * Arm the interrupt timer before entering a blocking operation.
+ *
+ * @param timeout
+ * number of milliseconds before the interrupt should trigger.
+ * Must be > 0.
+ */
+ public void begin(final int timeout) {
+ if (timeout <= 0)
+ throw new IllegalArgumentException("Invalid timeout: " + timeout);
+ Thread.interrupted();
+ state.begin(timeout);
+ }
+
+ /** Disable the interrupt timer, as the operation is complete. */
+ public void end() {
+ state.end();
+ }
+
+ /** Shutdown the timer thread, and wait for it to terminate. */
+ public void terminate() {
+ state.terminate();
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ //
+ }
+ }
+
+ static final class AlarmThread extends Thread {
+ AlarmThread(final String name, final AlarmState q) {
+ super(q);
+ setName(name);
+ setDaemon(true);
+ }
+ }
+
+ // The trick here is, the AlarmThread does not have a reference to the
+ // AutoKiller instance, only the InterruptTimer itself does. Thus when
+ // the InterruptTimer is GC'd, the AutoKiller is also unreachable and
+ // can be GC'd. When it gets finalized, it tells the AlarmThread to
+ // terminate, triggering the thread to exit gracefully.
+ //
+ private static final class AutoKiller {
+ private final AlarmState state;
+
+ AutoKiller(final AlarmState s) {
+ state = s;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ state.terminate();
+ }
+ }
+
+ static final class AlarmState implements Runnable {
+ private Thread callingThread;
+
+ private long deadline;
+
+ private boolean terminated;
+
+ AlarmState() {
+ callingThread = Thread.currentThread();
+ }
+
+ public synchronized void run() {
+ while (!terminated && callingThread.isAlive()) {
+ try {
+ if (0 < deadline) {
+ final long delay = deadline - now();
+ if (delay <= 0) {
+ deadline = 0;
+ callingThread.interrupt();
+ } else {
+ wait(delay);
+ }
+ } else {
+ wait(1000);
+ }
+ } catch (InterruptedException e) {
+ // Treat an interrupt as notice to examine state.
+ }
+ }
+ }
+
+ synchronized void begin(final int timeout) {
+ if (terminated)
+ throw new IllegalStateException("Timer already terminated");
+ callingThread = Thread.currentThread();
+ deadline = now() + timeout;
+ notifyAll();
+ }
+
+ synchronized void end() {
+ if (0 == deadline)
+ Thread.interrupted();
+ else
+ deadline = 0;
+ notifyAll();
+ }
+
+ synchronized void terminate() {
+ if (!terminated) {
+ deadline = 0;
+ terminated = true;
+ notifyAll();
+ }
+ }
+
+ private static long now() {
+ return System.currentTimeMillis();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java
new file mode 100644
index 0000000000..19d7933e1b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+
+/** InputStream with a configurable timeout. */
+public class TimeoutInputStream extends FilterInputStream {
+ private final InterruptTimer myTimer;
+
+ private int timeout;
+
+ /**
+ * Wrap an input stream with a timeout on all read operations.
+ *
+ * @param src
+ * base input stream (to read from). The stream must be
+ * interruptible (most socket streams are).
+ * @param timer
+ * timer to manage the timeouts during reads.
+ */
+ public TimeoutInputStream(final InputStream src,
+ final InterruptTimer timer) {
+ super(src);
+ myTimer = timer;
+ }
+
+ /** @return number of milliseconds before aborting a read. */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * @param millis
+ * number of milliseconds before aborting a read. Must be > 0.
+ */
+ public void setTimeout(final int millis) {
+ if (millis < 0)
+ throw new IllegalArgumentException("Invalid timeout: " + millis);
+ timeout = millis;
+ }
+
+ @Override
+ public int read() throws IOException {
+ try {
+ beginRead();
+ return super.read();
+ } catch (InterruptedIOException e) {
+ throw readTimedOut();
+ } finally {
+ endRead();
+ }
+ }
+
+ @Override
+ public int read(byte[] buf) throws IOException {
+ return read(buf, 0, buf.length);
+ }
+
+ @Override
+ public int read(byte[] buf, int off, int cnt) throws IOException {
+ try {
+ beginRead();
+ return super.read(buf, off, cnt);
+ } catch (InterruptedIOException e) {
+ throw readTimedOut();
+ } finally {
+ endRead();
+ }
+ }
+
+ @Override
+ public long skip(long cnt) throws IOException {
+ try {
+ beginRead();
+ return super.skip(cnt);
+ } catch (InterruptedIOException e) {
+ throw readTimedOut();
+ } finally {
+ endRead();
+ }
+ }
+
+ private void beginRead() {
+ myTimer.begin(timeout);
+ }
+
+ private void endRead() {
+ myTimer.end();
+ }
+
+ private static InterruptedIOException readTimedOut() {
+ return new InterruptedIOException("Read timed out");
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java
new file mode 100644
index 0000000000..a826086cd1
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util.io;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+
+/** OutputStream with a configurable timeout. */
+public class TimeoutOutputStream extends OutputStream {
+ private final OutputStream dst;
+
+ private final InterruptTimer myTimer;
+
+ private int timeout;
+
+ /**
+ * Wrap an output stream with a timeout on all write operations.
+ *
+ * @param destination
+ * base input stream (to write to). The stream must be
+ * interruptible (most socket streams are).
+ * @param timer
+ * timer to manage the timeouts during writes.
+ */
+ public TimeoutOutputStream(final OutputStream destination,
+ final InterruptTimer timer) {
+ dst = destination;
+ myTimer = timer;
+ }
+
+ /** @return number of milliseconds before aborting a write. */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * @param millis
+ * number of milliseconds before aborting a write. Must be > 0.
+ */
+ public void setTimeout(final int millis) {
+ if (millis < 0)
+ throw new IllegalArgumentException("Invalid timeout: " + millis);
+ timeout = millis;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ try {
+ beginWrite();
+ dst.write(b);
+ } catch (InterruptedIOException e) {
+ throw writeTimedOut();
+ } finally {
+ endWrite();
+ }
+ }
+
+ @Override
+ public void write(byte[] buf) throws IOException {
+ write(buf, 0, buf.length);
+ }
+
+ @Override
+ public void write(byte[] buf, int off, int len) throws IOException {
+ try {
+ beginWrite();
+ dst.write(buf, off, len);
+ } catch (InterruptedIOException e) {
+ throw writeTimedOut();
+ } finally {
+ endWrite();
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ try {
+ beginWrite();
+ dst.flush();
+ } catch (InterruptedIOException e) {
+ throw writeTimedOut();
+ } finally {
+ endWrite();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ beginWrite();
+ dst.close();
+ } catch (InterruptedIOException e) {
+ throw writeTimedOut();
+ } finally {
+ endWrite();
+ }
+ }
+
+ private void beginWrite() {
+ myTimer.begin(timeout);
+ }
+
+ private void endWrite() {
+ myTimer.end();
+ }
+
+ private static InterruptedIOException writeTimedOut() {
+ return new InterruptedIOException("Write timed out");
+ }
+}