Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGit Development Community2009-09-29 23:47:03 +0000
committerShawn O. Pearce2009-09-29 23:47:03 +0000
commit1a6964c8274c50f0253db75f010d78ef0e739343 (patch)
treeca833cc7cf6fc8c7b9850dee258f3a356c790ffc /org.eclipse.jgit
downloadjgit-1a6964c8274c50f0253db75f010d78ef0e739343.tar.gz
jgit-1a6964c8274c50f0253db75f010d78ef0e739343.tar.xz
jgit-1a6964c8274c50f0253db75f010d78ef0e739343.zip
Initial JGit contribution to eclipse.org
Per CQ 3448 this is the initial contribution of the JGit project to eclipse.org. It is derived from the historical JGit repository at commit 3a2dd9921c8a08740a9e02c421469e5b1a9e47cb. Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
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");
+ }
+}

Back to the top